views:

779

answers:

1

Hi,

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

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

star paper sculpture

triangles

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

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

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

alt text

+1  A: 

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);
drhirsch
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
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.
drhirsch
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
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.
drhirsch
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
...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
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.
drhirsch
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.
drhirsch
And there is a bug: cn2 is never written to, it should be written with points[i].normalized(cn2);
drhirsch
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.
drhirsch
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
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...
drhirsch
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.
drhirsch
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
I've uploaded the progress so far here:http://www.openprocessing.org/visuals/?visualID=6346
George Profenza
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 ;-)
drhirsch
Cool. I'll play with s and s2 then and see how that goes. Thanks again !
George Profenza