Walk along PShape contours and divide them?

2019-07-31 08:39发布

问题:

I’m looking for a proper way of finding points along a PShape contour.

My goal is to generate the same number of points along the two distances from a given point to another (right distance and left distance), then mark a point in the exact center between the the two points that are the same step number on each side. (I’m not sure if I’m being easily understandable, and I cannot attach img already, so I attach processing code).

I imagine that the first step for getting it done is to calculate the exact distance between the start and end points, following the path. Maybe I’m wrong.

Any help on this matter would be very very welcome.

PGraphics g ;
PVector[] values = new PVector[7];

void setup(){
  size(1024,768,P3D);
  fillVal();
  smooth();
}

void draw(){
  background(0);
  drawSiluette(g);
}

void fillVal(){
  values[0]=new PVector ( 336.0, 272.0, 0.0 );
  values[1]=new PVector ( 305.0, 428.0, 0.0 );
  values[2]=new PVector ( 489.0, 516.0, 0.0 );
  values[3]=new PVector ( 639.0, 400.0, 0.0);
  values[4]=new PVector ( 565.0, 283.0, 0.0 );
  values[5]=new PVector ( 469.0, 227.0, 0.0 );
  values[6]=new PVector ( 403.0, 216.0, 0.0 );
}


void drawSiluette(PGraphics _s){
  _s = createGraphics(width,height);
  pushMatrix();
  _s.beginDraw();
  _s.noFill();
  _s.strokeWeight(3);
  _s.stroke(255);
  _s.beginShape();  
  for(int i = 0; i <values.length;i++){
    if(i==0 || i==values.length-1){
      for(int it = 0; it<2;it++)
         _s.curveVertex(values[0].x,values[0].y);
    }else   
        _s.curveVertex(values[i].x,values[i].y);
  }
  _s.endShape(CLOSE);
  popMatrix();
  _s.endDraw();
  image(_s,0,0);


  //start and end points
  pushMatrix();
  noStroke();
  fill(255,0,0);
  ellipseMode(CENTER);
  ellipse(values[0].x,values[0].y,10,10);
  ellipse(values[int(values.length/2)].x,values[int(values.length/2)].y,10,10);
  popMatrix();
}

回答1:

The question is a little unclear. To

generate the same number of points along the two distances from a given point to another

you can simply linearly interpolate between two points (lerp for short). This functionality is built into the PVector's lerp() function.

The function takes three parameters:

  1. the start point
  2. the end point
  3. a normalised value, which is a value between 0.0 and 1.0

You can think of the normalised value as a percentage:

  • 0.0 = 0%
  • 0.25 = 25%
  • 1.0 = 100%
  • etc.

Here's a basic example demonstration interpolation between two points with a given number of points in between:

PVector from = new PVector(100,100);
PVector to   = new PVector(300,300);

int numPoints = 10;

void setup(){
  size(400,400);
  fill(0);
}
void draw(){
  background(255);

  for(int i = 0; i <= numPoints; i++){
    //compute interpolation amount = a number from 0.0 and 1.0 , where 0 = 0% along the line and 1.0 = 100 % along the line (0.5 = 50%, etc.)
    float interpolationAmount = (float)i / numPoints;
    //float interpolationAmount = map(i,0,numPoints,0.0,1.0);

    //linearly interpolate point based on the interpolation amount
    PVector interpolatedPoint = PVector.lerp(from,to,interpolationAmount);
    //render the point on screen
    ellipse(interpolatedPoint.x,interpolatedPoint.y,10,10);
  }
  text("numPoints: " + numPoints,10,15);
}

void mouseDragged(){
  if(keyPressed) {
    to.set(mouseX,mouseY);
  }else{
    from.set(mouseX,mouseY);
  }
}

void keyPressed(){
  if(keyCode == UP)   numPoints++;
  if(keyCode == DOWN && numPoints > 0) numPoints--;
}

You can run this as a demo bellow:

var from,to;

var numPoints = 10;

