TL; DR: https://youtu.be/6qNHy48iOOs
I've mentioned that I have (long) been working on a model of my house. For some reason I re-obsessed today. I don't remember what got me started, but I said "it would be nice to be able to make a walkthrough animation". Walkthrough is a bit awkward, because normal $vpr/$vpt is, you might say, model-centric rather than camera-centric. Critically, simply changing $vpr orbits the camera around the model, keeping it pointed at $vpt, or rotates the model around $vpt, whichever way you want to look at it. That's not what you want for a walkthrough; for a walkthrough you want to rotate the camera while the model stays still. Unsurprisingly, it turns out not to be very hard turn camera-centric positions and rotations into $vpr/$vpt. For a camera position pos and a look direction (theta and phi) dir, what you need is: $vpd = 140; $vpt = pos + torect([$vpd, dir[0], dir[1]]); $vpr = [180-dir[1], 0, (dir[0]+270)%360]; torect is a spherical-to-rectangular converter. It takes [rho, theta, phi] and returns [x,y,z]. If you don't know [rho, theta, phi]: https://en.wikipedia.org/wiki/Spherical_coordinate_system Or, to type it correctly: [r, θ, φ]. Well, except rho is ρ; apparently sometimes people are consistently Greek and sometimes they mix English with Greek. // Given a [rho, theta] or [rho, theta, phi], transform to // an [x,y] or [x,y,z]. function torect(p) = len(p) == 3 ? torect3(p) : torect2(p); function torect2(p) = [ p[0] * cos(p[1]), p[0] * sin(p[1]) ]; function torect3(p) = [ p[0] * cos(p[1]) * sin(p[2]), p[0] * sin(p[1]) * sin(p[2]), p[0] * cos(p[2]) ]; OK, now we have a way to position and turn a camera, in a camera-centric way.
So let's make an animation out of it. route = [ [0, [-230,-800,72], [90, 95]], // outside front door [2, [-230,-150,72], [90, 95]], // into living room [1, [-230,-150,72], [180, 100]], // look at fireplace [.25, [-230,-150,72], [180, 100]], // pause a moment [1, [-230,-150,72], [90, 95]], // turn back west [1, [-230,25,72], [90, 95]], // forward to family room [1, [-230,25,72], [0, 95]], // turn to parallel bar [1, [-35,25,72], [0, 95]], // forward to kitchen entrance [1, [-35,25,72], [-90, 95]], // turn towards kitchen [1, [-35,-40,72], [-90, 95]], // enter kitchen [1, [-35,-40,72], [-200, 100]], // turn to look at kitchen [.25, [-35,-40,72], [-200, 100]], // pause a moment [1, [-35,-40,72], [-90, 95]], // turn back east [1, [-15,-180,72], [-90, 95]], // into dining room [1, [-15,-180,72], [-180, 95]], // into dining room [1, [-230,-180,72], [-180, 95]], // into dining room [1, [-230,-180,72], [-90, 95]], // into dining room [1, [-230,-300,72], [-90, 95]], // into dining room ]; The first element is the time for that particular movement, from the previous position. (The units are relative; whatever they total up to is the full range of the animation. The actual number of frames and duration are set by the animation controls.) The second is the [x,y,z] coordinate the camera is to move to. The third is the [theta, phi] that the camera is to point at. Tracking around a curve exceeds my cleverness right now, so all of these are either moves or turns. (Subtlety: if you keep making turns in the same direction, you have to keep going past 0 and 360; if you try to go from 359 to 1 a simple mechanism (like the one below) will go the long way. You need to go from 359 to 361.) The times in that route are not very helpful, because you need to be able to index $t into the array. Here's a perhaps horribly-inefficient way to turn them into absolute times: function routesum(a, start, end) = start > end ? 0 : a[start][0] + routesum(a, start+1, end); r2 = [ for (i = [0:len(route)-1]) [ routesum(route, 0, i), route[i][1], route[i][2] ] ]; That will give us an array where the first element of each entry is [0, 2, 3, 3.25, 4.25, ...]. t_start = 0; t_end = r2[len(r2)-1][0]; This says what subset of the animation to display, in time units. This is "all of it". positions = [ for (e = r2) [ e[0], e[1] ]]; directions = [ for (e = r2) [ e[0], e[2] ]]; I'm going to interpolate in a moment. I have an interpolator that will do a triplet at a time, but not two triplets at a time. pos = xyzinterp($t*(t_end-t_start)+t_start, positions); dir = xyzinterp($t*(t_end-t_start)+t_start, directions); As you will see in the comments for xyzinterp when I show it in a moment, it seems like lookup() should be able to do this. But it doesn't. (Or at least it didn't the last time I checked.) So this gives us a camera position and direction for the current point in the animation, and we come back to the magic lines I mentioned at the top: $vpd = 140; $vpt = pos + torect([$vpd, dir[0], dir[1]]); $vpr = [180-dir[1], 0, (dir[0]+270)%360]; (I haven't looked into what effect $vpd has. I suspect that mathematically it doesn't have any effect, that all values yield the same result.) One thing that was helpful was to have a variation: //$vpr=[0,0,0]; //translate(pos) color("red") { // rotate(dir[0]) translate([0,-2,-1]) cube([10,4,2]); // sphere(5); //} which has you watching down as a sphere with a nose walks through
the model. I suppose if I was really clever I'd set $vpt to pos,
so that the sphere stays in the center of the screen and the model
moves past it. Here's xyzinterp: // Given a value and a table of value/position pairs, interpolate a // position for that value. // It seems like lookup() should do this sort of vector interpolation // on its own, but it doesn't seem to. function xyzinterp(v, table) = let (x= [for (i=[0:len(table)-1]) [table[i][0], table[i][1][0]]]) let (y= [for (i=[0:len(table)-1]) [table[i][0], table[i][1][1]]]) let (z= [for (i=[0:len(table)-1]) [table[i][0], table[i][1][2]]]) [lookup(v, x), lookup(v, y), lookup(v,z)]; OK, so I fed that all to you in snippets. Here's a complete
self-contained demo. (I have this running in 2019.05.) We're in a helicopter facing south. (You can tell because of the big S.) We lift off, fly through the gate to the other side of the gate, turn around, fly back through the gate, turn around to face south again, and land. // Animate a flythrough of a model. // Jordan Brown 20 December 2020 // Public Domain. Go for it. // First a couple of utility functions. These really belong in // separate library files. // Given a [rho, theta] or [rho, theta, phi], transform to // an [x,y] or [x,y,z]. function torect(p) = len(p) == 3 ? torect3(p) : torect2(p); function torect2(p) = [ p[0] * cos(p[1]), p[0] * sin(p[1]) ]; function torect3(p) = [ p[0] * cos(p[1]) * sin(p[2]), p[0] * sin(p[1]) * sin(p[2]), p[0] * cos(p[2]) ]; // Given a value and a table of value/position pairs, interpolate a // position for that value. // It seems like lookup() should do this sort of vector interpolation // on its own, but it doesn't seem to. function xyzinterp(v, table) = let (x= [for (i=[0:len(table)-1]) [table[i][0], table[i][1][0]]]) let (y= [for (i=[0:len(table)-1]) [table[i][0], table[i][1][1]]]) let (z= [for (i=[0:len(table)-1]) [table[i][0], table[i][1][2]]]) [lookup(v, x), lookup(v, y), lookup(v,z)]; // Lay an array of cubes, to make it less confusing when we spin. for (x=[-1000:100:1000], y=[-1000:100:1000]) translate([x,y,0]) cube(10); // Put some tall ones at the corners, again to help you stay oriented. for (x=[-1000,1000], y=[-1000,1000]) translate([x,y,0]) cube([10,10,100]); // Put NSEW at the compass points. for (e = [[90, "N"], [180, "W"], [270, "S"], [0, "E"]]) { rotate(e[0]) translate([1000,0,0]) rotate([90,0,-90]) linear_extrude(height=1) text(e[1], size=100, halign="center"); } // Finally, add a gate to fly through. module gate() { difference() { translate([-50,-5,0]) cube([100,10,100]); translate([0,0,50]) cube([80,12,80], center=true); } } gate(); // Now that we have some terrain to fly through, let's fly! // Let's start facing south through the gate, lift off, // fly through the gate, turn around, fly back, turn around again, // and land in our original position. route = [ [0, [0,300,0], [270, 90]], [1, [0,300,50], [270, 90]], [1, [0,-300,50], [270, 90]], [3, [0,-300,50], [90, 90]], [1, [0,300,50], [90, 90]], [2, [0,300,50], [-90, 90]], [1, [0,300,0], [-90, 90]], [1, [0,300,0], [-90, 90]] ]; // Convert our "relative" times above into absolute times. // There might be a much more efficient way to do it; this one // is quadratic on the number of steps in the animation. function routesum(a, start, end) = start > end ? 0 : a[start][0] + routesum(a, start+1, end); r2 = [ for (i = [0:len(route)-1]) [ routesum(route, 0, i), route[i][1], route[i][2] ] ]; // Start and stop points. These are helpful if you want to work on // a small part of a larger animation. t_start = 0; t_end = r2[len(r2)-1][0]; // Extract a list of times-and-positions // and a list of times-and-look-directions. positions = [ for (e = r2) [ e[0], e[1] ]]; directions = [ for (e = r2) [ e[0], e[2] ]]; // Get the current position and look direction. pos = xyzinterp($t*(t_end-t_start)+t_start, positions); dir = xyzinterp($t*(t_end-t_start)+t_start, directions); // And here's where we make it happen. $vpd = 140; $vpt = pos + torect([$vpd, dir[0], dir[1]]); $vpr = [180-dir[1], 0, (dir[0]+270)%360]; _______________________________________________ OpenSCAD mailing list [hidden email] http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org |
On 12/20/2020 10:44 PM, Jordan Brown
wrote:
Not that it matters but, sigh, I was copying and pasting entries to make each additional entry, and never got around to fixing up the comments on the last few. They should be "turn towards living room, forward to living room, turn towards front door, leave house". _______________________________________________ OpenSCAD mailing list [hidden email] http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org |
In reply to this post by JordanBrown
I eventually want the "route" to be specified turtle-style: move
forward N, turn left N, et cetera. However, that adds significant
trickiness. First, there's the obvious thing that the positions are
all relative and to get the position and look direction at time N
you have to add up all of the moves before that point.
Trickier, though, is that the position isn't simply added (even rotated by the look angle) because a forward move combined with a turn yields an arc. It would also be tempting to add turtle-style view navigation to the OpenSCAD UI, so that you could move forward or back, and turn the camera left/right/up/down. Any interest? _______________________________________________ OpenSCAD mailing list [hidden email] http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org |
I thought about mentioning this when I first saw your example: I've
implemented turtle both in 2d and 3d versions, including turns with arcs. The 3d version produces a transformation list as output, so it would give you look angle as well as position. (Accumulating to time N isn't hard in a turtle implementation. You compute the next "move" and multiply its matrix by the current transform (in the appropriate way). The hardest thing about a 3d turtle is deciding what rotations actually mean and how to calculate them. https://github.com/revarbat/BOSL2/wiki/turtle3d.scad JordanBrown wrote > I eventually want the "route" to be specified turtle-style: move > forward N, turn left N, et cetera. However, that adds significant > trickiness. First, there's the obvious thing that the positions are all > relative and to get the position and look direction at time N you have > to add up all of the moves before that point. > > Trickier, though, is that the position isn't simply added (even rotated > by the look angle) because a forward move combined with a turn yields an > arc. > > It would also be tempting to add turtle-style view navigation to the > OpenSCAD UI, so that you could move forward or back, and turn the camera > left/right/up/down. Any interest? > > _______________________________________________ > OpenSCAD mailing list > Discuss@.openscad > http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org -- Sent from: http://forum.openscad.org/ _______________________________________________ OpenSCAD mailing list [hidden email] http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org |
Free forum by Nabble | Edit this page |