/*------------------------------------------------------------------------ * Copyright 2007-2008 (c) Dmitri Sviridov, cast3d.com. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. *------------------------------------------------------------------------ */ /** * * @author Dmitri Sviridov - sds * @version .90 * @date April, 23 2008 */ package { import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.*; import flash.utils.getTimer; import flash.utils.Timer; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFieldType; import flash.display.MovieClip; import flash.net.URLRequest; // Cast3D dependencies import cast3d.core.Cast3d; import cast3d.core.events.LoadEvent; import cast3d.loader.Xc3Loader; import cast3d.utils.controls.*; import cast3d.utils.manipulators.Manipulator; import cast3d.utils.manipulators.ppv2.TrackBall; import cast3d.utils.controllers.NavigationController; import cast3d.nodes.Node3d; import cast3d.tracks.Track3d; import cast3d.geom.Part3d; import cast3d.geom.Skin3d; import cast3d.frames.KeyFrame3d; // Papervision3d dependencies import org.papervision3d.scenes.Scene3D; // Import Papervision3D import org.papervision3d.cameras.*; import org.papervision3d.scenes.*; import org.papervision3d.cameras.*; import org.papervision3d.scenes.*; import org.papervision3d.lights.*; import org.papervision3d.render.*; import org.papervision3d.view.*; import org.papervision3d.materials.*; import org.papervision3d.core.proto.MaterialObject3D; [SWF(backgroundColor="#335566", frameRate="30")] public class Sample extends Sprite { private var manipulator:TrackBall; private var animator:Cast3d; private var cp:ControlPanel; private var scene:Scene3D; private var camera:Camera3D; private var viewport:Viewport3D; private var renderer:BasicRenderEngine; private var loader:Xc3Loader; private var loaded:Boolean; private var statusText:TextField; private var statusTimer:Timer; private var _navigations:Array = new Array; private var _current_nav:int = 0; public function Sample() { setup3DScene(); setupStage(); } /** * Configures the Stage object */ private function setupStage(): void { this.stage.scaleMode = StageScaleMode.NO_SCALE; this.stage.align = StageAlign.TOP_LEFT; } private function setup3DScene(): void { this.setupPpv3D(); this.setupCast3D(); this.setupControls(); this.loadData(); this.addEventListener(Event.ENTER_FRAME, this.handleEnterFrame); } /** * initial setup for Papervision3D. */ public function setupPpv3D(): void { this.viewport = new Viewport3D(300, 400, true, false,false,false); addChild( viewport ); this.scene = new Scene3D(); this.camera = new Camera3D(); this.renderer = new BasicRenderEngine(); } /** * initial setup for Cast3D. */ public function setupCast3D(): void { this.loaded = false; this.animator = new Cast3d(this.scene, this.camera); this.animator.animationType = Cast3d.ANIMATION_TYPE_BYFRAME; // ANIMATION_TYPE_REAL; // this.animator.animationStyle = Cast3d.ANIMATION_STYLE_FORWARD; Cast3d.fps = 22; this.animator.autoRewind = true; } /** * Function setups visual animation control panel. */ public function setupControls(): void { cp = new ControlPanel(animator); this.stage.addChild(cp); // cp.visible = false; statusText = new TextField(); statusText.textColor = 0x0000ff; statusText.autoSize = TextFieldAutoSize.LEFT; statusText.type = TextFieldType.DYNAMIC; statusText.y = cp.height; this.stage.addChild(statusText); } /** * Function performs 3D data load from a X3c file. */ private function loadData(): void { var modelpath:String = loaderInfo.parameters.model; var rpath:String = loaderInfo.parameters.rpath; this.loader = new Xc3Loader( modelpath ? modelpath: ""); this.loader.resourcePath = rpath ? rpath : ""; if (!modelpath || modelpath.length == 0) { // this.loader = new Xc3Loader("../../cast3dImport/models/walk/fig.xc3"); // this.loader.resourcePath = "../../cast3dImport/models/walk"; this.loader = new Xc3Loader("../../cast3dImport/models/walk/fc_b.xc3"); this.loader.resourcePath = "../../cast3dImport/models/walk"; } statusText.text = "loading file: " + loader.sourceURL; statusTimer = new Timer(1000, 0.2); // designates listeners for the interval and completion events statusTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete); statusTimer.start(); this.loader.preCalcMotion = false; this.loader.addEventListener(LoadEvent.LOAD_COMPLETE, this.cast3dLoadComplete); this.loader.addEventListener(LoadEvent.LOAD_ERROR, this.cast3dLoadError); this.loader.addEventListener(LoadEvent.LOAD_PROGRESS, this.cast3dLoadProgress); // this.loader.portLoader.onCreateColorMaterial = onColorMaterial; // this.loader.portLoader.onCreateAssetMaterial = onAssetMaterial; /** Set Call back functions to modify, augumnet or subsitute * material or/and geometry data. */ this.loader.load(this.animator.source); } private function onColorMaterial (name:String, material:MaterialObject3D):MaterialObject3D { material = new WireframeMaterial(0x777700,1); material.doubleSided = true; return material; } private function onAssetMaterial ( name:String, material:BitmapMaterial):MaterialObject3D { return new WireframeMaterial(0x00FF00,1); } /** * Timer handler */ private function onTimerComplete(event:TimerEvent):void { trace("Time's Up!"); statusText.textColor = 0xff0000; statusText.text = "Loading time exceeded 20 nimutes!"; removeTimer(); } /** * At the end of load, removes timer */ private function removeTimer():void { statusTimer.stop(); statusTimer.removeEventListener(TimerEvent.TIMER_COMPLETE,onTimerComplete); } /** * Handles the ENTER_FRAME event and updates the 3D scene. */ private function handleEnterFrame(event: Event): void { if (!this.loaded) return; var time:Number = getTimer(); // Update cast3D first this.animator.render(); // then render scene this.renderer.renderScene(scene, camera, viewport); // Update stat data if (this.animator.source && cp) { var frame:int = this.animator.source.currentFrame; var kframe:int = this.animator.source.currentKeyFrame; cp.setCurrentFrame(kframe,frame); cp.setCurrentTime(animator.currentTime); cp.currentFps = 1000.0/(getTimer() - time); if (this.manipulator) manipulator.update(); } } private function daeLoadComplete(e:Event):void { trace("loaded"); // view.singleRender(); this.manipulator = new TrackBall(this.animator,this.stage, this.viewport.viewportWidth, this.viewport.viewportHeight, Manipulator.Y_UP, Manipulator.X_S,Manipulator.Z_S,Manipulator.NY_S ); return; } /** * Handles the load complete event */ private function cast3dLoadComplete(event: LoadEvent): void { makeClone(); setupNavigator(); trace("cast3dLoadComplete "); this.manipulator = new TrackBall(this.animator,this.stage, this.viewport.viewportWidth, this.viewport.viewportHeight, Manipulator.Y_UP, Manipulator.X_S,Manipulator.Z_S,Manipulator.NY_S ); // manipulator.showCamera = true; // manipulator.showCOR = true; cp.manipulator = this.manipulator; this.loaded = true; removeTimer(); if (loader.loaderror.length) { statusText.textColor = 0xff0000; statusText.text = loader.loaderror; } else { statusText.visible = false; } this.animator.play(); } private function cast3dLoadProgress(event: LoadEvent): void { var n:Number = event.scenesTotal != 0.0 ? event.scenesLoaded/event.scenesTotal : 0; var percent:int = n*100; statusText.text = "Loading " + event.file + " ....... "+ percent.toString() + "%"; } /** * Handles the load Error event */ private function cast3dLoadError(event: LoadEvent): void { trace("cast3dLoadError ", event.message ); removeTimer(); statusText.textColor = 0xff0000; statusText.text = event.message; } /** * This functin make a clone of Model node and sets Navigaion Contraller */ private function makeClone():void { // First lets find the root node of a character // we know in advance it's Id is 'Cube' var nodename:String = "Cube"; var model_node:Node3d = this.animator.source.find(nodename) as Node3d; if (!model_node) { statusText.visible = true; statusText.text = "Failed to make clone. Not found node: " + nodename; return; } // next step is to make a clone instance of a character node var cloned_model_node:Node3d = model_node.clone(); // A cloned instance of a node if not attached is handing in the air // To make it visible we need to add it to a scene, whic is KeyFrame // There is only one KeyFrame ( with infinite duration) and we know Id // in advance by looking at the file. var kfname:String = "Scene_kf"; var kf:KeyFrame3d = this.animator.source.find(kfname) as KeyFrame3d; if (!kf) { statusText.visible = true; statusText.text = "Failed to make clone. Not found keyframe: " + kfname; return; } // adding cloned node to Keyframe kf.addNode(cloned_model_node); // This step is also importan to understand. // Although we added node to a scene, the actual rendering is taking place // in Rendering engine, in this case papervision3D. So Cast3d is just manupulating // with transforms and geometry. This step populates rendering engine with newly created // node's data. Xc3 file loader does it for you, after it's done, you need to do that explicitly. cloned_model_node.register(this.animator, kf); // lets create navigation conrloller for new node. var nc:NavigationController = new NavigationController(cloned_model_node,"navigator"); _navigations.push(nc); // We know that "cube" node actually represents Skined geometry, which means // the motion is controlled by another skeleton node(s), in this case "lowerBack" node is // root skeleton node( see source file) nodename = "lowerBack"; var skeleton_node:Node3d = this.animator.source.find(nodename) as Node3d; // lets make a clone of that too. Otherwise both original chatecter skin and cloned one // will be controlled by same skeleton node(s). var cloned_skeleton_node:Node3d = skeleton_node.clone(); if (!cloned_skeleton_node) { statusText.visible = true; statusText.text = "Failed to set Cloning for node: " + nodename; return; } // add to same scene kf.addNode(cloned_skeleton_node); // populate rendering engine cloned_skeleton_node.register(this.animator, kf); // This step required only for Skin var skin:Skin3d = cloned_model_node.part as Skin3d; if (skin) { // The cloned version of skinned node holds the references to original skeleton bones // Now we need to reassign to the bones of new ( cloned) skeleton nodes // binding works in the way that it tries to find matching id of old bone in provided skeleton bone branhes // once it finds it does the replacement. If fails to find any single bone, the whole process fails. if (!skin.bindBones(cloned_skeleton_node)) { statusText.visible = true; statusText.text = "Failed to make clone. Clould not bind skin: " + skin.id + " to node: " + cloned_skeleton_node.id; return; } } var tarck_id:String; var motionAlias:String; // now we add a 'walking' motion which is represented by MotionGroup class instance with id == "lowerBack_motion" // again, we know that by looking at source file. tarck_id = "lowerBack_motion311"; motionAlias = "walk"; if (!nc.addMotion(cloned_skeleton_node, tarck_id, motionAlias)) { statusText.visible = true; statusText.text = "Failed to add Motion " + motionAlias + " for track: " + tarck_id; return; } // another motion is a 'jump' motion with id == "lowerBack_motionjump" motionAlias = "jump"; tarck_id = "lowerBack_motionjump"; if (!nc.addMotion(cloned_skeleton_node, tarck_id, motionAlias)) { statusText.visible = true; statusText.text = "Failed to add Motion " + motionAlias + " for track: " + tarck_id; return; } // lets move the newly created model away from intial position so it does not interlap with original nc.position.x += 5.0; nc.rotation.x = 0; nc.rotation.y = 0; nc.rotation.z = 1; nc.rotation.w = 60 * Math.PI/180.0 ; this.stage.addEventListener(KeyboardEvent.KEY_DOWN, this.keyDownHandler); } private function setupNavigator():void { // First lets find the root node of a character // we know by looking at source file, its Id is 'Cube' var nodename:String = "Cube"; var node:Node3d = this.animator.source.find(nodename) as Node3d; if (!node) { statusText.visible = true; statusText.text = "Failed to set Navigation control for node: " + nodename; return; } // ---- Camera node attachement to a walking character node. --- // First, lets find a camera node var camnodename:String = "Camera"; var camnode:Node3d = this.animator.source.find(camnodename) as Node3d; if (camnode) { // At the moment caera node could be attached to other node // if so we need remove it from parent node children list var parentnode:Node3d = camnode.parent; if (parentnode) { // find cam node in list of children and remove from there var node_idx:int = parentnode.children.indexOf(camnode); if ( node_idx != -1) parentnode.children.splice(node_idx,1); } else { // if parent is null, then it may be a root node and attache directly to Key Frame var kfname:String = "Scene_kf"; var kf:KeyFrame3d = this.animator.source.find(kfname) as KeyFrame3d; if (kf) { // find cam node in KeyFrame children list and remove from there node_idx = kf.nodes.indexOf(camnode); if ( node_idx != -1) kf.nodes.splice(node_idx,1); } } // next, just attach it to a character node. node.children.push(camnode); // if you want adjust camera potision/orientation in local camera space // camnode.translate( 5,3,2); // camnode.rotate( 1,0,0, 60 * Math.PI/180.0); // convert top radians // Also you may remove other motion attache to this node // camnode.tracks.splice(0,camnode.tracks.length); } // create navigation conrloller fo this node. var nc:NavigationController = new NavigationController(node,"navigator"); _navigations.push(nc); // We know that "Cube" node actually represents Skinned geometry, which means // the motion is controlled by another skeleton node(s), in this case "lowerBack" node is // root skeleton node( see source file) nodename = "lowerBack"; node = this.animator.source.find(nodename) as Node3d; if (!node) { statusText.visible = true; statusText.text = "Failed to set Navigation control for node: " + nodename; return; } var tarck_id:String; var motionAlias:String; // now we add a 'walking' motion which is represented by MotionGroup class instance with id == "lowerBack_motion" // again, we know that by looking at source file. // Notice that 'motion' is produced by different node that we created nvigation controlled, which in that case "cube" tarck_id = "lowerBack_motion311"; motionAlias = "walk"; if (!nc.addMotion(node, tarck_id, motionAlias)) { statusText.visible = true; statusText.text = "Failed to add Motion " + motionAlias + " for track: " + tarck_id; return; } // another motion motionAlias = "jump"; tarck_id = "lowerBack_motionjump"; if (!nc.addMotion(node, tarck_id, motionAlias)) { statusText.visible = true; statusText.text = "Failed to add Motion " + motionAlias + " for track: " + tarck_id; return; } nc.position.x -= 5.0; nc.rotation.x = 0; nc.rotation.y = 0; nc.rotation.z = 1; nc.rotation.w = 60 * Math.PI/180.0 ; _current_nav = 1; this.stage.addEventListener(KeyboardEvent.KEY_DOWN, this.keyDownHandler); } public function keyDownHandler( event :KeyboardEvent ):void { var nc:NavigationController = _navigations[_current_nav]; if (!nc) return; trace(event.target + "(" + event.currentTarget + "): " + event.keyCode + "/" + event.charCode); switch( event.keyCode ) { case 9: // TAB _current_nav++; if (_current_nav >= _navigations.length) _current_nav = 0; break; case 37: // left // start walking motion ant rotate model over 1/4 of motion cycle ( which is one step) // in local coordinates by 30 degree rotation about Z nc.run("walk",0.25, null, { x:0, y:0, z:1, w: 30.0*Math.PI/180.0 }); break; case 38: // up // start walking motion by moving model over 1/4 of motion cycle ( which is one step) // and propogation node forward in local coordinates by Y = -0.33 nc.run("walk",.25,{x:0, y:-1.0, z:0}); break; case 39: // right // start walking motion ant rotate model over 1/4 of motion cycle ( which is one step) // in local coordinates by 30 degree rotation about -Z nc.run("walk",.25, null, {x:0, y:0, z:-1, w:30.0*Math.PI/180.0 }); break; case 40: // down // start walking motion by moving model over 1/4 of motion cycle ( which is one step) // and propogation node backwards in local coordinates by Y = 0.33 // also we reverse timing (last argument) for that motion so characted walks backwards. nc.run("walk",.25,{x:0, y:1.0, z:0}, null, true); break; case 32: // space nc.run("jump",1.0); break; } } } }