package away3d.containers { import away3d.arcane; import away3d.cameras.*; import away3d.core.base.*; import away3d.core.block.BlockerArray; import away3d.core.clip.*; import away3d.core.draw.*; import away3d.core.math.MatrixAway3D; import away3d.core.render.*; import away3d.core.stats.*; import away3d.core.traverse.*; import away3d.core.utils.*; import away3d.events.*; import away3d.materials.*; import flash.display.BitmapData; import flash.display.BlendMode; import flash.display.DisplayObject; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; use namespace arcane; /** * Dispatched when a user moves the cursor while it is over a 3d object * * @eventType away3d.events.MouseEvent3D */ [Event(name="mouseMove",type="away3d.events.MouseEvent3D")] /** * Dispatched when a user presses the let hand mouse button while the cursor is over a 3d object * * @eventType away3d.events.MouseEvent3D */ [Event(name="mouseDown",type="away3d.events.MouseEvent3D")] /** * Dispatched when a user releases the let hand mouse button while the cursor is over a 3d object * * @eventType away3d.events.MouseEvent3D */ [Event(name="mouseUp",type="away3d.events.MouseEvent3D")] /** * Dispatched when a user moves the cursor over a 3d object * * @eventType away3d.events.MouseEvent3D */ [Event(name="mouseOver",type="away3d.events.MouseEvent3D")] /** * Dispatched when a user moves the cursor away from a 3d object * * @eventType away3d.events.MouseEvent3D */ [Event(name="mouseOut",type="away3d.events.MouseEvent3D")] /** * Sprite container used for storing camera, scene, session, renderer and clip references, and resolving mouse events */ public class View3D extends Sprite { /** @private */ arcane var _interactiveLayer:Sprite = new Sprite(); /** @private */ arcane function dispatchMouseEvent(event:MouseEvent3D):void { if (!hasEventListener(event.type)) return; dispatchEvent(event); } private var _scene:Scene3D; private var _session:AbstractRenderSession; private var _camera:Camera3D; private var _renderer:IRenderer; private var _defaultclip:Clipping = new Clipping(); private var _ini:Init; private var _mousedown:Boolean; private var _lastmove_mouseX:Number; private var _lastmove_mouseY:Number; private var _oldclip:Clipping; private var _internalsession:AbstractRenderSession; private var _updatescene:ViewEvent; private var _updated:Boolean; private var _cleared:Boolean; private var _pritraverser:PrimitiveTraverser = new PrimitiveTraverser(); private var _ddo:DrawDisplayObject = new DrawDisplayObject(); private var _container:DisplayObject; private var _hitPointX:Number; private var _hitPointY:Number; private var _sc:ScreenVertex = new ScreenVertex(); private var _consumer:IPrimitiveConsumer; private var screenX:Number; private var screenY:Number; private var screenZ:Number = Infinity; private var element:Object; private var drawpri:DrawPrimitive; private var material:IUVMaterial; private var object:Object3D; private var uv:UV; private var sceneX:Number; private var sceneY:Number; private var sceneZ:Number; private var primitive:DrawPrimitive; private var inv:MatrixAway3D = new MatrixAway3D(); private var persp:Number; private function checkSession(session:AbstractRenderSession):void { if (session is BitmapRenderSession) { _container = session.getContainer(this); _hitPointX += _container.x; _hitPointY += _container.y; } if (session.getContainer(this).hitTestPoint(_hitPointX, _hitPointY)) { for each (primitive in session.getConsumer(this).list()) checkPrimitive(primitive); for each (session in session.sessions) checkSession(session); } if (session is BitmapRenderSession) { _container = session.getContainer(this); _hitPointX -= _container.x; _hitPointY -= _container.y; } } private function checkPrimitive(pri:DrawPrimitive):void { if (pri is DrawFog) return; if (!pri.source || !pri.source._mouseEnabled) return; if (pri.minX > screenX) return; if (pri.maxX < screenX) return; if (pri.minY > screenY) return; if (pri.maxY < screenY) return; if (pri.contains(screenX, screenY)) { var z:Number = pri.getZ(screenX, screenY); if (z < screenZ) { if (pri is DrawTriangle) { var tri:DrawTriangle = pri as DrawTriangle; var testuv:UV = tri.getUV(screenX, screenY); if (tri.material is IUVMaterial) { var testmaterial:IUVMaterial = (tri.material as IUVMaterial); //return if material pixel is transparent if (!(tri.material is BitmapMaterialContainer) && !(testmaterial.getPixel32(testuv.u, testuv.v) >> 24)) return; uv = testuv; } material = testmaterial; } else { uv = null; } screenZ = z; persp = camera.zoom / (1 + screenZ / camera.focus); inv = camera.invView; sceneX = screenX / persp * inv.sxx + screenY / persp * inv.sxy + screenZ * inv.sxz + inv.tx; sceneY = screenX / persp * inv.syx + screenY / persp * inv.syy + screenZ * inv.syz + inv.ty; sceneZ = screenX / persp * inv.szx + screenY / persp * inv.szy + screenZ * inv.szz + inv.tz; drawpri = pri; object = pri.source; element = null; // TODO face or segment } } } private function notifySceneUpdate():void { //dispatch event if (!_updatescene) _updatescene = new ViewEvent(ViewEvent.UPDATE_SCENE, this); dispatchEvent(_updatescene); } private function createStatsMenu(event:Event):void { statsPanel = new Stats(this, stage.frameRate); statsOpen = false; } private function onSessionUpdate(event:SessionEvent):void { if (event.target is BitmapRenderSession) _scene.updatedSessions[event.target] = event.target; } private function onCameraTransformChange(e:Object3DEvent):void { _updated = true; } private function onCameraUpdated(e:CameraEvent):void { _updated = true; } private function onSessionChange(e:Object3DEvent):void { _session.sessions = [e.object.session]; } private function onMouseDown(e:MouseEvent):void { _mousedown = true; fireMouseEvent(MouseEvent3D.MOUSE_DOWN, mouseX, mouseY, e.ctrlKey, e.shiftKey); } private function onMouseUp(e:MouseEvent):void { _mousedown = false; fireMouseEvent(MouseEvent3D.MOUSE_UP, mouseX, mouseY, e.ctrlKey, e.shiftKey); } private function onMouseOut(e:MouseEvent):void { //if (e.eventPhase != EventPhase.AT_TARGET) // return; fireMouseEvent(MouseEvent3D.MOUSE_OUT, mouseX, mouseY, e.ctrlKey, e.shiftKey); } private function onMouseOver(e:MouseEvent):void { //if (e.eventPhase != EventPhase.AT_TARGET) // return; fireMouseEvent(MouseEvent3D.MOUSE_OVER, mouseX, mouseY, e.ctrlKey, e.shiftKey); } private function bubbleMouseEvent(event:MouseEvent3D):void { var tar:Object3D = event.object; while (tar != null) { if (tar.dispatchMouseEvent(event)) break; tar = tar.parent; } } /** * A background sprite positioned under the rendered scene. */ public var background:Sprite = new Sprite(); /** * A container for 2D overlays positioned over the rendered scene. */ public var hud:Sprite = new Sprite(); /** * Enables/Disables stats panel. * * @see away3d.core.stats.Stats */ public var stats:Boolean; /** * Keeps track of whether the stats panel is currently open. * * @see away3d.core.stats.Stats */ public var statsOpen:Boolean; /** * Object instance of the stats panel. * * @see away3d.core.stats.Stats */ public var statsPanel:Stats; /** * Optional string for storing source url. */ public var sourceURL:String; /** * Forces mousemove events to fire even when cursor is static. */ public var mouseZeroMove:Boolean; /** * Current object under the mouse. */ public var mouseObject:Object3D; /** * Current material under the mouse. */ public var mouseMaterial:IUVMaterial; /** * Defines whether the view always redraws on a render, or just redraws what 3d objects change. Defaults to true. * * @see #render() */ public var forceUpdate:Boolean; public var blockerarray:BlockerArray = new BlockerArray(); /** * Clipping area used when rendering. * * If null, the visible edges of the screen are located with the Clipping.screen() method. * * @see #render() * @see away3d.core.render.Clipping.scene() */ public var clip:Clipping; /** * Renderer object used to traverse the scenegraph and output the drawing primitives required to render the scene to the view. */ public function get renderer():IRenderer { return _renderer; } public function set renderer(val:IRenderer):void { if (_renderer == val) return; _renderer = val; _updated = true; if (!_renderer) throw new Error("View cannot have renderer set to null"); } /** * Flag used to determine if the camera has updated the view. * * @see #camera */ public function get updated():Boolean { return _updated; } /** * Camera used when rendering. * * @see #render() */ public function get camera():Camera3D { return _camera; } public function set camera(val:Camera3D):void { if (_camera == val) return; if (_camera) { _camera.removeOnSceneTransformChange(onCameraTransformChange); _camera.removeOnCameraUpdate(onCameraUpdated); } _camera = val; if (_camera) { _camera.addOnSceneTransformChange(onCameraTransformChange); _camera.addOnCameraUpdate(onCameraUpdated); } else { throw new Error("View cannot have camera set to null"); } } /** * Scene used when rendering. * * @see render() */ public function get scene():Scene3D { return _scene; } public function set scene(val:Scene3D):void { if (_scene == val) return; if (_scene) { _scene.internalRemoveView(this); delete _scene.viewDictionary[this]; _scene.removeOnSessionChange(onSessionChange); if (_session) _session.internalRemoveSceneSession(_scene.ownSession); } _scene = val; _updated = true; if (_scene) { _scene.internalAddView(this); _scene.addOnSessionChange(onSessionChange); _scene.viewDictionary[this] = this; if (_session) _session.internalAddSceneSession(_scene.ownSession); } else { throw new Error("View cannot have scene set to null"); } } /** * Session object used to draw all drawing primitives returned from the renderer to the view container. * * @see #renderer * @see #getContainer() */ public function get session():AbstractRenderSession { return _session; } public function set session(val:AbstractRenderSession):void { if (_session == val) return; if (_session) { _session.removeOnSessionUpdate(onSessionUpdate); if (_scene) _session.internalRemoveSceneSession(_scene.ownSession); } _session = val; _updated = true; if (_session) { _session.addOnSessionUpdate(onSessionUpdate); if (_scene) _session.internalAddSceneSession(_scene.ownSession); } else { throw new Error("View cannot have session set to null"); } //clear children while (numChildren) removeChildAt(0); //add children addChild(background); addChild(_session.getContainer(this)); addChild(_interactiveLayer); addChild(hud); } /** * Creates a new View3D object. * * @param init [optional] An initialisation object for specifying default instance properties. */ public function View3D(init:Object = null) { _ini = Init.parse(init) as Init; var stats:Boolean = _ini.getBoolean("stats", true); session = _ini.getObject("session") as AbstractRenderSession || new SpriteRenderSession(); scene = _ini.getObjectOrInit("scene", Scene3D) as Scene3D || new Scene3D(); camera = _ini.getObjectOrInit("camera", Camera3D) as Camera3D || new Camera3D({x:0, y:0, z:1000, lookat:"center"}); renderer = _ini.getObject("renderer") as IRenderer || new BasicRenderer(); clip = _ini.getObject("clip", Clipping) as Clipping; x = _ini.getNumber("x", 0); y = _ini.getNumber("y", 0); forceUpdate = _ini.getBoolean("forceUpdate", false); mouseZeroMove = _ini.getBoolean("mouseZeroMove", false); //setup blendmode for hidden interactive layer _interactiveLayer.blendMode = BlendMode.ALPHA; //setup view property on traverser _pritraverser.view = this; //setup events on view addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); addEventListener(MouseEvent.MOUSE_UP, onMouseUp); addEventListener(MouseEvent.MOUSE_OUT, onMouseOut); addEventListener(MouseEvent.MOUSE_OVER, onMouseOver); //setup default clip value if (!clip) clip = _defaultclip; //setup stats panel creation if (stats) addEventListener(Event.ADDED_TO_STAGE, createStatsMenu); } /** * Collects all information from the given type of 3d mouse event into a MouseEvent3D object that can be accessed from the getMouseEvent() method. * * @param type The type of 3d mouse event being triggered - can be MOUSE_UP, MOUSE_DOWN, MOUSE_OVER, MOUSE_OUT, and MOUSE_MOVE. * @param x The x coordinate being used for the 3d mouse event. * @param y The y coordinate being used for the 3d mouse event. * @param ctrlKey [optional] The ctrl key value being used for the 3d mouse event. * @param shiftKey [optional] The shift key value being used for the 3d mouse event. * * @see #getMouseEvent() * @see away3d.events.MouseEvent3D */ public function fireMouseEvent(type:String, x:Number, y:Number, ctrlKey:Boolean = false, shiftKey:Boolean = false):void { findHit(_internalsession, x, y); var event:MouseEvent3D = getMouseEvent(type); var target:Object3D = event.object; var targetMaterial:IUVMaterial = event.material; event.ctrlKey = ctrlKey; event.shiftKey = shiftKey; if (type != MouseEvent3D.MOUSE_OUT && type != MouseEvent3D.MOUSE_OVER) { dispatchMouseEvent(event); bubbleMouseEvent(event); } //catch rollover/rollout object3d events if (mouseObject != target || mouseMaterial != targetMaterial) { if (mouseObject != null) { event = getMouseEvent(MouseEvent3D.MOUSE_OUT); event.object = mouseObject; event.material = mouseMaterial; dispatchMouseEvent(event); bubbleMouseEvent(event); mouseObject = null; buttonMode = false; } if (target != null && mouseObject == null) { event = getMouseEvent(MouseEvent3D.MOUSE_OVER); event.object = target; event.material = mouseMaterial = targetMaterial; dispatchMouseEvent(event); bubbleMouseEvent(event); buttonMode = target.useHandCursor; } mouseObject = target; } } /** * Finds the object that is rendered under a certain view coordinate. Used for mouse click events. */ public function findHit(session:AbstractRenderSession, x:Number, y:Number):void { if (!session) return; screenX = x; screenY = y; screenZ = Infinity; material = null; object = null; _hitPointX = stage.mouseX; _hitPointY = stage.mouseY; if (this.session is BitmapRenderSession) { _container = this.session.getContainer(this); _hitPointX += _container.x; _hitPointY += _container.y; } checkSession(session); } /** * Returns a 3d mouse event object populated with the properties from the hit point. */ public function getMouseEvent(type:String):MouseEvent3D { var event:MouseEvent3D = new MouseEvent3D(type); event.screenX = screenX; event.screenY = screenY; event.screenZ = screenZ; event.sceneX = sceneX; event.sceneY = sceneY; event.sceneZ = sceneZ; event.view = this; event.drawpri = drawpri; event.material = material; event.element = element; event.object = object; event.uv = uv; return event; } /** * Returns the DisplayObject container of the rendered scene. * * @return The DisplayObject containing the output from the render session of the view. * * @see #session * @see away3d.core.render.BitmapRenderSession * @see away3d.core.render.SpriteRenderSession */ public function getContainer():DisplayObject { return _session.getContainer(this); } /** * Returns the bitmapData of the rendered scene. * * session is required to be an instance of BitmapRenderSession, otherwise an error is thrown. * * @throws Error incorrect session object - require BitmapRenderSession. * @return The rendered view image. * * @see #session * @see away3d.core.render.BitmapRenderSession */ public function getBitmapData():BitmapData { if (_session is BitmapRenderSession) return (_session as BitmapRenderSession).getBitmapData(this); else throw new Error("incorrect session object - require BitmapRenderSession"); } /** * Clears previously rendered view from all render sessions. * * @see #session */ public function clear():void { _updated = true; if (_internalsession) session.clear(this); } /** * Renders a snapshot of the view to the render session's view container. * * @see #session */ public function render():void { //update session if (_session != _internalsession) _internalsession = session; //update renderer if (_session.renderer != _renderer as IPrimitiveConsumer) _session.renderer = _renderer as IPrimitiveConsumer; //update scene notifySceneUpdate(); _oldclip = clip; //if clip set to default, determine screen clipping if (clip == _defaultclip) clip = _defaultclip.screen(this); //clear session _session.clear(this); //draw scene into view session if (_session.updated) { _ddo.view = this; _ddo.displayobject = _scene.session.getContainer(this); _ddo.session = _session; _ddo.screenvertex = _sc; _ddo.calc(); _consumer = _session.getConsumer(this); _consumer.primitive(_ddo); } //traverse scene _scene.traverse(_pritraverser); //render scene _session.render(this); _updated = false; //dispatch stats if (statsOpen) statsPanel.updateStats(_session.getTotalFaces(this), camera); //revert clip value clip = _oldclip; //debug check Init.checkUnusedArguments(); //check for mouse interaction fireMouseMoveEvent(); } /** * Defines a source url string that can be accessed though a View Source option in the right-click menu. * * Requires the stats panel to be enabled. * * @param url The url to the source files. */ public function addSourceURL(url:String):void { sourceURL = url; if (statsPanel) statsPanel.addSourceURL(url); } /** * Manually fires a mouseMove3D event. */ public function fireMouseMoveEvent(force:Boolean = false):void { if (!(mouseZeroMove || force)) if ((mouseX == _lastmove_mouseX) && (mouseY == _lastmove_mouseY)) return; fireMouseEvent(MouseEvent3D.MOUSE_MOVE, mouseX, mouseY); _lastmove_mouseX = mouseX; _lastmove_mouseY = mouseY; } /** * Default method for adding a mouseMove3d event listener. * * @param listener The listener function. */ public function addOnMouseMove(listener:Function):void { addEventListener(MouseEvent3D.MOUSE_MOVE, listener, false, 0, false); } /** * Default method for removing a mouseMove3D event listener. * * @param listener The listener function. */ public function removeOnMouseMove(listener:Function):void { removeEventListener(MouseEvent3D.MOUSE_MOVE, listener, false); } /** * Default method for adding a mouseDown3d event listener. * * @param listener The listener function. */ public function addOnMouseDown(listener:Function):void { addEventListener(MouseEvent3D.MOUSE_DOWN, listener, false, 0, false); } /** * Default method for removing a mouseDown3d event listener. * * @param listener The listener function. */ public function removeOnMouseDown(listener:Function):void { removeEventListener(MouseEvent3D.MOUSE_DOWN, listener, false); } /** * Default method for adding a mouseUp3d event listener. * * @param listener The listener function. */ public function addOnMouseUp(listener:Function):void { addEventListener(MouseEvent3D.MOUSE_UP, listener, false, 0, false); } /** * Default method for removing a 3d mouseUp event listener. * * @param listener The listener function. */ public function removeOnMouseUp(listener:Function):void { removeEventListener(MouseEvent3D.MOUSE_UP, listener, false); } /** * Default method for adding a 3d mouseOver event listener. * * @param listener The listener function. */ public function addOnMouseOver(listener:Function):void { addEventListener(MouseEvent3D.MOUSE_OVER, listener, false, 0, false); } /** * Default method for removing a 3d mouseOver event listener. * * @param listener The listener function. */ public function removeOnMouseOver(listener:Function):void { removeEventListener(MouseEvent3D.MOUSE_OVER, listener, false); } /** * Default method for adding a 3d mouseOut event listener. * * @param listener The listener function. */ public function addOnMouseOut(listener:Function):void { addEventListener(MouseEvent3D.MOUSE_OUT, listener, false, 0, false); } /** * Default method for removing a 3d mouseOut event listener. * * @param listener The listener function. */ public function removeOnMouseOut(listener:Function):void { removeEventListener(MouseEvent3D.MOUSE_OUT, listener, false); } } }