kikkpunk
kikkpunk

Reputation: 1337

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.

Upvotes: 1

Views: 3382

Answers (1)

George Profenza
George Profenza

Reputation: 51867

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

Upvotes: 1

Related Questions