discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Some thoughts on deriving world coordinates for objects

JB
Jordan Brown
Fri, Oct 23, 2020 4:15 PM

On 10/23/2020 1:26 AM, Revar Desmera wrote:

Actually if you want to go to the effort of overriding all the
transformation built-ins (translate, rotate, scale) with user modules
calling multmatrix(), and provide an alternate call for multmatrix()
itself, it should become fairly trivial to keep track of the world
coordinates compared to local.

Yes.  I did that, mostly, but you did even more of it, and you supplied
the matrix invert that I hadn't done yet.  (Mostly because I haven't
done a matrix invert in >40 years...)

Cool!  Thanks!

I haven't tried it, but I suspect that you have a bug in
reference_frame().  When you supply a "mat" argument to it, I think you
need to first multiply by $local_matrix to get back to world
coordinates, before multiplying by the specified matrix to get to the
desired reference frame.

On 10/23/2020 1:26 AM, Revar Desmera wrote: > Actually if you want to go to the effort of overriding all the > transformation built-ins (translate, rotate, scale) with user modules > calling multmatrix(), and provide an alternate call for multmatrix() > itself, it should become fairly trivial to keep track of the world > coordinates compared to local. Yes.  I did that, mostly, but you did even more of it, and you supplied the matrix invert that I hadn't done yet.  (Mostly because I haven't done a matrix invert in >40 years...) Cool!  Thanks! I haven't tried it, but I suspect that you have a bug in reference_frame().  When you supply a "mat" argument to it, I think you need to first multiply by $local_matrix to get back to world coordinates, before multiplying by the specified matrix to get to the desired reference frame.
JB
Jordan Brown
Fri, Oct 23, 2020 4:16 PM

On 10/22/2020 7:48 PM, Jordan Brown wrote:

Combined with the $vxx variables, I think you could do things like
have label floating in a constant place on the screen, with an arrow
pointing at a particular point on the model.

And indeed, I've got a test program that does exactly that.

Can anybody point me at how to take an OpenSCAD animation sequence and
turn it into an animated GIF?

I did run into an ... interesting ... variable scoping issue. I'll
send a separate message on that.

On 10/22/2020 7:48 PM, Jordan Brown wrote: > Combined with the $vxx variables, I think you could do things like > have label floating in a constant place on the screen, with an arrow > pointing at a particular point on the model. And indeed, I've got a test program that does exactly that. Can anybody point me at how to take an OpenSCAD animation sequence and turn it into an animated GIF? I *did* run into an ... interesting ... variable scoping issue. I'll send a separate message on that.
RD
Revar Desmera
Fri, Oct 23, 2020 8:15 PM

I’m afraid I didn’t see your code until after I’d posted, but yeah, that looks like the same idea.

You’re right about the bug in reference_frame().  I wrote this code off the top of my head, and in parts cannibalized from my BOSL2 library at 3am.  Only minimal testing was actually done.  Proof of concept or some such.  The matrix inverse I implemented based on https://www.mathsisfun.com/algebra/matrix-inverse-minors-cofactors-adjugate.html

I suspect I can re-implement this code to not even need the matrix inverse code, if we construct the inverse matrix as we go.  Either by keeping a stack of the individual transforms, or by reversing the matrix multiplications in another $var.  Not sure if that works, but I think it will.

I had also written code to get the local scaling and rotation as well as the local translation, but I’m pretty sure those will get screwed up by any skew transforms.

  • Revar

On Oct 23, 2020, at 9:15 AM, Jordan Brown openscad@jordan.maileater.net wrote:

On 10/23/2020 1:26 AM, Revar Desmera wrote:

Actually if you want to go to the effort of overriding all the transformation built-ins (translate, rotate, scale) with user modules calling multmatrix(), and provide an alternate call for multmatrix() itself, it should become fairly trivial to keep track of the world coordinates compared to local.

