walkthrough / flythrough

4 messages
Open this post in threaded view
|

walkthrough / flythrough

 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. Astute readers will note that I don't yet have a way to roll the camera.  A roll angle would be a straightforward addition to rho, theta, phi, and would undoubtedly involve setting the y-rotate component of \$vpr, but I haven't tried it yet and there might be unpleasant details. 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
Open this post in threaded view
|

Re: walkthrough / flythrough

 On 12/20/2020 10:44 PM, Jordan Brown wrote: ```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 ]; ``` 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