Walk along PShape contours and divide them? - processing

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();
}

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:
the start point
the end point
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.

Related

Tangent to the curve

I’m new at coding and I have a problem, I would like to draw a program where there is a curve and but for now I just have the static drawing but I don’t know how to generate a tangent line on the curve thanks to his derivate, as the mouse move…
This is all I have for now
void draw(){
background(255);
noFill();
stroke(0);
beginShape();
for(float a=0; a < TWO_PI; a+=0.01) {
float r=78;
float x=sin (a);
float y=(pow(cos(a),2)/(2-cos(a)))
my idea was to make a cursor, that moved by the curve, and every tima generated is own tangent.
THANK YOU SO MUCH !!!!!
I recommend to use PVector for the computation.
Create a function which computes a point on the shape:
PVector fSahpe(float a) {
float r = 200;
float x = r * sin(a);
float y = -r *(pow(cos(a),2)/(2-cos(a)));
return new PVector(x, y);
}
You have to find the point on the shape which is closest to the mouse position. Find the nearest point while you draw the shape. Note, since the shape is translated, the mouse position which is used to compare the mouse position to a point on the shape has to be shifted in the opposite direction:
PVector m = new PVector(mouseX-width/2, mouseY-height/2);
dist() can be used to compute the Euclidean distance between 2 points:
float mindist = 1000;
float mina = 0;
for(float a=0; a < TWO_PI; a+=0.01) {
PVector p = fSahpe(a);
// [...]
float dist = PVector.dist(p, m);
if (dist < mindist) {
mindist = dist;
mina = a;
}
}
Define a threshold distance. If the distance of the mouse to the closest point on the curve falls below the distance, the draw the tangent:
if (mindist < 10) {
// [...] draw tangent
}
Compute 2 points on the curve, which are close to each another, where one point is the point which is closest to the mouse cursor:
PVector p0 = fSahpe(mina);
PVector p1 = fSahpe(mina+0.01);
This 2 points ar on the approximated tangent. Compute the vector from on point to the other and scale it to a certain length (the length is the half length of the tangent):
PVector dir = PVector.sub(p1, p0);
dir.normalize().mult(100);
Compute the start point and the end point of the tangent:
PVector l0 = PVector.add(p0, dir);
PVector l1 = PVector.sub(p0, dir);
See the complete example:
void setup() {
size(500, 500);
}
PVector fSahpe(float a) {
float r = 200;
float x = r * sin(a);
float y = -r *(pow(cos(a),2)/(2-cos(a)));
return new PVector(x, y);
}
void draw(){
background(0);
translate(width/2, height/2);
noFill();
strokeWeight(1);
stroke(255);
float mindist = 1000;
float mina = 0;
PVector m = new PVector(mouseX-width/2, mouseY-height/2);
beginShape();
for(float a=0; a < TWO_PI; a+=0.01) {
PVector p = fSahpe(a);
vertex(p.x, p.y);
float dist = PVector.dist(p, m);
if (dist < mindist) {
mindist = dist;
mina = a;
}
}
endShape();
if (mindist < 10) {
PVector p0 = fSahpe(mina);
PVector p1 = fSahpe(mina+0.01);
PVector dir = PVector.sub(p1, p0);
dir.normalize().mult(100);
PVector l0 = PVector.add(p0, dir);
PVector l1 = PVector.sub(p0, dir);
strokeWeight(3);
stroke(255, 0, 0);
line(l0.x, l0.y, l1.x, l1.y);
}
}

How to add a gradient in a Bezier curve?

I have drawn curves that denote the customer country and the country where he is headed for a trip in a map.
But I could not add a gradient so that the lines would denote the said information and gives this random color between two colors at random. Here's what I tried.
int steps = 10;
noFill();
//stroke(#5A38FA, 50);
strokeWeight(1);
for(int i=0; i<steps; i++) {
strokeWeight(1);
noFill();
stroke(lerpColor(#31B5E8, #F0E62E, (float)i/steps));
bezier(locationX, locationY, locationX+random(15, 50), locationY+random(13,50), customerLocationX+random(15, 30), customerLocationY+random(15, 70), customerLocationX, customerLocationY);
}
You can decompose a bezier curve into points using the bezierPoint()method and then draw straight line segments between successive points, specifying the colour for each individual line segment (meanwhile gradually lerping the colour of course).
I've produced a method which can do that in the code example below.
Additionally, with the method, you can specify the curve's magnitude (curve) and the direction of the curve (dir); the method calculates the bezier control point using the point on a line perpendicular to the midpoint between the start point (head) and end point (tail).
void setup() {
size(500, 500);
smooth(4);
noLoop();
redraw();
strokeWeight(5);
noFill();
}
void draw() {
background(35);
drawCurve(new PVector(50, 50), new PVector(456, 490), #31B5E8, #F0E62E, 50, -1);
drawCurve(new PVector(150, 75), new PVector(340, 410), #B9FF00, #FF00C5, 150, 1);
drawCurve(new PVector(200, 480), new PVector(480, 30), #007CFF, #89CA7F, 100, 1);
}
void drawCurve(PVector head, PVector tail, color headCol, color tailCol, float curve, int curveDir) {
final float theta2 = angleBetween(tail, head);
final PVector midPoint = new PVector((head.x + tail.x) / 2,
(head.y + tail.y) / 2);
final PVector bezierCPoint = new PVector(midPoint.x + (sin(-theta2) * (curve * 2 * curveDir)),
midPoint.y + (cos(-theta2) * (curve * 2 * curveDir)));
PVector point = head.copy();
for (float t=0; t<=1; t+=0.025) {
float x1 = bezierPoint(head.x, bezierCPoint.x, bezierCPoint.x, tail.x, t);
float y1 = bezierPoint(head.y, bezierCPoint.y, bezierCPoint.y, tail.y, t);
PVector pointB = new PVector(x1, y1);
stroke(lerpColor(headCol, tailCol, t));
line(point.x, point.y, pointB.x, pointB.y);
point = pointB.copy();
}
}
static float angleBetween(PVector tail, PVector head) {
float a = PApplet.atan2(tail.y - head.y, tail.x - head.x);
if (a < 0) {
a += PConstants.TWO_PI;
}
return a;
}
Result:

Translating horizontally inverted quads

A couple of days ago I asked a question about translations and rotations in Processing.
I wanted to:
translate, invert and rotate a single quadrilateral (PShape object) multiple times
then change the height of one of its 2 top vertices
so as the whole thing act as an articulated arm that can be bent either to the right or the left.
Thanks to the help of #Rabbid76 I was able to achieve this effect but I am now facing another issue when translating the last 5 top horizontally inverted quads.
When bending the object, the first 3 quads get separated from the last 5 and. And the more the bending leg is curved, the farther they get apart.
I would really appreciate if someone could help me fix the translation part (from line 65 to 68) so as the quads stay attached to each other to matter how strong the bending is.
Any suggestion regarding that matter would be also greatly appreciated.
SCRIPT
int W = 40;
int H = 40;
int nQuads = 8;
int xOffset = 27;
float[] p0 = {-W/2 + xOffset, -H/2};
float[] p1 = {-W/2, H/2};
float[] p2 = {W/2, H/2};
float[] p3 = {W/2, -H/2};
PShape object;
void setup(){
size(600, 600, P2D);
smooth(8);
}
void draw(){
background(255);
// Bending to the left
float bending = sin(frameCount*.05) * .1;
p0[1] -= bending;
pushMatrix();
translate(width/2, height/2);
float minX = min( min(p0[0], p3[0]), min(p2[0], p1[0]) );
float maxX = max( max(p0[0], p3[0]), max(p2[0], p1[0]) );
float cptX = (minX+maxX)/2;
//Rotation Angle
float angle = atan2(p3[1]-p0[1], p3[0]-p0[0]);
//Pivot Height
float PH = p0[1] + (p3[1]-p0[1]) * (cptX-p0[0])/(p3[0]-p0[0]);
for (int i = 0; i < nQuads; i++){
float PivotHeight = (i % 2 == 1) ? PH : H/2;
//Height translation
if (i > 0){
translate(0, PivotHeight);
}
//Rotate once every 2 quads
if (i%2 == 1){
rotate(angle*2);
}
//Height translation
//Flip all quads except 1st one
if (i > 0){
translate(0, PivotHeight);
scale(1, -1);
}
//NOT working --> Flipping horizontally the last 5 top QUADS
if (i == 3){
scale(-1, 1);
translate(- xOffset, 0); //trying to align the quads on the X axis. Y translation is missing
rotate(-angle*2);
}
object();
}
popMatrix();
}
void object() {
beginShape(QUADS);
vertex(p0[0], p0[1]);
vertex(p1[0], p1[1]);
vertex(p2[0], p2[1]);
vertex(p3[0], p3[1]);
endShape();
}
Just providing a workaround to my own question but won't accept it as a valid answer as I don't really understand what I'm doing and it's probably not the most efficient solution.
int W = 40;
int H = 40;
int nQuads = 8;
int xOffset = 27;
float[] p0 = {-W/2 + xOffset, -H/2};
float[] p1 = {-W/2, H/2};
float[] p2 = {W/2, H/2};
float[] p3 = {W/2, -H/2};
PShape object;
void setup(){
size(600, 600, P2D);
smooth(8);
}
void draw(){
background(255);
// Bending to the left
float bending = sin(frameCount*.05) * .3;
p0[1] -= bending;
pushMatrix();
translate(width/2, height/2);
float minX = min( min(p0[0], p3[0]), min(p2[0], p1[0]) );
float maxX = max( max(p0[0], p3[0]), max(p2[0], p1[0]) );
float cptX = (minX+maxX)/2;
//Rotation Angle
float angle = atan2(p3[1]-p0[1], p3[0]-p0[0]);
//Pivot Height
float PH = p0[1] + (p3[1]-p0[1]) * (cptX-p0[0])/(p3[0]-p0[0]);
for (int i = 0; i < nQuads; i++){
float PivotHeight = (i % 2 == 1) ? PH : H/2;
//Height translation
if (i > 0){
translate(0, PivotHeight);
}
//Rotate once every 2 quads
if (i%2 == 1){
rotate(angle*2);
}
//Height translation
//Flip all quads except 1st one
if (i > 0){
translate(0, PivotHeight);
scale(1, -1);
}
//Flipping horizontally the last 5 top QUADS
if (i == 3){
scale(-1, 1);
translate(0, PivotHeight);
rotate(-angle*2);
translate(0, PivotHeight);
translate(-xOffset , H/2 - p0[1]);
}
object();
}
popMatrix();
}
void object() {
beginShape(QUADS);
vertex(p0[0], p0[1]);
vertex(p1[0], p1[1]);
vertex(p2[0], p2[1]);
vertex(p3[0], p3[1]);
endShape();
}

"Mirroring" a PShape object (rotation / translation issue) with Processing

I would like to "mirror" a PShape object like in the picture below:
I know how to display multiple shapes and how to invert them (screenshot below) but things get complicated when I have to rotate them (and probably translating them) so as they "stick" to the preceding shapes (first picture).
I've been trying to compute an angle with the first 2 vertices of the original shape (irregular quadrilateral) and the atan2() function but to no avail.
I would really appreciate if someone could help figuring how to solve this problem.
int W = 20;
int H = 20;
int D = 20;
PShape object;
void setup(){
size(600, 600, P2D);
smooth();
}
void draw(){
background(255);
pushMatrix();
translate(width/2, height/1.3);
int td = -1;
for (int i = 0; i < 6; i++){
translate(0, td*H*2);
scale(-1, 1);
rotate(PI);
object();
td *= -1;
}
popMatrix();
}
void object() {
beginShape(QUADS);
vertex(-20, 20);
vertex(20, 0);
vertex(20, -20);
vertex(-20, -20);
endShape();
}
To do what you want you have to create a shape by 2 given angles for the top and the bottom of the shape angleT and `angleB´. The origin (0,0) is in the center of the shape. This causes that the pivots for the rotations are in the middle of the slopes of the shape :
int W = 40;
int H = 40;
float angleT = -PI/18;
float angleB = PI/15;
PShape object;
void object() {
float H1 = -H/2 + W*tan(angleB);
float H2 = H/2 + W*tan(angleT);
beginShape(QUADS);
vertex(-W/2, -H/2);
vertex(W/2, H1);
vertex(W/2, H2);
vertex(-W/2, H/2);
endShape();
}
When you draw the parts, then you should distinguish between even and odd parts. The parts have to be flipped horizontal by inverting the y axis (scale(1, -1)). The even parts have to be rotated by the double of angleB and the odd parts have to be rotated by the doubled of angleT. For the rotation, the center of the slopes (pivots) have to be translated to the origin:
void setup(){
size(600, 600, P2D);
smooth();
}
void draw(){
background(255);
translate(width/2, height/2);
float HC1 = -H/2 + W*tan(angleB)/2;
float HC2 = H/2 + W*tan(angleT)/2;
for (int i = 0; i < 15; i++){
float angle = (i % 2 == 0) ? -angleB : -angleT;
float HC = (i % 2 == 0) ? HC1 : HC2;
translate(0, -HC);
rotate(angle*2);
translate(0, -HC);
object();
scale(1, -1);
}
}
The algorithm works for any angle, positive and negative including 0.
This algorithm can be further improved. Let's assume you have a quad, defined by 4 points (p0, p1, p2, p3):
float[] p0 = {10, 0};
float[] p1 = {40, 10};
float[] p2 = {60, 45};
float[] p3 = {0, 60};
PShape object;
void object() {
beginShape(QUADS);
vertex(p0[0], p0[1]);
vertex(p1[0], p1[1]);
vertex(p2[0], p2[1]);
vertex(p3[0], p3[1]);
endShape();
}
Calculate the the minimum, maximum, centerpoint, pivots and angles:
float minX = min( min(p0[0], p1[0]), min(p2[0], p3[0]) );
float maxX = max( max(p0[0], p1[0]), max(p2[0], p3[0]) );
float minY = min( min(p0[1], p1[1]), min(p2[1], p3[1]) );
float maxY = max( max(p0[1], p1[1]), max(p2[1], p3[1]) );
float cptX = (minX+maxX)/2;
float cptY = (minY+maxY)/2;
float angleB = atan2(p1[1]-p0[1], p1[0]-p0[0]);
float angleT = atan2(p2[1]-p3[1], p2[0]-p3[0]);
float HC1 = p0[1] + (p1[1]-p0[1])*(cptX-p0[0])/(p1[0]-p0[0]);
float HC2 = p3[1] + (p2[1]-p3[1])*(cptX-p3[0])/(p2[0]-p3[0]);
Draw the shape like before:
for (int i = 0; i < 6; i++){
float angle = (i % 2 == 0) ? -angleB : -angleT;
float HC = (i % 2 == 0) ? HC1 : HC2;
translate(cptX, -HC);
rotate(angle*2);
translate(-cptX, -HC);
object();
scale(1, -1);
}
Another approach would be to stack the shape on both sides:
For this you have to know the heights of the pivots (HC1, HC2) and the angles (angleB, angleT). So this can be implemented based on both of the above approaches.
Define the pivot points and the directions of the top and bottom edge:
PVector dir1 = new PVector(cos(angleB), sin(angleB));
PVector dir2 = new PVector(cos(angleT), sin(angleT));
PVector pv1 = new PVector(0, HC1); // or PVector(cptX, HC1)
PVector pv2 = new PVector(0, HC2); // or PVector(cptX, HC2)
Calculate the intersection point (X) of the both edges. Of course this will work only if the
edges are not parallel:
PVector v12 = pv2.copy().sub(pv1);
PVector nDir = new PVector(dir2.y, -dir2.x);
float d = v12.dot(nDir) / dir1.dot(nDir);
PVector X = pv1.copy().add( dir1.copy().mult(d) );
The stack algorithm works as follows:
for (int i = 0; i < 8; i++){
float fullAngle = angleT-angleB;
float angle = fullAngle * floor(i/2);
if ((i/2) % 2 != 0)
angle += fullAngle;
if (i % 2 != 0)
angle = -angle;
float flip = 1.0;
if (i % 2 != 0)
flip *= -1.0;
if ((i/2) % 2 != 0)
flip *= -1.0;
pushMatrix();
translate(X.x, X.y);
rotate(angle);
scale(1, flip);
rotate(-angleB);
translate(-X.x, -X.y);
object();
popMatrix();
}

Problems rendering a Polar Zonohedron in Processing

I've recently been looking into Zonohedrons and Rob Bell made beautiful ones. I had a play with the free Polar Zonohedron Sketchup Plugin and thought about playing with the geometry using Processing. So far I've open up the plugin/Ruby script and tried to port it directly, but I am not experienced with Ruby and have been using the Sketchup Ruby API reference.
The geometry part of the code is mostly in the polar_zonohedron function:
def polar_zonohedron #frequency, pitch = atan(sqrt(2)/2), len = 1.0 # frequency,pitch,length
mo = Sketchup.active_model
mo.start_operation "polar_zonohedron"
prompts = ["Frequency", "Pitch in radians", "Length"]
values = [8, "atan( sqrt(2)/2 )", 12.inch]
results = inputbox prompts, values, "Polar Zonohedron"
return if not results # This means that the user canceld the operation
ents = mo.active_entities
grp = ents.add_group
ents = grp.entities
grp.frequency = results[0]
grp.pitch = eval( results[1] )
grp.length = results[2]
pts=[]
#we begin by setting pts[0] to the origin
pts[0] = Geom::Point3d.new(0,0,0)
vector = Geom::Vector3d.new(cos(grp.pitch),0,sin(grp.pitch) ) #tilt pitch vector up the xz plane
vector.length = grp.length
#Using the origin as the initial generator we iterate thru each zone of the zonohedron
#our first task is to define the four points of the base rhomb for this zone
#at the end the pts[3] becomes our new origin for the rhomb of the next zone
1.upto(grp.frequency-1){ |i|
p_rotate = Geom::Transformation.rotation( pts[0] , Geom::Vector3d.new(0,0,1), i*2*PI/grp.frequency )
#obtain the other three points of the rhomb face
pts[1] = pts[0].transform vector
pts[3] = pts[1].transform( p_rotate )
pts[2] = pts[3].transform( vector )
#we now have the 4 points which make this zone's base rhomb
#so we rotate around the origin frequency times making a star pattern of faces
0.upto(grp.frequency-1){ |j|
f_rotate = Geom::Transformation.rotation( Geom::Point3d.new(0,0,0) , Geom::Vector3d.new(0,0,1), j*2*PI/grp.frequency )
ents.add_face( pts.collect{|p| p.transform(f_rotate)} )
}
#set the origin for the rhomb of the next zone
pts[0] = pts[3]
}
mo.commit_operation
end
I've understood the loops but am slightly confused by transforms:
pts[1] = pts[0].transform vector
pts[3] = pts[1].transform( p_rotate )
pts[2] = pts[3].transform( vector )
As far as I can tell pts[1] is the vector addiction of pts[0] and vector,
and pts[3] is pts[1] multiplied by the p_rotate rotation matrix. Would pts[2] also be an addition (between pts[3] and vector )?
Here's what my attempt looks like so far:
//a port attempt of Rob Bell's polar_zonohedron.rb script - http://zomebuilder.com/
int frequency = 3;
float pitch = atan(sqrt(2)/2);
float length = 24;
ArrayList<Face> faces = new ArrayList<Face>();
void setup(){
size(400,400,P3D);
strokeWeight(3);
setupZome();
}
void setupZome(){
faces.clear();
PVector[] pts = new PVector[4];
pts[0] = new PVector();
PVector vector = new PVector(cos(pitch),0,sin(pitch));
vector.mult(length);
for(int i = 1 ; i < frequency; i++){
PMatrix3D p_rotate = new PMatrix3D();
p_rotate.rotate(i * TWO_PI / frequency, 0,0,1);
//PVector v = new PVector();
//p_rotate.mult(pts[0],v);
//pts[0] = v;
pts[1] = PVector.add(pts[0],vector);
pts[3] = new PVector();
p_rotate.mult(pts[1],pts[3]);
pts[2] = PVector.add(pts[3],vector);
for(int j = 0; j < frequency; j++){
PMatrix3D f_rotate = new PMatrix3D();
f_rotate.rotate(j*2*PI/frequency , 0,0,1);
Face f = new Face();
for(PVector pt : pts){
PVector p = new PVector();
f_rotate.mult(pt,p);
f.add(p.get());
}
faces.add(f);
}
pts[0] = pts[3];
}
}
void draw(){
background(255);
lights();
translate(width * .5, height * .5,0);
rotateY(map(mouseX,0,width,-PI,PI));
rotateX(map(mouseY,0,height,-PI,PI));
drawAxes(100);
pushMatrix();
translate(0,0,-frequency * length * .25);
for(Face f : faces){
beginShape(mousePressed ? QUADS : POINTS);
for(PVector p : f.pts) vertex(p.x,p.y,p.z);
endShape();
}
popMatrix();
}
void keyPressed(){
if(keyCode == UP && frequency < 32) frequency++;
if(keyCode == DOWN && frequency > 2) frequency--;
setupZome();
}
void drawAxes(int size){
stroke(192,0,0);
line(0,0,0,size,0,0);
stroke(0,192,0);
line(0,0,0,0,size,0);
stroke(0,0,192);
line(0,0,0,0,0,size);
}
class Face{
ArrayList<PVector> pts = new ArrayList<PVector>();
Face(){}
void add(PVector p){
if(pts.size() <= 4) pts.add(p);
}
}
I feel I'm close, but I'm getting the loop conditionals and vertex indices wrong.
Any tips on how to fix this?
I was very close, but not paying attention to all the details.
Turns out I get the correct mesh if I don't increment the rotation on p_rotate:
p_rotate.rotate(TWO_PI / frequency, 0,0,1);
instead of
p_rotate.rotate(i * TWO_PI / frequency, 0,0,1);
Here is the full code listing:
//a port attempt of Rob Bell's polar_zonohedron.rb script - http://zomebuilder.com/
int frequency = 3;
float pitch = atan(sqrt(2)/2);
float length = 24;
ArrayList<Face> faces = new ArrayList<Face>();
void setup(){
size(400,400,P3D);
strokeWeight(3);
setupZome();
}
void setupZome(){
faces.clear();
PVector[] pts = new PVector[4];
pts[0] = new PVector();
PVector vector = new PVector(cos(pitch),0,sin(pitch));
vector.mult(length);
for(int i = 1 ; i < frequency-1; i++){
PMatrix3D p_rotate = new PMatrix3D();
p_rotate.rotate(TWO_PI / frequency, 0,0,1);
pts[1] = PVector.add(pts[0],vector);
pts[3] = new PVector();
p_rotate.mult(pts[1],pts[3]);
pts[2] = PVector.add(pts[3],vector);
for(int j = 0; j < frequency; j++){
PMatrix3D f_rotate = new PMatrix3D();
f_rotate.rotate(j*2*PI/frequency , 0,0,1);
Face f = new Face();
for(PVector pt : pts){
PVector p = new PVector();
f_rotate.mult(pt,p);
f.add(p.get());
}
faces.add(f);
}
pts[0] = pts[3];
}
}
void draw(){
background(255);
lights();
translate(width * .5, height * .5,0);
rotateY(map(mouseX,0,width,-PI,PI));
rotateX(map(mouseY,0,height,-PI,PI));
drawAxes(100);
pushMatrix();
translate(0,0,-frequency * length * .25);
for(Face f : faces){
beginShape(mousePressed ? QUADS : POINTS);
for(PVector p : f.pts) vertex(p.x,p.y,p.z);
endShape();
}
popMatrix();
}
void keyPressed(){
if(keyCode == UP && frequency < 32) frequency++;
if(keyCode == DOWN && frequency > 3) frequency--;
setupZome();
}
void drawAxes(int size){
stroke(192,0,0);
line(0,0,0,size,0,0);
stroke(0,192,0);
line(0,0,0,0,size,0);
stroke(0,0,192);
line(0,0,0,0,0,size);
}
class Face{
ArrayList<PVector> pts = new ArrayList<PVector>();
Face(){}
void add(PVector p){
if(pts.size() <= 4) pts.add(p);
}
}
And here a couple of screenshots:

Resources