12

I'm trying to extrude a path in 3d. Nothing fancy yet, just following some points and using a regular polygon for 'tubing'. I'm using Processing for now to quickly prototype, but will later turn the code into OpenGL.

My problem is rotating the 'joints' at the right angles. I think I have a rough idea how to get the angles, not sure.

I've started from a sample by Simon Greenwold(Processing > File > Examples > 3D > Form > Vertices).Here's my attempt so far:

UPDATE > REFACTORED/SIMPLIFIED CODE

Here is the main sketch code:
int pointsNum = 10;
Extrusion star;

int zoom = 0;

void setup() {
  size(500, 500, P3D);

  PVector[] points = new PVector[pointsNum+1];
  for(int i = 0 ; i <= pointsNum ; i++){
    float angle = TWO_PI/pointsNum * i;
    if(i % 2 == 0)
      points[i] = new PVector(cos(angle) * 100,sin(angle) * 100,0);
    else
      points[i] = new PVector(cos(angle) * 50,sin(angle) * 50,0);
  }

  star = new Extrusion(10,10,points,3);
}

void draw() {
  background(0);
  lights();
  translate(width / 2, height / 2,zoom);
  rotateY(map(mouseX, 0, width, 0, PI));
  rotateX(map(mouseY, 0, height, 0, PI));
  rotateZ(-HALF_PI);
  noStroke();
  fill(255, 255, 255);
  translate(0, -40, 0);
  star.draw();
}

void keyPressed(){
  if(key == 'a') zoom += 5;
  if(key == 's') zoom -= 5;
}

And here is the Extrusion class:

import processing.core.PMatrix3D;

class Extrusion{

  float topRadius,bottomRadius,tall,sides;
  int pointsNum;
  PVector[] points;

  Extrusion(){}

  Extrusion(float topRadius, float bottomRadius, PVector[] points, int sides) {
    this.topRadius = topRadius;
    this.bottomRadius = bottomRadius;
    this.points = points;
    this.pointsNum = points.length;
    this.sides = sides;
  }

  void draw() {
    if(pointsNum >= 2){  
      float angle = 0;
      float angleIncrement = TWO_PI / sides;

      //begin draw segments between caps
      angle = 0;
      for(int i = 1; i < pointsNum ; ++i){
        beginShape(QUAD_STRIP);
        for(int j = 0; j < sides + 1; j++){
          vertex(points[i-1].x + cos(angle) * topRadius, points[i-1].y, points[i-1].z + sin(angle) * topRadius);
          vertex(points[i].x + cos(angle) * bottomRadius, points[i].y, points[i].z + sin(angle) * bottomRadius);

          angle += angleIncrement;
          }
        endShape();
      }
      //begin draw segments between caps
    }else println("Not enough points: " + pointsNum);
  }
}

UPDATE

Here is how my sketch looks like:

processing extrude http://doc.gold.ac.uk/~ma802gp/extrude.gif

The problem is the joints aren't at the right angle, so the extrude looks wrong. This isn't a very good example, as this could be achieved with a lathe. If I can get a lathe to work with an arbitrary set of points and an axis that will be great. I am using extrusion because I am trying to create geometric bodies based on the art of Liviu Stoicoviciu.

Here are some samples:

star painting http://doc.gold.ac.uk/~ma802gp/star_painting.jpg

star paper sculpture http://doc.gold.ac.uk/~ma802gp/star_paper_sculpture.jpg

triangles http://doc.gold.ac.uk/~ma802gp/triangles_pencil.jpg

Sorry about the poor quality.

As you can see in the triangles image, that would be achieved with extrusions.

UPDATE

Here's my attempt to use drhirsch's help in the draw method:

void draw() {
    if(pointsNum >= 2){  
      float angle = 0;
      float angleIncrement = TWO_PI / sides;

      //begin draw segments between caps
      angle = 0;
      for(int i = 1; i < pointsNum ; ++i){
        beginShape(QUAD_STRIP);
        for(int j = 0; j < sides + 1; j++){

          PVector s = new PVector(0,0,1);
          PVector cn = new PVector();
          points[i].normalize(cn);
          PVector r = s.cross(cn);
          float a = acos(s.dot(cn));
          PMatrix3D rot = new PMatrix3D(1,0,0,0,
                                        0,1,0,0,
                                        0,0,1,0,
                                        0,0,0,1);
          rot.rotate(a,r.x,r.y,r.z);
          PVector rotVec = new PVector();
          rot.mult(points[i],rotVec);
          rotVec.add(new PVector(cos(angle) * topRadius,0,sin(angle) * topRadius));

          vertex(points[i-1].x + cos(angle) * topRadius, points[i-1].y, points[i-1].z + sin(angle) * topRadius);
          vertex(rotVec.x,rotVec.y,rotVec.y);

          //vertex(points[i-1].x + cos(angle) * topRadius, points[i-1].y, points[i-1].z + sin(angle) * topRadius);
          //vertex(points[i].x + cos(angle) * bottomRadius, points[i].y, points[i].z + sin(angle) * bottomRadius);

          angle += angleIncrement;
          }
        endShape();
      }
      //begin draw segments between caps
    }else println("Not enough points: " + pointsNum);
  }

