From b89a7ee8b956c96a1dcee995ea840feddc5d4b27 Mon Sep 17 00:00:00 2001 From: Pierre Frisch Date: Thu, 22 Dec 2011 07:25:50 -0800 Subject: First commit of Ninja to ninja-internal Signed-off-by: Valerio Virgillito --- .../RDGE/src/core/script/animation.js | 322 +++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 js/helper-classes/RDGE/src/core/script/animation.js (limited to 'js/helper-classes/RDGE/src/core/script/animation.js') diff --git a/js/helper-classes/RDGE/src/core/script/animation.js b/js/helper-classes/RDGE/src/core/script/animation.js new file mode 100644 index 00000000..63eca0a2 --- /dev/null +++ b/js/helper-classes/RDGE/src/core/script/animation.js @@ -0,0 +1,322 @@ +/* +This file contains proprietary software owned by Motorola Mobility, Inc.
+No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+(c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ + +var g_numChannels = new stat( "animation", "numChannels", 0, null, false ); +var g_numTracks = new stat( "animation", "numTracks", 0, null, false ); + +/** + * channelController + * The channel controller is really the workhorse of the RDGE animation system. It handles timing, + * interpolation, and sampling of attributes over the lifetime of an animation. Each channel controller + * is responsible for animating a single attribute. The current implementation supports animating vector, + * boolean, or quaternion attributes. This class is used internally by the animation system. + * + * @param _animation - the animation resource + * @param _channel - the channel id + * + */ +channelController = function(_animation, _channel) { + /** + * this.interpolate - Enable/Disable interpolation between animation frames. + * Typically this should be enabled for smoother looking animation. However, + * there may be applications where interpolation is undesireable. + */ + this.interpolate = false; + + /** + * this.animation - the animation resource. + * This is where the keyframes for the channel are stored. + */ + this.animation = _animation; + + /** + * this.channel - the channel id. This is used to look up the keyframe data for this channel. + */ + this.channel = _channel; + + /** + * this.localTime - the current time, relative to the start time. + */ + this.localTime = 0.0; + + /** + * this.startTime - the start time of the animation clip window. + */ + this.startTime = this.animation.clipStart / this.animation.framesPerSec; + + /** + * this.endTime - the end time of the animation clip window. + */ + this.endTime = this.animation.clipEnd / this.animation.framesPerSec; + + /** + * this.cachedFrame - cached frame index, this optimizes best case scenario computeFrame calls. + */ + this.cachedFrame = -1; + + /** + * oneFrameInSecs - stores the interval of a single frame in seconds. This is used for internal calculations. + */ + oneFrameInSecs = 1.0 / _animation.framesPerSec; + + /** + * this.channel.timeline - stores the animation timeline. + * Currently this is calculated based on the framePerSec settings of the animation. + * Eventually the timeline should be exported with the animation. Individual channels + * may have different timelines depending on which frames are keyed. + */ + this.channel.timeline = new Array(this.channel.numKeys + 1); + for (i = 0; i <= this.channel.numKeys; ++i) { + this.channel.timeline[i] = i / this.animation.framesPerSec; + } + + /** this.computeFrame + * Calculates the current frame index of the animation at the current time. + * In the worst case, this function will perform a binary search for the frame + * whose time is closest to and less than the current time. In the best case, + * the current frame is near the most recently cached frame, or it remains unchanged. + */ + this.computeFrame = function() { + var absTime = this.localTime + this.startTime; + var start = this.animation.clipStart; + var end = this.animation.clipEnd; + + if (this.cachedFrame != -1) { + // if the current time is reasonably close to the last frame processed try searching + // forward or backward. best case it is the next frame. + if (Math.abs(absTime - this.channel.timeline[this.cachedFrame]) < 5 * oneFrameInSecs) { + if (this.channel.timeline[this.cachedFrame] < absTime) { + while (this.channel.timeline[this.cachedFrame + 1] <= absTime) { + this.cachedFrame++; + if (this.animation.looping) { + if (this.cachedFrame > this.numFrames - 1) { + this.cachedFrame -= this.numFrames - 1; + } + } else { + if (this.cachedFrame > this.numFrames - 1) { + this.cachedFrame = this.numFrames - 1; + } + } + } + return this.cachedFrame; + + } else { + while (this.channel.timeline[this.cachedFrame] > absTime) { + this.cachedFrame--; + if (this.animation.looping) { + if (this.cachedFrame < 0) { + this.cachedFrame += this.numFrames - 1; + } + } else { + if (this.cachedFrame > this.numFrames - 1) { + this.cachedFrame = this.numFrames - 1; + } + } + } + return this.cachedFrame; + } + } + } + + // binary search... + while (start + 1 < end) { + var mid = Math.floor((start + end) / 2); + if (absTime > this.channel.timeline[mid]) { + start = mid + 1; + } else { + end = mid - 1; + } + } + + this.cachedFrame = start; + + return start; + } + + /* this.sampleBool - Sample a boolean at the current frame, booleans are not interpolated. + * This function is used internally. + */ + this.sampleBool = function() { + // no interpolation on flags... + var index = this.computeFrame(); + return this.channel.keys[index]; + } + + /* this.sampleQuat - Sample a quaternion at the current frame. + * if this.interpolate == true, quaternions are interpolated inbetween frames using spherical linear interpolation (SLERP). + */ + this.sampleQuat = function() { + var frame0 = this.computeFrame(); + var frame1 = frame0 + 1; + + var k0 = this.channel.timeline[frame0]; + var k1 = this.channel.timeline[frame1]; + var index0 = frame0 * 4; + var index1 = frame1 * 4; + + if (this.interpolate) { + var absTime = this.localTime + this.startTime; + var t = (absTime - k0) / (k1 - k0); + var a = [this.channel.keys[index0 + 0], this.channel.keys[index0 + 1], this.channel.keys[index0 + 2], this.channel.keys[index0 + 3]]; + var b = [this.channel.keys[index1 + 0], this.channel.keys[index1 + 1], this.channel.keys[index1 + 2], this.channel.keys[index1 + 3]]; + return quat.slerp(a, b, t); + } + + return [this.channel.keys[index0 + 0], this.channel.keys[index0 + 1], this.channel.keys[index0 + 2], this.channel.keys[index0 + 3]]; + } + + /* this.sampleVec3 - Sample a vector3 at the current frame. + * if this.interpolate == true, vectors are interpolated inbetween frames using linear interpolation (LERP). + */ + this.sampleVec3 = function() { + var frame0 = this.computeFrame(); + var frame1 = frame0 + 1; + + var k0 = this.channel.timeline[frame0]; + var k1 = this.channel.timeline[frame1]; + var index0 = frame0 * 3; + var index1 = frame1 * 3; + + if (this.interpolate) { + var absTime = this.localTime + this.startTime; + var t = (absTime - k0) / (k1 - k0); + var a = [this.channel.keys[index0 + 0], this.channel.keys[index0 + 1], this.channel.keys[index0 + 2]]; + var b = [this.channel.keys[index1 + 0], this.channel.keys[index1 + 1], this.channel.keys[index1 + 2]]; + + return vec3.lerp(a, b, t); + } + return [this.channel.keys[index0 + 0], this.channel.keys[index0 + 1], this.channel.keys[index0 + 2]]; + } + + /* this.setTime - set the current time. + */ + this.setTime = function(t) { + this.localTime = t; + if (this.localTime < 0.0) { + this.localTime = 0.0; + } + if (this.localTime > this.animation.duration - oneFrameInSecs) { + this.localTime = this.animation.duration - oneFrameInSecs; + } + } + + /* this.setProgress - set the current time as a percentage of the duration. + */ + this.setProgress = function(f) { + this.setTime(f * this.animation.duration); + } + + /* this.setFrame - set the current time by frame number. + */ + this.setFrame = function(f) { + this.setTime(f / this.animation.framesPerSec); + } + + /* this.step - advance time by the given timestep and wrap if looping. + */ + this.step = function(_dt) { + this.localTime += _dt; + if (this.animation.looping) { + while (this.localTime < 0.0) { + this.localTime += this.animation.duration - oneFrameInSecs; + } + while (this.localTime >= this.animation.duration - oneFrameInSecs) { + this.localTime -= this.animation.duration - oneFrameInSecs; + } + } else { + if (this.localTime < 0.0) { + this.localTime = 0.0; + } + if (this.localTime > this.animation.duration) { + this.localTime = this.animation.duration; + } + } + } +} + +/** + * track + * Each track advances and samples from a list of channel controllers, and is assigned to a scene graph node. + * + * @param _animation - the animation resource + * @param _track - the track id + * @param _node - the scene node + * + */ +track = function(_animation, _track, _node) { + this.track = _track; + this.node = _node; + + this.channelControllers = new Array(); + for (ch in _track) { + this.channelControllers[ch] = new channelController(_animation, _track[ch]); + g_numChannels.value++; + } + + this.step = function(_dt) { + for (cc in this.channelControllers) { + this.channelControllers[cc].step(_dt); + } + var rotate = this.channelControllers["rotate"].sampleQuat(); + var scale = this.channelControllers["scale"].sampleVec3(); + var translate = this.channelControllers["translate"].sampleVec3(); + if (this.channelControllers["vis"] != null) { + var vis = this.channelControllers["vis"].sampleBool(); + this.node.hide = !vis; + } + + var m = mat4.identity(); + m = mat4.scale(m, scale); + m = mat4.mul(m, quat.toMatrix(rotate)); + m = mat4.translate(m, translate); + this.node.local = m; + } +} + +animation = function(_scene, _clipStart, _clipEnd, _loop) { + this.animation = _scene.scene.animation; + this.clipStart = _clipStart; + // this is a little hacky, but it works for now. + if (_clipEnd == -1) { + for (tr in this.animation) { + for (ch in this.animation[tr]) { + _clipEnd = this.animation[tr][ch].numKeys - 1; + break; + } + break; + } + } + this.clipEnd = _clipEnd; + this.numFrames = _clipEnd - _clipStart; + this.framesPerSec = 30.0; + this.duration = this.numFrames / this.framesPerSec; + this.rate = 1.0; + this.looping = _loop; + this.tracks = new Array(); + + // creating a mapping here to make binding tracks a little more + // straightforward. + mapping = new Array(); + mapping.process = function(trNode, parent) { + mapping[trNode.name] = trNode; + } + + g_Engine.getContext().getScene().Traverse(mapping); + //g_sg.Traverse(mapping); + + for (tr in this.animation) { + if (mapping[tr] !== undefined) { + this.tracks.push(new track(this, this.animation[tr], mapping[tr])); + g_numTracks.value++; + } + } + + this.step = function(_dt) { + for (tr in this.tracks) { + this.tracks[tr].step(g_animationRate * this.rate * _dt); + } + } +} \ No newline at end of file -- cgit v1.2.3