/* ** Copyright (C) 2005 Antonin Stefanutti ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ ///////////////////////////////////////////////////////////////////////////////////////// class ViewPort extends MovieClip { //array of objects in the scene public var object:Array; //XML object allowing to load the scene from an external file public var sceneXML:eXML; //viewing base definition public var r:Point3D; public var r0:Point3D; public var e:Point3D; public var h:Point3D; //viewing base backup public var r_:Point3D; public var e_:Point3D; public var h_:Point3D; //viewing base vectors public var n:Point3D; public var u:Point3D; public var v:Point3D; //light settings (ambiant & illumination) public var Ia:Number; public var Il:Number; //interval id for keyboard events private var intID:Number; //interval period for keyboard events static private var _F:Number = 5; //old mouse position memory private var _xmouseOld:Number; private var _ymouseOld:Number; public var minZoom:Number; public var wheelFactor:Number; //z value of the near clipping plane public var near:Number; public var angle:Number; public var width:Number; public var height:Number; //focal length public var f:Number; //the four clipping planes (left,top,right,bottom) public var lp:Plane3D; public var tp:Plane3D; public var rp:Plane3D; public var bp:Plane3D; //clipped faces (polygons) vertices buffers private var p0buf:Array; private var p1buf:Array; //faces (polygons) scan conversion buffer private var leBuf:Array; private var reBuf:Array; //maximum number of vertices of the face after clipping static private var _MAX_VERTEX = 12; //S-buffer private var sBuf:SBuffer; //maximum number of segments inside the S-buffer static private var _MAX_SEGMENTS = 3500; public var display:MovieClip; //viewport boolean states private var isSceneLoaded:Boolean; private var mustStartRender:Boolean; private var isRendering:Boolean; ///////////////////////////////////////////////////////////////////////////////////////// //constructor public function ViewPort() { isSceneLoaded = false; mustStartRender = false; isRendering = false; r = new Point3D(0,0,0); r0 = new Point3D(0,0,0); e = new Point3D(0,0,0); h = new Point3D(0,0,0); r_ = new Point3D(0,0,0); e_ = new Point3D(0,0,0); h_ = new Point3D(0,0,0); n = new Point3D(0,0,0); u = new Point3D(0,0,0); v = new Point3D(0,0,0); sceneXML = new eXML(); sceneXML.parameters.viewPort = this; sceneXML.ignoreWhite = true; sceneXML.onLoad = endLoadScene; minZoom = 20; wheelFactor = -15; lp = new Plane3D(); tp = new Plane3D(); rp = new Plane3D(); bp = new Plane3D(); p0buf = new Array(_MAX_VERTEX); p1buf = new Array(_MAX_VERTEX); leBuf = new Array(_MAX_VERTEX); reBuf = new Array(_MAX_VERTEX); for(var i = 0; i < _MAX_VERTEX; i++){ p0buf[i] = new Point3D(0,0,0); p1buf[i] = new Point3D(0,0,0); leBuf[i] = new Edge3D(); reBuf[i] = new Edge3D(); } sBuf = new SBuffer(); } ///////////////////////////////////////////////////////////////////////////////////////// //set the way the S-buffer render each segment public function setRender(t:Number, s:Number, a:Number):Void { sBuf.setRender(t,s,a); } ///////////////////////////////////////////////////////////////////////////////////////// //callback function usefull when you want to display, for example the camera position public function updateInfos():Void { } ///////////////////////////////////////////////////////////////////////////////////////// //load the XML external file describing the scene public function loadScene(XMLfile:String) { if (isRendering) mustStartRender = true; stopRender(); isSceneLoaded = false; if (display) removeMovieClip(display); display = createEmptyMovieClip("dp", 0); onLoadScene(); sceneXML.load(XMLfile); } ///////////////////////////////////////////////////////////////////////////////////////// private function endLoadScene(success:Boolean) { var eXml = this; var vp:MovieClip = eXml.parameters.viewPort; if (success) { vp.initScene(); vp.isSceneLoaded = true; if (vp.mustStartRender){ vp.startRender(); vp.mustStartRender = false; } } else { trace("ViewPort::endLoadScene : loading error !"); } vp.onEndLoadScene(success); } ///////////////////////////////////////////////////////////////////////////////////////// //callback function which can be overriden outside the class //to know when the scene is starting loading public function onLoadScene(success:Boolean):Void { } ///////////////////////////////////////////////////////////////////////////////////////// //callback function which can be ocerriden outside the class //to know when the scene is totally loaded public function onEndLoadScene(success:Boolean):Void { } ///////////////////////////////////////////////////////////////////////////////////////// //parse the previously loaded XML to initialize the scene private function initScene():Void { if (object) delete object; object = new Array(); var tmpArray:Array = new Array(); var scene:Array = sceneXML.firstChild.childNodes; var sceneData = sceneXML.firstChild.attributes; tmpArray = sceneData.lookAt.split(","); r.set(Number(tmpArray[0]), Number(tmpArray[1]), Number(tmpArray[2])); r0.setP(r); r_.setP(r); tmpArray = sceneData.camera.split(","); e.set(Number(tmpArray[0]), Number(tmpArray[1]), Number(tmpArray[2])); e_.setP(e); tmpArray = sceneData.up.split(","); h.set(Number(tmpArray[0]), Number(tmpArray[1]), Number(tmpArray[2])); h_.setP(h); Ia = Number(sceneData.Ia); Il = Number(sceneData.Il); near = Number(sceneData.near); angle = Number(sceneData.angle); width = Number(sceneData.width); height = Number(sceneData.height); sBuf.set(height,_MAX_SEGMENTS); for (var l = 0; l < scene.length; l++){ //object creation var obj:Array = scene[l].childNodes; var objData = scene[l].attributes; var node:Array = new Array(); var poly:Array = new Array(); object[objData.id] = display.attachMovie("Object3D",objData.id,display.getNextHighestDepth(),{node:node,poly:poly,color:Number(objData.color),Ka:Number(objData.Ka),Kd:Number(objData.Kd),Ks:Number(objData.Ks),bfc:(objData.bfc) ? Boolean(Number(objData.bfc)) : true}); object.push(object[objData.id]); //node (vertex) array creation var objNode:Array = obj[0].childNodes; for (var i = 0; i < objNode.length; i++) { var nodeData = objNode[i].attributes; tmpArray = nodeData.xyz.split(","); node[nodeData.id] = new Point3D(Number(tmpArray[0]),Number(tmpArray[1]),Number(tmpArray[2])); } //poly (face) array creation var objPoly:Array = obj[1].childNodes; for (var i = 0; i < objPoly.length; i++) { var polyData = objPoly[i].attributes; var polyNode:Array = objPoly[i].childNodes; var nodeArray:Array = new Array(); for (var j = 0; j < polyNode.length; j++) { nodeArray[j] = node[polyNode[j].attributes.id]; } poly[polyData.id] = new Polygon3D(this, object[objData.id], nodeArray, (polyData.color) ? Number(polyData.color) : null, (polyData.bfc) ? Boolean(Number(polyData.bfc)) : null, p0buf, p1buf, leBuf, reBuf, sBuf); } /*if (objData.scale){ tmpArray = objData.scale.split(","); switch(tmpArray.length){ case 1: object[objData.id].sclx0(Number(tmpArray[0]),Number(tmpArray[0]),Number(tmpArray[0])); break; case 3: object[objData.id].sclx0(Number(tmpArray[0]),Number(tmpArray[1]),Number(tmpArray[2])); break; } }*/ } //set the Field Of View setFOV(); //compute the viewing base (camera base) viewBase(); //call the 'callback function' to display extra information updateInfos(); } ///////////////////////////////////////////////////////////////////////////////////////// //re-initialized the viewing base with the initial settings public function resetView():Void { r.setP(r_); e.setP(e_); h.setP(h_); viewBase(); updateInfos(); } ///////////////////////////////////////////////////////////////////////////////////////// //set the 4 clipping planes part of the Field Of View : //top, bottom, left, right //and the focal length private function setFOV():Void { f = width / (2*Math.tan(angle*(Math.PI/180)/2)); var left:Number = -width / (2*f); var top:Number = (height+1) / (2*f); var right:Number = width / (2*f); var bottom:Number = -(height+1) / (2*f); lp.set(0,0,0,left,1,-1,left,0,-1); tp.set(0,0,0,1,top,-1,0,top,-1); rp.set(0,0,0,right,0,-1,right,1,-1); bp.set(0,0,0,0,bottom,-1,1,bottom,-1); } ///////////////////////////////////////////////////////////////////////////////////////// //render the scene in the display MovieClip public function render():Void { //flush the S-Buffer sBuf.reset(); //fill the S-Buffer with each object for (var i in object) object[i].render(); //clear each MovieClip related with each object in the scene for (var i in display) display[i].clear(); //and finally draw all the segments in the S-Buffer into the display MovieClip sBuf.render(); } ///////////////////////////////////////////////////////////////////////////////////////// //just one part of the render process for external call and customized render function //clear each MovieClip related with each object in the scene //and then draw all the segments in the S-Buffer into the display MovieClip public function draw():Void { for (var mc in display) display[mc].clear(); sBuf.render(); } ///////////////////////////////////////////////////////////////////////////////////////// //again just one part of the render process for external call and customized render function //flush the S-Buffer public function reset():Void { sBuf.reset(); } ///////////////////////////////////////////////////////////////////////////////////////// //function to be called in order to start the rendering process //which consists in rendering the scene each frame public function startRender():Void { if (isSceneLoaded){ commands(true); this.onEnterFrame = render; isRendering = true; } else { mustStartRender = true; } } ///////////////////////////////////////////////////////////////////////////////////////// //stop the each frame rendering process public function stopRender():Void { delete this.onEnterFrame; commands(false); mustStartRender = false; isRendering = false; } ///////////////////////////////////////////////////////////////////////////////////////// //compute the view base (n,u,v) in the scene world coordinates private function viewBase():Void { n.x = e.x - r.x; n.y = e.y - r.y; n.z = e.z - r.z; var d:Number = Math.sqrt(n.x*n.x + n.y*n.y + n.z*n.z); n.x /= d; n.y /= d; n.z /= d; u.x = h.y*n.z - n.y*h.z; u.y = h.z*n.x - n.z*h.x; u.z = h.x*n.y - n.x*h.y; d = Math.sqrt(u.x*u.x + u.y*u.y + u.z*u.z); u.x /= d; u.y /= d; u.z /= d; v.x = n.y*u.z - u.y*n.z; v.y = n.z*u.x - u.z*n.x; v.z = n.x*u.y - u.x*n.y; } ///////////////////////////////////////////////////////////////////////////////////////// //dolly the camera i.e. dolly the camera position e:Point3D towards the 'look at' position r:Point3D public function dolly(d:Number):Void { if (e.dist(r) > minZoom || d>0){ e.x += n.x*d; e.y += n.y*d; e.z += n.z*d; updateInfos(); } } ///////////////////////////////////////////////////////////////////////////////////////// //tumble the camera horizontally public function tumbleH(w:Number):Void { e.rotQxP(w, v, r); h.rotQx0(w, v); viewBase(); updateInfos(); } ///////////////////////////////////////////////////////////////////////////////////////// //tumble the camera vertically public function tumbleV(w:Number):Void { e.rotQxP(w, u, r); h.rotQx0(w, u); viewBase(); updateInfos(); } ///////////////////////////////////////////////////////////////////////////////////////// //tumble the camera public function tumble(w0:Number,w1:Number):Void { e.rotQxP(w0, v, r); h.rotQx0(w0, v); e.rotQxP(w1, u, r); h.rotQx0(w1, u); viewBase(); } ///////////////////////////////////////////////////////////////////////////////////////// //pan the camera public function pan(w0:Number, w1:Number):Void { r.rotQxP(w0, v, e); h.rotQx0(w0, v); r.rotQxP(w1, u, e); h.rotQx0(w1, u); viewBase(); r0.setP(r); updateInfos(); } ///////////////////////////////////////////////////////////////////////////////////////// //tilt the camera public function tilt(w:Number):Void { h.rotQx0(w, n); viewBase(); updateInfos(); } ///////////////////////////////////////////////////////////////////////////////////////// //shake the camera randomly public function shake(d:Number):Void { r.x = r0.x + d*(Math.random()-0.5); r.y = r0.y + d*(Math.random()-0.5); r.z = r0.z + d*(Math.random()-0.5); viewBase(); updateInfos(); } ///////////////////////////////////////////////////////////////////////////////////////// public function trackXY(dx:Number, dy:Number):Void { var DX:Number = u.x*dx + v.x*dy; var DY:Number = u.y*dx + v.y*dy; var DZ:Number = u.z*dx + v.z*dy; e.x += DX; e.y += DY; e.z += DZ; r.x += DX; r.y += DY; r.z += DZ; r0.x = r.x; r0.y = r.y; r0.z = r.z; viewBase(); updateInfos(); } ///////////////////////////////////////////////////////////////////////////////////////// public function trackX(dx:Number):Void { e.x += u.x*dx; e.y += u.y*dx; e.z += u.z*dx; r.x += u.x*dx; r.y += u.y*dx; r.z += u.z*dx; r0.x = r.x; r0.y = r.y; r0.z = r.z; viewBase(); updateInfos(); } ///////////////////////////////////////////////////////////////////////////////////////// public function trackY(dy:Number):Void { e.x += v.x*dy; e.y += v.y*dy; e.z += v.z*dy; r.x += v.x*dy; r.y += v.y*dy; r.z += v.z*dy; r0.x = r.x; r0.y = r.y; r0.z = r.z; viewBase(); updateInfos(); } ///////////////////////////////////////////////////////////////////////////////////////// public function trackZ(dy:Number):Void { e.x += n.x*dy; e.y += n.y*dy; e.z += n.z*dy; r.x += n.x*dy; r.y += n.y*dy; r.z += n.z*dy; r0.x = r.x; r0.y = r.y; r0.z = r.z; viewBase(); updateInfos(); } ///////////////////////////////////////////////////////////////////////////////////////// public function commands(state:Boolean):Void { if (state) { Mouse.addListener(this); Key.addListener(this); onKeyDown = keyDown; } else { clearInterval(intID); Key.removeListener(this); Mouse.removeListener(this); } } ///////////////////////////////////////////////////////////////////////////////////////// private function keyDown():Void { switch (Key.getCode()){ case Key.PGUP: intID = setInterval(this, "trackZ", _F, -2); break case Key.PGDN: intID = setInterval(this, "trackZ", _F, 2); break case Key.LEFT: intID = setInterval(this, "trackX", _F, -2); break case Key.RIGHT: intID = setInterval(this, "trackX", _F, 2); break case Key.UP: intID = setInterval(this, "trackY", _F, -2); break case Key.DOWN: intID = setInterval(this, "trackY", _F, 2); break case Key.CONTROL: _xmouseOld = _xmouse; _ymouseOld = _ymouse; intID = setInterval(this, "mousePan", _F, 0.002); break case Key.SHIFT: _xmouseOld = _xmouse; _ymouseOld = _ymouse; intID = setInterval(this, "mouseTumble", _F, 0.002); break case Key.SPACE: _xmouseOld = _xmouse; intID = setInterval(this, "mouseTilt", _F, 0.002); break /*case Key.ALT: _xmouseOld = _xmouse; _ymouseOld = _ymouse; intID = setInterval(this, "mouseTrack", _F, 0.002); break*/ case 83: /*'S'*/ intID = setInterval(this, "shake", _F, 10); break default: onDefaultKey(Key.getCode()); return; } delete onKeyDown; onKeyUp = keyUp; } ///////////////////////////////////////////////////////////////////////////////////////// private function onDefaultKey(code:Number):Void { onKey(code); delete onKeyDown; onKeyUp = offDefaultKey; } ///////////////////////////////////////////////////////////////////////////////////////// private function offDefaultKey():Void { delete onKeyUp; onKeyDown = keyDown; offKey(); } ///////////////////////////////////////////////////////////////////////////////////////// public function onKey(code:Number):Void { } ///////////////////////////////////////////////////////////////////////////////////////// public function offKey():Void { } ///////////////////////////////////////////////////////////////////////////////////////// private function keyUp():Void { clearInterval(intID); delete onKeyUp; onKeyDown = keyDown; } ///////////////////////////////////////////////////////////////////////////////////////// private function onMouseWheel(delta:Number){ dolly(delta/Math.abs(delta)*wheelFactor); } ///////////////////////////////////////////////////////////////////////////////////////// private function mousePan(d:Number):Void { var dx:Number = -d*(_xmouse - _xmouseOld); var dy:Number = -d*(_ymouse - _ymouseOld); pan(dx,dy); _xmouseOld = _xmouse; _ymouseOld = _ymouse; } ///////////////////////////////////////////////////////////////////////////////////////// private function mouseTrack(d:Number):Void { var dx:Number = d*(_xmouse - _xmouseOld); var dy:Number = -d*(_ymouse - _ymouseOld); trackXY(dx,dy); _xmouseOld = _xmouse; _ymouseOld = _ymouse; } ///////////////////////////////////////////////////////////////////////////////////////// private function mouseTumble(d:Number):Void { var dx:Number = d*(_xmouse - _xmouseOld); var dy:Number = d*(_ymouse - _ymouseOld); tumble(dx,dy); _xmouseOld = _xmouse; _ymouseOld = _ymouse; updateInfos(); } ///////////////////////////////////////////////////////////////////////////////////////// private function mouseTilt(d:Number):Void { var dx:Number = -d*(_xmouse - _xmouseOld); tilt(dx); _xmouseOld = _xmouse; } ///////////////////////////////////////////////////////////////////////////////////////// }