Yes.  I did that, mostly, but you did even more of it, and you supplied the matrix invert that I hadn't done yet.  (Mostly because I haven't done a matrix invert in >40 years...)

Cool!  Thanks!

I haven't tried it, but I suspect that you have a bug in reference_frame().  When you supply a "mat" argument to it, I think you need to first multiply by $local_matrix to get back to world coordinates, before multiplying by the specified matrix to get to the desired reference frame.

I’m afraid I didn’t see your code until after I’d posted, but yeah, that looks like the same idea. You’re right about the bug in reference_frame(). I wrote this code off the top of my head, and in parts cannibalized from my BOSL2 library at 3am. Only minimal testing was actually done. Proof of concept or some such. The matrix inverse I implemented based on https://www.mathsisfun.com/algebra/matrix-inverse-minors-cofactors-adjugate.html I suspect I can re-implement this code to not even need the matrix inverse code, if we construct the inverse matrix as we go. Either by keeping a stack of the individual transforms, or by reversing the matrix multiplications in another $var. Not sure if that works, but I think it will. I had also written code to get the local scaling and rotation as well as the local translation, but I’m pretty sure those will get screwed up by any skew transforms. - Revar > On Oct 23, 2020, at 9:15 AM, Jordan Brown <openscad@jordan.maileater.net> wrote: > > On 10/23/2020 1:26 AM, Revar Desmera wrote: >> Actually if you want to go to the effort of overriding all the transformation built-ins (translate, rotate, scale) with user modules calling multmatrix(), and provide an alternate call for multmatrix() itself, it should become fairly trivial to keep track of the world coordinates compared to local. > > Yes. I did that, mostly, but you did even more of it, and you supplied the matrix invert that I hadn't done yet. (Mostly because I haven't done a matrix invert in >40 years...) > > Cool! Thanks! > > I haven't tried it, but I suspect that you have a bug in reference_frame(). When you supply a "mat" argument to it, I think you need to first multiply by $local_matrix to get back to world coordinates, before multiplying by the specified matrix to get to the desired reference frame. >
JB
Jordan Brown
Fri, Oct 23, 2020 8:27 PM

On 10/23/2020 1:15 PM, Revar Desmera wrote:

I suspect I can re-implement this code to not even need the matrix inverse code, if we construct the inverse matrix as we go.

That's what I was doing, but when you get to a multmatrix() you need to
invert it.  Since my guess is that multmatrix is more common than
resetting to the world coordinate system, it seems like it'd be better
to do the invert at the end.

I had also written code to get the local scaling and rotation as well as the local translation, but I’m pretty sure those will get screwed up by any skew transforms.

Yes.

On 10/23/2020 1:15 PM, Revar Desmera wrote: > I suspect I can re-implement this code to not even need the matrix inverse code, if we construct the inverse matrix as we go. That's what I was doing, but when you get to a multmatrix() you need to invert it.  Since my guess is that multmatrix is more common than resetting to the world coordinate system, it seems like it'd be better to do the invert at the end. > I had also written code to get the local scaling and rotation as well as the local translation, but I’m pretty sure those will get screwed up by any skew transforms. Yes.
JB
Jordan Brown
Tue, Oct 27, 2020 7:04 AM

On 10/23/2020 9:16 AM, Jordan Brown wrote:

On 10/22/2020 7:48 PM, Jordan Brown wrote:

Combined with the $vxx variables, I think you could do things like
have label floating in a constant place on the screen, with an arrow
pointing at a particular point on the model.

And indeed, I've got a test program that does exactly that.

So there's several things going on in that animation (assuming that it
works - it's my first time)...

There's a cube, translated away from the origin, orbiting around the Z axis.
There's a cube created inside that translation and rotation, that's
locked back to the origin.
The camera is orbiting around the scene.
There's a label stuck at a fixed location on the display.
There's an arrow from the label to the orbiting cube.

Here's the source.  I don't claim that this is the cleanest or best way
to do this... it's just a proof of concept.


