Translating horizontally inverted quads


#1

A week 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.

I was able to achieve this effect but I am now facing another issue when translating the last 5 top horizontally inverted quads.

enter image description here

When bending the object, the first 3 quads get separated from the last 5. 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.

enter image description here

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

#2

Please link between crossposts. This question has also been asked here:


#3

Hi @Kevin,

Do you have an idea how I could possibly compute the pivot height ?


#4

Right, so I found some of my older functions that are useful for this that come in pairs and output only 1 number, while they should output PVector instead or something. In any case, I used them as is to create pretty horrific code that makes your final ideal output possible.
Move mouse left/right to give the tower a tilt. Hold mouse to set the second tilt used more at the top parts of the tower. Move mouse up/down to set the steepness between the first and second tilt.

int sign(float in){
  //Tells the sign of the input number.
  if(in<0){return -1;}
  if(in>0){return 1;}
  return 0;
}

float mix(float in1,float in2,float mix){
  //Blends between 2 values - 0 mix equals in1, 1 mix equals in2, 0.5 mix equals half of both i.e. average.
 return in1+(in2-in1)*mix; 
}


float angle(float x, float y){
  //If you were to draw an arrow going from 0,0 to x,y coordinates in 2D space, considering up is 0 degrees and right is 90 degrees, this is the angle of that arrow.
  if(x==-0){x=0;}
  if(y==-0){y=0;}
return y!=0?(x<0?(180*sign(y)-(atan(y/abs(x))/(PI)*180)):atan(y/abs(x))/(PI)*180):sign(x)<0?(180):0;
//return y!=0?(-sign(x)*(180*sign(y)-(atan(y/abs(x))/(PI)*180))/(PI)*180):sign(x)<0?(180):0;
}
float rotX(float x, float y, float degrees){
  //Takes current x,y on 2D space, rotates them around the 0,0 pivot point at degrees degrees, and outputs X of the result.
  return sqrt(x*x+y*y)*cos((angle(x,y)+degrees)/180*PI);
}

float rotY(float x, float y, float degrees){
  //Takes current x,y on 2D space, rotates them around the 0,0 pivot point at degrees degrees, and outputs Y of the result.
  return sqrt(x*x+y*y)*sin((angle(x,y)+degrees)/180*PI);
}

float rotX(float originx, float originy, float x, float y, float degrees){
  //Takes current x,y on 2D space, rotates them around the originx, originy pivot point at degrees degrees, and outputs X of the result.
  return rotX(x-originx,y-originy,degrees)+originx;
}
float rotY(float originx, float originy, float x, float y, float degrees){
  //Takes current x,y on 2D space, rotates them around the originx, originy pivot point at degrees degrees, and outputs Y of the result.
  return rotY(x-originx,y-originy,degrees)+originy;
}



float offset=-50;
float sideSizeL = 25;
float sideSizeR = 25;
int count = 10;

float angle = 0;
float angle2 = 0;

float angleMixSteepness = 0.5;

int oddSegment = 5;
//At which shape it should change which side of the tower zigzags.

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

void draw(){
  
  if(mousePressed) angle2 = (mouseX-width/2)*0.1;
              else angle  = (mouseX-width/2)*0.1;
  angleMixSteepness = (float)mouseY*2/height;
  background(255);
  float currentX=width/2;
  float currentY=height-10;
  float currentAngle = 0;
//  line(width/2,height-10,rotX(width/2,height-10,width/2,height-60,angle),rotY(width/2,height-10,width/2,height-60,angle));
  for(int i=0;i<count;i++){
    float nextAngle=currentAngle+mix(angle,angle2,pow((float)i/count,angleMixSteepness));
    float nextX=rotX(currentX,currentY,currentX,currentY+offset,nextAngle);
    float nextY=rotY(currentX,currentY,currentX,currentY+offset,nextAngle);
    
    //Current coordinates:
    //currentX currentY currentAngle - starting position and angle of this current segment.
    //nextX nextY nextAngle - ending position of this current segment - these values will be used for the start of the next segment.
    //All lines are based around these coordinates and rotating them around these angles.
    
      
      float sideSizeLeftCurrent = sideSizeL;
      float sideSizeLeftNext = sideSizeL;
      float sideSizeRightCurrent = sideSizeR;
      float sideSizeRightNext = sideSizeR;
      //sideSizeLeft/RightCurrent - Offsets from the starting point to the left/right to form the base of this segment. 
      //sideSizeLeft/RightCurrent - Offsets from the starting point to the left/right to form the roof of this segment or base of the next segment. 
      
      //Make them do the zig-zag thing.
            if(i<oddSegment){
        if(i%2==1) sideSizeLeftCurrent-=30;
              else sideSizeLeftNext-=30;
      }else if(i==oddSegment){
        sideSizeLeftCurrent-=30;
      }else if(i>oddSegment){
        if(i%2==1) sideSizeRightCurrent-=30;
              else sideSizeRightNext-=30;
      }
        
    
    line(rotX(currentX,currentY,currentX-sideSizeLeftCurrent,currentY,currentAngle),rotY(currentX,currentY,currentX-sideSizeLeftCurrent,currentY,currentAngle),
         rotX(currentX,currentY,currentX+sideSizeRightCurrent,currentY,currentAngle),rotY(currentX,currentY,currentX+sideSizeRightCurrent,currentY,currentAngle));
    //Line going below the segment. Makes the base.
      //line(lastX,lastY,lastX1,lastY1);
      //Line going through the middle of the segment.
      beginShape(QUADS);
      vertex(rotX(currentX,currentY,currentX-sideSizeLeftCurrent,currentY,currentAngle),rotY(currentX,currentY,currentX-sideSizeLeftCurrent,currentY,currentAngle));
      //Top left
      
      vertex(rotX(currentX,currentY,currentX+sideSizeRightCurrent,currentY,currentAngle),rotY(currentX,currentY,currentX+sideSizeRightCurrent,currentY,currentAngle));
      //Top right
      
      vertex(rotX(nextX,   nextY,   nextX+sideSizeRightNext,   nextY,   nextAngle)   ,rotY(nextX   ,nextY   ,nextX+sideSizeRightNext   ,nextY   ,nextAngle));
      //Bottom right
      
      vertex(rotX(nextX   ,nextY   ,nextX-sideSizeLeftNext   ,nextY   ,nextAngle)   ,rotY(nextX   ,nextY   ,nextX-sideSizeLeftNext   ,nextY   ,nextAngle));
      //Bottom left
      endShape();
    
    currentAngle+=mix(angle,angle2,pow((float)i/count,angleMixSteepness));
    currentX = nextX; currentY = nextY;
    
  }
  
}

