discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Rods between 3D points

LG
Laurence Gonsalves
Thu, Jul 9, 2015 6:11 PM

I'm trying to construct a model where I have a set of 3D points, and I want
to create "rods" connecting certain pairs of these points, where a "rod" is
cylinder connecting the points, capped with hemispheres.

This brute force solution works:

module rod(a, b, r) {
    hull() {
        translate(a) sphere(r);
        translate(b) sphere(r);
    }
}

but it's really slow for high values of $fn if I have a bunch of these
rods.

I was thinking that I could do something like this instead:

module rod(a, b, r) {
    rotate([?,?,?]) translate(a) union() {
        sphere(r);
        cylinder(r=thickness, h=h);
        translate([0,0,h]) sphere(r);
    }
    h = norm(a-b);
}

which should be much faster, but before I try to work out the trig for that
rotation, I'm wondering if there's an existing solution to "generate a rod
between two points" or to "create a rotation that points z in the direction
of a vector".

I'm trying to construct a model where I have a set of 3D points, and I want to create "rods" connecting certain pairs of these points, where a "rod" is cylinder connecting the points, capped with hemispheres. This brute force solution works: module rod(a, b, r) { hull() { translate(a) sphere(r); translate(b) sphere(r); } } but it's *really* slow for high values of $fn if I have a bunch of these rods. I was thinking that I could do something like this instead: module rod(a, b, r) { rotate([?,?,?]) translate(a) union() { sphere(r); cylinder(r=thickness, h=h); translate([0,0,h]) sphere(r); } h = norm(a-b); } which should be much faster, but before I try to work out the trig for that rotation, I'm wondering if there's an existing solution to "generate a rod between two points" or to "create a rotation that points z in the direction of a vector".
LG
Laurence Gonsalves
Thu, Jul 9, 2015 6:36 PM

On Thu, Jul 9, 2015 at 11:11 AM, Laurence Gonsalves
from-openscad@xenomachina.com wrote:

I was thinking that I could do something like this instead:

 module rod(a, b, r) {
     rotate([?,?,?]) translate(a) union() {

Oops! This should be:

    translate(a) rotate([?,?,?]) union() {
On Thu, Jul 9, 2015 at 11:11 AM, Laurence Gonsalves <from-openscad@xenomachina.com> wrote: > > I was thinking that I could do something like this instead: > > module rod(a, b, r) { > rotate([?,?,?]) translate(a) union() { Oops! This should be: translate(a) rotate([?,?,?]) union() {
RK
Roland Koebler
Thu, Jul 9, 2015 9:10 PM

Hi,

but before I try to work out the trig for that
rotation, I'm wondering if there's an existing solution to [...]
"create a rotation that points z in the direction
of a vector".

yes, there is: multmatrix().
With multmatrix(), you can use a 4x4 transformation-matrix, and don't
need to play with rotate and angles. It takes some time to understand
transformation-matrices, but it's a lot easier afterwards.

In more detail:

multmatrix() uses a matrix-vector-multiplication for
coordinate-transformation, so:
[new_x]  [u0 v2 w0 t0]  [x]
[new_y] = [u1 v1 w1 t1] * [y]
[new_z]  [u2 v0 w2 t2]  [z]
[1]      [0  0  0  1]    [1]

All this does is:

  • translate by t
  • transform (rotate, stretch, shear, mirror), so that
    • the new x-axis points to u (so, [1,0,0] is transformed to [u0,u1,u2])
    • the new y-axis points to v (so, [0,1,0] is transformed to [v0,v1,v2])
    • the new z-axis points to w (so, [0,0,1] is transformed to [w0,w1,w2])

So, you need to determine u,v and w:

  • w / z-axis:
    In OpenSCAD, a cylinder is created in z-direction, so it goes from [0,0,0]
    to [0,0,h]. But you want it to go from [0,0,0] to (b-a). So, you already
    know the new z-axis:
    w = (b-a) / norm(b-a).

    So, if you use:
    [new_x]  [1  0 (b-a)[0] a0]  [x]
    [new_y] = [0  1 (b-a)[1] a1] * [y]
    [new_z]  [0  0 (b-a)[2] a2]  [z]
    [1]      [0  0  0      1]    [1]
    your cylinder would already point into the correct direction
    (but wouldn't be a real cylinder anymore).

  • u / x-axis: The new x-axis must be perpendicular to the new z-axis;
    otherwise the cylinder wouldn't look like a cylinder. For simplicity,
    we chose that it should also be perpendicular to the old z-axis.
    So, we need the cross-product of the old and the new z-axis:
    u = cross(w, [0,0,1]) / norm(w, [0,0,1])

  • v / y-axis: The new y-axis must be perpendicular to the new x- and
    the new z-axis. So, we can again use the cross-product:
    v = cross(w, u) / norm(w, u)

That's it.

So, in OpenSCAD:

module rod(a, b, r) {
translate(a) sphere(r=r);
translate(b) sphere(r=r);

dir = b-a;
h   = norm(dir);
if(dir[0] == 0 && dir[1] == 0) {
    // no transformation necessary
    cylinder(r=r, h=h);
}
else {
    w  = dir / h;
    u0 = cross(w, [0,0,1]);
    u  = u0 / norm(u0);
    v0 = cross(w, u);
    v  = v0 / norm(v0);
    multmatrix(m=[[u[0], v[0], w[0], a[0]],
                  [u[1], v[1], w[1], a[1]],
                  [u[2], v[2], w[2], a[2]],
                  [0,    0,    0,    1]])
    cylinder(r=r, h=h);
}

}

enjoy,
Roland

Hi, > but before I try to work out the trig for that > rotation, I'm wondering if there's an existing solution to [...] > "create a rotation that points z in the direction > of a vector". yes, there is: multmatrix(). With multmatrix(), you can use a 4x4 transformation-matrix, and don't need to play with rotate and angles. It takes some time to understand transformation-matrices, but it's a lot easier afterwards. In more detail: multmatrix() uses a matrix-vector-multiplication for coordinate-transformation, so: [new_x] [u0 v2 w0 t0] [x] [new_y] = [u1 v1 w1 t1] * [y] [new_z] [u2 v0 w2 t2] [z] [1] [0 0 0 1] [1] All this does is: - translate by t - transform (rotate, stretch, shear, mirror), so that - the new x-axis points to u (so, [1,0,0] is transformed to [u0,u1,u2]) - the new y-axis points to v (so, [0,1,0] is transformed to [v0,v1,v2]) - the new z-axis points to w (so, [0,0,1] is transformed to [w0,w1,w2]) So, you need to determine u,v and w: - w / z-axis: In OpenSCAD, a cylinder is created in z-direction, so it goes from [0,0,0] to [0,0,h]. But you want it to go from [0,0,0] to (b-a). So, you already know the new z-axis: w = (b-a) / norm(b-a). So, if you use: [new_x] [1 0 (b-a)[0] a0] [x] [new_y] = [0 1 (b-a)[1] a1] * [y] [new_z] [0 0 (b-a)[2] a2] [z] [1] [0 0 0 1] [1] your cylinder would already point into the correct direction (but wouldn't be a real cylinder anymore). - u / x-axis: The new x-axis must be perpendicular to the new z-axis; otherwise the cylinder wouldn't look like a cylinder. For simplicity, we chose that it should also be perpendicular to the old z-axis. So, we need the cross-product of the old and the new z-axis: u = cross(w, [0,0,1]) / norm(w, [0,0,1]) - v / y-axis: The new y-axis must be perpendicular to the new x- and the new z-axis. So, we can again use the cross-product: v = cross(w, u) / norm(w, u) That's it. So, in OpenSCAD: module rod(a, b, r) { translate(a) sphere(r=r); translate(b) sphere(r=r); dir = b-a; h = norm(dir); if(dir[0] == 0 && dir[1] == 0) { // no transformation necessary cylinder(r=r, h=h); } else { w = dir / h; u0 = cross(w, [0,0,1]); u = u0 / norm(u0); v0 = cross(w, u); v = v0 / norm(v0); multmatrix(m=[[u[0], v[0], w[0], a[0]], [u[1], v[1], w[1], a[1]], [u[2], v[2], w[2], a[2]], [0, 0, 0, 1]]) cylinder(r=r, h=h); } } enjoy, Roland
M
MichaelAtOz
Thu, Jul 9, 2015 11:01 PM

Thank you Roland, for that clear and concise explanation.
I think I even remember some of my pure math from Uni 30-odd years ago...


Unless specifically shown otherwise above, my contribution is in the Public Domain; To the extent possible under law, I have waived all copyright and related or neighbouring rights to this work. This work is published globally via the internet. :) Inclusion of works of previous authors is not included in the above.

The TPP is no simple “trade agreement.”  Fight it! http://www.ourfairdeal.org/

View this message in context: http://forum.openscad.org/Rods-between-3D-points-tp13104p13111.html
Sent from the OpenSCAD mailing list archive at Nabble.com.

Thank you Roland, for that clear and concise explanation. I think I even remember some of my pure math from Uni 30-odd years ago... ----- Unless specifically shown otherwise above, my contribution is in the Public Domain; To the extent possible under law, I have waived all copyright and related or neighbouring rights to this work. This work is published globally via the internet. :) Inclusion of works of previous authors is not included in the above. The TPP is no simple “trade agreement.” Fight it! http://www.ourfairdeal.org/ -- View this message in context: http://forum.openscad.org/Rods-between-3D-points-tp13104p13111.html Sent from the OpenSCAD mailing list archive at Nabble.com.
M
MichaelAtOz
Thu, Jul 9, 2015 11:03 PM

The cylinders should be transformed too, they look odd.


Unless specifically shown otherwise above, my contribution is in the Public Domain; To the extent possible under law, I have waived all copyright and related or neighbouring rights to this work. This work is published globally via the internet. :) Inclusion of works of previous authors is not included in the above.

The TPP is no simple “trade agreement.”  Fight it! http://www.ourfairdeal.org/

View this message in context: http://forum.openscad.org/Rods-between-3D-points-tp13104p13112.html
Sent from the OpenSCAD mailing list archive at Nabble.com.

The cylinders should be transformed too, they look odd. ----- Unless specifically shown otherwise above, my contribution is in the Public Domain; To the extent possible under law, I have waived all copyright and related or neighbouring rights to this work. This work is published globally via the internet. :) Inclusion of works of previous authors is not included in the above. The TPP is no simple “trade agreement.” Fight it! http://www.ourfairdeal.org/ -- View this message in context: http://forum.openscad.org/Rods-between-3D-points-tp13104p13112.html Sent from the OpenSCAD mailing list archive at Nabble.com.
M
MichaelAtOz
Thu, Jul 9, 2015 11:03 PM

strike cylinder, insert sphere.


Unless specifically shown otherwise above, my contribution is in the Public Domain; To the extent possible under law, I have waived all copyright and related or neighbouring rights to this work. This work is published globally via the internet. :) Inclusion of works of previous authors is not included in the above.

The TPP is no simple “trade agreement.”  Fight it! http://www.ourfairdeal.org/

View this message in context: http://forum.openscad.org/Rods-between-3D-points-tp13104p13113.html
Sent from the OpenSCAD mailing list archive at Nabble.com.

strike cylinder, insert sphere. ----- Unless specifically shown otherwise above, my contribution is in the Public Domain; To the extent possible under law, I have waived all copyright and related or neighbouring rights to this work. This work is published globally via the internet. :) Inclusion of works of previous authors is not included in the above. The TPP is no simple “trade agreement.” Fight it! http://www.ourfairdeal.org/ -- View this message in context: http://forum.openscad.org/Rods-between-3D-points-tp13104p13113.html Sent from the OpenSCAD mailing list archive at Nabble.com.
LG
Laurence Gonsalves
Mon, Jul 13, 2015 11:27 PM

On Thu, Jul 9, 2015 at 2:10 PM, Roland Koebler rk-list@simple-is-better.org
wrote:

With multmatrix(), you can use a 4x4 transformation-matrix, and don't
need to play with rotate and angles. It takes some time to understand
transformation-matrices, but it's a lot easier afterwards.

Thank you! That's a great explanation.

I just tried your code out, and it worked perfectly, though I was surprised
to find that it turns out to actually be slower than using hull() when
rendering. This was a complete surprise to me. Creating a single rod with
$fn=72 took 20 seconds to render! This is no fault of your code -- even
rendering a cylinder and two spheres without the multmatrix takes longer to
render than the hull of two spheres. (I guess I really should have tested
that before I asked my original question.)

In any case, I'll definitely remember to consider using multmatrix when
working out the transformation I need as individual rotations turns out to
be too hairy.

Thanks again.

On Thu, Jul 9, 2015 at 2:10 PM, Roland Koebler <rk-list@simple-is-better.org> wrote: > With multmatrix(), you can use a 4x4 transformation-matrix, and don't > need to play with rotate and angles. It takes some time to understand > transformation-matrices, but it's a lot easier afterwards. Thank you! That's a great explanation. I just tried your code out, and it worked perfectly, though I was surprised to find that it turns out to actually be slower than using hull() when rendering. This was a complete surprise to me. Creating a single rod with $fn=72 took 20 seconds to render! This is no fault of your code -- even rendering a cylinder and two spheres without the multmatrix takes longer to render than the hull of two spheres. (I guess I really should have tested that before I asked my original question.) In any case, I'll definitely remember to consider using multmatrix when working out the transformation I need as individual rotations turns out to be too hairy. Thanks again.
R
runsun
Tue, Jul 14, 2015 4:46 AM

xenomachina wrote

I just tried your code out, and it worked perfectly, though I was
surprised
to find that it turns out to actually be slower than using hull() when
rendering. This was a complete surprise to me. Creating a single rod with
$fn=72 took 20 seconds to render! This is no fault of your code -- even
rendering a cylinder and two spheres without the multmatrix takes longer
to
render than the hull of two spheres. (I guess I really should have tested
that before I asked my original question.)

I recalled reading somewhere that multimatrix is a 4x4 matrix and
is slow in nature, so many game programmers separate the rotation
part (3x3) and the translation part (3x1) to speed it up. Not sure if it
that's the case though.

It will be much faster if you can generate points on both end points
and use polyhedron. Steps :

Let P,Q are the two points where the rod links.

  1. Generate points around P ==> P_pts = [p0,p1,p2.....]
  2. Generate points around Q ==> Q_pts= [q0,q1,q2 ...]

they are points of a circle at P,Q, resp:

  1. then pts = concat( P_pts, Q_pts) = [ p0,p1.... q0,q1 ...]

  2. Use the faces() (see  here
    http://forum.openscad.org/A-faces-function-for-simple-polyhedrons-td12809.html
    ) to generate faces:

    faces = faces(shape="rod", nside= len(P_pts), nseg=1)

  3. Make a polyhedron:

    polyhedron( point= pts, faces=faces )

In cases of 72 sides, this takes about 1~2 sec or less .


$  Runsun Pan, PhD

$ -- OpenScad_DocTest ( Thingiverse ), faces , Offliner

$ -- hash parameter model: here , here

$ -- Linux Mint 17.1 Rebecca x64  + OpenSCAD 2015.03.15/2015.04.01.nightly

--
View this message in context: http://forum.openscad.org/Rods-between-3D-points-tp13104p13138.html
Sent from the OpenSCAD mailing list archive at Nabble.com.

xenomachina wrote > I just tried your code out, and it worked perfectly, though I was > surprised > to find that it turns out to actually be slower than using hull() when > rendering. This was a complete surprise to me. Creating a single rod with > $fn=72 took 20 seconds to render! This is no fault of your code -- even > rendering a cylinder and two spheres without the multmatrix takes longer > to > render than the hull of two spheres. (I guess I really should have tested > that before I asked my original question.) I recalled reading somewhere that multimatrix is a 4x4 matrix and is slow in nature, so many game programmers separate the rotation part (3x3) and the translation part (3x1) to speed it up. Not sure if it that's the case though. It will be much faster if you can generate points on both end points and use polyhedron. Steps : Let P,Q are the two points where the rod links. 1) Generate points around P ==> P_pts = [p0,p1,p2.....] 2) Generate points around Q ==> Q_pts= [q0,q1,q2 ...] they are points of a circle at P,Q, resp: 3) then pts = concat( P_pts, Q_pts) = [ p0,p1.... q0,q1 ...] 4) Use the faces() (see here <http://forum.openscad.org/A-faces-function-for-simple-polyhedrons-td12809.html> ) to generate faces: faces = faces(shape="rod", nside= len(P_pts), nseg=1) 5) Make a polyhedron: polyhedron( point= pts, faces=faces ) In cases of 72 sides, this takes about 1~2 sec or less . ----- $ Runsun Pan, PhD $ -- OpenScad_DocTest ( Thingiverse ), faces , Offliner $ -- hash parameter model: here , here $ -- Linux Mint 17.1 Rebecca x64 + OpenSCAD 2015.03.15/2015.04.01.nightly -- View this message in context: http://forum.openscad.org/Rods-between-3D-points-tp13104p13138.html Sent from the OpenSCAD mailing list archive at Nabble.com.