// Initially, the local coordinates are the same as world coordinates.
$mm = [
[1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1]
];

$rm = $mm;

// Given a point in local coordinates, return its location in world coordinates.
function whereis(p3) =
let(r4 = $mm * [p3.x, p3.y, p3.z, 1])
[ r4.x, r4.y, r4.z];

// Translate, but tracking the transformation and reverse transformation.
module translate(delta) {
m = [
[1,0,0,delta.x],
[0,1,0,delta.y],
[0,0,1,delta.z],
[0,0,0,1]
];
$mm = $mm * m;
rm = [
[1,0,0,-delta.x],
[0,1,0,-delta.y],
[0,0,1,-delta.z],
[0,0,0,1]
];
$rm = rm * $rm;
multmatrix(m) children();
}

// Rotate, tracking transformations.
module rotate(a) {
a = is_num(a) ? [ 0,0,a ] : a;
rx = [
[1, 0,        0,        0],
[0, cos(a.x), -sin(a.x), 0],
[0, sin(a.x), cos(a.x),  0],
[0, 0,        0,        1]
];
ry = [
[ cos(a.y),  0, sin(a.y), 0],
[ 0,        1, 0,        0],
[ -sin(a.y), 0, cos(a.y), 0],
[ 0,        0, 0,        1]
];
rz = [
[ cos(a.z), -sin(a.z), 0, 0],
[ sin(a.z), cos(a.z),  0, 0],
[ 0,        0,        1, 0],
[ 0,        0,        0, 1]
];
m = rxryrz;
$mm = $mm * m;

 b = -a;
 rbx = [
     [1, 0,        0,         0],
     [0, cos(b.x), -sin(b.x), 0],
     [0, sin(b.x), cos(b.x),  0],
     [0, 0,        0,         1]
 ];
 rby = [
     [ cos(b.y),  0, sin(b.y), 0],
     [ 0,         1, 0,        0],
     [ -sin(b.y), 0, cos(b.y), 0],
     [ 0,         0, 0,        1]
 ];
 rbz = [
     [ cos(b.z), -sin(b.z), 0, 0],
     [ sin(b.z), cos(b.z),  0, 0],
     [ 0,        0,         1, 0],
     [ 0,        0,         0, 1]
 ];
 rm = rbz*rby*rbx;
 $rm = rm * $rm;
 multmatrix(m) children();

}

// Scale, tracking transformations
module scale(s) {
s = is_num(s) ? [ s,s,s ] : s;
m = [
[ s.x, 0,  0,  0 ],
[ 0,  s.y, 0,  0 ],
[ 0,  0,  s.z, 0 ],
[ 0,  0,  0,  1 ]
];
$mm = $mm * m;
rm = [
[ 1/s.x, 0,    0,    0 ],
[ 0,    1/s.y, 0,    0 ],
[ 0,    0,    1/s.z, 0 ],
[ 0,    0,    0,    1 ]
];
$rm = rm * $rm;

 multmatrix(m) children();

}

// Lock the children to the viewport.
// Undoes any model transforms and viewport transforms.
// I don't know if I really have the $vpd interaction correct.
module vp() {
r = $vpr;
origin() translate($vpt) rotate([0,0,r.z]) rotate([0,r.y,0]) rotate([r.x,0,0]) scale($vpd/250) children();
}

// Reset the transform for children to the world coordinate
// system, undoing any model transforms.
module origin() {
multmatrix($rm) {
// And, since we're back at the basic transformation,
// reset our record of the transforms.
$mm = [
[1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1]
];

     $rm = $mm;

     children();
 }

}

// Given an [x,y] or [x,y,z], transform to a
// [rho, theta] or [rho, theta, phi]
function topolar(p) =
len(p) == 3 ? topolar3(p) : topolar2(p);

function topolar2(p) = [
norm(p),
atan2(p.y, p.x)
];

