5

I have two objects at arbitrary locations, and I want to draw a cylinder between one and another. Searching for math solutions tells me I need the dot product and the cross product as angle and axis respectively, but I'm lost on producing a quaternion or transforming my results into the parameters for rotate.

This is what I have so far:

function dot (v1, v2) = [v1[0] * v2[0], v1[1] * v2[1], v1[2] * v2[2]];
function normalize (v) = v / norm(v);
function distance (v1, v2) = sqrt(
    pow(v2[0] - v1[0], 2) +
    pow(v2[1] - v1[1], 2) +
    pow(v2[2] - v1[2], 2)
);
function lookAt(v1, v2) =
    let(n1 = normalize(v1))
    let(n2 = normalize(v2))
    let(v = dot(n1, n2))
    let(angle = [acos(v[0]), acos(v[1]), acos(v[2])])
    let(axis = normalize(cross(n1, n2)))
    [0, 0, 0]; // I don't know what to return here


module cylTo(v1, v2) {
    translate(v1)
    rotate(lookAt(v1, v2))
    cylinder(distance(v1, v2), 2);
}

v1 = [-33, -20, 22];
v2 = [20, 20, -20];

// Draw sphere 1
translate(v1)
sphere(10);

// Draw sphere 2
translate(v2)
sphere(10);

// Draw cylinder between the two
cylTo(v1, v2);

What is needed for rotate() so a cylinder() points at the other object?

wizulus
  • 5,653
  • 2
  • 23
  • 40

5 Answers5

3

I found a simple solution that approximates a cylinder between two points, but it's not exact because it produced a pill (with rounded ends), not a cylinder with flat ends.

// Draw cylinder between the two
hull() {
    translate(v1)
    sphere(2);

    translate(v2)
    sphere(2);
}
wizulus
  • 5,653
  • 2
  • 23
  • 40
3

Here is how to construct a cylinder between two points in OpenSCAD with minimal arithmetic and no trigonometry. This is an exact answer to the question above.

We do this by computing the transformation matrix directly:

function transpose(A) = [for (j = [0:len(A[0])-1]) [for(i = [0:len(A)-1]) 

module cyl_between(P, Q, r){
    v = Q - P;    
    L = norm(v);  
    c = v / L;    
    is_c_vertical = ( 1 - abs(c * [0, 0, 1]) < 1e-6); 
    u = is_c_vertical ? [1, 0, 0] : cross([0, 0, 1], c); 
    a = u / norm(u); 
    b = cross(c, a);     
    MT = [a, b, c, P]; 
    M = transpose(MT); 
    multmatrix(M) cylinder(h=L, r=r, $fn=30);
}

Here is a well-commented version of the above which explains the math:

// Transpose of matrix A (swap rows and columns)
function transpose(A) = [for (j = [0:len(A[0])-1]) [for(i = [0:len(A)-1]) A[i][j]]];

//  Cylinder of radius r from P to Q
module cyl_between(P, Q, r){
    v = Q - P;    // vector from P to Q
    L = norm(v);  // height of the cylnder = dist(P, Q) 
    c = v / L;    // unit vector: direction from P to Q    
    is_c_vertical = ( 1 - abs(c * [0, 0, 1]) < 1e-6); //is c parallel to z axis?
    u = is_c_vertical ? [1, 0, 0] : cross([0, 0, 1], c); // normal to c and Z axis
    a = u / norm(u); // unit vector normal to c and the Z axis
    b = cross(c, a); // unit vector normal to a and b
    // [a, b, c] is an orthonormal basis, i.e. the rotation matrix; P is the translation
    MT = [a, b, c, P]; // the transformation matrix
    M = transpose(MT); // OpenSCAD wants vectors in columns, so we need to transpose
    multmatrix(M)
        cylinder(h=L, r=r, $fn=30);
}

Usage:

cyl_between([1,2,3], [4,5,6], 1);
Roman Kogan
  • 379
  • 2
  • 5
1

Here's the function you are looking for, there's a very similar example on the manual page, I think the tricky part is that the rotation is applied in the Z, Y, X order:

function lookAt(v1, v2) =
    let(v = v2-v1)
    [
       0,
       acos(v[2]/distance(v1,v2)),
       atan2(v[1], v[0])
    ];
Cash Lo
  • 1,052
  • 1
  • 8
  • 20
1

Give this a try. It should do what you need.

$fn=40;

function vectorLength(v1,v2) = sqrt(
    (v2[0]-v1[0])*(v2[0]-v1[0])+
    (v2[1]-v1[1])*(v2[1]-v1[1])+
    (v2[2]-v1[2])*(v2[2]-v1[2]));

function lookAt(v1, v2) =
    let(v = v2-v1)
    [
       0,
       acos(v[2]/vectorLength(v1,v2)),
       atan2(v[1], v[0])
    ];

module cylinderBetween(p1,p2,radius)
{
    translate(p1)
    rotate(lookAt(p1,p2))
    cylinder(vectorLength(p1,p2),radius,radius);
}

P1=[2,-7,2];
P2=[4,0,6];

// Draw the cylinder between
cylinderBetween(P1,P2,.25);

// Draw the ends
translate(P1)
rotate(lookAt(P1,P2))
cylinder(.01,1,1);

translate(P2)
rotate(lookAt(P1,P2))
cylinder(.01,1,1);
Scott Leslie
  • 1,147
  • 9
  • 11
0

Sorry to bump a four year old thread, but I believe this solves your problem: I took a different approach of rotating in two separate steps: The first is to give it the correct slope relative to the Z axis and the second is to give it the correct angle relative to the XY plane. I also perform a couple ugly if-else statements to deal with all the possible cases of positives and negatives.

vec1=[-300,20,-30];
vec2=[10,-30,-30];
 
d=5;
$fn=64;

coord_cylinder(vec1,vec2,5);

module coord_cylinder(v1,v2,d) {
//translates the vector so v1 is at the origin
pos=[(v2[0]-v1[0]),(v2[1]-v1[1]),(v2[2]-v1[2])];
fix_x  = v1[0] > v2[0] ? 0 : 180;
fix_z  = v1[2] > v2[2] ? 0 : 180;

xy_hyp=sqrt(pos[0]^2+pos[1]^2); //Diagonal of the XY face
cube_hyp=sqrt(pos[0]^2+pos[1]^2+pos[2]^2); //Long diagonal of the cube

translate(v1)
    fix_xz(v1,v2)
        rotate([0,0,atan(pos[1]/pos[0])+fix_z])
            rotate([0,atan(xy_hyp/pos[2])+fix_x,0])
                cylinder(h=cube_hyp,d=d);
}

module fix_xz(v1,v2) {
    if        (v1[2] >  v2[2] && v1[0] >  v2[0]){
        mirror([0,0,1])
            children();
    } else if (v1[2] <= v2[2] && v1[0] <= v2[0]){
        mirror([0,0,1])
            children();
    } else {
        children();
    }
}