WEBGL cylinder between two points

Hi there!

The 3D primitive cylinder function draws a cylinder from its midpoint. I am trying to write a function that will enable me to draw a cylinder between two specified points.

My approach so far has been to find the length and midpoint of the line that connects the two points. So I now have a cylinder at the correct position, and I need to rotate it so the ends meet the target start and end points. I’m not sure how to proceed. I’m guessing I need to find an angle for X rotation and an angle for Y rotation, but how do I go about this? Can someone point me in the right direction?

function setup() {
  createCanvas(500, 500, WEBGL);
  noStroke();
}

function draw() {
  background(220);

  //point 1 for reference
  push();
  fill(0,0,255);
  translate(30,30,30);
  sphere(3);
  pop();

  //point 2
  push();
  fill(255,0,0);
  translate(60,60,60);
  sphere(3);
  pop();

  //cylinder
  drawCylinder(createVector(30,30,30),    createVector(60,60,60));

}

function drawCylinder(startPoint,endPoint) {
    push();
    //height of cylinder is distance between startPoint and endPoint
    var h = sqrt((((startPoint.x - endPoint.x)**2) +
            ((startPoint.y - endPoint.y)**2) +
            ((startPoint.z - endPoint.z)**2)))

    //translate to midpoint
    var c = createVector(((startPoint.x+endPoint.x)/2),
                        ((startPoint.y + endPoint.y)/2),
                        ((startPoint.z+endPoint.z)/2))
    //marker 
    translate(c.x,c.y,c.z);
    fill(0);
    sphere(4);

    //find rotation angles?

    //draw the cylinder
    fill(0,150,0);
    cylinder(3,h);

    pop();
}

Any pointers will be greatly appreciated, I’m out of my depth with the maths/ geometry here.

1 Like

Hello everyone,

I found a solution to this using trigonometry - as far as I can tell, this works. But if anyone reading it does have suggestions, if it can be improved please let me know.



var point1;
var point2;

function setup() {
  createCanvas(500, 500, WEBGL);
  background(220);
  noStroke();
  ambientLight(150);
  pointLight(250, 250, 250, 50, 50, 400);

  point1 = createVector(-100,-0,30);
  point2 = createVector(200,80,50);

  //point 1
  push();
  fill(0,0,255);
  translate(point1.x,point1.y,point1.z);
  sphere(5);
  pop();

  //point 2
  push();
  fill(255,0,0);
   translate(point2.x,point2.y,point2.z);
  sphere(5);
  pop();

  //cylinder
  drawCylinder(point1,point2);

}

function draw() {
 

}

function drawCylinder(startPoint,endPoint) {
    push();
    //height of cylinder is distance between startPoint and endPoint
    var h = sqrt((((startPoint.x - endPoint.x)**2) +
            ((startPoint.y - endPoint.y)**2) +
            ((startPoint.z - endPoint.z)**2)))

    //translate to midpoint
    var c = createVector(((startPoint.x+endPoint.x)/2),
                        ((startPoint.y + endPoint.y)/2),
                        ((startPoint.z+endPoint.z)/2))

     translate(c.x,c.y,c.z);
    // fill(0);
    // sphere(4);

    //first rotation
    p1 = c;
    p2 = createVector(c.x,c.y+(h*0.5), c.z);
    p3 = endPoint;

    //distance 1
    var d1 = sqrt(  (p2.x - p1.x)**2
                    +(p2.y-p1.y)**2 );
                    //print(a);
    //distance 2
    var d2 = sqrt( (p3.x - p1.x)**2
                    +(p3.y - p1.y)**2);
                    //print(b);
    //distance 3
    var d3 =    sqrt( (p3.x - p2.x)**2
                    +(p3.y - p2.y)**2);
                    //print(c);

    var zrot =  acos((d1**2 + d2**2 - d3**2)/(2*d1*d2));

    //second rotation
    p1 = c;
    p2 = createVector(endPoint.x,endPoint.y, c.z);
    p3 = endPoint;

    //distance 1
    var a = sqrt(  (p2.x - p1.x)**2
                    +(p2.y-p1.y)**2 );
                    print(a);
    //distance 2
    var b = sqrt( (p3.x - p1.x)**2
                    +(p3.y - p1.y)**2);
                    print(b);
    //distance 3
    var c =    sqrt( (p3.x - p2.x)**2
                    +(p3.y - p2.y)**2
                    +(p3.z - p2.z)**2);
                    print(c);

    var xrot =  acos((a**2 + b**2 - c**2)/(2*a*b));
    print(xrot);

    //draw
    rotateZ(-zrot);
    rotateX(xrot);

    fill(0,150,0);
    cylinder(3,h);

    pop();
}

