Search code examples
design-patternsmoveprocessinglinear-algebrashapes

how to move shape in a certain arc and line in processing?


i made classic random moving circles as in the picture:

enter image description here

And here's what i want to realize:

when mouseClicked , the circles line up like: enter image description here

i think i can check the position of all the circles. if theirs y position are within 1/2height, they should form the arc. if are larger then 1/2height, they form the line.

But the trick is : how to form those shape?

i mean, i know how to form a round , just move their centre of circle towards a point. but LINE(how to move theirs x position)??? even ARC ??? really have no idea.

Does anyone know? Thank you very much.


Solution

  • If I understand this correctly, you're trying to compute intermediary positions between two points. Those points can be be either on a line or on an arc.

    Getting the intermediary positions on a line is fairly simple and there multiple ways to tackle this. One idea that comes to mind is to use the lerp() function (which does linear interpolation). Here's a very basic example:

    //draw stuff
    smooth();strokeWeight(5);
    //line stuff
    PVector start = new PVector(10,10);
    PVector end   = new PVector(90,90);
    int numPts = 5;
    float increment = 1.0/numPts;
    
    for(int i = 0; i < numPts; i++){//for each point that should be on the line
      float t = increment * i ; //'traversal' on the line (0.0 is at start 1.0 is at end)
      point(lerp(start.x,end.x,t),//interpolate and draw
            lerp(start.y,end.y,t));
    }
    

    Run this in a new sketch to see what I mean. This can also be done manually, making use of the PVector class and the interpolation formula:

      point(percentage) = point(start) + ((point(end)-point(start)) * percentage)
    

    hence:

    //draw stuff
    smooth();strokeWeight(5);
    //line stuff
    PVector start = new PVector(10,10);
    PVector end   = new PVector(90,90);
    int numPts = 5;
    float increment = 1.0/numPts;
    
    for(int i = 0; i < numPts; i++){//for each point that should be on the line
      float t = increment * i ; //'traversal' on the line (0.0 is at start 1.0 is at end)
      PVector current = PVector.add(start,PVector.mult(PVector.sub(end,start),t));
      point(  current.x, current.y );
    }
    

    But lerp() seems more lose and could easily fit into your existing setup.

    For the arc, things are just a tad complicated as you'll need a bit of trigonometry: converting cartesian to polar coordinates. It sounds a bit more complicated that it should but it's not that hard once you visualize things mentally. Imagine you're looking at a clock.

    You can tell precisely what time it is by looking at the positions of the two needles (one for hour and one for minute). Using the clock positions, or "coordinates" you can easily tell when it's noon/midnight. Similarly you can convert back from 'clock coordinates' and say what the needle positions for noon/midnight.

    Looking at the clock you can also imagine the cartesian system overlayed: 0,0 is at the centre and noon/midnight in cartesian would be (0,1) if 1 would the unit used for a needle length. For 15:15 you'd get (1,0), (0,-1) for 18:30, (-1,0) for 20:45, etc. You're converting from one 2D coordinate system (cartesian with x and y) to another ("clock" with hour and minutes)

    In a very similar way you can convert from cartesian (uses x and y) to polar( uses angle and radius) and back. For example, 12:00 would mean (0,1) but can also be expressed as (90 degrees, needle length).

    Now back to the arc: you probably know the start and and end end angle and you know the radius (distance from centre of circle) so you've got the polar coordinates. You simply need to convert to cartesian(x,y) coordinates, which can be done using this formula:

    x = cos(angle) * radius;
    y = sin(angle) * radius;
    

    At this point it's worth noting that all trigonometric function(sin/cos/tan/atan/etc.) use radians. Luckily Processing already provides a radians() which simplifies degrees to radians conversion.

    Here's a basic sketch to illustrate the idea:

    //draw stuff
    smooth();strokeWeight(5);
    //arc stuff
    float distance   = 35;//100 pixels away from the centre
    float startAngle = radians(30);
    float endAngle   = radians(120);
    int numPts = 10;
    float increment = 1.0/numPts;
    
    for(int i = 0; i < numPts; i++){//for each point on the arc
      float intermediaryAngle = lerp(startAngle,endAngle,increment*i);
      float x = cos(intermediaryAngle) * distance;
      float y = sin(intermediaryAngle) * distance;
      point(x+50,y+50);//50 is offset to draw from the centre of the sketch
    }
    

    I assume you should able to check if the y coordinate of your objects is smaller than height/2 and compute either start/end positions for line positioning or start angle/end angle, radius/distance and offset for the arc positioning

    UPDATE If you want to animate/interpolate from the current position to the computed position (be it on the line or on the arc), you need to handle that as the code above handles only calculating the destinations. Here's a basic example of what I mean, based on some of your code:

    int maxCircle = 10;
    Circle[] circles = new Circle[maxCircle];
    float increment = (1.0/maxCircle);
    float traversal = 0.0;
    
    void setup(){
      size(400,400);
      smooth();strokeWeight(5);
      for(int i=0;i<maxCircle;i++){
       circles[i] = new Circle(random(width),random(height),random(2,20));
      }
    }
    void draw(){
      background(255);
      for(int i=0;i<maxCircle;i++){
        if(!mousePressed)    circles[i].update(width,height);//default
        else{//if some event happens
          //compute destination
          float x,y;
          float offx = width/2;
          float offy = height/2;
          //move to line
            float startX = 0;
            float endX = width;
            float t = increment * i;
            x = lerp(startX,endX,t);
            y = offy-10;
          //interpolate/move to computed position
          if(traversal < 1.0){//if circle hasn't reached destination yet
            traversal += 0.0001;//move closer to the destination
            circles[i].x = lerp(circles[i].x,x,traversal);
            circles[i].y = lerp(circles[i].y,y,traversal);
          }
        }
        circles[i].display();
      }
    }
    
    void mouseReleased(){
      traversal = 0;
    }
    
    class Circle{
          float x,y,vx,vy,r,speed;
    
      Circle(float tempx, float tempy, float tempr){  
         x=tempx;
         y=tempy;
         vx=random(-1,1);
         vy=random(-1,1);
         r=tempr;
        }
    
      void update(int w,int h){
       x+=vx;
       y+=vy;
    
       if(x<r || x>w-r){
         vx*=-1;};
       if(y<r || y>h-r){
         vy*=-1;};
        }
    
    
       void display(){
          fill(0,50);
          noStroke();
          ellipse(x,y,r,r);
        }  
    
        } 
    

    When the mouse is pressed, the circles will animate towards the line positions. Another way to do something similar to the above is to use velocities of Circle:

    1. compute direction vector (by subtracting the destination vector from the current position vector)
    2. find the current speed (or magnitude of the velocity vector)
    3. scale the velocity vector based on the direction vector and it's speed. (This will allow a sudden stop or deceleration if you like)

    Here's a code example:

    int maxCircle = 10;
    Circle[] circles = new Circle[maxCircle];
    
    void setup() {
      size(400, 400);
      smooth();
      strokeWeight(5);
      for (int i=0;i<maxCircle;i++) {
        circles[i] = new Circle(random(width), random(height), random(2, 20));
      }
    }
    void draw() {
      background(255);
      for (int i=0;i<maxCircle;i++) {
        if (!mousePressed)    circles[i].update(width, height);//default
        else {//if some event happens
          //compute destination
          float x = map(i,0,maxCircle,0,width);
          float y = (height * .5) - 10;
          //update to destination
          circles[i].update(x,y,2);
        }
        circles[i].display();
      }
    }
    
    class Circle {
      float x, y, vx, vy, r, speed;
    
      Circle(float tempx, float tempy, float tempr) {  
        x=tempx;
        y=tempy;
        vx=random(-1, 1);
        vy=random(-1, 1);
        r=tempr;
      }
    
      void update(int w, int h) {
        x+=vx;
        y+=vy;
    
        if (x<r || x>w-r) {
          vx*=-1;
        };
        if (y<r || y>h-r) {
          vy*=-1;
        };
      }
      void update(float x,float y,float speed){
        //compute direction vector
        float dx = x - this.x;
        float dy = y - this.y;
        //find the current 'speed': vector's length or magnitude
        float len = sqrt(dx*dx + dy*dy);//PVector's mag() does this for you
        //normalize the vector
        dx /= len;
        dy /= len;
        //scale the vector
        dx *= speed;
        dy *= speed;
        //interpolate/move to computed position
        if(dist(this.x,this.y,x,y) > 2){//if circle hasn't reached destination yet (isn't close enough)
          this.x += dx;
          this.y += dy;
        }
      }
    
        void display() {
        fill(0, 50);
        noStroke();
        ellipse(x, y, r, r);
      }
    } 
    

    Which you can run bellow:

    var maxCircle = 10;
    var circles = new Array(maxCircle);
    
    function setup() {
      createCanvas(400, 400);
      smooth();
      fill(0,50);
      noStroke();
      for (var i=0;i<maxCircle;i++) {
        circles[i] = new Circle(random(width), random(height), random(2, 20));
      }
    }
    function draw() {
      background(255);
      for (var i=0;i<maxCircle;i++) {
        if (!isMousePressed)    circles[i].updateBounds(width, height);//default
        else {//if some event happens
          //compute destination
          var x = map(i,0,maxCircle,0,width);
          var y = (height * .5) - 10;
          //update to destination
          circles[i].update(x,y,2);
        }
        circles[i].display();
      }
    }
    
    function Circle(tempx, tempy, tempr){
      this.x=tempx;
      this.y=tempy;
      this.vx=random(-1, 1);
      this.vy=random(-1, 1);
      this.r=tempr;
      
    
      this.updateBounds = function(w,h) {
        this.x+=this.vx;
        this.y+=this.vy;
    
        if(this.x < this.r || this.x>this.w-this.r) {
          this.vx*=-1;
        }
        if (this.y<this.r || this.y>this.h-this.r) {
          this.vy*=-1;
        }
      }
      this.update = function(ax,ay,speed){
        //compute direction vector
        var dx = ax - this.x;
        var dy = ay - this.y;
        //find the current 'speed': vector's length or magnitude
        var len = sqrt(dx*dx + dy*dy);//PVector's mag() does this for you
        //normalize the vector
        dx /= len;
        dy /= len;
        //scale the vector
        dx *= speed;
        dy *= speed;
        //varerpolate/move to computed position
        if(dist(this.x,this.y,ax,ay) > 2){//if circle hasn't reached destination yet (isn't close enough)
          this.x += dx;
          this.y += dy;
        }
      }
    
      this.display = function() {
        ellipse(this.x, this.y, this.r, this.r);
      }
    } 
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.4/p5.min.js"></script>

    sketch preview

    A few quick notes:

    1. Notice I've defined another update method where I do the vector maths. In Processing/Java you can have a method/function with the same name but different parameters, this can come in handy at times. In this case, void update(float x,float y,float speed) might as well be void seek(float x,float y,float speed) or void moveTo(float x,float y,float speed).
    2. The vector terminology might seem complicated at first, but it makes a lot of sense once you get into it. Also it's pretty damn useful in computer graphics (in Processing or any other language you decide to use in the future). I warmly recommend Daniel Shiffman's Vector chapter from Nature of Code on the topic. It's really well explained with easy to understand examples. Processing has the handy PVector class at your disposal.
    3. In the future, once you're comfortable with vectors and if you'd like to explore more advanced movement algorithms, feel free to explore Autonomous Steering Behaviors/Boids. (This is an advanced topic though, albeit an interesting/fun one).

    HTH