function topolar3(p) = [
norm(p),
atan2(p.y, p.x),
atan2(norm([p.x,p.y]), p.z)
];

// Draw a line (a cylinder) from p1 to p2, with a diameter of d.
// Shorten the p1 end by off0 and the p2 end by off1.
// Add arrowheads of length h0 at the p1 and and h1 at the p2 end.
module line(p1, p2, d, off0, off1, h0, h1) {
off0 = off0 ? off0 : 0;
off1 = off1 ? off1 : 0;
translate(p1) {
polar = topolar(p2-p1);
rho = polar[0];
theta = polar[1];
phi = polar[2];
rotate([0,0,theta])
rotate([0,phi,0])
translate([0,0,off0]) {
if (h0) {
cylinder(d1=d,d2=3d, h=h0);
}
if (h1) {
translate([0,0,rho-off1-off0-h1])
cylinder(d1=3
d, d2=d, h=h1);
}
cylinder(d=d, h=rho-off1-off0);
}
}
}

// OK, let's set up a demo.
// Take a cube.
// Scale it up.  Translate and rotate it so that it orbits the origin.
// Inside that transform, reset back to the original world
// coordinates and place another cube at the origin.
// Have the camera orbit the origin.
// Add a label at a fixed place on the viewport.
// Add an arrow from the label to a point on the orbiting cube.
$vpr = [$vpr[0],$vpr[1],360*$t];
$vpt = [0,0,0];

scale(2) rotate(2360$t) translate([0,5,0]) translate([10,0,0]) group() {
cube(3);
p1 = whereis([0,0,3]);
origin() cube(3);
vp() translate([30,20,0]) group() {
p2 = whereis([0,0,0]);
text("cube", valign="center");
origin() line(p1,p2,d=0.5,off0=2, off1=2, h0=2);
}
}