function setup(){
  createCanvas(400,400);
  fill(0);
  
  from = createVector(100,100);
  to   = createVector(300,300);
}
function draw(){
  background(255);
  
  for(var i = 0; i <= numPoints; i++){
    //compute interpolation amount = a number from 0.0 and 1.0 , where 0 = 0% along the line and 1.0 = 100 % along the line (0.5 = 50%, etc.)
    //var interpolationAmount = (float)i / numPoints;
    var interpolationAmount = map(i,0,numPoints,0.0,1.0);
    
    //linearly interpolate point based on the interpolation amount
    var interpolatedPoint = p5.Vector.lerp(from,to,interpolationAmount);//PVector.lerp(from,to,interpolationAmount);
    //render the point on screen
    ellipse(interpolatedPoint.x,interpolatedPoint.y,10,10);
  }
  text("usage:\nclick & drag to move start point\nhold a key pressed while clicking and drag to move end point\nuse LEFT/RIGHT arrow to change number of points: " + numPoints,10,15);
}

function mouseDragged(){
  if(keyIsPressed) {
    to.set(mouseX,mouseY);
  }else{
    from.set(mouseX,mouseY);
  }
}

function keyPressed(){
  if(keyCode == LEFT_ARROW)   numPoints++;
  if(keyCode == RIGHT_ARROW && numPoints > 0) numPoints--;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.4/p5.min.js"></script>

The functionality could be encapsulated into a reusable function:

void drawPointsInbetween(PVector from,PVector to,int numPoints){
  for(int i = 0; i <= numPoints; i++){
    //compute interpolation amount = a number from 0.0 and 1.0 , where 0 = 0% along the line and 1.0 = 100 % along the line (0.5 = 50%, etc.)
    float interpolationAmount = (float)i / numPoints;
    //float interpolationAmount = map(i,0,numPoints,0.0,1.0);

    //linearly interpolate point based on the interpolation amount
    PVector interpolatedPoint = PVector.lerp(from,to,interpolationAmount);
    //render the point on screen
    ellipse(interpolatedPoint.x,interpolatedPoint.y,10,10);
  }
}

Back to your code, one thing that sticks out, although it's not related your main question is the fact that you're creating a new PGraphics instance multiple times per second. You probably don't want to do that. Currently, you should be able to draw straight into Processing with no need for PGraphics.

PVector[] values = new PVector[7];

void setup(){
  size(1024,768,P3D);
  fillVal();
  smooth();
}

void draw(){
  background(0);
  drawSiluette(g);
}

void fillVal(){
  values[0]=new PVector ( 336.0, 272.0, 0.0 );
  values[1]=new PVector ( 305.0, 428.0, 0.0 );
  values[2]=new PVector ( 489.0, 516.0, 0.0 );
  values[3]=new PVector ( 639.0, 400.0, 0.0);
  values[4]=new PVector ( 565.0, 283.0, 0.0 );
  values[5]=new PVector ( 469.0, 227.0, 0.0 );
  values[6]=new PVector ( 403.0, 216.0, 0.0 );
}


void drawSiluette(PGraphics _s){
  //_s = createGraphics(width,height);
  pushMatrix();
  //_s.beginDraw();
  noFill();
  strokeWeight(3);
  stroke(255);
  beginShape();  
  for(int i = 0; i <values.length;i++){
    if(i==0 || i==values.length-1){
      for(int it = 0; it<2;it++)
         curveVertex(values[0].x,values[0].y);
    }else   
        curveVertex(values[i].x,values[i].y);
  }
  endShape(CLOSE);
  popMatrix();


  //start and end points
  pushMatrix();
  noStroke();
  fill(255,0,0);
  ellipseMode(CENTER);
  ellipse(values[0].x,values[0].y,10,10);
  ellipse(values[int(values.length/2)].x,values[int(values.length/2)].y,10,10);
  popMatrix();
}

Adding the points in between function would be as simple as this:

PVector[] values = new PVector[7];

int numPoints = 10;

void setup(){
  size(1024,768,P3D);
  fillVal();
  smooth();
}

void draw(){
  background(0);
  drawSiluette(g);
}

void fillVal(){
  values[0]=new PVector ( 336.0, 272.0, 0.0 );
  values[1]=new PVector ( 305.0, 428.0, 0.0 );
  values[2]=new PVector ( 489.0, 516.0, 0.0 );
  values[3]=new PVector ( 639.0, 400.0, 0.0);
  values[4]=new PVector ( 565.0, 283.0, 0.0 );
  values[5]=new PVector ( 469.0, 227.0, 0.0 );
  values[6]=new PVector ( 403.0, 216.0, 0.0 );
}


void drawSiluette(PGraphics _s){
  //_s = createGraphics(width,height);
  pushMatrix();
  //_s.beginDraw();
  noFill();
  strokeWeight(3);
  stroke(255);
  beginShape();  
  for(int i = 0; i <values.length;i++){
    if(i==0 || i==values.length-1){
      for(int it = 0; it<2;it++)
         curveVertex(values[0].x,values[0].y);
    }else   
        curveVertex(values[i].x,values[i].y);
  }
  endShape(CLOSE);
  popMatrix();


  //start and end points
  pushMatrix();
  noStroke();
  fill(255,0,0);
  ellipseMode(CENTER);
  ellipse(values[0].x,values[0].y,10,10);
  ellipse(values[int(values.length/2)].x,values[int(values.length/2)].y,10,10);
  popMatrix();

  //draw inbetween points
  for(int i = 1 ;  i < values.length; i++){
    drawPointsInbetween(values[i-1],values[i],numPoints);
  }
  //draw last to first
  drawPointsInbetween(values[values.length-1],values[0],numPoints);
}
void drawPointsInbetween(PVector from,PVector to,int numPoints){
  for(int i = 0; i <= numPoints; i++){
    //compute interpolation amount = a number from 0.0 and 1.0 , where 0 = 0% along the line and 1.0 = 100 % along the line (0.5 = 50%, etc.)
    float interpolationAmount = (float)i / numPoints;
    //float interpolationAmount = map(i,0,numPoints,0.0,1.0);

    //linearly interpolate point based on the interpolation amount
    PVector interpolatedPoint = PVector.lerp(from,to,interpolationAmount);
    //render the point on screen
    ellipse(interpolatedPoint.x,interpolatedPoint.y,10,10);
  }
}

Here's a preview:

Notice that the interpolation is linear. For curves you might want to look at higher order interpolation functions such as quadratic or cubic.

Hermite curves are an example of cubic curve.

Here's a basic the formula:

and here's a basic Processing demo interpolating points on a Hermite curve:

float percent = 0;
PVector P0 = new PVector(10,90);//1st control pt
PVector T0 = new PVector(300,200);//1st anchor pt - NOTE! The anchors are relative to the controls
PVector P1 = new PVector(400,90);//2nd control pt
PVector T1 = new PVector(-100,400);//2nd anchor pt
PVector[] points = {P0,T0,P1,T1};
PVector pointAtPercent;

void setup(){
  size(500,500);
  reset();
}

void reset(){
    P1.x = 200 + random(200);//randomize a wee bit
    T1.x = random(-100,100);
    percent = 0;
    background(255);
    loop();
}
void draw() {
    pointAtPercent = hermite(percent, points);//compute point

    //render on screen
    ellipse(pointAtPercent.x,pointAtPercent.y,10,10);

    //update percentage of traversal along curve
    percent += .015;

    //if the curve has been drawn, stop drawing
    if(percent >= 1) noLoop();
}

void mousePressed(){
  reset();
}

PVector hermite(float t,PVector[] points){
    PVector result = new PVector();
    result.x = (2 * pow(t,3) - 3 * t * t + 1) * points[0].x+
                (pow(t,3) - 2 * t * t + t) * points[1].x + 
                (- 2 * pow(t,3) + 3*t*t) * points[2].x +
                ( pow(t,3) - t*t) * points[3].x;
    result.y = (2 * pow(t,3) - 3 * t * t + 1) * points[0].y+
                (pow(t,3) - 2 * t * t + t) * points[1].y + 
                (- 2 * pow(t,3) + 3*t*t) * points[2].y +
                ( pow(t,3) - t*t) * points[3].y;
    return result;
}

It's unclear how exactly you're aiming to interpolate between your points, but hopefully the above concepts should help you achieve your goal.