Bits & Bytes

Posts Tagged ‘3d’

Perspective Projections in 3D with Actionscript 3.0

The most fundamental transformation in 3D graphics is the perspective transformation that projects points in 3D space onto the 2D image plane. In Actionscript, this is referred to as a Perspective Projection. This transformation is absolutely essential to creating an image from a 3D model. Most models are composed of sets of points. So, we will show how to project a single 3D point onto a 2D image. Once you know how to project a point, you can project virtually any complex object in 3D. For example, you can project a polygon by projecting each vertex, individually. Once the vertices are projected, you can connect them to create the projected polygon.

"Basic 1D Perspective Transformation"

Before we get into 3D, we want to consider how we can project a point in 2D onto a 1D line segment. This will demonstrate the essence of the problem and make the 3D case much easier to understand. So, we begin with 2D plane, and we want to project it onto the x-axis or the line y = 0. Every perspective projection has a viewpoint, which represents the observer’s eye. In this case, we set the viewpoint at (0, -1) and say that we are looking down the y-axis in the direction in which y is increasing (as shown by the white arrow). Now any point in the 2D can be projected onto our 1D image line given by the x-axis. For example, the point P’ = (1.5, 2) gets projected, by triangle similarity, onto the x-axis at P” = (.5, 0), where

x” = x’*(1/(2 + 1))

= 1.5*(1/3)

= .5

In general, a point (x’, y’) is transformed to (x”, 0) by x” = x’*(1/(y’ + 1)).

"Generalized 1D Perspective Projection"

This result can be further generalized by allowing the viewpoint to be anywhere on the negative y-axis. Here’s we have the viewpoint at (0, -f), and f is refered to as the focal length. Here, the point (x’, y’) is transformed to (x”, 0) by x” = x’*(f/(y’ + f)) in much the same way as before.

"1D Image with Field of View"

Unlike the x-axis, images have finite length. Here, we used the interval [-w/2, w/2] to represent a one dimensional image with its width equal to w. If we project a point to the x-axis and it falls outside of this interval, it will not show up in our image. In this case, we say that the point is clipped. However, the finite image size presents us with an opportunity to define the field of view. The field of view is the angle θ defined at the viewpoint that encompasses the image width. By trigonometry, we have tan(θ/2) = (w/2)/f. So, the focal length, f, can be written in terms of the image width, w, and field of view like this f = w/(2*tan(θ/2)).

Next, we move to three dimensions and the default case for Actionscript. In this case, our origin is positioned in the upper-left corner of our image with the positive z-axis pointing into the screen. The image width and height are given by the stage object, and we will refer to them simply as w and h, respectively. With this, we have viewpoint located in the default position at (w/2, h/2, -f). The point (w/2, h/2) defines the vanishing point of the image and is called the projection center in Actionscript. By default, the field of view is 55 degrees, and the focal length is calculated from the field of view as f = w/(2*tan(θ/2)), which follows from the fact that tan(θ/2) = w/(2*f). In 3D, the projection of the 3D point P’ = (x’, y’, z’) to P” = (x”, y”, 0) in the 2D image space defined by the xy-plane or z = 0 is accomplished with the following equations

x” = w/2 + (x’ – w/2)*(z’/(z’ + f))

y” = h/2 + (y’ – h/2)*(z’/(z’ + f))

"Default Actionscript Perspective Projection"

In Actionscript, perspective transformations are accomplished by the PerspectiveProjection class, which has three members named: fieldOfView, focalLength, and projectionCenter. These properties correspond to the field of view, the focal length, and the projection center, respectively.

Keep in mind that the focal length and the field of view are not independent, but are related by the formula above. So, changing the value of one will change the other. The field of view is not used in the projection equations, but it is used as a convenient way to understand how projections work and set the focal length.

Creating a 3D Animation in Actionscript 3.0

In this example, I create a 3D box with a ball bouncing around inside it. To do this, I create an animation using the ENTER_FRAME event that I used in the previous animation. The entire program is below at the end of this post.

The first line of code sets the callback for each frame of animation via a call to addEventListener() with ENTER_FRAME as the event and the function NextFrame() as the callback. After this, we use the stage’s graphics object to paint the entire screen black; this will be the background.

