package { import flash.display.Sprite; import flash.display.Graphics; import flash.events.*; import flash.geom.*; import flash.utils.getTimer; import flash.utils.Timer; import flash.text.TextFormat; import fl.events.SliderEvent; import fl.controls.SliderDirection; import fl.controls.Button; import fl.controls.Slider; import fl.controls.RadioButton; import fl.controls.RadioButtonGroup; //Two kinds of interface controls for a 3D scene. public class Scene extends Sprite { //core scene setup variables private var tray:ModelSprite; private const TRAY_X:int = 300; private const TRAY_Y:int = 175; //Variables for the two approaches to changing the viewpoint //For viewpoint movement private var center:Sprite; private var zoomr:Slider; private var boundaryTimer:Timer; //For pan and orbit movement private var pan:Button; private var orbit:Button; private var dragStart:Point; private var currentXrotation:Number = 0; //common variables for operations private var inDrag:Boolean = false; private var offsetX:int; private var offsetY:int; //Create scene and user interface controls public function Scene():void { super(); //Setup the radiobutton "menus" and 3D view //turn off titles (until a radiobutton is selected) alsoTitle.visible = false; zoomTitle.visible = false; shiftTitle.visible = false; //create the 3D object tray = new ModelSprite(); tray.x = TRAY_X; tray.y = TRAY_Y; //Setting a nonzero z position creates the tray.transform.matrix3D member //object, needed for rotating the object with the Matrix3D approach tray.z = -1; addChild(tray); //Increase the font size var tf:TextFormat = new TextFormat(null,13); tf.kerning = true; viewpointNav.setStyle("textFormat", tf); var tf2:TextFormat = new TextFormat(null,13); tf2.kerning = true; moveobjectsNav.setStyle("textFormat" ,tf2); //Three actions are available - selecting a nav method and a reset viewpointNav.addEventListener(MouseEvent.CLICK, viewpointHandler); moveobjectsNav.addEventListener(MouseEvent.CLICK, moveObjectsHandler); resetButton.addEventListener(MouseEvent.CLICK, resetPosition); } public function viewpointHandler(event:MouseEvent):void { //turn off the other method undoPanOrbit(); //Setup the UI controls zoomr = new Slider(); zoomr.move(24,250); zoomr.direction = SliderDirection.VERTICAL; zoomr.liveDragging = true; zoomr.minimum = 1; zoomr.maximum = 179; //A Slider value must be set after the minimum and maximum for some reason zoomr.value = root.transform.perspectiveProjection.fieldOfView; addChild(zoomr); zoomr.addEventListener(SliderEvent.CHANGE, zoomHandler); zoomTitle.visible = true; alsoTitle.visible = true; createCenter(); } public function moveObjectsHandler(event:MouseEvent):void { //turn off the other method undoViewpoint(); //Setup the nav buttons pan = new Button(); pan.label = "Pan"; pan.move(52,75); orbit = new Button(); orbit.label = "Orbit"; orbit.move(89,75); pan.toggle = orbit.toggle = true; pan.width = orbit.width = 35; pan.height = orbit.height = 18; pan.selected = true; var tf:TextFormat = new TextFormat(null,11); tf.kerning = true; pan.setStyle("textFormat", tf); pan.setStyle("textPadding", 2); var tf2:TextFormat = new TextFormat(null,11); tf2.kerning = true; orbit.setStyle("textFormat" ,tf2); orbit.setStyle("textPadding", 2); addChild(pan); addChild(orbit); pan.addEventListener(MouseEvent.CLICK, panClick); orbit.addEventListener(MouseEvent.CLICK, orbitClick); //Listeners are on the stage so that click and drag can be done anywhere on //the stage stage.addEventListener(MouseEvent.MOUSE_DOWN, startMoveObject); stage.addEventListener(MouseEvent.MOUSE_UP, stopMoveObject); stage.addEventListener( MouseEvent.MOUSE_MOVE, doMoveObject); shiftTitle.visible = true; } public function createCenter():void { const centerRadius:int = 15; center = new Sprite(); // circle center.graphics.lineStyle(1, 0x000099); center.graphics.beginFill(0xCCCCCC, 0.7); center.graphics.drawCircle(0, 0, centerRadius); center.graphics.endFill(); // cross hairs center.graphics.moveTo(0, centerRadius); center.graphics.lineTo(0, -centerRadius); center.graphics.moveTo(centerRadius, 0); center.graphics.lineTo(-centerRadius, 0); center.x = root.transform.perspectiveProjection.projectionCenter.x; center.y = root.transform.perspectiveProjection.projectionCenter.y; center.z = 0; addChild(center); //Hook up listeners for dragging the viewpoint center center.addEventListener(MouseEvent.MOUSE_DOWN, startDragViewpoint); center.addEventListener(MouseEvent.MOUSE_UP, stopDragViewpoint); center.addEventListener( MouseEvent.MOUSE_MOVE, doDragViewpoint); center.addEventListener( MouseEvent.MOUSE_OUT, doDragViewpointOut); } //Remove the drag viewpoint controls public function undoViewpoint():void { stopTimer(); if (center) { center.removeEventListener(MouseEvent.MOUSE_DOWN, startDragViewpoint); center.removeEventListener(MouseEvent.MOUSE_UP, stopDragViewpoint); center.removeEventListener(MouseEvent.MOUSE_MOVE, doDragViewpoint); center.removeEventListener(MouseEvent.MOUSE_OUT, doDragViewpointOut); removeChild(center); center = null; } if (zoomr) { zoomr.removeEventListener(SliderEvent.CHANGE, zoomHandler); removeChild(zoomr); zoomr = null; } zoomTitle.visible = false; alsoTitle.visible = false; } //Remove the Pan and Orbit buttons and other setup public function undoPanOrbit():void { if (pan) { pan.removeEventListener(MouseEvent.CLICK, panClick); orbit.removeEventListener(MouseEvent.CLICK, orbitClick); stage.removeEventListener(MouseEvent.MOUSE_DOWN, startMoveObject); stage.removeEventListener(MouseEvent.MOUSE_UP, stopMoveObject); stage.removeEventListener( MouseEvent.MOUSE_MOVE, doMoveObject); removeChild(pan); removeChild(orbit); pan = null; orbit = null; if (dragStart) { dragStart = null; } } shiftTitle.visible = true; tiltcontrol.visible = false; } //The viewpoint drag approach is limited by the mouse movement //within the screen size. To allow more movement, when the mouse //rolls out of bounds, a timer-based function allow the viewpoint //to keep moving in that direction public function startDragViewpoint(e:MouseEvent) { center.startDrag(); inDrag = true; offsetY = e.stageY; if (boundaryTimer) { stopTimer(); } } //The action is simple - set the viewpoint to the mouse position //and the Flash Player redraws the perspective public function doDragViewpoint(e:MouseEvent) { if (inDrag) { //Check for zoom instead if (e.shiftKey) { //Zoom the viewpoint closer to the object zoomr.value -= e.stageY - offsetY; root.transform.perspectiveProjection.fieldOfView = zoomr.value; } else { root.transform.perspectiveProjection.projectionCenter=new Point(center.x,center.y); } offsetY=e.stageY; } if (boundaryTimer) { stopTimer(); } } public function stopDragViewpoint(e:MouseEvent) { center.stopDrag(); root.transform.perspectiveProjection.projectionCenter=new Point(center.x,center.y); if (boundaryTimer) { stopTimer(); } inDrag=false; } //When the crosshairs are dragged out of the window, the timer // lets the viewpoint keep on changing public function doDragViewpointOut(e:MouseEvent) { //If rolled off the crosshairs and user was dragging at the time if (e.target==center && inDrag) { boundaryTimer=new Timer(75); boundaryTimer.addEventListener(TimerEvent.TIMER, timerHandler); boundaryTimer.start(); //Save which direction to keep moving the point of view //Divide by 9 sets the rate of change offsetX = (e.stageX - stage.stageWidth / 2) / 9; offsetY = (e.stageY - stage.stageHeight / 2) / 9; } } //Stop and remove the pov angle changer timer public function stopTimer():void { if (boundaryTimer) { boundaryTimer.stop(); boundaryTimer.removeEventListener(TimerEvent.TIMER, timerHandler); boundaryTimer=null; } } //As long as the mouse is out of bounds, increase the point of view angle //in the same direction public function timerHandler(ev:TimerEvent):void { root.transform.perspectiveProjection.projectionCenter= new Point( root.transform.perspectiveProjection.projectionCenter.x + offsetX, root.transform.perspectiveProjection.projectionCenter.y + offsetY); } //Zoom slider changes the fieldOfView angle as the method of setting //the zoom. public function zoomHandler(e:SliderEvent):void { root.transform.perspectiveProjection.fieldOfView = e.value; } //Toggle the pan or orbit navigation tools. Only allow one to be selected public function panClick(e:MouseEvent) { //Act like a pushbutton pan.selected = true; orbit.selected = false; tiltcontrol.visible = false; } public function orbitClick(e:MouseEvent) { //Act like a pushbutton orbit.selected = true; pan.selected = false; tiltcontrol.visible = true; tiltcontrol.selected = true; } //Functions for pan and orbit save the beginning mouse position //to determine the direction of drag public function startMoveObject(e:MouseEvent):void { dragStart = new Point(e.stageX,e.stageY); inDrag = true; } public function stopMoveObject(e:MouseEvent):void { inDrag = false; } public function doMoveObject(e:MouseEvent):void { if (inDrag) { //Convert to integer to avoid rounding errors that could accumulate var posX:int=e.stageX; var posY:int=e.stageY; //Check for zoom instead if (e.shiftKey) { //Unlike the viewpoint zoom, this time move the object //closer to the viewer var offsetY:int = posY - dragStart.y; tray.z += offsetY; } else if (pan.selected) { //move the object left, right, or up and down moveObjects(posX - dragStart.x, posY - dragStart.y); } else if (orbit.selected) { //rotate the object left, right, or up and down orbitObjects(posX - dragStart.x, posY - dragStart.y); } //trace("pos= " + tray.x + "," + tray.y + "," + tray.z); dragStart=new Point(posX,posY); } } //Move the object on the stage left/right and up/down public function moveObjects(offsetX:int, offsetY:int):void { //Don't bother if no move if (offsetX==0&&offsetY==0) { return; } //Set the new position. Both matrix3D and setting the x and y //properties work to accomplish this. tray.transform.matrix3D.appendTranslation(offsetX, offsetY, 0); //or //tray.x += offsetX; //tray.y += offsetY; } //Rotate the object on the stage public function orbitObjects(offsetX:int, offsetY:int):void { //Don't bother if no rotate if (offsetX==0 && offsetY==0) { return; } if (tiltcontrol.selected) { //Since most of the time the rotation will involve both //x and y rotation, and given that 3D rotations are not commutative, //meaning that the order of operation is important, this code will always //undo the previous X axis rotation, then perform the Y and X rotations. //This preserves the not tilted attribute (no Z rotation). If the two rotations //were performed without the "undo", then soon the object gets tilted even when //trying to rotate back to the original position. Use the transform.matrix3D API //as it is more efficient when performing multiple transformations //"Undo" the X rotation from previous rotation tray.transform.matrix3D.prependRotation(-currentXrotation, Vector3D.X_AXIS); //Apply the new Y rotation tray.transform.matrix3D.prependRotation(-offsetX /2, Vector3D.Y_AXIS); //Add the new X rotation to previous X rotation and "re"apply it currentXrotation += offsetY / 2; tray.transform.matrix3D.prependRotation(currentXrotation, Vector3D.X_AXIS); //trace("rotation= " + tray.rotationX + "," + tray.rotationY + "," + tray.rotationZ); } else { //Perform both rotations together tray.transform.matrix3D.prependRotation(-offsetX /2, Vector3D.Y_AXIS); tray.transform.matrix3D.prependRotation( offsetY / 2, Vector3D.X_AXIS); } } //Reset the position and view for both navigation methods public function resetPosition(e:Event):void { tray.x = TRAY_X; tray.y = TRAY_Y; tray.z = -1; //Use the rotation properties of the object instead of the matrix3D approach. //(Either one works, but matrix3D is more efficient for multiple operations) tray.rotationX=tray.rotationY=tray.rotationZ=0; root.transform.perspectiveProjection.projectionCenter=new Point(stage.stageWidth/2,stage.stageHeight/2); root.transform.perspectiveProjection.fieldOfView=55; } } }