Recently, I used papervision for a project. I needed to create a carousel like 3D system. Even though I think it would be helpful and interesting to some to write about the carousel, I’m going to focus on the equations this task led me to instead. I think they’ll prove to be very useful to anyone that needs to match design compositions with pixel perfect precision. To really get the most out of this article, you’ll need some papervision experience. Having made a carousel will help too At the moment, I’m unable to share any working example of the carousel, but at some point I should be able to post an example.
[Update 08/22/2008: The equations in this article and in the source will only work for builds prior to 639. I am in the process of writing an article about a set of functions that should achieve these effects for versions of papervision after 639. I am leaving this article intact for educational purposes.]
[Update 08/15/2008: sample source - it is not the carousel, but it demonstrates the use of the equations pretty well.]
Think of a carousel as having the interactive properties of an ordinary list component. The way this (and probably all) carousels work is by displaying a collection of selectable items on the screen. Only one item is selectable at a time. The selected item is visually distinguishable in some way (highlighted, repositioned, etc.) In our carousel, the selected item is distinguished by repositioning it to the center of the collection of items. The items to the left and to the right of the selected item are rotated slightly, and moved down the z axis to create the contrast needed between selected and deselected items. With papervision, the display object for each item is typically a Plane, but anything can be used.
The problem started out with having the selected plane object to display at the scale of the design composition I was given. Usually, the projects we do don’t have any 3D UI. Consequently, we have no process for exporting Collada, since the medium the composition is designed in cannot actually produce such a format (Please let me know if there is a way to export Collada from After Effects.) Consequently, I was left to replicate the composition by measuring 3D data on a 2D plane.
I soon came to realize that I essentially needed to be able to negate perspective (later this becomes even more critical since I need to apply perspective to 2D measurements, and negate perspective from 3D measurements), but I wasn’t exactly sure about how to do this. I started by trying different constants on the cameras zoom and focus properties, I kept the object Z at 100, and camera focus at default (100) because this obviously had a lot to do with whether the object was at the intended scale. I did find some constants that gave me the results I was looking for, but at a Z position of 100, I was getting hardcore perspective distortion.
After some revisions, I checked out the papervision source and came across the equation for computing perspective. It looks like this:
perspective = focus*zoom/(focus+screenZ)
screenZ is the distance between said DisplayObject3D from the camera on the z axis, focus and zoom are properties of the camera, or in other words, our perspective of the object. Given that the object and camera both have zero rotation, or, possibly a right angle or 180 depending on the object type, modifying any one of focus, zoom, or screenZ(which can be computed, -cam.z + targetZ or just object.screen.z if the object is already in position) to a certain value always produced a pixel perfect match to the composition. After spacing out into a series of day dreams, and some trial and error, I came up with equations that will solve for any of the variables, which when set, will produce the correct distance between the camera and an object to display it at a scale that matches a regular DisplayObject as it would be if you were to simply add it to the stage, that is, as opposed to making it the material of a Plane, and possibly other display types.
//Solve for cam.zoom
cam.zoom = (cam.focus + item.screen.z) / cam.focus;
//Solve for cam.focus
cam.focus = item.screen.z / (cam.zoom * (1-1/cam.zoom));
//Solve for screenZ
screenZ = cam.focus*cam.zoom-cam.focus;
screenZ = -cam.z + targetZ;
//This is obviously pointless when you have a reference to item.screen.z, but useful if you're
//precomputing before a transition. negate perspective from screenX/screenY
var negatePersp:Number = 1/(cam.focus*cam.zoom/(cam.focus + item.screen.z));
Being able to get the position that a 2D coordinate should have on the x/z plane or the y/z plane was also necessary. I needed to evenly distribute unselected objects over the left and the right areas of the selected object.
We need a way to compute the x position that a plane should have based on a limited amount of screen real estate on the x axis at some variable z position. We will need some predefined variables. item is the Plane we’re computing the x position for, cam is our Camera3D, and xArea is the area we have to display objects on the left or right side of the selected object. Note that in the actual carousel, I only compute these values once, and use them as needed during transitions. The main difference is that a predefined z position is used to compute screenZ, which would take the place of item.screen.z.
//Compute the perspective
var persp:Number = cam.focus*cam.zoom/(cam.focus + item.screen.z);
//Compute half of the width of the item at item.screen.z,
//we need this value because the registration point of 3D display objects are at their center,
//and we don't want objects falling outside of xArea
var perspItemWidth:Number = item.width/2*persp;
//The following operation gives us a value which will produce the inverse ratio of persp
var negatePersp:Number = 1/persp;
//First subtract item.width,
//which we've computed perspective for at item.screen.z from xArea,
//then apply the inverse operation of perspective on xArea,
//we should think of xArea as if it were the screen.x of an object at item.sceen.z
var negatedXArea:Number = (xArea-perspItemWidth)*negatedPersp;
Inverse Ratio of Perspective Explained
Let’s say xArea is a 2D object at 0,0 with a width of 100. It’s taking up 100 pixels on the screen on the x axis. If we want to evenly distribute objects across this area at a z position of 1300, we can’t just use the width of xArea to compute the distribution. Let’s say we magically add a z property to xArea, and we send it to z position 1300. Now that it’s further away, it doesn’t take up as much space as it did when it was right in our face, so it doesn’t take up the 100 pixels of screen space that we need it to. What we do is compute the perspective to apply to an object at such a distance, and then we divide 1 by the perspective to get the inverse ratio of the perspective. We then multiply our new ratio by the width of xArea (100.) This gives us the distribution scale we need (127) in order to use a screen pixel width of 100 (as opposed to 78) at z position 1300. We can say that we need to distribute objects across 127 “papervision 3D units” in order to distribute them evenly across 100 screen pixels at a z position of 1300.