# as3 3D camera lookat

I'm making a 3D camera scene in Flash, draw using drawTriangles() and rotated and translated using a Matrix3D. I've got the camera to look after a specific point, but only on the Y-axis, using the x and z coordinates, here is my code so far:

var dx:Number = camera.x - lookAt.x;
var dy:Number = camera.y - lookAt.y;
var dz:Number = camera.z - lookAt.z;

camera.rotationY = Math.atan2(dz, dx) * (180 / Math.PI) + 270;


so no matter the x or z position, the point is always on the mid of the screen, IF and only if y matches with the camera. So what I need is to calculate the rotationX (which are measured in degrees not radians), and I was wondering how I would do this?

Well, if you're going to do it manually like you're doing right now, you're going to have a bad time.

What you need is actually two matrices: a projection matrix and a camera matrix. First you transform your triangles from world space (in the scene) to camera space (around the camera) and then from camera space to screen space (on the screen).

The first transformation is done using a camera matrix, while the second is done using a projection matrix.

Now, I'm going to assume you don't care about correct perspective, so I'll give you a matrix for an orthographic projection. It looks like there is no matrix class in standard AS3, so I'll give you a generic answer.

First, you will need to store 16 floating point numbers. They are aligned in the following configuration:

[ X1 Y1 Z1 WX ]
[ X2 Y2 Z2 WY ]
[ X3 Y3 Z3 WZ ]
[ TX TY TZ CZ ]


So that's four rows of four floating point numbers.

Let's build an identity matrix, which is a special matrix that doesn't change the input vector.

 [ 1.0 0.0 0.0 0.0 ]
[ 0.0 1.0 0.0 0.0 ]
[ 0.0 0.0 1.0 0.0 ]
[ 0.0 0.0 0.0 1.0 ]


Next, we will need to know the viewport, the dimensions of the screen.

 view_x = 0;
view_y = 0;
view_w = GetScreenWidth();
view_h = GetScreenHeight();


And also a value for z-near and z-far. But we can set that to -1 and 1, because we're not interested in perspective projection.

 z_near = -1;
z_far = 1;


Now we can fill our matrix:

Identity();

values[X1] =  2.f / (view_w - view_x);
values[Y2] =  2.f / (view_y - view_h);
values[Z3] = -2.f / (z_far - z_near);

values[TX] = -(view_w + view_x) / (view_w - view_x);
values[TY] = -(view_y + view_h) / (view_y - view_h);
values[TZ] = -(z_far + z_near) / (z_far - z_near);


But was just the first step! Now we have a projection matrix, but not a camera matrix.

 Identity();


We'll need three vectors: the camera position, the camera target and a world up vector. World up is usually (0.0, 1.0, 0.0), so we'll keep it that way for now.

 Vec3 camera_pos = GetCameraPosition();
Vec3 camera_target = GetCameraTarget();
Vec3 world_up = Vec3(0.0, 1.0, 0.0);


Now we can build three normalized vectors: f, s and u.

Vec3 f = (camera_target - camera_position).GetNormalized();
Vec3 u = world_up.GetNormalized();

Vec3 s = f.GetCrossProduct(u).Normalize();
u = s.GetCrossProduct(f);


And now we can fill our camera matrix:

values[X1] = s.x;
values[X2] = s.y;
values[X3] = s.z;
values[WX] = 0.0;

values[Y1] = u.x;
values[Y2] = u.y;
values[Y3] = u.z;
values[WY] = 0.0;

values[Z1] = -f.x;
values[Z2] = -f.y;
values[Z3] = -f.z;
values[WZ] = 0.0;

values[TX] = -u.GetDotProduct(camera_position);
values[TY] = -s.GetDotProduct(camera_position);
values[TZ] = f.GetDotProduct(camera_position);
values[CZ] = 1.0;


Once you have these matrices, it becomes a piece of cake to transform coordinates to screen space:

Vec3 homogenized_position = camera_projection * camera_transform * src_position;


Here, we're transform the incoming src_position with the matrices and the matrices with each other. Multiplying matrices:

Mat4x4 Mat4x4::operator * (Mat4x4 a_Other)
{
float a0, a1, a2, a3;

Mat4x4 result;

a0 = a_Other.values[X1];
a1 = a_Other.values[Y1];
a2 = a_Other.values[Z1];
a3 = a_Other.values[WX];

result.values[X1] = (a0 * values[X1]) + (a1 * values[X2]) + (a2 * values[X3]) + (a3 * values[TX]);
result.values[Y1] = (a0 * values[Y1]) + (a1 * values[Y2]) + (a2 * values[Y3]) + (a3 * values[TY]);
result.values[Z1] = (a0 * values[Z1]) + (a1 * values[Z2]) + (a2 * values[Z3]) + (a3 * values[TZ]);
result.values[WX] = (a0 * values[WX]) + (a1 * values[WY]) + (a2 * values[WZ]) + (a3 * values[CZ]);

a0 = a_Other.values[X2];
a1 = a_Other.values[Y2];
a2 = a_Other.values[Z2];
a3 = a_Other.values[WY];

result.values[X2] = (a0 * values[X1]) + (a1 * values[X2]) + (a2 * values[X3]) + (a3 * values[TX]);
result.values[Y2] = (a0 * values[Y1]) + (a1 * values[Y2]) + (a2 * values[Y3]) + (a3 * values[TY]);
result.values[Z2] = (a0 * values[Z1]) + (a1 * values[Z2]) + (a2 * values[Z3]) + (a3 * values[TZ]);
result.values[WY] = (a0 * values[WX]) + (a1 * values[WY]) + (a2 * values[WZ]) + (a3 * values[CZ]);

a0 = a_Other.values[X3];
a1 = a_Other.values[Y3];
a2 = a_Other.values[Z3];
a3 = a_Other.values[WZ];

result.values[X3] = (a0 * values[X1]) + (a1 * values[X2]) + (a2 * values[X3]) + (a3 * values[TX]);
result.values[Y3] = (a0 * values[Y1]) + (a1 * values[Y2]) + (a2 * values[Y3]) + (a3 * values[TY]);
result.values[Z3] = (a0 * values[Z1]) + (a1 * values[Z2]) + (a2 * values[Z3]) + (a3 * values[TZ]);
result.values[WZ] = (a0 * values[WX]) + (a1 * values[WY]) + (a2 * values[WZ]) + (a3 * values[CZ]);

a0 = a_Other.values[TX];
a1 = a_Other.values[TY];
a2 = a_Other.values[TZ];
a3 = a_Other.values[CZ];

result.values[TX] = (a0 * values[X1]) + (a1 * values[X2]) + (a2 * values[X3]) + (a3 * values[TX]);
result.values[TY] = (a0 * values[Y1]) + (a1 * values[Y2]) + (a2 * values[Y3]) + (a3 * values[TY]);
result.values[TZ] = (a0 * values[Z1]) + (a1 * values[Z2]) + (a2 * values[Z3]) + (a3 * values[TZ]);
result.values[CZ] = (a0 * values[WX]) + (a1 * values[WY]) + (a2 * values[WZ]) + (a3 * values[CZ]);

return result;
}


And multiplying a Vec3 with a matrix:

Vec3 Mat4x4::operator * (Vec3 a_Vector)
{
Vec3 result;

float x = (a_Vector.x * values[X1]) + (a_Vector.y * values[X2]) + (a_Vector.z * values[X3]) + values[TX];
float y = (a_Vector.x * values[Y1]) + (a_Vector.y * values[Y2]) + (a_Vector.z * values[Y3]) + values[TY];
float z = (a_Vector.x * values[Z1]) + (a_Vector.y * values[Z2]) + (a_Vector.z * values[Z3]) + values[TZ];
float w = (a_Vector.x * values[WX]) + (a_Vector.y * values[WY]) + (a_Vector.z * values[WZ]) + values[CZ];

result.x = x / w;
result.y = y / w;
result.z = z / w;

return result;
}


However, one last step is still required. These coordinates will be in homogenized space, ranging (-1, 1) for x and y. That's not what we want, so we'll convert it to pixel coordinates:

Vec2 screenspace_position = Vec2(
(homogenized_position.x * 0.5 + 0.5) * GetScreenWidth(),
(1.f - (homogenized_position.y * 0.5 + 0.5)) * GetScreenHeight()
);


If you are having trouble understanding any of this, first try to implement it. Look up things you don't know. Perhaps you don't know how to do the cross product of two Vec3's or what a normalized vector is. Figure out how to implement it, then try to understand what all this code does.

• Ah, thanks a lot for your elaborate answer. The only thing I'm insecure about is what you meant with values[SX], SY and SZ as I can't see them in the matrix you first posted Jul 5 '12 at 8:25
• Ah! My mistake. Those values are shorthand for X1, Y2 and Z3. I'll fix it in my answer. Jul 5 '12 at 8:30
• Also, what's "a_Position" when filling out the camera matrix? Jul 5 '12 at 8:37
• It means that I forgot to change the parameter name when I made my answer. :P Jul 5 '12 at 8:54
• @JohannesJensen You don't need to implement any matrix or vector classes, it's all there and ready to use: Matrix3D and Vector3D. These are available in Actionscript-3, Flash-Player version 10 and above. Jul 5 '12 at 16:14