///
/*
MD5 animation loading and interaction example in Away3d
Demonstrates:
How to load MD5 mesh and anim files with bones animation from embedded resources.
How to map animation data after loading in order to playback an animation sequence.
How to control the movement of a game character using keys.
Code by Rob Bateman & David Lenaerts
rob@infiniteturtles.co.uk
http://www.infiniteturtles.co.uk
david.lenaerts@gmail.com
http://www.derschmale.com
This code is distributed under the MIT License
Copyright (c) The Away Foundation http://www.theawayfoundation.org
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.
*/
module examples
{
import CrossfadeTransition = away.animators.CrossfadeTransition;
import Skeleton = away.animators.Skeleton;
import SkeletonAnimationSet = away.animators.SkeletonAnimationSet;
import SkeletonAnimator = away.animators.SkeletonAnimator;
import SkeletonClipNode = away.animators.SkeletonClipNode;
import Camera3D = away.cameras.Camera3D;
import ObjectContainer3D = away.containers.ObjectContainer3D;
import Scene3D = away.containers.Scene3D;
import View3D = away.containers.View3D;
import LookAtController = away.controllers.LookAtController;
import Mesh = away.entities.Mesh;
import SkyBox = away.entities.SkyBox;
import Sprite3D = away.entities.Sprite3D;
import AnimationStateEvent = away.events.AnimationStateEvent;
import AssetEvent = away.events.AssetEvent;
import LoaderEvent = away.events.LoaderEvent;
import AssetLibrary = away.library.AssetLibrary;
import AssetType = away.library.AssetType;
import PointLight = away.lights.PointLight;
import DirectionalLight = away.lights.DirectionalLight;
import NearDirectionalShadowMapper = away.lights.NearDirectionalShadowMapper;
import MD5AnimParser = away.loaders.MD5AnimParser;
import MD5MeshParser = away.loaders.MD5MeshParser;
import FogMethod = away.materials.FogMethod;
import StaticLightPicker = away.materials.StaticLightPicker;
import TextureMaterial = away.materials.TextureMaterial;
import SoftShadowMapMethod = away.materials.SoftShadowMapMethod;
import NearShadowMapMethod = away.materials.NearShadowMapMethod;
import URLRequest = away.net.URLRequest;
import PlaneGeometry = away.primitives.PlaneGeometry;
import HTMLImageElementCubeTexture = away.textures.HTMLImageElementCubeTexture;
import HTMLImageElementTexture = away.textures.HTMLImageElementTexture;
import Keyboard = away.ui.Keyboard;
import Cast = away.utils.Cast;
import RequestAnimationFrame = away.utils.RequestAnimationFrame;
export class Intermediate_MD5Animation
{
//engine variables
private scene:Scene3D;
private camera:Camera3D;
private view:View3D;
private cameraController:LookAtController;
//animation variables
private animator:SkeletonAnimator;
private animationSet:SkeletonAnimationSet;
private stateTransition:CrossfadeTransition = new CrossfadeTransition(0.5);
private skeleton:Skeleton;
private isRunning:Boolean;
private isMoving:Boolean;
private movementDirection:number;
private onceAnim:string;
private currentAnim:string;
private currentRotationInc:number = 0;
//animation constants
private static IDLE_NAME:string = "idle2";
private static WALK_NAME:string = "walk7";
private static ANIM_NAMES:Array = new Array(Intermediate_MD5Animation.IDLE_NAME, Intermediate_MD5Animation.WALK_NAME, "attack3", "turret_attack", "attack2", "chest", "roar1", "leftslash", "headpain", "pain1", "pain_luparm", "range_attack2");
private static ROTATION_SPEED:number = 3;
private static RUN_SPEED:number = 2;
private static WALK_SPEED:number = 1;
private static IDLE_SPEED:number = 1;
private static ACTION_SPEED:number = 1;
//light objects
private redLight:PointLight;
private blueLight:PointLight;
private whiteLight:DirectionalLight;
private lightPicker:StaticLightPicker;
private shadowMapMethod:NearShadowMapMethod;
private fogMethod:FogMethod;
private count:number = 0;
//material objects
private redLightMaterial:TextureMaterial;
private blueLightMaterial:TextureMaterial;
private groundMaterial:TextureMaterial;
private bodyMaterial:TextureMaterial;
private gobMaterial:TextureMaterial;
private cubeTexture:HTMLImageElementCubeTexture;
//scene objects
private placeHolder:ObjectContainer3D;
private mesh:Mesh;
private ground:Mesh;
private skyBox:SkyBox;
private _timer:RequestAnimationFrame;
private _time:number = 0;
/**
* Constructor
*/
constructor()
{
this.init();
}
/**
* Global initialise function
*/
private init():void
{
this.initEngine();
//this.initText();
this.initLights();
this.initMaterials();
this.initObjects();
this.initListeners();
}
/**
* Initialise the engine
*/
private initEngine():void
{
this.view = new View3D();
this.scene = this.view.scene;
this.camera = this.view.camera;
this.camera.lens.far = 5000;
this.camera.z = -200;
this.camera.y = 160;
//setup controller to be used on the camera
this.placeHolder = new ObjectContainer3D();
this.placeHolder.y = 50;
this.cameraController = new LookAtController(this.camera, this.placeHolder);
}
/**
* Create an instructions overlay
*/
// private initText():void
// {
// text = new TextField();
// text.defaultTextFormat = new TextFormat("Verdana", 11, 0xFFFFFF);
// text.width = 240;
// text.height = 100;
// text.selectable = false;
// text.mouseEnabled = false;
// text.text = "Cursor keys / WSAD - move\n";
// text.appendText("SHIFT - hold down to run\n");
// text.appendText("numbers 1-9 - Attack\n");
// text.filters = [new DropShadowFilter(1, 45, 0x0, 1, 0, 0)];
//
// addChild(text);
// }
/**
* Initialise the lights
*/
private initLights():void
{
//create a light for shadows that mimics the sun's position in the skybox
this.redLight = new PointLight();
this.redLight.x = -1000;
this.redLight.y = 200;
this.redLight.z = -1400;
this.redLight.color = 0xff1111;
this.scene.addChild(this.redLight);
this.blueLight = new PointLight();
this.blueLight.x = 1000;
this.blueLight.y = 200;
this.blueLight.z = 1400;
this.blueLight.color = 0x1111ff;
this.scene.addChild(this.blueLight);
this.whiteLight = new DirectionalLight(-50, -20, 10);
this.whiteLight.color = 0xffffee;
this.whiteLight.castsShadows = true;
this.whiteLight.ambient = 1;
this.whiteLight.ambientColor = 0x303040;
this.whiteLight.shadowMapper = new NearDirectionalShadowMapper(.2);
this.scene.addChild(this.whiteLight);
this.lightPicker = new StaticLightPicker([this.redLight, this.blueLight, this.whiteLight]);
//create a global shadow method
this.shadowMapMethod = new NearShadowMapMethod(new SoftShadowMapMethod(this.whiteLight, 15, 8));
this.shadowMapMethod.epsilon = .1;
//create a global fog method
this.fogMethod = new FogMethod(0, this.camera.lens.far*0.5, 0x000000);
}
/**
* Initialise the materials
*/
private initMaterials():void
{
//red light material
this.redLightMaterial = new TextureMaterial();
this.redLightMaterial.alphaBlending = true;
this.redLightMaterial.addMethod(this.fogMethod);
//blue light material
this.blueLightMaterial = new TextureMaterial();
this.blueLightMaterial.alphaBlending = true;
this.blueLightMaterial.addMethod(this.fogMethod);
//ground material
this.groundMaterial = new TextureMaterial();
this.groundMaterial.smooth = true;
this.groundMaterial.repeat = true;
this.groundMaterial.lightPicker = this.lightPicker;
this.groundMaterial.shadowMethod = this.shadowMapMethod;
this.groundMaterial.addMethod(this.fogMethod);
//body material
this.bodyMaterial = new TextureMaterial();
this.bodyMaterial.gloss = 20;
this.bodyMaterial.specular = 1.5;
this.bodyMaterial.addMethod(this.fogMethod);
this.bodyMaterial.lightPicker = this.lightPicker;
this.bodyMaterial.shadowMethod = this.shadowMapMethod;
//gob material
this.gobMaterial = new TextureMaterial();
this.gobMaterial.alphaBlending = true;
this.gobMaterial.smooth = true;
this.gobMaterial.repeat = true;
this.gobMaterial.animateUVs = true;
this.gobMaterial.addMethod(this.fogMethod);
this.gobMaterial.lightPicker = this.lightPicker;
this.gobMaterial.shadowMethod = this.shadowMapMethod;
}
/**
* Initialise the scene objects
*/
private initObjects():void
{
//create light billboards
var redSprite:Sprite3D = new Sprite3D(this.redLightMaterial, 200, 200);
redSprite.castsShadows = false;
var blueSprite:Sprite3D = new Sprite3D(this.blueLightMaterial, 200, 200);
blueSprite.castsShadows = false;
this.redLight.addChild(redSprite);
this.blueLight.addChild(blueSprite);
AssetLibrary.enableParser(MD5MeshParser);
AssetLibrary.enableParser(MD5AnimParser);
//create a rocky ground plane
this.ground = new Mesh(new PlaneGeometry(50000, 50000, 1, 1), this.groundMaterial);
this.ground.geometry.scaleUV(200, 200);
this.ground.castsShadows = false;
this.scene.addChild(this.ground);
}
/**
* Initialise the listeners
*/
private initListeners():void
{
window.onresize = (event) => this.onResize(event);
document.onkeydown = (event) => this.onKeyDown(event);
document.onkeyup = (event) => this.onKeyUp(event);
this.onResize();
this._timer = new away.utils.RequestAnimationFrame(this.onEnterFrame, this);
this._timer.start();
//setup the url map for textures in the cubemap file
var assetLoaderContext:away.loaders.AssetLoaderContext = new away.loaders.AssetLoaderContext();
assetLoaderContext.dependencyBaseUrl = "assets/skybox/";
//load hellknight mesh
AssetLibrary.addEventListener(AssetEvent.ASSET_COMPLETE, this.onAssetComplete, this);
AssetLibrary.addEventListener(away.events.LoaderEvent.RESOURCE_COMPLETE, this.onResourceComplete, this);
AssetLibrary.load(new URLRequest("assets/hellknight/hellknight.md5mesh"), null, null, new MD5MeshParser());
//load environment texture
AssetLibrary.load(new URLRequest("assets/skybox/grimnight_texture.cube"), assetLoaderContext);
//load light textures
AssetLibrary.load(new URLRequest("assets/redlight.png"));
AssetLibrary.load(new URLRequest("assets/bluelight.png"));
//load floor textures
AssetLibrary.load(new URLRequest("assets/rockbase_diffuse.jpg"));
AssetLibrary.load(new URLRequest("assets/rockbase_normals.png"));
AssetLibrary.load(new URLRequest("assets/rockbase_specular.png"));
//load hellknight textures
AssetLibrary.load(new URLRequest("assets/hellknight/hellknight_diffuse.jpg"));
AssetLibrary.load(new URLRequest("assets/hellknight/hellknight_normals.png"));
AssetLibrary.load(new URLRequest("assets/hellknight/hellknight_specular.png"));
AssetLibrary.load(new URLRequest("assets/hellknight/gob.png"));
}
/**
* Navigation and render loop
*/
private onEnterFrame(dt:number):void
{
this._time += dt;
this.cameraController.update();
//update character animation
if (this.mesh) {
this.mesh.subMeshes[1].offsetV = this.mesh.subMeshes[2].offsetV = this.mesh.subMeshes[3].offsetV = (-this._time/2000 % 1);
this.mesh.rotationY += this.currentRotationInc;
}
this.count += 0.01;
this.redLight.x = Math.sin(this.count)*1500;
this.redLight.y = 250 + Math.sin(this.count*0.54)*200;
this.redLight.z = Math.cos(this.count*0.7)*1500;
this.blueLight.x = -Math.sin(this.count*0.8)*1500;
this.blueLight.y = 250 - Math.sin(this.count*.65)*200;
this.blueLight.z = -Math.cos(this.count*0.9)*1500;
this.view.render();
}
/**
* Listener for asset complete event on loader
*/
private onAssetComplete(event:AssetEvent):void
{
if (event.asset.assetType == AssetType.ANIMATION_NODE) {
var node:SkeletonClipNode = event.asset;
var name:string = event.asset.assetNamespace;
node.name = name;
this.animationSet.addAnimation(node);
if (name == Intermediate_MD5Animation.IDLE_NAME || name == Intermediate_MD5Animation.WALK_NAME) {
node.looping = true;
} else {
node.looping = false;
node.addEventListener(AnimationStateEvent.PLAYBACK_COMPLETE, this.onPlaybackComplete, this);
}
if (name == Intermediate_MD5Animation.IDLE_NAME)
this.stop();
} else if (event.asset.assetType == AssetType.ANIMATION_SET) {
this.animationSet = event.asset;
this.animator = new SkeletonAnimator(this.animationSet, this.skeleton);
for (var i:number /*uint*/ = 0; i < Intermediate_MD5Animation.ANIM_NAMES.length; ++i)
AssetLibrary.load(new URLRequest("assets/hellknight/" + Intermediate_MD5Animation.ANIM_NAMES[i] + ".md5anim"), null, Intermediate_MD5Animation.ANIM_NAMES[i], new MD5AnimParser());
this.mesh.animator = this.animator;
} else if (event.asset.assetType == AssetType.SKELETON) {
this.skeleton = event.asset;
} else if (event.asset.assetType == AssetType.MESH) {
//grab mesh object and assign our material object
this.mesh = event.asset;
this.mesh.subMeshes[0].material = this.bodyMaterial;
this.mesh.subMeshes[1].material = this.mesh.subMeshes[2].material = this.mesh.subMeshes[3].material = this.gobMaterial;
this.mesh.castsShadows = true;
this.mesh.rotationY = 180;
this.scene.addChild(this.mesh);
//add our lookat object to the mesh
this.mesh.addChild(this.placeHolder);
}
}
/**
* Listener function for resource complete event on asset library
*/
private onResourceComplete (event:LoaderEvent)
{
switch( event.url )
{
//environment texture
case 'assets/skybox/grimnight_texture.cube':
this.cubeTexture = event.assets[ 0 ];
this.skyBox = new SkyBox(this.cubeTexture);
this.scene.addChild(this.skyBox);
break;
//lights textures
case "assets/redlight.png" :
this.redLightMaterial.texture = event.assets[ 0 ];
break;
case "assets/bluelight.png" :
this.blueLightMaterial.texture = event.assets[ 0 ];
break;
//floor textures
case "assets/rockbase_diffuse.jpg" :
this.groundMaterial.texture = event.assets[ 0 ];
break;
case "assets/rockbase_normals.png" :
this.groundMaterial.normalMap = event.assets[ 0 ];
break;
case "assets/rockbase_specular.png" :
this.groundMaterial.specularMap = event.assets[ 0 ];
break;
//hellknight textures
case "assets/hellknight/hellknight_diffuse.jpg" :
this.bodyMaterial.texture = event.assets[ 0 ];
break;
case "assets/hellknight/hellknight_normals.png" :
this.bodyMaterial.normalMap = event.assets[ 0 ];
break;
case "assets/hellknight/hellknight_specular.png" :
this.bodyMaterial.specularMap = event.assets[ 0 ];
break;
case "assets/hellknight/gob.png" :
this.bodyMaterial.specularMap = this.gobMaterial.texture = event.assets[ 0 ];
break;
}
}
private onPlaybackComplete(event:AnimationStateEvent):void
{
if (this.animator.activeState != event.animationState)
return;
this.onceAnim = null;
this.animator.play(this.currentAnim, this.stateTransition);
this.animator.playbackSpeed = this.isMoving? this.movementDirection*(this.isRunning? Intermediate_MD5Animation.RUN_SPEED : Intermediate_MD5Animation.WALK_SPEED) : Intermediate_MD5Animation.IDLE_SPEED;
}
private playAction(val:number /*uint*/):void
{
this.onceAnim = Intermediate_MD5Animation.ANIM_NAMES[val + 2];
this.animator.playbackSpeed = Intermediate_MD5Animation.ACTION_SPEED;
this.animator.play(this.onceAnim, this.stateTransition, 0);
}
/**
* Key up listener
*/
private onKeyDown(event):void
{
switch (event.keyCode) {
case Keyboard.SHIFT:
this.isRunning = true;
if (this.isMoving)
this.updateMovement(this.movementDirection);
break;
case Keyboard.UP:
case Keyboard.W:
case Keyboard.Z: //fr
this.updateMovement(this.movementDirection = 1);
break;
case Keyboard.DOWN:
case Keyboard.S:
this.updateMovement(this.movementDirection = -1);
break;
case Keyboard.LEFT:
case Keyboard.A:
case Keyboard.Q: //fr
this.currentRotationInc = -Intermediate_MD5Animation.ROTATION_SPEED;
break;
case Keyboard.RIGHT:
case Keyboard.D:
this.currentRotationInc = Intermediate_MD5Animation.ROTATION_SPEED;
break;
}
}
/**
* Key down listener for animation
*/
private onKeyUp(event):void
{
switch (event.keyCode) {
case Keyboard.SHIFT:
this.isRunning = false;
if (this.isMoving)
this.updateMovement(this.movementDirection);
break;
case Keyboard.UP:
case Keyboard.W:
case Keyboard.Z: //fr
case Keyboard.DOWN:
case Keyboard.S:
this.stop();
break;
case Keyboard.LEFT:
case Keyboard.A:
case Keyboard.Q: //fr
case Keyboard.RIGHT:
case Keyboard.D:
this.currentRotationInc = 0;
break;
case Keyboard.NUMBER_1:
this.playAction(1);
break;
case Keyboard.NUMBER_2:
this.playAction(2);
break;
case Keyboard.NUMBER_3:
this.playAction(3);
break;
case Keyboard.NUMBER_4:
this.playAction(4);
break;
case Keyboard.NUMBER_5:
this.playAction(5);
break;
case Keyboard.NUMBER_6:
this.playAction(6);
break;
case Keyboard.NUMBER_7:
this.playAction(7);
break;
case Keyboard.NUMBER_8:
this.playAction(8);
break;
case Keyboard.NUMBER_9:
this.playAction(9);
break;
}
}
private updateMovement(dir:number):void
{
this.isMoving = true;
this.animator.playbackSpeed = dir*(this.isRunning? Intermediate_MD5Animation.RUN_SPEED : Intermediate_MD5Animation.WALK_SPEED);
if (this.currentAnim == Intermediate_MD5Animation.WALK_NAME)
return;
this.currentAnim = Intermediate_MD5Animation.WALK_NAME;
if (this.onceAnim)
return;
//update animator
this.animator.play(this.currentAnim, this.stateTransition);
}
private stop():void
{
this.isMoving = false;
if (this.currentAnim == Intermediate_MD5Animation.IDLE_NAME)
return;
this.currentAnim = Intermediate_MD5Animation.IDLE_NAME;
if (this.onceAnim)
return;
//update animator
this.animator.playbackSpeed = Intermediate_MD5Animation.IDLE_SPEED;
this.animator.play(this.currentAnim, this.stateTransition);
}
/**
* stage listener for resize events
*/
private onResize(event:Event = null):void
{
this.view.width = window.innerWidth;
this.view.height = window.innerHeight;
}
}
}
window.onload = function ()
{
new examples.Intermediate_MD5Animation();
}