3 Likes

Actually this doesn’t seem to work in every case :confused: I’ll keep thinking about it but suggestions still welcome.

what is the error?
if you get a NaN it’s that the acos get values outside of -1 … 1
https://p5js.org/reference/#/p5/acos


with your code i get warning / see
var c is created 2 times ?? as vector and as number


yes, there is a bug in your code ??
https://editor.p5js.org/kll/sketches/GGs8gxaiL
possibly sign/math problem when p1.x > p2.x

1 Like

Usually Vector has the trig you need already built-in.

A simple solution expressed in terms of Vector methods (which makes the code shorter and easier to write / read) is to:

  1. translate to the midpoint of the line
  2. rotate to the heading of the line
  3. draw the cylinder to the magnitude of the line.

Here is an example of drawing cylinders in that way, with simple mouse input.

https://editor.p5js.org/jeremydouglass/sketches/rbRfS7TSd

Note that the methods used (Vector.lerp(), Vector.heading(), vector.mag()) are all listed here:

4 Likes

Thankyou jeremydouglass! I knew it should have been easier than what I was doing.

Can you unpack a little, just for my conceptual understanding, what the PI/2.0 is doing in this line?

 rotate(heading.heading() + PI/2.0);

Edit: I’ve just realised heading() is for 2D vectors. How would I need to change this to to make it work with 3D vectors?

Just try taking it out, draw some short lines, and see what happens.

The cylinder radius lies along the heading – so the two points define the plane of the circular cross section, and then extrudes out of that plane based on the distance between the lines. But you don’t want that. You want the cylinder height axis to lie along the heading. So rotate it by 90 degrees. Now the radius extrudes around the line, and the height runs along the line.

This depends a lot on what you are trying to do. Do the cylinders all lie in a plane? Will you be animating the tumbling of these cylinders, or will they have fixed orientations? Where is your point data coming from – is there an origin point and a heading towards a second derived point, or are they two inputs (probably not with mouse, if you are trying to input in 3D?).

Continuous 3D rotation has a bunch of potential problems – including gimbal lock – and the closest to a one-size-fits-all solution is quaternions, which are complex. Here’s an overview (in Processing, not p5.js, but the relevant API is the same).

The easiest thing might be to rotateZ into the 2D plane where you want your cylinder, then use the 2D solution that you already have within that plane.

1 Like

Thanks, I will read that link tomorrow with a cup of coffee and try to make sense of it.

The 3D points are joint positions from Kinect data- I am trying to create a (non-animated) visualization of several frames of Kinect data, the cylinders are to join the spheres that represent the joints.

The points share an origin (between the shoulders, if I remember correctly).
I don’t want to animate the skeleton, but I would like to be able to rotate the entire visual around the Y axis. Apologies if I’ve misunderstood your questions - my background is decidedly non mathsy.

Thanks again for the link, I’ll check it out.

Hi AshaSato,
Some of the solutions (e.g. the ones using 'heading() ) above will only work in 2D.
I did something similar with a 3D-Vector ‘v’, with a magnitude representing the desired length of your cylinder. When you only have 2 3D or 2D points, you may calculate the magnitude with dist(x1,x2,y1,y2,z1,z2) and your vector v using vector_to_endpoint.sub(vector_to_startpoint)
I calculate the rotation angles around x,y and z with arctan atan(), and rotate the cylinder accordingly.

Like this

   push();
   rotateX(atan(v[i].y / v[i].z));
   rotateY(atan(v[i].x / v[i].z));
   rotateZ(atan(v[i].y / v[i].x));
   translate(0, v[i].mag() / 2, 0);
   cylinder(cyldiam, v[i].mag());
   pop();
2 Likes