Chain hierarchy

Here’s something I’ve been trying to figure out.

Im trying to build a hierarchy of ellipses such that each child ellipse has to stay within a certain range of its parent.

I understand how it needs to work, Im just not sure how to go about it in processing.

I built a prototype in 3D software that works perfectly.
starting with the smallest; each circle has a larger child which is constrained to it. (see .gif)

rings

Do you want this to happen in 2D (ellipses) or in 3D (ellipses with different depth) ?

Thoughts:

  • If the radius of the inner ellipse gets moved, the outer ellipse move.

  • So the hierarchy is from inside out.

  • The outer ellipse (number 1) moves when its predecessor (inner one, number 0) touches it on the inside; this can be seen as dist ( x0,y0 , x1,y1 ) < r0 or something (x,y being position and r radius).

  • If this is the case we got to move (1) (not sure how; atan2 comes to mind).

  • And so forth from inside towards out.

Chrisir

I wrote a really long answer giving very complicated and partially wrong instructions on how you could accomplish this, but then removed it and just did the code.

Circle[] circles;

void setup() {
  size(640, 480, P2D);

  // Initialize and populate the array
  circles = new Circle[20];
  for (int i=0; i<circles.length; i++) {
    circles[i]=new Circle(300, 300, 25+i*5);
  }
}
void draw() {
  // Draw everything in blackness
  background(0);
  // If a mouse button is held, move first circle
  if (mousePressed) {
    circles[0].posX+=mouseX-pmouseX;
    circles[0].posY+=mouseY-pmouseY;
  }
  // Various drawing parameters - no fill, white border
  noFill();
  stroke(255);
  // Iterate over all circles
  for (int i=0; i<circles.length; i++) {
    // If statement to not run this on first circle - will give nullPointerException
    if (i!=0) {
      // Move this circle so that it contains the previous one within itself
      circles[i].occludeCircle(circles[i-1]);
    }
    // Draw all circles
    circles[i].draw();
  }
}

class Circle {
  float posX, posY, radius;
  Circle(float posX, float posY, float radius) {
    this.posX=posX;
    this.posY=posY;
    this.radius=radius;
  }
  void draw() {
    circle(posX, posY, radius*2); // *2 is because this function takes diameter
  }
  // Function that keeps the "other" circle inside of this one by moving this one
  void occludeCircle(Circle other) {
    float distance=dist(posX, posY, other.posX, other.posY);
    float distanceOffset=radius-other.radius;
    
    // Check if the other circle is partially or completely outside of this one
    if (distance>distanceOffset) {
      // Get angle and distance needed to travel
      float angle = angle(other.posX-posX, other.posY-posY);
      float travelDistance=distance-distanceOffset;

      // Transform them into X/Y coordinates to add to current circle's position
      posX+=cos(radians(angle))*travelDistance;
      posY+=sin(radians(angle))*travelDistance;
    }
  }
}

// Function that returns the angle on a 2D plane we should go forward to if we want to go to input coordinates
// Made during one really busy angry night of hacking things with edge cases like result being 180 degrees and stuff
// Probably best to replace with something less horribly looking - but so far this works. :V
// Eh, triggonometry makes me ultra hacky automatically for some reason, sorry for this weird thing!
float angle(float x, float y) {
  return y!=0?(x<0?(180*Math.signum(y)-(atan(y/abs(x))/(PI)*180)):atan(y/abs(x))/(PI)*180):Math.signum(x)<0?(180):0;
}

P.S. Looks like the software you are using is taking a completely different approach that does not care about the circles and instead drags the chain of those red dots around, drawing circles with varied diameters around them. With that in mind, my approach here is different, but as long as what is shown in your GIF is what you wanted, this does the thing.

4 Likes

ha! sometimes its easier just to do it :slight_smile:
well done :slight_smile:

Appreciate your notes too btw!
nailed it.

1 Like

Great solution! Thanks for sharing it.

One other option when doing a pair comparison loop is to instead start with 1 – then i-1 is always a valid index without any in-loop checks.

circles[0].draw();
for (int i=1; i<circles.length; i++) {
  circles[i].occludeCircle(circles[i-1]);
  circles[i].draw();
}
1 Like

True. But, being unnecessarily pragmatic here, what if the array has 0 Circle objects in it? The circles[0].draw(); statement will throw an exception! :smiley:

1 Like

Good point. I’d say guard it by checking the minimum length – this could be either 1 or 2, depending on if you want a single item to be rendered, or require a pair at a minimum.

Of course, you can also guard against null, etc. etc. It really depends on the rest of the sketch.

if(circles!=null && circles.length>0){
  circles[0].draw();
  for (int i=1; i<circles.length; i++) {
    circles[i].occludeCircle(circles[i-1]);
    circles[i].draw();
  }
}