P.S. Forgive me for these long names - I named them like X1 Y4 and so on and it was pretty confusing. If you want to have a bit less horrific experience with my code, I suggest to right-click values, “Rename…”, and set them to something shorter… ._.


#5

Hi @Architector_4,

Thank you for the reply, I really appreciate you took the time to try to provide a solution. However, I’m afraid you haven’t understood the problem, and as result your answer is inconsistent with my question.

Your approach is not based on the original code I provided and the output is very (very) different from what I’m trying to achieve.

Regarding my issue, I’ve finally found the correct way to compute the pivot height but I still have to figure out how to compute the translation so as the array of quads stay connected no matter how strong the bending is.
Question has been edited.

Thank you anyway.


#6

Oh. Yeah, what I remember when replying is that I didn’t read your text, saw “ideal output” and went on my adventures. Sorry!

Anyways, I think I solved your problem. I replaced:

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

With:

         //YES working --> Flipping horizontally the last 5 top QUADS
         if (i == 3){
           scale(-1, 1);
           rotate(angle*2);
           translate(-xOffset, xOffset*angle); //trying to align the quads on the X axis. Y translation is missing
           //Yes, angle is meant for rotation and not translation. I don't care! :D
         }
        //Height translation
        //Flip all quads except 1st one
        if (i > 0){
          translate(0, PivotHeight);
          scale(1, -1);
       }

And it produces the “ideal output” now.
Basically I slammed an angle into a translate function and moved everything around, and it’s still almost your code and somehow now works.

Did I do it now? :smiley:


#7

Almost !

If you increase the bending you’ll see that quad n°3 and quad n°4 get separated at some point.

Fortunately I just found a workaround. I don’t really get what I’m doing and it’s probably not the most efficient solution but it works.

if (i == 3) {
  scale(-1, 1);
  translate(0, PivotHeight); 
  rotate(-angle*2);
  translate(0, PivotHeight);
  translate(-xOffset , H/2 - p0[1]);
}

Thank you for your time and suggestions !


#8

hi @solub
i worry many of your problems from resent questions
( that the QUADs got separated… and you had to solve it with??? )
might have been avoided
by a different structure:
-a- use QUAD STRIP
-b- make 2 start points ( like by pvector )
-c- make a loop over…

  • -d- make 2 more points to FINISH one QUAD each

now while i try with random distance ( startline to endline )
you can use your moving math…

anyhow that strip can never be separated to your QUADs problem
but i am not sure it fits for your application.
still hope it helps.

int many = 8;
PVector[] points = new PVector[many];
void setup() {
  size(400, 400);
  points[0] = new PVector(0, 0);
  points[1] = new PVector(20, 0);  
  for ( int i = 2; i<many; i++) 
    points[i] = new PVector(points[i-2].x+int(random(10, 20)), points[i-2].y+int(random(20, 30)));
  printArray(points);
}
void make_shape() {
  beginShape(QUAD_STRIP); 
  fill(0, 200, 0);
  stroke(0, 0, 200);
  for ( int i = 0; i<many; i++) vertex(points[i].x, points[i].y);
  endShape();
}
void draw() { 
  background(200, 200, 0);
  translate(width/2, height/2);
  make_shape();
}

this does the same but using a named shape and regenerate at mouse click

int many = 8;
PVector[] points = new PVector[many];
PShape Qstrip;

void setup() {
  size(400, 400);
  make_points();
  make_shape();
}
void make_points() {  
  points[0] = new PVector(0, 0);
  points[1] = new PVector(20, 0);  
  for ( int i = 2; i<many; i++) 
    points[i] = new PVector(points[i-2].x+int(random(5, 30)), points[i-2].y+int(random(5, 30)));
  printArray(points);
}
void make_shape() {
  Qstrip = createShape();
  Qstrip.beginShape(QUAD_STRIP); 
  Qstrip.fill(0, 200, 0);
  Qstrip.stroke(0, 0, 200);
  for ( int i = 0; i<many; i++) Qstrip.vertex(points[i].x, points[i].y);
  Qstrip.endShape();
}
void draw() { 
  background(200, 200, 0);
  shape(Qstrip,width/2, height/2);
}

void mousePressed() {
  make_points();
  make_shape();  
}


#9

@kll Many thanks for the kind suggestion. It doesn’t quite fit into my project ritgh now but could be interesting for future sketches. Thanks again.