The next four sections of code each draw a rectangle in various shades of red and rotate them to make them to be perpendicular to the view. These are the four sides of our box. They are assigned a z-value of 25 to move them slightly away from the view plane at z = 0. Finally, the back to the box is created at z = 325 with a “XoaX.net” logo Bitmap. The dimensions of the box are 320x240x300.

After the walls of the box are created, we have three sets of variables for each dimension of movement for the ball inside the box. After this, we have Bitmap that represents the ball. This ball will be moved around by our animation callback function. Lastly, we have the callback function, which reflects the ball in each coordinate, similar to what we did in the last animation with the x-coordinate. Using these reflections, we get a ball that bounces around the box as we see in window above. Note that the projection is created via the default perspective projection, which we have not discussed yet.

addEventListener(Event.ENTER_FRAME, NextFrame);

// Paint the stage black 
var uiColorBack:uint = 0x00000000;
graphics.beginFill(uiColorBack);
    graphics.drawRect(0, 0, 320, 240);
graphics.endFill();

// Top
// Create rect for sprite and add to stage
var qTop:Sprite = new Sprite();
var uiColor:uint = 0x00900000;
qTop.graphics.beginFill(uiColor);
    qTop.graphics.drawRect(0, 0, 320, 300);
qTop.graphics.endFill();
qTop.rotationX = 90;
qTop.z = 25;
addChild(qTop);

// Left
var qLeft:Sprite = new Sprite();
var uiColor2:uint = 0x00B00000;
qLeft.graphics.beginFill(uiColor2);
    qLeft.graphics.drawRect(0, 0, 300, 240);
qLeft.graphics.endFill();
qLeft.rotationY = -90;
qLeft.z = 25;
addChild(qLeft);

// Right
var qRight:Sprite = new Sprite();
var uiColor3:uint = 0x00D00000;
qRight.graphics.beginFill(uiColor3);
    qRight.graphics.drawRect(0, 0, 300, 240);
qRight.graphics.endFill();
qRight.rotationY = -90;
qRight.x = 320;
qRight.z = 25;
addChild(qRight);

// Bottom
var qBottom:Sprite = new Sprite();
var uiColor4:uint = 0x00F00000;
qBottom.graphics.beginFill(uiColor4);
    qBottom.graphics.drawRect(0, 0, 320, 300);
qBottom.graphics.endFill();
qBottom.rotationX = 90;
qBottom.y = 240;
qBottom.z = 25;
addChild(qBottom);

// Back
// Use a logo image to create a bitmap
var qLogo:Bitmap = new Bitmap(new Logo(320, 240), "never", true);
qLogo.z = 325;
addChild(qLogo);

// Variables for motion control
var uiX:uint = 0;
var uiDeltaX:uint = 4;
var kuiMaxX:uint = 260;

var uiY:uint = 0;
var uiDeltaY:uint = 3;
var kuiMaxY:uint = 180;

var uiZ:uint = 0;
var uiDeltaZ:uint = 3;
var kuiMaxZ:uint = 240;

// Use a ball image to create a bitmap
var qBall:Bitmap = new Bitmap(new Ball(60, 60), "never", true);
addChild(qBall);

// Animation callback
function NextFrame(e:Event) : void {
	uiX = ((uiX + uiDeltaX) % (kuiMaxX + 1));
	qBall.x = uiX;
	if (uiX == kuiMaxX || uiX == 0) {
		uiDeltaX = (kuiMaxX - uiDeltaX);
	}
	
	uiY = ((uiY + uiDeltaY) % (kuiMaxY + 1));
	qBall.y = uiY;
	if (uiY == kuiMaxY || uiY == 0) {
		uiDeltaY = (kuiMaxY - uiDeltaY);
	}
	
	uiZ = ((uiZ + uiDeltaZ) % (kuiMaxZ + 1));
	qBall.z = uiZ + 30 + 25;
	if (uiZ == kuiMaxZ || uiZ == 0) {
		uiDeltaZ = (kuiMaxZ - uiDeltaZ);
	}
}