I've refactored the code so now the class that used to be called CShape is called Extrude, the code is less and hopefully simples, and I use an array of PVector objects instead of a Vector of PVector objects which might be confusing.

Here is my yet another attempt with some escher-esque results:

upated draw

void draw() {
    if(pointsNum >= 2){  
      float angle = 0;
      float angleIncrement = TWO_PI / sides;

      //begin draw segments between caps
      angle = 0;
      for(int i = 1; i < pointsNum ; ++i){
        beginShape(QUAD_STRIP);
        float angleBetweenNextAndPrevious = 0.0;
        if(i < pointsNum - 1) angleBetweenNextAndPrevious = PVector.angleBetween(points[i],points[i+1]);

        for(int j = 0; j < sides + 1; j++){

          PVector s = new PVector(0,0,1);
          PVector s2 = new PVector(0,0,1);
          PVector cn = new PVector();
          PVector cn2 = new PVector();
          points[i-1].normalize(cn);
          points[i].normalize(cn);
          PVector r = s.cross(cn);
          PVector r2 = s.cross(cn2);
          PMatrix3D rot = new PMatrix3D(1,0,0,0,
                                        0,1,0,0,
                                        0,0,1,0,
                                        0,0,0,1);
          PMatrix3D rot2 = new PMatrix3D(1,0,0,0,
                                        0,1,0,0,
                                        0,0,1,0,
                                        0,0,0,1);

          rot.rotate(angleBetweenNextAndPrevious,r.x,r.y,r.z);
          rot2.rotate(angleBetweenNextAndPrevious,r2.x,r2.y,r2.z);

          PVector rotVec = new PVector();
          rot.mult(points[i-1],rotVec);
          rotVec.add(new PVector(cos(angle) * topRadius,0,sin(angle) * topRadius));
          PVector rotVec2 = new PVector();
          rot2.mult(points[i],rotVec2);
          rotVec2.add(new PVector(cos(angle) * topRadius,0,sin(angle) * topRadius));

          vertex(rotVec.x,rotVec.y,rotVec.z);
          vertex(rotVec2.x,rotVec2.y,rotVec2.z);
          //vertex(points[i-1].x + cos(angle) * topRadius, points[i-1].y, points[i-1].z + sin(angle) * topRadius);
          //vertex(points[i].x + cos(angle) * bottomRadius, points[i].y, points[i].z + sin(angle) * bottomRadius);

          angle += angleIncrement;
          }
        endShape();
      }
      //begin draw segments between caps
    }else println("Not enough points: " + pointsNum);
  }
}

fix_test http://doc.gold.ac.uk/~ma802gp/extrude2.gif

Edit by drhirsch This should work:

void draw() {
    if(pointsNum >= 2){  
      float angle = 0;
      float angleIncrement = TWO_PI / sides;

      //begin draw segments between caps
      angle = 0;
      for(int i = 1; i < pointsNum ; ++i){
        beginShape(QUAD_STRIP);
        float angleBetweenNextAndPrevious = 0.0;
        if(i < pointsNum - 1) angleBetweenNextAndPrevious = PVector.angleBetween(points[i],points[i+1]);
        PVector s = new PVector(0,0,1);
        PVector s2 = new PVector(0,0,1);
        PVector cn = new PVector();
        PVector cn2 = new PVector();
        points[i-1].normalize(cn);
        points[i].normalize(cn2);
        PVector r = s.cross(cn);
        PVector r2 = s.cross(cn2);
        PMatrix3D rot = new PMatrix3D(1,0,0,0,
                                      0,1,0,0,
                                      0,0,1,0,
                                      0,0,0,1);
        PMatrix3D rot2 = new PMatrix3D(1,0,0,0,
                                       0,1,0,0,
                                       0,0,1,0,
                                       0,0,0,1);

        rot.rotate(angleBetweenNextAndPrevious,r.x,r.y,r.z);
        rot2.rotate(angleBetweenNextAndPrevious,r2.x,r2.y,r2.z);
        PVector rotVec = new PVector();
        PVector rotVec2 = new PVector();

        for(int j = 0; j < sides + 1; j++){
          // I am still not sure about this. Should the shape be in the xy plane 
          // if the extrusion is mainly along the z axis? If the shape is now in
          // the xz plane, you need to use (0,1,0) as normal vector of the shape
          // (this would be s and s2 above, don't use the short names I have
          // used, sorry)
          PVector shape = new PVector(cos(angle) * topRadius,0,sin(angle) * topRadius);

          rot.mult(shape, rotVec);
          rot2.mult(shape,rotVec2);

          rotVec.add(points[i-1]);
          rotVec2.add(points[i]);

          vertex(rotVec.x,rotVec.y,rotVec.z);
          vertex(rotVec2.x,rotVec2.y,rotVec2.z);
          //vertex(points[i-1].x + cos(angle) * topRadius, points[i-1].y, points[i-1].z + sin(angle) * topRadius);
          //vertex(points[i].x + cos(angle) * bottomRadius, points[i].y, points[i].z + sin(angle) * bottomRadius);

          angle += angleIncrement;
          }
        endShape();
      }
      //begin draw segments between caps
    }else println("Not enough points: " + pointsNum);
  }
}

UPDATE

Here is a simple illustration of my problem:

description http://doc.gold.ac.uk/~ma802gp/description.gif

The blue path is equivalent to the points[] PVector array in my code, if pointsNum = 6. The red path is what I'm struggling to solve, the green path is what I want to achieve.

UPDATE

Some minor issues with the order of vertices I think. Here are some print screens using 6 points and no (if/else % 2) star condition.

points1 http://doc.gold.ac.uk/~ma802gp/points1.gif

alt text http://doc.gold.ac.uk/~ma802gp/points2.gif

George Profenza
  • 50,687
  • 19
  • 144
  • 218
  • The only reason for calculating the angles would be generating normals. Why don't you do the extrusion by a translation along the z-axis? And you could use a QUAD_STRIP at the sides. – Gunther Piez Nov 30 '09 at 09:28
  • Or do you want to do a extrusion along a path with an automatic rotation to match the inclination at the tangent of this path? – Gunther Piez Nov 30 '09 at 09:30
  • @drhirsch Thanks for the tip on QUAD_STRIP, I'll use that. I want an extrusion along a path with an automatic rotation to match the inclination at the tangent of the path. I think I worked it out in 2d, having issues when moving to 3d. to get the inclination for a 2nd point I use the atan2 of the difference between the 3rd point and the 1st point and add 90 degrees(a perpendicular). Not sure how to apply that in 3d. – George Profenza Nov 30 '09 at 12:37
  • What you are trying to do sounds more like sweeping a polygon along a path than extrusion. – Eric Dec 01 '09 at 11:10
  • some help with lathing a path could be handy, but in some cases I will need extrusions, while in others lathes. – George Profenza Dec 01 '09 at 21:17
  • Thanks again drhirsch. I've posted a result on this page: http://doc.gold.ac.uk/~ma802gp/abstract It has tons of keys setup, no GUI and you have to press Trust for the sun dialog, as the sketch applet uses OpenGL – George Profenza Jan 17 '10 at 01:33

1 Answers1

4

Assuming your shape has a normal vector S. In your example S would be (0,0,1), because your shape is flat in xy. You can use the cross product between the current path vector V (normalized) and S to obtain the rotation axis vector R. You need to rotate your shape around R. The angle of rotation can be obtained from the dot product between S and V. So:

R = S x V
a = arc cos(S . V)

Now you can setup a rotation matrix with R and a and rotate the shape by it.

You can use glRotate(...) to rotate the current matrix on the stack, but this can't be done between glBegin() and glEnd(). So you have to do the matrix multiplication by yourself or with a library.

Edit: After a short look at the library you are using, you should be able to setup the rotation matrix with

PVector s = new PVector(0,0,1);  // is already normalized (meaning is has length 1)
PVector cn;
current.normalize(cn);
PVector r = s.cross(cn);
float a = acos(s.dot(cn));
PMatrix rot = new PMatrix(1, 0, 0, 0,
                          0, 1, 0, 0,
                          0, 0, 1, 0,
                          0, 0, 0, 1);
rot.rotate(a, r.x, r.y, r.z);

and now multiply each element of your shape with rot and translate it by your current path vector:

PVector rotVec;
rot.mult((PVector)shape[i], rotVec);
rotVec.add(current);
Gunther Piez
  • 29,760
  • 6
  • 71
  • 103
  • I am trying to use your help, and so far I tried this: PVector s = new PVector(0,0,1);s.normalize();//shape normal vector PVector r = s.cross(current); float a = acos(s.dot(current)); Did I get a right ? a is equal to arc cosine of the dot product between S and V ? – George Profenza Dec 01 '09 at 21:19
  • Yes, you got it exactly right. The cross product is always perpendicular to both vectors. There is a special case if both vectors have the same direction (at the very beginning of your path probably), but its easy to handle. – Gunther Piez Dec 01 '09 at 22:34
  • I've added the code with my attempt to use your explanations. Still a few things I'm lost with. I'm trying to understand 'multiply each element of your shape with rot'. each celement, means each vector, or each component of each vector ? It's this last bit of concatenating the rotations that gives me headaches. translate should be just getting the rotated vertices and adding a translation vector to that. Thanks for all the patience drhirsch! – George Profenza Dec 02 '09 at 15:59
  • I don't know the strcuture of CShape, so I just assume it consists of vertices, which will be represented as a PVector (which I assumed is a vector with 3 elements). If not (if ist consists of vector2 or something), you need to convert it to a PVector. You can multiply each of those vectors with the matrix rot. The product of a matrix and a vector is a vector again, now an element of the rotated shape. Add to this the current path vector to shift (translate) the rotated shape element to the right position. – Gunther Piez Dec 02 '09 at 16:39
  • I have updated the code to something a bit more readable hopefully. You never initialize PVector cn; and PVector rotVec; Should they be (0,0,0) PVectors.I didn't know you can normalize a vector with another vector. I somehow seem to miss the point. On a higher level I understand that the acos of the dot product of 2 vectors will give me the angle between them. I don't know how to use matrices well, but I understand that I use an identity matrix and rotate it. The cross product will give me perpendicular vector. So, once I got the rotation, the product between a vector and a matrix will be a... – George Profenza Dec 03 '09 at 03:00
  • ...vector...and to that vector I add the position vector ( translate as you well explained ). It's so strange that I feel so close and still lost. Wish I knew more math. – George Profenza Dec 03 '09 at 03:02
  • Yes, you seem to understand it :-) I didn't initialize rotVec and cn because I used them as target paramenters. If I interpret the documentation correct, they will be written to. You can't normalize a vector with another vector mathematically, this is just how the normalize() function is used. At least how I understood it. – Gunther Piez Dec 03 '09 at 21:10
  • Looking at your code, you can calculate the roatation matrix in the outer loop, because it should not change with different points of the shape, that is with j. – Gunther Piez Dec 03 '09 at 21:15
  • And there is a bug: cn2 is never written to, it should be written with points[i].normalized(cn2); – Gunther Piez Dec 03 '09 at 21:17
  • I have added code with some corrections. It seems that the normal vectors are wrong now, you need to correct them to 0,1,0 if the extrusion starts along the y axis and the outline or shape lies flat in the xz plane. – Gunther Piez Dec 03 '09 at 21:33
  • Totally awesome drhirsch ! This works well for my situation,but I was looking for something slightly different. I think I didn't make my problem clear. My apologies. I have attached an illustration of what my problem is at the end of the question. Should I accept this answer and post a new question ? – George Profenza Dec 04 '09 at 14:07
  • I don understand waht you want. It looks like the rotation is missing. Please check if angleBetweenNextAndPrevious has a meaningful value. Wait, I think I found the bug... – Gunther Piez Dec 04 '09 at 15:23
  • The points of the shape (the sin/cos thing) need to be rotated and then translated by the path vector. Currently the path vector is rotated and then translated by the shape vectors. – Gunther Piez Dec 04 '09 at 15:26
  • This is amazing ! There's one tiny detail with a few vertices, I've attached screen shots, but other than that IT IS PERFECT ! – George Profenza Dec 04 '09 at 16:47
  • I've uploaded the progress so far here: http://www.openprocessing.org/visuals/?visualID=6346 – George Profenza Dec 04 '09 at 16:55
  • The wrong vertices come probably from the wrong normal vector of the shape, as I did write in my comment. Nice to see it moving, and thanks for the rep ;-) – Gunther Piez Dec 04 '09 at 17:04
  • Cool. I'll play with s and s2 then and see how that goes. Thanks again ! – George Profenza Dec 04 '09 at 17:17