Stage3D Model Matrix: Scale, Rotation, Translation
One essential to a Stage3D
app is the ability to manipulate models in the 3D scene. Today’s article presents a class for making this really easy by avoiding all the manual work you’d to to save translation, rotation, and scale values as fields and manipulate various Matrix3D
instances. Read on for the source code and a demo app.
The following ModelMatrix
class allows you to control three aspects of the model you apply it to:
- Scale in the X, Y, and X axes
- Rotation by an angle about an axis
- Translation/position by an offset along the X, Y, and Z axes
It does this by holding a Matrix3D
for each of these as well as all the data used to create them. This allows for a really easy-to-use API:
// Create the matrix. By default it is identity. You can optionally // specify all settings for all three sub-matrices here. var mm:ModelMatrix = new ModelMatrix(); // Get and set a single translation component mm.translationX = 50; trace("Translation is now: " + mm.translationX); // Or set all three from Numbers or a Vector3D mm.setTranslationValues(1, 2, 3); mm.setTranslationVector(new Vector3D(4, 5, 6)); // Similar functions exist for scale mm.scaleX = 3; mm.scaleXYZ = 3; // set all axes to the same value // And for rotation mm.rotationAngle += 30; // degrees mm.setRotationAngleAxisValues( 0, 1, 0, // axis 20 // angle ); // See the source code for the full API
You can use a ModelMatrix
anywhere you need Matrix3D
. That means you can pass it directly to Context3D
functions like setProgramConstantsFromMatrix
.
Now for a little test app to show how to use it. This is built off of the test app from Procedurally-Generated Sphere and all I’ve done is swap out the custom Matrix3D
in Sphere3D
with a ModelMatrix
.
package { import com.adobe.utils.*; import flash.display.*; import flash.display3D.*; import flash.display3D.textures.*; import flash.events.*; import flash.geom.*; import flash.text.*; import flash.utils.*; public class ModelMatrixTest extends Sprite { /** UI Padding */ private static const PAD:Number = 5; [Embed(source="earth.jpg")] private static const TEXTURE:Class; /** Temporary matrix to avoid allocation during drawing */ private static const TEMP_DRAW_MATRIX:Matrix3D = new Matrix3D(); /** 3D context to draw with */ private var context3D:Context3D; /** Shader program to draw with */ private var program:Program3D; /** Texture of all spheres */ private var texture:Texture; /** Camera viewing the 3D scene */ private var camera:Camera3D; /** Sphere to draw */ private var sphere:Sphere3D; /** Framerate display */ private var fps:TextField = new TextField(); /** Last time the framerate display was updated */ private var lastFPSUpdateTime:uint; /** Time when the last frame happened */ private var lastFrameTime:uint; /** Number of frames since the framerate display was updated */ private var frameCount:uint; /** 3D rendering driver display */ private var driver:TextField = new TextField(); /** * Entry point */ public function ModelMatrixTest() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; stage.frameRate = 60; var stage3D:Stage3D = stage.stage3Ds[0]; stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreated); stage3D.requestContext3D(Context3DRenderMode.AUTO); } protected function onContextCreated(ev:Event): void { // Setup context var stage3D:Stage3D = stage.stage3Ds[0]; stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated); context3D = stage3D.context3D; context3D.configureBackBuffer( stage.stageWidth, stage.stageHeight, 0, true ); // Setup camera camera = new Camera3D( 0.1, // near 100, // far stage.stageWidth / stage.stageHeight, // aspect ratio 40*(Math.PI/180), // vFOV 0, 0, 5, // position 0, 0, 0, // target 0, 1, 0 // up dir ); // Setup UI fps.background = true; fps.backgroundColor = 0xffffffff; fps.autoSize = TextFieldAutoSize.LEFT; fps.text = "Getting FPS..."; addChild(fps); driver.background = true; driver.backgroundColor = 0xffffffff; driver.text = "Driver: " + context3D.driverInfo; driver.autoSize = TextFieldAutoSize.LEFT; driver.y = fps.height; addChild(driver); makeButtons( "Translation -X", "Translation -Y", "Translation -Z", null, "Translation +X", "Translation +Y", "Translation +Z", null, "Rotation Angle -", "Rotation Angle +", null, "Scale -X", "Scale -Y", "Scale -Z", null, "Scale +X", "Scale +Y", "Scale +Z", null ); var assembler:AGALMiniAssembler = new AGALMiniAssembler(); // Vertex shader var vertSource:String = "m44 op, va0, vc0\nmov v0, va1\n"; assembler.assemble(Context3DProgramType.VERTEX, vertSource); var vertexShaderAGAL:ByteArray = assembler.agalcode; // Fragment shader var fragSource:String = "tex oc, v0, fs0 <2d,linear,mipnone>"; assembler.assemble(Context3DProgramType.FRAGMENT, fragSource); var fragmentShaderAGAL:ByteArray = assembler.agalcode; // Shader program program = context3D.createProgram(); program.upload(vertexShaderAGAL, fragmentShaderAGAL); // Setup textures var bmd:BitmapData = (new TEXTURE() as Bitmap).bitmapData; texture = context3D.createTexture( bmd.width, bmd.height, Context3DTextureFormat.BGRA, true ); texture.uploadFromBitmapData(bmd); sphere = new Sphere3D(context3D, 30, 30); // Start the simulation addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function makeButtons(...labels): Number { var curX:Number = PAD; var curY:Number = stage.stageHeight - PAD; for each (var label:String in labels) { if (label == null) { curX = PAD; curY -= button.height + PAD; continue; } var tf:TextField = new TextField(); tf.mouseEnabled = false; tf.selectable = false; tf.defaultTextFormat = new TextFormat("_sans"); tf.autoSize = TextFieldAutoSize.LEFT; tf.text = label; tf.name = "lbl"; var button:Sprite = new Sprite(); button.buttonMode = true; button.graphics.beginFill(0xF5F5F5); button.graphics.drawRect(0, 0, tf.width+PAD, tf.height+PAD); button.graphics.endFill(); button.graphics.lineStyle(1); button.graphics.drawRect(0, 0, tf.width+PAD, tf.height+PAD); button.addChild(tf); button.addEventListener(MouseEvent.CLICK, onButton); if (curX + button.width > stage.stageWidth - PAD) { curX = PAD; curY -= button.height + PAD; } button.x = curX; button.y = curY - button.height; addChild(button); curX += button.width + PAD; } return curY - button.height; } private function onButton(ev:MouseEvent): void { var mode:String = ev.target.getChildByName("lbl").text; switch (mode) { case "Translation -X": sphere.modelToWorld.translationX--; break; case "Translation -Y": sphere.modelToWorld.translationY--; break; case "Translation -Z": sphere.modelToWorld.translationZ--; break; case "Translation +X": sphere.modelToWorld.translationX++; driver.text= ""+sphere.modelToWorld.rawData; break; case "Translation +Y": sphere.modelToWorld.translationY++; break; case "Translation +Z": sphere.modelToWorld.translationZ++; break; case "Rotation Angle -": sphere.modelToWorld.rotationAngle -= 5; break; case "Rotation Angle +": sphere.modelToWorld.rotationAngle += 5; break; case "Scale -X": sphere.modelToWorld.scaleX--; break; case "Scale -Y": sphere.modelToWorld.scaleY--; break; case "Scale -Z": sphere.modelToWorld.scaleZ--; break; case "Scale +X": sphere.modelToWorld.scaleX++; break; case "Scale +Y": sphere.modelToWorld.scaleY++; break; case "Scale +Z": sphere.modelToWorld.scaleZ++; break; } } private function onEnterFrame(ev:Event): void { // Set up rendering context3D.setProgram(program); context3D.setTextureAt(0, texture); context3D.clear(0.5, 0.5, 0.5); // Draw spheres var worldToClip:Matrix3D = camera.worldToClipMatrix; var drawMatrix:Matrix3D = TEMP_DRAW_MATRIX; context3D.setVertexBufferAt(0, sphere.positions, 0, Context3DVertexBufferFormat.FLOAT_3); context3D.setVertexBufferAt(1, sphere.texCoords, 0, Context3DVertexBufferFormat.FLOAT_2); sphere.modelToWorld.copyToMatrix3D(drawMatrix); drawMatrix.prepend(worldToClip); context3D.setProgramConstantsFromMatrix( Context3DProgramType.VERTEX, 0, drawMatrix, false ); context3D.drawTriangles(sphere.tris); context3D.present(); // Update stat displays frameCount++; var now:int = getTimer(); var elapsed:int = now - lastFPSUpdateTime; if (elapsed > 1000) { var framerateValue:Number = 1000 / (elapsed / frameCount); fps.text = "FPS: " + framerateValue.toFixed(1); lastFPSUpdateTime = now; frameCount = 0; } lastFrameTime = now; } } }
If you spot any bugs or have any suggestions, please let me know in the comments section. Otherwise, enjoy the ModelMatrix
class!
package { import flash.geom.Vector3D; import flash.geom.Matrix3D; /** * A model matrix supporting scale, translation, and rotation * @author Jackson Dunstan, http://JacksonDunstan.com */ public class ModelMatrix extends Matrix3D { /** The scaling matrix */ private var __scale:Matrix3D; /** Scale in the X */ private var __scaleX:Number; /** Scale in the Y */ private var __scaleY:Number; /** Scale in the Z */ private var __scaleZ:Number; /** Rotation matrix about x,y,z */ private var __rotation:Matrix3D; /** Rotation angle about the axis (degrees) */ private var __rotationAngle:Number; /** Axis of rotation */ private var __rotationAxis:Vector3D; /** Translation matrix */ private var __translation:Matrix3D; /** Translation in the X */ private var __translationX:Number; /** Translation in the Y */ private var __translationY:Number; /** Translation in the Z */ private var __translationZ:Number; /** * Make the model matrix. It is initally the identity matrix. The * angle/axis sub-matrix is initially about the Y axis. * @param scaleX Scale in the X * @param scaleY Scale in the Y * @param scaleZ Scale in the Z * @param angle Angle of rotation about the axis (degrees) * @param axisX The axis of rotation's X component * @param axisY The axis of rotation's Y component * @param axisZ The axis of rotation's Z component * @param translationX Translation along the X axis * @param translationY Translation along the Y axis * @param translationZ Translation along the Z axis */ public function ModelMatrix( scaleX:Number=1, scaleY:Number=1, scaleZ:Number=1, angle:Number=0, axisX:Number=0, axisY:Number=1, axisZ:Number=0, translationX:Number=0, translationY:Number=0, translationZ:Number=0 ) { __scale = new Matrix3D(); __rotation = new Matrix3D(); __rotationAxis = new Vector3D(); __translation = new Matrix3D(); setAllValues( scaleX, scaleY, scaleZ, angle, axisX, axisY, axisZ, translationX, translationY, translationZ ); } /** * The scale in the X */ public function get scaleX(): Number { return __scaleX; } public function set scaleX(x:Number): void { __scaleX = x; updateScale(); update(); } /** * The scale in the Y */ public function get scaleY(): Number { return __scaleY; } public function set scaleY(y:Number): void { __scaleY = y; updateScale(); update(); } /** * The scale in the Z */ public function get scaleZ(): Number { return __scaleZ; } public function set scaleZ(z:Number): void { __scaleZ = z; updateScale(); update(); } /** * Set all scale values equally * @param val The value of X, Y, and Z scales */ public function set scaleXYZ(val:Number): void { __scaleX = val; __scaleY = val; __scaleZ = val; updateScale(); update(); } /** * Set all scale values at once * @param x Scale in the X * @param y Scale in the Y * @param z Scale in the Z */ public function setScaleValues(x:Number, y:Number, z:Number): void { __scaleX = x; __scaleY = y; __scaleZ = z; updateScale(); update(); } /** * Set the scale factors from a vector * @param Vector whose XYZ components are the scale factors */ public function setScaleVector(v:Vector3D): void { __scaleX = v.x; __scaleY = v.y; __scaleZ = v.z; updateScale(); update(); } /** * The angle of rotation (degrees) */ public function get rotationAngle(): Number { return __rotationAngle; } public function set rotationAngle(angle:Number): void { __rotationAngle = angle; updateRotation(); update(); } /** * The axis of rotation's X component */ public function get rotationAxisX(): Number { return __rotationAxis.x; } public function set rotationAxisX(comp:Number): void { __rotationAxis.x = comp; updateRotation(); update(); } /** * The axis of rotation's Y component */ public function get rotationAxisY(): Number { return __rotationAxis.y; } public function set rotationAxisY(comp:Number): void { __rotationAxis.y = comp; updateRotation(); update(); } /** * The axis of rotation's Z component */ public function get rotationAxisZ(): Number { return __rotationAxis.z; } public function set rotationAxisZ(comp:Number): void { __rotationAxis.z = comp; updateRotation(); update(); } /** * Set all the components of the axis at once * @param axisX The axis of rotation's X component * @param axisY The axis of rotation's Y component * @param axisZ The axis of rotation's Z component */ public function setRotationAxisValues(axisX:Number, axisY:Number, axisZ:Number): void { __rotationAxis.x = axisX; __rotationAxis.y = axisY; __rotationAxis.z = axisZ; updateRotation(); update(); } /** * Set the axis of rotation * @param axis Vector whose components are the rotation axis */ public function setRotationAxisVector(axis:Vector3D): void { __rotationAxis.x = axis.x; __rotationAxis.y = axis.y; __rotationAxis.z = axis.z; updateRotation(); update(); } /** * Set the angle and the axis all at once * @param angle Angle of rotation about the axis (degrees) * @param axisX The axis of rotation's X component * @param axisY The axis of rotation's Y component * @param axisZ The axis of rotation's Z component */ public function setRotationAngleAxisValues(angle:Number, axisX:Number, axisY:Number, axisZ:Number): void { __rotationAngle = angle; __rotationAxis.x = axisX; __rotationAxis.y = axisY; __rotationAxis.z = axisZ; updateRotation(); update(); } /** * Set the angle/axis rotation from a vector * @param Vector whose XYZ components are the axis and W is the angle (degrees) */ public function setRotationAngleAxisVector(v:Vector3D): void { __rotationAngle = v.w; __rotationAxis.x = v.x; __rotationAxis.y = v.y; __rotationAxis.z = v.z; updateRotation(); update(); } /** * Set the angle/axis rotation from a number and a vector * @param angle Rotation angle (degrees) * @param Vector whose XYZ components are the axis */ public function setRotationAngleAxisNumberVector(angle:Number, v:Vector3D): void { __rotationAngle = angle; __rotationAxis.x = v.x; __rotationAxis.y = v.y; __rotationAxis.z = v.z; updateRotation(); update(); } /** * The translation along the X */ public function get translationX(): Number { return __translationX; } public function set translationX(x:Number): void { __translationX = x; updateTranslation(); update(); } /** * The translation along the Y */ public function get translationY(): Number { return __translationY; } public function set translationY(y:Number): void { __translationY = y; updateTranslation(); update(); } /** * The translation along the Z */ public function get translationZ(): Number { return __translationZ; } public function set translationZ(z:Number): void { __translationZ = z; updateTranslation(); update(); } /** * Set the translation amounts * @param x Translation along the X axis * @param y Translation along the Y axis * @param z Translation along the Z axis */ public function setTranslationValues(x:Number, y:Number, z:Number): void { __translationX = x; __translationY = y; __translationZ = z; updateTranslation(); update(); } /** * Set the translation * @param t Vector whose components are the translation */ public function setTranslationVector(t:Vector3D): void { __translationX = t.x; __translationY = t.y; __translationZ = t.z; updateTranslation(); update(); } /** * Set all scale, rotation (angle/axis), and translation values at once * @param scaleX Scale in the X * @param scaleY Scale in the Y * @param scaleZ Scale in the Z * @param angle Angle of rotation about the axis * @param axisX The axis of rotation's X component * @param axisY The axis of rotation's Y component * @param axisZ The axis of rotation's Z component * @param translationX Translation along the X axis * @param translationY Translation along the Y axis * @param translationZ Translation along the Z axis */ public function setAllValues( scaleX:Number, scaleY:Number, scaleZ:Number, angle:Number, axisX:Number, axisY:Number, axisZ:Number, translationX:Number, translationY:Number, translationZ:Number ): void { __scaleX = scaleX; __scaleY = scaleY; __scaleZ = scaleZ; __rotationAngle = angle; __rotationAxis.x = axisX; __rotationAxis.y = axisY; __rotationAxis.z = axisZ; __translationX = translationX; __translationY = translationY; __translationZ = translationZ; updateScale(); updateRotation(); updateTranslation(); update(); } /** * @inheritDoc */ override public function clone(): Matrix3D { var ret:ModelMatrix = new ModelMatrix(); ret.setAllValues( __scaleX, __scaleY, __scaleZ, __rotationAngle, __rotationAxis.x, __rotationAxis.y, __rotationAxis.z, __translationX, __translationY, __translationZ ); return ret; } /** * Update the scale matrix */ private function updateScale(): void { __scale.identity(); __scale.appendScale(__scaleX, __scaleY, __scaleZ); } /** * Update the rotation matrix */ private function updateRotation(): void { __rotation.identity(); __rotation.appendRotation(__rotationAngle, __rotationAxis); } /** * Update the translation matrix */ private function updateTranslation(): void { __translation.identity(); __translation.appendTranslation(__translationX, __translationY, __translationZ); } /** * Update the whole matrix */ private function update(): void { identity(); append(__scale); append(__rotation); append(__translation); transpose(); } } }
#1 by AlexG on May 6th, 2013 ·
Thanks!
How about some collision detection in 3D?
#2 by jackson on May 6th, 2013 ·
Like physics collisions? That’s a big area to discuss, so in the meantime you might want to check out jiglib.