On 10/23/2020 9:16 AM, Jordan Brown wrote: > On 10/22/2020 7:48 PM, Jordan Brown wrote: >> Combined with the $vxx variables, I think you could do things like >> have label floating in a constant place on the screen, with an arrow >> pointing at a particular point on the model. > > And indeed, I've got a test program that does exactly that. So there's several things going on in that animation (assuming that it works - it's my first time)... There's a cube, translated away from the origin, orbiting around the Z axis. There's a cube created *inside* that translation and rotation, that's locked back to the origin. The camera is orbiting around the scene. There's a label stuck at a fixed location on the display. There's an arrow from the label to the orbiting cube. Here's the source.  I don't claim that this is the cleanest or best way to do this... it's just a proof of concept. --- // Initially, the local coordinates are the same as world coordinates. $mm = [ [1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1] ]; $rm = $mm; // Given a point in local coordinates, return its location in world coordinates. function whereis(p3) = let(r4 = $mm * [p3.x, p3.y, p3.z, 1]) [ r4.x, r4.y, r4.z]; // Translate, but tracking the transformation and reverse transformation. module translate(delta) { m = [ [1,0,0,delta.x], [0,1,0,delta.y], [0,0,1,delta.z], [0,0,0,1] ]; $mm = $mm * m; rm = [ [1,0,0,-delta.x], [0,1,0,-delta.y], [0,0,1,-delta.z], [0,0,0,1] ]; $rm = rm * $rm; multmatrix(m) children(); } // Rotate, tracking transformations. module rotate(a) { a = is_num(a) ? [ 0,0,a ] : a; rx = [ [1, 0, 0, 0], [0, cos(a.x), -sin(a.x), 0], [0, sin(a.x), cos(a.x), 0], [0, 0, 0, 1] ]; ry = [ [ cos(a.y), 0, sin(a.y), 0], [ 0, 1, 0, 0], [ -sin(a.y), 0, cos(a.y), 0], [ 0, 0, 0, 1] ]; rz = [ [ cos(a.z), -sin(a.z), 0, 0], [ sin(a.z), cos(a.z), 0, 0], [ 0, 0, 1, 0], [ 0, 0, 0, 1] ]; m = rx*ry*rz; $mm = $mm * m; b = -a; rbx = [ [1, 0, 0, 0], [0, cos(b.x), -sin(b.x), 0], [0, sin(b.x), cos(b.x), 0], [0, 0, 0, 1] ]; rby = [ [ cos(b.y), 0, sin(b.y), 0], [ 0, 1, 0, 0], [ -sin(b.y), 0, cos(b.y), 0], [ 0, 0, 0, 1] ]; rbz = [ [ cos(b.z), -sin(b.z), 0, 0], [ sin(b.z), cos(b.z), 0, 0], [ 0, 0, 1, 0], [ 0, 0, 0, 1] ]; rm = rbz*rby*rbx; $rm = rm * $rm; multmatrix(m) children(); } // Scale, tracking transformations module scale(s) { s = is_num(s) ? [ s,s,s ] : s; m = [ [ s.x, 0, 0, 0 ], [ 0, s.y, 0, 0 ], [ 0, 0, s.z, 0 ], [ 0, 0, 0, 1 ] ]; $mm = $mm * m; rm = [ [ 1/s.x, 0, 0, 0 ], [ 0, 1/s.y, 0, 0 ], [ 0, 0, 1/s.z, 0 ], [ 0, 0, 0, 1 ] ]; $rm = rm * $rm; multmatrix(m) children(); } // Lock the children to the viewport. // Undoes any model transforms and viewport transforms. // I don't know if I really have the $vpd interaction correct. module vp() { r = $vpr; origin() translate($vpt) rotate([0,0,r.z]) rotate([0,r.y,0]) rotate([r.x,0,0]) scale($vpd/250) children(); } // Reset the transform for children to the world coordinate // system, undoing any model transforms. module origin() { multmatrix($rm) { // And, since we're back at the basic transformation, // reset our record of the transforms. $mm = [ [1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1] ]; $rm = $mm; children(); } } // Given an [x,y] or [x,y,z], transform to a // [rho, theta] or [rho, theta, phi] function topolar(p) = len(p) == 3 ? topolar3(p) : topolar2(p); function topolar2(p) = [ norm(p), atan2(p.y, p.x) ]; function topolar3(p) = [ norm(p), atan2(p.y, p.x), atan2(norm([p.x,p.y]), p.z) ]; // Draw a line (a cylinder) from p1 to p2, with a diameter of d. // Shorten the p1 end by off0 and the p2 end by off1. // Add arrowheads of length h0 at the p1 and and h1 at the p2 end. module line(p1, p2, d, off0, off1, h0, h1) { off0 = off0 ? off0 : 0; off1 = off1 ? off1 : 0; translate(p1) { polar = topolar(p2-p1); rho = polar[0]; theta = polar[1]; phi = polar[2]; rotate([0,0,theta]) rotate([0,phi,0]) translate([0,0,off0]) { if (h0) { cylinder(d1=d,d2=3*d, h=h0); } if (h1) { translate([0,0,rho-off1-off0-h1]) cylinder(d1=3*d, d2=d, h=h1); } cylinder(d=d, h=rho-off1-off0); } } } // OK, let's set up a demo. // Take a cube. // Scale it up. Translate and rotate it so that it orbits the origin. // *Inside* that transform, reset back to the original world // coordinates and place another cube at the origin. // Have the camera orbit the origin. // Add a label at a fixed place on the viewport. // Add an arrow from the label to a point on the orbiting cube. $vpr = [$vpr[0],$vpr[1],360*$t]; $vpt = [0,0,0]; scale(2) rotate(2*360*$t) translate([0,5,0]) translate([10,0,0]) group() { cube(3); p1 = whereis([0,0,3]); origin() cube(3); vp() translate([30,20,0]) group() { p2 = whereis([0,0,0]); text("cube", valign="center"); origin() line(p1,p2,d=0.5,off0=2, off1=2, h0=2); } }
R
rickan
Tue, Oct 27, 2020 12:46 PM

I think this is way cool!

I'm unable to find group() in

https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language

but I suppose it does nothing more than ensure its parent sees a single
child?

--
Sent from: http://forum.openscad.org/

I think this is way cool! I'm unable to find group() in https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language but I suppose it does nothing more than ensure its parent sees a single child? -- Sent from: http://forum.openscad.org/
JB
Jordan Brown
Tue, Oct 27, 2020 3:12 PM

On 10/27/2020 5:46 AM, rickan wrote:

I think this is way cool!

I'm unable to find group() in

https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language

but I suppose it does nothing more than ensure its parent sees a single
child?

Yes, though the precise need is to defer execution of the assignments in
the block until the parent runs.  (Ref my message " 'interesting'
scoping behavior for $ variables" of 10/23/2020 at 9:48am Pacific.)

I should have swapped it out for something documented like union().

On 10/27/2020 5:46 AM, rickan wrote: > I think this is way cool! > > I'm unable to find group() in > > https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language > > but I suppose it does nothing more than ensure its parent sees a single > child? Yes, though the precise need is to defer execution of the assignments in the block until the parent runs.  (Ref my message " 'interesting' scoping behavior for $ variables" of 10/23/2020 at 9:48am Pacific.) I should have swapped it out for something documented like union().
J
jon
Tue, Oct 27, 2020 9:22 PM

Very cool, Jordan!

On 10/27/2020 3:04 AM, Jordan Brown wrote:

On 10/23/2020 9:16 AM, Jordan Brown wrote:

On 10/22/2020 7:48 PM, Jordan Brown wrote:

Combined with the $vxx variables, I think you could do things like
have label floating in a constant place on the screen, with an arrow
pointing at a particular point on the model.

And indeed, I've got a test program that does exactly that.

Very cool, Jordan! On 10/27/2020 3:04 AM, Jordan Brown wrote: > On 10/23/2020 9:16 AM, Jordan Brown wrote: >> On 10/22/2020 7:48 PM, Jordan Brown wrote: >>> Combined with the $vxx variables, I think you could do things like >>> have label floating in a constant place on the screen, with an arrow >>> pointing at a particular point on the model. >> >> And indeed, I've got a test program that does exactly that.
JB
Jordan Brown
Sun, Jul 4, 2021 4:38 PM

On 10/27/2020 8:12 AM, Jordan Brown wrote:

On 10/27/2020 5:46 AM, rickan wrote:

[ use of group() ]

Yes, though the precise need is to defer execution of the assignments
in the block until the parent runs.  (Ref my message " 'interesting'
scoping behavior for $ variables" of 10/23/2020 at 9:48am Pacific.)

And in the current snapshot, child-block assignments are done at the
time that children() is invoked, and so this workaround is no longer
required.

I don't know whether this is really anything more than a toy, but it's a
kind of interesting toy.


For those that for some reason don't remember an exchange from 8 months
ago and don't readily have those messages available: 
http://forum.openscad.org/Some-thoughts-on-deriving-world-coordinates-for-objects-td30377.html

On 10/27/2020 8:12 AM, Jordan Brown wrote: > On 10/27/2020 5:46 AM, rickan wrote: >> [ use of group() ] > Yes, though the precise need is to defer execution of the assignments > in the block until the parent runs.  (Ref my message " 'interesting' > scoping behavior for $ variables" of 10/23/2020 at 9:48am Pacific.) And in the current snapshot, child-block assignments are done at the time that children() is invoked, and so this workaround is no longer required. I don't know whether this is really anything more than a toy, but it's a kind of interesting toy. --- For those that for some reason don't remember an exchange from 8 months ago and don't readily have those messages available:  http://forum.openscad.org/Some-thoughts-on-deriving-world-coordinates-for-objects-td30377.html