22.11.2011 00:51
Basics of Stage3D part II: camera movement and pointAt function [expert]
In the previous part of this article I wrote about extending an ActionScript3 Documentation example of 3D graphics to make it work with multiple objects in such way that every object could still be transformed separately. The example also has a bit about the matrix used for camera movement ("view"), which truth to be told did not work like it should because it was missing some basic set-up. And this is where it gets unpleasant for people that are new to the 3d graphics, as information available on the Internet about that set-up are rather mixed and mostly confusing, especially since proper camera movement will actually depend on 3D environment settings. That is why this part of Stage3D basics article will be dedicated to virtual camera and its construction, positioning and pointAt function usage.
Starting where we left of two weeks ago (source code from the guide's previous part: Tutorial3D.zip) is a good idea since we already have some basic camera stuff laid down. Although creating a separate class for the camera would probably be a good idea, the already existing matrix by it self is sufficient for now. In any case, the matrix is ready, however results of its transformation are somewhat… unpredictable, or at least not something we could expect (i.e. x+=-5 will actually result in movement to the right instead of left). This is related to a small omission on Adobe's part in the rendering function, which can cause some sleepless nights. What I am exactly talking about? Lets have a look how finalTransform matrix is created in render function:
(Movement on the arrow keys)
Just in case you can always use the code available at the very and of this post.
Well, now that everything is properly set up, it is time to use pointAt function. And to think that without the knowledge about view matrix inversion prior to finalTransform construction, pointAt would never work as intended! This however doesn't solve all of the problems, there is still a question of parameters used by pointAt, because obviously default ones won't work. Those parameters are:
1. Target's position - matrix will be turned to face that point.
2. Facing direction - in other words "front" direction without any transformations; for camera it always will be Vector3D(0, 0, -1).
3. Up direction - which is the "rising" direction; by default Vector3D(0, -1, 0).
That should be enough. Properly working pointAt should look like this:
Unfortunately, translation won't be affect by the pointAt transformation - lets leave that for another day.
Source code: Tutorial3D-2.zip
finalTransform.identity(); finalTransform.append(box.getMatrix3D()); finalTransform.append(world); finalTransform.append(view); finalTransform.append(projection);It doesn't seem like there is anything out of ordinary here, but if we stop for a moment and take a close look - lets say we want to move whole world to the left, and camera to the right, and for that we set world to x = -10 and view to x = 5. In perfect world there would 15 units of space between world and camera now, but of course it won't be the case in our application and everything because how finalTransform is being made. Lets take another look, world is moved by -10 and view by +5:
finalTransform.append(-10); finalTransform.append(5);And what the result will be? Of course every object will be moved to the left (-10), then to the right (+5), in the end ending up on x = -5. True, it's very basic stuff, but it is very important to understand that it is not the camera that we are moving but the whole world around it. And this is that little detail thingy we were missing: we aren't moving the camera to the left, but whole to the right! Of course it could be done by just saying "simply move the camera in opposite direction", but … is bad idea - rotation and positioning could be done that way, but poinatAt function will be pretty much useless. Fortunately the solution is very simple, we just have to invert the view matrix:
var camera:Matrix3D = view.clone(); camera.invert();Yup, that is it. Whole render function will look like this:
private function render(event:Event):void { renderContext.clear(.3, .3, .3); var camera:Matrix3D = view.clone(); camera.invert(); for each(var box:Box3D in vBox) { finalTransform.identity(); finalTransform.append(box.getMatrix3D()); finalTransform.append(world); finalTransform.append(camera); finalTransform.append(projection); renderContext.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, finalTransform, true); box.render(renderContext); } renderContext.present(); }Those two lines of code are enough to make our camera work as intended, view translation to the left will in face move it to the left and what is most important pointAt will actually work, which I will explain shortly. Right now it is best to test out the camera movement and hopefully get something like this:
(Movement on the arrow keys)
Just in case you can always use the code available at the very and of this post.
Well, now that everything is properly set up, it is time to use pointAt function. And to think that without the knowledge about view matrix inversion prior to finalTransform construction, pointAt would never work as intended! This however doesn't solve all of the problems, there is still a question of parameters used by pointAt, because obviously default ones won't work. Those parameters are:
1. Target's position - matrix will be turned to face that point.
2. Facing direction - in other words "front" direction without any transformations; for camera it always will be Vector3D(0, 0, -1).
3. Up direction - which is the "rising" direction; by default Vector3D(0, -1, 0).
That should be enough. Properly working pointAt should look like this:
Unfortunately, translation won't be affect by the pointAt transformation - lets leave that for another day.
Source code: Tutorial3D-2.zip