Simple Stage3D Camera
There are many fine 3D frameworks for the new hardware-accelerated Stage3D
class in Flash 11 that are loaded with features. But, if you just want to get some simple 3D up and running or would just prefer to do things yourself, today’s article shows you a simple 3D camera that you can use to view your 3D scenes and models. Read on for the source code and a demo app.
The following camera class has the following features:
- Perspective (i.e. realistic) projection
- No dependencies on any other classes
- Movement functions (i.e. forward/backward, up/down, left/right)
- Aviation-style functions (i.e. yaw, pitch, roll)
- Reasonably efficient (e.g. matrix caching is used)
- Reasonably safe (e.g. values are checked)
Here is the source code:
package { import flash.geom.Vector3D; import flash.geom.Matrix3D; /** * A 3D camera using perspective projection * @author Jackson Dunstan */ public class Camera3D { /** Minimum distance the near plane can be */ public static const MIN_NEAR_DISTANCE:Number = 0.001; /** Minimum distance between the near and far planes */ public static const MIN_PLANE_SEPARATION:Number = 0.001; /** Position of the camera */ private var __position:Vector3D; /** What the camera is looking at */ private var __target:Vector3D; /** Direction that is "up" */ private var __upDir:Vector3D; /** Direction that is "up" */ private var __realUpDir:Vector3D; /** Near clipping plane distance */ private var __near:Number; /** Far clipping plane distance */ private var __far:Number; /** Aspect ratio of the camera lens */ private var __aspect:Number; /** Vertical field of view */ private var __vFOV:Number; /** World->View transformation */ private var __worldToView:Matrix3D; /** View->Clip transformation */ private var __viewToClip:Matrix3D; /** World->Clip transformation */ private var __worldToClip:Matrix3D; /** Direction the camera is pointing */ private var __viewDir:Vector3D; /** Magnitude of the view direction */ private var __viewDirMag:Number; /** Direction to the right of where the camera is pointing */ private var __rightDir:Vector3D; /** A temporary matrix for use during world->view calculation */ private var __tempWorldToViewMatrix:Matrix3D; /** * Make the camera * @param near Distance to the near clipping plane. Capped to MIN_NEAR_DISTANCE. * @param far Distance to the far clipping plane. Must be MIN_PLANE_SEPARATION greater than near. * @param aspect Aspect ratio of the camera lens * @param vFOV Vertical field of view * @param positionX X component of the camera's position * @param positionY Y component of the camera's position * @param positionZ Z component of the camera's position * @param targetX X compoennt of the point the camera is aiming at * @param targetY Y compoennt of the point the camera is aiming at * @param targetZ Z compoennt of the point the camera is aiming at * @param upDirX X component of the direction considered to be "up" * @param upDirX X component of the direction considered to be "up" * @param upDirY Y component of the direction considered to be "up" */ public function Camera3D( near:Number, far:Number, aspect:Number, vFOV:Number, positionX:Number, positionY:Number, positionZ:Number, targetX:Number, targetY:Number, targetZ:Number, upDirX:Number, upDirY:Number, upDirZ:Number ) { if (near < MIN_NEAR_DISTANCE) { near = MIN_NEAR_DISTANCE; } if (far < near+MIN_PLANE_SEPARATION) { far = near + MIN_PLANE_SEPARATION; } __near = near; __far = far; __aspect = aspect; __vFOV = vFOV; __position = new Vector3D(positionX, positionY, positionZ); __target = new Vector3D(targetX, targetY, targetZ); __upDir = new Vector3D(upDirX, upDirY, upDirZ); __upDir.normalize(); __viewDir = new Vector3D(); __rightDir = new Vector3D(); __realUpDir = new Vector3D(); __tempWorldToViewMatrix = new Matrix3D(); __worldToView = new Matrix3D(); __viewToClip = new Matrix3D(); __worldToClip = new Matrix3D(); updateWorldToView(); updateViewToClip(); updateWorldToClip(); } /** * Get the world->clip transformation * @return The world->clip transformation */ public function get worldToClipMatrix(): Matrix3D { return __worldToClip; } /** * Get the camera's position in the X * @return The camera's position in the X */ public function get positionX(): Number { return __position.x; } /** * Set the camera's position in the X * @param x The camera's position in the X */ public function set positionX(x:Number): void { __position.x = x; updateWorldToView(); updateWorldToClip(); } /** * Get the camera's position in the Y * @return The camera's position in the Y */ public function get positionY(): Number { return __position.y; } /** * Set the camera's position in the Y * @param y The camera's position in the Y */ public function set positionY(y:Number): void { __position.y = y; updateWorldToView(); updateWorldToClip(); } /** * Get the camera's position in the Z * @return The camera's position in the Z */ public function get positionZ(): Number { return __position.z; } /** * Set the camera's position in the Z * @param z The camera's position in the Z */ public function set positionZ(z:Number): void { __position.z = z; updateWorldToView(); updateWorldToClip(); } /** * Set the camera's position * @param x The camera's position in the X * @param y The camera's position in the Y * @param z The camera's position in the Z */ public function setPositionValues(x:Number, y:Number, z:Number): void { __position.x = x; __position.y = y; __position.z = z; updateWorldToView(); updateWorldToClip(); } /** * Get the camera's target in the X * @return The camera's target in the X */ public function get targetX(): Number { return __target.x; } /** * Set the camera's target in the X * @param x The camera's target in the X */ public function set targetX(x:Number): void { __target.x = x; updateWorldToView(); updateWorldToClip(); } /** * Get the camera's target in the Y * @return The camera's target in the Y */ public function get targetY(): Number { return __target.y; } /** * Set the camera's target in the Y * @param y The camera's target in the Y */ public function set targetY(y:Number): void { __target.y = y; updateWorldToView(); updateWorldToClip(); } /** * Get the camera's target in the Z * @return The camera's target in the Z */ public function get targetZ(): Number { return __target.z; } /** * Set the camera's target in the Z * @param z The camera's target in the Z */ public function set targetZ(z:Number): void { __target.z = z; updateWorldToView(); updateWorldToClip(); } /** * Set the camera's target * @param x The camera's target in the X * @param y The camera's target in the Y * @param z The camera's target in the Z */ public function setTargetValues(x:Number, y:Number, z:Number): void { __target.x = x; __target.y = y; __target.z = z; updateWorldToView(); updateWorldToClip(); } /** * Get the near clipping distance * @return The near clipping distance */ public function get near(): Number { return __near; } /** * Set the near clipping distance * @param near The near clipping distance */ public function set near(near:Number): void { __near = near; updateViewToClip(); updateWorldToClip(); } /** * Get the far clipping distance * @return The far clipping distance */ public function get far(): Number { return __far; } /** * Set the far clipping distance * @param far The far clipping distance */ public function set far(far:Number): void { __far = far; updateViewToClip(); updateWorldToClip(); } /** * Get the vertical field of view angle * @return The vertical field of view angle */ public function get vFOV(): Number { return __vFOV; } /** * Set the vertical field of view angle * @param vFOV The vertical field of view angle */ public function set vFOV(vFOV:Number): void { __vFOV = vFOV; updateViewToClip(); updateWorldToClip(); } /** * Get the aspect ratio * @return The aspect ratio */ public function get aspect(): Number { return __aspect; } /** * Set the aspect ratio * @param aspect The aspect ratio */ public function set aspect(aspect:Number): void { __aspect = aspect; updateViewToClip(); updateWorldToClip(); } /** * Move the camera toward the target * @param units Number of units to move forward */ public function moveForward(units:Number): void { moveAlongAxis(units, __viewDir); } /** * Move the camera away from the target * @param units Number of units to move backward */ public function moveBackward(units:Number): void { moveAlongAxis(-units, __viewDir); } /** * Move the camera right * @param units Number of units to move right */ public function moveRight(units:Number): void { moveAlongAxis(units, __rightDir); } /** * Move the camera left * @param units Number of units to move left */ public function moveLeft(units:Number): void { moveAlongAxis(-units, __rightDir); } /** * Move the camera up * @param units Number of units to move up */ public function moveUp(units:Number): void { moveAlongAxis(units, __upDir); } /** * Move the camera down * @param units Number of units to move down */ public function moveDown(units:Number): void { moveAlongAxis(-units, __upDir); } /** * Move the camera right toward the target * @param units Number of units to move right * @param axis Axis to move along */ private function moveAlongAxis(units:Number, axis:Vector3D): void { var delta:Vector3D = axis.clone(); delta.scaleBy(units); var newPos:Vector3D = __position.add(delta); setPositionValues(newPos.x, newPos.y, newPos.z); var newTarget:Vector3D = __target.add(delta); setTargetValues(newTarget.x, newTarget.y, newTarget.z); } /** * Yaw the camera left/right * @param numDegrees Number of degrees to yaw. Positive is clockwise, * negative is counter-clockwise. If NaN, this * function does nothing. */ public function yaw(numDegrees:Number): void { rotate(numDegrees, __realUpDir); } /** * Pitch the camera up/down * @param numDegrees Number of degrees to pitch. Positive is clockwise, * negative is counter-clockwise. If NaN, this * function does nothing. */ public function pitch(numDegrees:Number): void { rotate(numDegrees, __rightDir); } /** * Roll the camera left/right * @param numDegrees Number of degrees to roll. Positive is clockwise, * negative is counter-clockwise. If NaN, this * function does nothing. */ public function roll(numDegrees:Number): void { if (isNaN(numDegrees)) { return; } // Make positive and negative make sense numDegrees = -numDegrees; var rotMat:Matrix3D = new Matrix3D(); rotMat.appendRotation(numDegrees, __viewDir); __upDir = rotMat.transformVector(__upDir); __upDir.normalize(); updateWorldToView(); updateWorldToClip(); } /** * Rotate the camera about an axis * @param numDegrees Number of degrees to rotate. Positive is clockwise, * negative is counter-clockwise. If NaN, this * function does nothing. * @param axis Axis of rotation */ private function rotate(numDegrees:Number, axis:Vector3D): void { if (isNaN(numDegrees)) { return; } // Make positive and negative make sense numDegrees = -numDegrees; var rotMat:Matrix3D = new Matrix3D(); rotMat.appendRotation(numDegrees, axis); var rotatedViewDir:Vector3D = rotMat.transformVector(__viewDir); rotatedViewDir.scaleBy(__viewDirMag); var newTarget:Vector3D = __position.add(rotatedViewDir); setTargetValues(newTarget.x, newTarget.y, newTarget.z); } /** * Update the world->view matrix */ private function updateWorldToView(): void { // viewDir = target - position var viewDir:Vector3D = __viewDir; viewDir.x = __target.x - __position.x; viewDir.y = __target.y - __position.y; viewDir.z = __target.z - __position.z; __viewDirMag = __viewDir.normalize(); // Up is already normalized var upDir:Vector3D = __upDir; // rightDir = viewDir X upPrime var rightDir:Vector3D = __rightDir; rightDir.x = viewDir.y*upDir.z - viewDir.z*upDir.y; rightDir.y = viewDir.z*upDir.x - viewDir.x*upDir.z; rightDir.z = viewDir.x*upDir.y - viewDir.y*upDir.x; // realUpDir = rightDir X viewDir var realUpDir:Vector3D = __realUpDir; realUpDir.x = rightDir.y*viewDir.z - rightDir.z*viewDir.y; realUpDir.y = rightDir.z*viewDir.x - rightDir.x*viewDir.z; realUpDir.z = rightDir.x*viewDir.y - rightDir.y*viewDir.x; // Translation by -position var rawData:Vector.<Number> = __worldToView.rawData; rawData[0] = 1; rawData[1] = 0; rawData[2] = 0; rawData[3] = -__position.x; rawData[4] = 0; rawData[5] = 1; rawData[6] = 0; rawData[7] = -__position.y; rawData[8] = 0; rawData[9] = 0; rawData[10] = 1; rawData[11] = -__position.z; rawData[12] = 0; rawData[13] = 0; rawData[14] = 0; rawData[15] = 1; __worldToView.rawData = rawData; // Look At matrix. Some parts of this are constant. rawData = __tempWorldToViewMatrix.rawData; rawData[0] = rightDir.x; rawData[1] = rightDir.y; rawData[2] = rightDir.z; rawData[3] = 0; rawData[4] = realUpDir.x; rawData[5] = realUpDir.y; rawData[6] = realUpDir.z; rawData[7] = 0; rawData[8] = -viewDir.x; rawData[9] = -viewDir.y; rawData[10] = -viewDir.z; rawData[11] = 0; rawData[12] = 0; rawData[13] = 0; rawData[14] = 0; rawData[15] = 1; __tempWorldToViewMatrix.rawData = rawData; __worldToView.prepend(__tempWorldToViewMatrix); } /** * Update the view->clip matrix */ private function updateViewToClip(): void { var f:Number = 1.0 / Math.tan(__vFOV); __viewToClip.rawData = new <Number>[ f / __aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, ((__far+__near)/(__near-__far)), ((2*__far*__near)/(__near-__far)), 0, 0, -1, 0 ]; } /** * Update the world->clip matrix */ private function updateWorldToClip(): void { __worldToView.copyToMatrix3D(__worldToClip); __worldToClip.prepend(__viewToClip); } } }
And here is the source code for the test app: (and the test texture)
package { import com.adobe.utils.*; import flash.display3D.textures.*; import flash.display3D.*; import flash.display.*; import flash.filters.*; import flash.events.*; import flash.text.*; import flash.geom.*; import flash.utils.*; public class Stage3DCameraTest extends Sprite { /** Positions of all cubes' vertices */ private static const POSITIONS:Vector.<Number> = new <Number>[ // back face - bottom tri -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, // back face - top tri -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, // front face - bottom tri -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, // front face - top tri -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, // left face - bottom tri -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, // left face - top tri -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, // right face - bottom tri 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, // right face - top tri 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, // bottom face - bottom tri -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, // bottom face - top tri -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, // top face - bottom tri -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, // top face - top tri -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5 ]; /** Texture coordinates of all cubes' vertices */ private static const TEX_COORDS:Vector.<Number> = new <Number>[ // back face - bottom tri 1, 1, 1, 0, 0, 1, // back face - top tri 1, 0, 0, 0, 0, 1, // front face - bottom tri 0, 1, 0, 0, 1, 1, // front face - top tri 0, 0, 1, 0, 1, 1, // left face - bottom tri 0, 1, 0, 0, 1, 1, // left face - top tri 0, 0, 1, 0, 1, 1, // right face - bottom tri 1, 1, 1, 0, 0, 1, // right face - top tri 1, 0, 0, 0, 0, 1, // bottom face - bottom tri 0, 0, 0, 1, 1, 0, // bottom face - top tri 0, 1, 1, 1, 1, 0, // top face - bottom tri 0, 1, 0, 0, 1, 1, // top face - top tri 0, 0, 1, 0, 1, 1 ]; /** Triangles of all cubes */ private static const TRIS:Vector.<uint> = new <uint>[ 2, 1, 0, // back face - bottom tri 5, 4, 3, // back face - top tri 6, 7, 8, // front face - bottom tri 9, 10, 11, // front face - top tri 12, 13, 14, // left face - bottom tri 15, 16, 17, // left face - top tri 20, 19, 18, // right face - bottom tri 23, 22, 21, // right face - top tri 26, 25, 24, // bottom face - bottom tri 29, 28, 27, // bottom face - top tri 30, 31, 32, // top face - bottom tri 33, 34, 35 // top face - bottom tri ]; [Embed(source="flash_logo.png")] private static const TEXTURE:Class; private static const TEMP_DRAW_MATRIX:Matrix3D = new Matrix3D(); private var context3D:Context3D; private var vertexBuffer:VertexBuffer3D; private var vertexBuffer2:VertexBuffer3D; private var indexBuffer:IndexBuffer3D; private var program:Program3D; private var texture:Texture; private var camera:Camera3D; private var cubes:Vector.<Cube> = new Vector.<Cube>(); private var fps:TextField = new TextField(); private var lastFPSUpdateTime:uint; private var lastFrameTime:uint; private var frameCount:uint; private var driver:TextField = new TextField(); public function Stage3DCameraTest() { 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 ); context3D.enableErrorChecking = true; // Setup camera camera = new Camera3D( 0.1, // near 100, // far stage.stageWidth / stage.stageHeight, // aspect ratio 40*(Math.PI/180), // vFOV 0, 0, 10, // position 0, 0, 0, // target 0, 1, 0 // up dir ); // Setup cubes for (var i:int; i < 5; ++i) { for (var j:int = 0; j < 5; ++j) { for (var k:int = 0; k < 5; ++k) { cubes.push(new Cube(i*2, j*2, k*2)); } } } // 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( "Move Forward", "Move Backward", "Move Left", "Move Right", "Move Up", "Move Down", "Yaw Left", "Yaw Right", "Pitch Up", "Pitch Down", "Roll Left", "Roll Right" ); 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 buffers vertexBuffer = context3D.createVertexBuffer(36, 3); vertexBuffer.uploadFromVector(POSITIONS, 0, 36); vertexBuffer2 = context3D.createVertexBuffer(36, 2); vertexBuffer2.uploadFromVector(TEX_COORDS, 0, 36); indexBuffer = context3D.createIndexBuffer(36); indexBuffer.uploadFromVector(TRIS, 0, 36); // Setup texture var bmd:BitmapData = (new TEXTURE() as Bitmap).bitmapData; texture = context3D.createTexture( bmd.width, bmd.height, Context3DTextureFormat.BGRA, true ); texture.uploadFromBitmapData(bmd); // Start the simulation addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function makeButtons(...labels): void { const PAD:Number = 5; var curX:Number = PAD; var curY:Number = stage.stageHeight - PAD; for each (var label:String in labels) { var tf:TextField = new TextField(); tf.mouseEnabled = false; tf.selectable = false; tf.defaultTextFormat = new TextFormat("_sans", 16, 0x0071BB); 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; } } private function onButton(ev:MouseEvent): void { var mode:String = ev.target.getChildByName("lbl").text; switch (mode) { case "Move Forward": camera.moveForward(1); break; case "Move Backward": camera.moveBackward(1); break; case "Move Left": camera.moveLeft(1); break; case "Move Right": camera.moveRight(1); break; case "Move Up": camera.moveUp(1); break; case "Move Down": camera.moveDown(1); break; case "Yaw Left": camera.yaw(-10); break; case "Yaw Right": camera.yaw(10); break; case "Pitch Up": camera.pitch(-10); break; case "Pitch Down": camera.pitch(10); break; case "Roll Left": camera.roll(10); break; case "Roll Right": camera.roll(-10); break; } } private function onEnterFrame(ev:Event): void { // Render scene context3D.setProgram(program); context3D.setVertexBufferAt( 0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3 ); context3D.setVertexBufferAt( 1, vertexBuffer2, 0, Context3DVertexBufferFormat.FLOAT_2 ); context3D.setTextureAt(0, texture); context3D.clear(0.5, 0.5, 0.5); // Draw all cubes var worldToClip:Matrix3D = camera.worldToClipMatrix; var drawMatrix:Matrix3D = TEMP_DRAW_MATRIX; for each (var cube:Cube in cubes) { cube.mat.copyToMatrix3D(drawMatrix); drawMatrix.prepend(worldToClip); context3D.setProgramConstantsFromMatrix( Context3DProgramType.VERTEX, 0, drawMatrix, false ); context3D.drawTriangles(indexBuffer, 0, 12); } context3D.present(); // Update frame rate display frameCount++; var now:int = getTimer(); var dTime:int = now - lastFrameTime; var elapsed:int = now - lastFPSUpdateTime; if (elapsed > 1000) { var framerateValue:Number = 1000 / (elapsed / frameCount); fps.text = "FPS: " + framerateValue.toFixed(4); lastFPSUpdateTime = now; frameCount = 0; } lastFrameTime = now; } } } import flash.geom.*; class Cube { public var mat:Matrix3D; public function Cube(x:Number, y:Number, z:Number) { mat = new Matrix3D( new <Number>[ 1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1 ] ); } }
I hope you find this class useful!
Spot a bug? Let me know in the comments.
#1 by AlexG on December 6th, 2011 ·
Great tutorial for Molehill neginers.
What is AGALMiniAssembler ? Could you make some tutorials about shaders? Thanks.
#2 by jackson on December 6th, 2011 ·
Stage3D
shaders are uploaded as AGAL (Adobe Graphics Assembly Language) bytecode. The simplest and most direct way of writing these shaders is to write in Adobe’s AGAL assembly language and use AGALMiniAssembler.as to assemble them into vertex and fragment shaders. Rather than posting it here (it’s so common it might as well be part of the Flash Player API at this point), you can find it at a number of locations via a web search.I have some articles about shaders in the pipeline now, so stay tuned. :)
#3 by orion on December 9th, 2011 ·
no slerping ?
come on, admit it: you know you’ve always wanted to write a slerp routine. ;)
#4 by Sam on December 11th, 2011 ·
I get this error:
“…Stage3DCameraTest\src\Camera3D.as(603): col: 27 Error: Call to a possibly undefined method copyToMatrix3D through a reference with static type flash.geom:Matrix3D.”
#5 by jackson on December 11th, 2011 ·
Make sure you’re using a Flash Player 11.0+ playerglobal.swc as that function is new to
Matrix3D
.#6 by Sam on December 12th, 2011 ·
Thanks, it works now! It took me some time to set in Flashdevelop as I haven’t used playerglobal.swc until now. I thought it wasn’t necessary (I wonder why the latest SDK and Flash player debugger is not enough), but now I see I can have an easier access to flash classes too -BTW, I just saw there is a “flash.system.Capabilities” one -very useful!
Your code is also very useful to me, as I need to have a better understanding of the camera, the way the GPU works and some aspects of as3. Great work!
#7 by jackson on December 12th, 2011 ·
No problem. :)
The reason you didn’t need playerglobal.swc in the past is that it is automatically linked based on your “-target-player” parameter to the compiler (or the compiler’s default if you don’t specify one). If you go to your Flex SDK/frameworks/libs/player/, you’ll see playerglobal.swc files in Flash Player version directories. Feel free to create a “11.0” folder there and put your playerglobal.swc in it. Then you won’t need to explicitly link the playerglobal.swc in your project as you can just set “-target-player=11.0”. Cleaner in my opinion, but essentially the same thing in the end.
Have fun learning 3D!
#8 by Ercan Gigi on December 26th, 2011 ·
I think I found a bug with the options “Pitch Up” and “Pitch Down”. These seem to effect the field-of-view.
Try going inside a cube and look all the way up or down. The field-of-view will increase until everything collapses into a single point.
#9 by jackson on December 27th, 2011 ·
This is actually not a bug, but an intrinsic problem with perspective cameras. What is happening in that situation is that the view direction (direction the camera is looking) is becoming closer and closer to the up direction (direction you define as “up”). When these two are the same, the matrix math breaks down and you get the effect you’re seeing. What needs to happen in this case is for the user of the camera to specify a new “up” direction since the old “up” direction is no longer valid. For example, if X is the view direction and Y is the up direction and you rotate the view direction until it is Y then you might specify a new up direction of -X. Unfortunately, since you could just as validly specify Z or -Z as the new up direction, there’s no way for the
Camera3D
class to reliably change the up direction for the user.Thanks for pointing this out though. I should probably have explained this limitation in the original article.
#10 by StimpY on June 25th, 2012 ·
One Question, in your code you build the Matrix for the Shader (example with empty localMatrix):
But how i can now move objects in world space arround?!?!!
I can append or prepand what and when i want – Still the Object not move or just a part of vectors move , others leave as there are…is there a bug?
Greetz
#11 by jackson on June 25th, 2012 ·
To move objects around, you change their model matrix. For more details, I’ve written an article on the subject: Stepping Through The Stage3D Pipeline.