Flash 3D – Molehill Cameras
photo © 2009 Masakazu “Matto” Matsumoto | more info (via: Wylio)
You can’t make a movie if you don’t have a camera….
…and you can’t render a 3d scene, if you don’t have a 3d camera.
You saw how to render…you saw how to do perspective.
But imagine how boring is your 3d scene going to be if you can’t roam through it?
Think of yourself as a (3d) news reporter…you go through this 3d world holding your camera….you’re actually a character in the world!!!
You don’t just want to observe the world from a fixed point of view. You want to roam around it, and see what the world looks like from where you are..
You might want to watch the world through your own character eyes (i.e. have a 1st person camera) or you could have the camera somehow follow your character and keep it in view all the time (i.e. have a 3rd person camera).
The Math of 3D Cameras
In order to be able to code this, you’re going to have to view what the 3d world looks like from the camera point of view, not just form the world coordinate system point of view, or from some other fixed point of view.
In a 3d scene you’re going to have a set of 3d models. All these models are described as a set of vertices and triangles, in their own coordinate system. This space is called model (or local) space.
Once you place these objects into a 3d scene, you transform these models vertices using a “world transform” matrix. Each object has its own world matrix that defines where the object is in the world, and how it is oriented.
This new reference system is called “world space” (or global space). A simple way to manage it by associating a world transform matrix to each object.
Then you want to reference the world not to the world origin, but to the reference system of the camera.
You take a “camera object” that is positioned at some coordinate (xc, yc, zc), and oriented along some direction. You get to a matrix, that is the “world transform” for the camera.
Then in order to transform the objects as they are seen from the camera point of view, you should calculate the inverse of this camera world matrix.
viewTransform.invert();
Then, in order to get your scene as seen through the camera, as you render each object you just append the viewTransform, to the worldTransform.
The perspective projection transform that I had discussed in my previous article on the perspective divide, goes right after the view transform.
So, the whole set of transforms goes like this:
- model space -> world space
- world space -> view space
- view space -> projection space
How do you do that in Molehill?
First, let me setup some code that translates and rotates the camera around through the keyboard arrows.
{
switch (e.keyCode)
{
case 37:
cameraRotationAcceleration = -ROTATION_ACCELERATION;
break
case 38:
cameraLinearAcceleration = LINEAR_ACCELERATION;
break
case 39:
cameraRotationAcceleration = ROTATION_ACCELERATION;
break;
case 40:
cameraLinearAcceleration = -LINEAR_ACCELERATION;
break;
}
}
{
switch (e.keyCode)
{
case 37:
case 39:
cameraRotationAcceleration = 0;
break
case 38:
case 40:
cameraLinearAcceleration = 0;
break
}
}
{
cameraLinearVelocity.z = calculateUpdatedVelocity(cameraLinearVelocity.z, cameraLinearAcceleration, MAX_FORWARD_VELOCITY);
cameraRotationVelocity = calculateUpdatedVelocity(cameraRotationVelocity, cameraRotationAcceleration, MAX_ROTATION_VELOCITY);
cameraWorldTransform.appendRotation(cameraRotationVelocity, Vector3D.Y_AXIS, cameraWorldTransform.position);
cameraWorldTransform.position = cameraWorldTransform.transformVector(cameraLinearVelocity);
viewTransform.copyFrom(cameraWorldTransform);
viewTransform.invert();
}
The updateViewMatrix function is where the magic happens. I calculate the new cameraWorldTransform matrix, starting from the old one, and applying rotation first, and translation last. Then, I calculate the viewTransform as the inverse of cameraWorldTransform.
The calculateUpdatedVelocity function simply adds a bit of easing:
if (curAcceleration != 0)
{
newVelocity = curVelocity + curAcceleration;
if (newVelocity > maxVelocity)
{
newVelocity = maxVelocity;
}
else if (newVelocity < -maxVelocity)
{
newVelocity = – maxVelocity;
}
}
else
{
newVelocity = curVelocity / DAMPING;
}
return newVelocity;
}
At every frame, right before rendering, I call the updateViewMatrix function.
Then, include the viewTransform matrix into the set of transforms used that get passed to the Vertex Shader:
var m:Matrix3D = new Matrix3D();
m.appendRotation(getTimer()/30, Vector3D.Y_AXIS);
m.appendRotation(getTimer()/10, Vector3D.X_AXIS);
m.appendTranslation(0, 0, 2);
m.append(viewTransform);
m.append(projectionTransform);
context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m, true);
The Cameras Application in Action
Here’s the final application for you to enjoy!
Use the arrows to roam around.
And here you can download the code:
Get The AGAL Reference Card
If you enjoyed this content, be sure to grab a free copy of my AGAL Refenrence Card. You can get it just by subscribing to the iFlash3D newsletter here (or just use the subscribe box in the right sidebar).
Filed under: Flash3D Coding
Like this post? Subscribe to my RSS feed and get loads more!

I am still unclear about the order of transformations for these matricies and why exactly you are inverting your view matrix.
The tutorial I went through had you create a local transform matrix for your model (for example, so you can scale your model based on whether it is a “baby” or “adult”), which then got appended to a global matrix describing where in the world that model is. And, here is the part where I get confused about order, you then create a view matrix describing the perspective from which you are viewing the model (and all others) from. This view matrix gets appended to the model global matrix (why not the other way around???). Then finally the model global matrix appends the camera settings matrix (FOV, etc.). I get that all these matricies are necessary, but I can’t figure out why they go in the order they go in. And this is important. You do it a different way, but again, I can’t figure out why. It all seems arbitrary at first glance, but I know that it is not.