448 lines
11 KiB
Haxe
448 lines
11 KiB
Haxe
package iron.object;
|
|
|
|
import haxe.ds.Vector;
|
|
import iron.math.Vec3;
|
|
import iron.math.Vec2;
|
|
import kha.FastFloat;
|
|
import kha.arrays.Uint32Array;
|
|
import iron.math.Vec4;
|
|
import iron.math.Mat4;
|
|
import iron.math.Quat;
|
|
import iron.data.SceneFormat;
|
|
|
|
class Animation {
|
|
|
|
public var isSkinned: Bool;
|
|
public var isSampled: Bool;
|
|
public var action = "";
|
|
#if lnx_skin
|
|
public var armature: iron.data.Armature; // Bone
|
|
#end
|
|
|
|
// Helper variables.
|
|
static var m1 = Mat4.identity();
|
|
static var m2 = Mat4.identity();
|
|
static var vpos = new Vec4();
|
|
static var vpos2 = new Vec4();
|
|
static var vscl = new Vec4();
|
|
static var vscl2 = new Vec4();
|
|
static var q1 = new Quat();
|
|
static var q2 = new Quat();
|
|
static var q3 = new Quat();
|
|
static var vp = new Vec4();
|
|
static var vs = new Vec4();
|
|
|
|
public var time: FastFloat = 0.0;
|
|
public var speed: FastFloat = 1.0;
|
|
public var loop = true;
|
|
public var frameIndex = 0;
|
|
public var onComplete: Void->Void = null;
|
|
public var paused = false;
|
|
var frameTime: FastFloat = 1 / 60;
|
|
|
|
var blendTime: FastFloat = 0.0;
|
|
var blendCurrent: FastFloat = 0.0;
|
|
var blendFactor: FastFloat = 0.0;
|
|
|
|
var lastFrameIndex = -1;
|
|
var markerEvents: Map<ActionSampler, Map<String, Array<Void->Void>>> = null;
|
|
|
|
public var activeActions: Map<String, ActionSampler> = null;
|
|
|
|
function new() {
|
|
Scene.active.animations.push(this);
|
|
if (Scene.active.raw.frame_time != null) {
|
|
frameTime = Scene.active.raw.frame_time;
|
|
}
|
|
play();
|
|
}
|
|
|
|
public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) {
|
|
if (blendTime > 0) {
|
|
this.blendTime = blendTime;
|
|
this.blendCurrent = 0.0;
|
|
frameIndex = 0;
|
|
time = 0.0;
|
|
}
|
|
else frameIndex = -1;
|
|
this.action = action;
|
|
this.onComplete = onComplete;
|
|
this.speed = speed;
|
|
this.loop = loop;
|
|
paused = false;
|
|
}
|
|
|
|
public function pause() {
|
|
paused = true;
|
|
}
|
|
|
|
public function resume() {
|
|
paused = false;
|
|
}
|
|
|
|
public function remove() {
|
|
Scene.active.animations.remove(this);
|
|
}
|
|
|
|
public function updateActionTrack(sampler: ActionSampler){
|
|
return;
|
|
}
|
|
|
|
public function update(delta: FastFloat) {
|
|
if(activeActions == null) return;
|
|
|
|
for(sampler in activeActions){
|
|
if (sampler.paused || sampler.speed == 0.0) {
|
|
continue;
|
|
}
|
|
else {
|
|
sampler.timeOld = sampler.time;
|
|
sampler.offsetOld = sampler.offset;
|
|
sampler.setTimeOnly(sampler.time + delta * sampler.speed);
|
|
updateActionTrack(sampler);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public function registerAction(actionID: String, sampler: ActionSampler){
|
|
if (activeActions == null) activeActions = new Map();
|
|
activeActions.set(actionID, sampler);
|
|
}
|
|
|
|
public function deRegisterAction(actionID: String) {
|
|
if (activeActions == null) return;
|
|
if(activeActions.exists(actionID)) activeActions.remove(actionID);
|
|
|
|
}
|
|
|
|
inline function isTrackEnd(track: TTrack, frameIndex: Int, speed: FastFloat): Bool {
|
|
return speed > 0 ?
|
|
frameIndex >= track.frames.length - 1 :
|
|
frameIndex <= 0;
|
|
}
|
|
|
|
inline function checkFrameIndex(frameValues: Uint32Array, time: FastFloat, frameIndex: Int, speed: FastFloat): Bool {
|
|
return speed > 0 ?
|
|
((frameIndex + 1) < frameValues.length && time > frameValues[frameIndex + 1] * frameTime) :
|
|
((frameIndex - 1) > -1 && time < frameValues[frameIndex - 1] * frameTime);
|
|
}
|
|
|
|
function rewind(track: TTrack) {
|
|
frameIndex = speed > 0 ? 0 : track.frames.length - 1;
|
|
time = track.frames[frameIndex] * frameTime;
|
|
}
|
|
|
|
function updateTrack(anim: TAnimation, sampler: ActionSampler) {
|
|
|
|
var time = sampler.time;
|
|
var frameIndex = sampler.offset;
|
|
var speed = sampler.speed;
|
|
sampler.cacheSet = false;
|
|
sampler.trackEnd = false;
|
|
|
|
var track = anim.tracks[0];
|
|
|
|
if (frameIndex == -1) {
|
|
sampler.timeOld = sampler.time;
|
|
sampler.offsetOld = sampler.offset;
|
|
frameIndex = speed > 0 ? 0 : track.frames.length - 1;
|
|
time = track.frames[frameIndex] * frameTime;
|
|
}
|
|
|
|
// Move keyframe
|
|
var sign = speed > 0 ? 1 : -1;
|
|
while (checkFrameIndex(track.frames, time, frameIndex, speed)) frameIndex += sign;
|
|
|
|
// Marker events
|
|
if (markerEvents != null && anim.marker_names != null && frameIndex != lastFrameIndex) {
|
|
if(markerEvents.get(sampler) != null){
|
|
for (i in 0...anim.marker_frames.length) {
|
|
if (frameIndex == anim.marker_frames[i]) {
|
|
var marketAct = markerEvents.get(sampler);
|
|
var ar = marketAct.get(anim.marker_names[i]);
|
|
if (ar != null) for (f in ar) f();
|
|
}
|
|
}
|
|
lastFrameIndex = frameIndex;
|
|
}
|
|
}
|
|
|
|
// End of track
|
|
if (isTrackEnd(track, frameIndex, speed)) {
|
|
if (sampler.loop) {
|
|
sampler.offsetOld = frameIndex;
|
|
frameIndex = speed > 0 ? 0 : track.frames.length - 1;
|
|
time = track.frames[frameIndex] * frameTime;
|
|
}
|
|
else {
|
|
frameIndex -= sign;
|
|
sampler.paused = true;
|
|
}
|
|
if (sampler.onComplete != null) for(func in sampler.onComplete){ func();};
|
|
sampler.trackEnd = true;
|
|
}
|
|
|
|
sampler.setFrameOffsetOnly(frameIndex);
|
|
sampler.speed = speed;
|
|
sampler.setTimeOnly(time);
|
|
|
|
}
|
|
|
|
public function notifyOnMarker(sampler: ActionSampler, name: String, onMarker: Void->Void) {
|
|
if (markerEvents == null) markerEvents = new Map();
|
|
|
|
var markerAct = markerEvents.get(sampler);
|
|
if(markerAct == null){
|
|
markerAct = new Map();
|
|
markerEvents.set(sampler, markerAct);
|
|
}
|
|
|
|
var ar = markerAct.get(name);
|
|
if (ar == null) {
|
|
ar = [];
|
|
markerAct.set(name, ar);
|
|
}
|
|
ar.push(onMarker);
|
|
}
|
|
|
|
public function removeMarker(sampler: ActionSampler, name: String, onMarker: Void->Void) {
|
|
var markerAct = markerEvents.get(sampler);
|
|
if(markerAct == null) return;
|
|
|
|
markerAct.get(name).remove(onMarker);
|
|
}
|
|
|
|
public function currentFrame(): Int {
|
|
return Std.int(time / frameTime);
|
|
}
|
|
|
|
public function getTotalFrames(sampler: ActionSampler): Int {
|
|
return 0;
|
|
}
|
|
|
|
#if lnx_debug
|
|
public static var animationTime = 0.0;
|
|
static var startTime = 0.0;
|
|
|
|
static function beginProfile() {
|
|
startTime = kha.Scheduler.realTime();
|
|
}
|
|
static function endProfile() {
|
|
animationTime += kha.Scheduler.realTime() - startTime;
|
|
}
|
|
public static function endFrame() {
|
|
animationTime = 0;
|
|
}
|
|
#end
|
|
}
|
|
|
|
/**
|
|
* Action Sampler State.
|
|
*/
|
|
class ActionSampler {
|
|
|
|
/**
|
|
* Name of the action.
|
|
*/
|
|
public var action(default, null): String;
|
|
/**
|
|
* Current time of the sampler.
|
|
*/
|
|
public var time(default, null): FastFloat = 0.0;
|
|
/**
|
|
* Current frame of the sampler.
|
|
*/
|
|
public var offset(default, null): Int = 0;
|
|
/**
|
|
* Total frames in the action.
|
|
*/
|
|
public var totalFrames: Null<Int> = null;
|
|
/**
|
|
* Speed of action sampling.
|
|
*/
|
|
public var speed: FastFloat;
|
|
/**
|
|
* Loop action.
|
|
*/
|
|
public var loop: Bool;
|
|
/**
|
|
* Sampler paused.
|
|
*/
|
|
public var paused: Bool = false;
|
|
/**
|
|
* Callback functions to call after action ends.
|
|
*/
|
|
public var onComplete: Array<Void -> Void>;
|
|
/**
|
|
* Action track ended.
|
|
*/
|
|
public var trackEnd: Bool = false;
|
|
public var timeOld: FastFloat = 0.0;
|
|
public var offsetOld: Int = 0;
|
|
/**
|
|
* Cache action data objects. May be Bones or Objects.
|
|
*/
|
|
var actionData: Array<TObj> = null;
|
|
/**
|
|
* Action data has been cached.
|
|
*/
|
|
public var actionDataInit(default, null): Bool = false;
|
|
/**
|
|
* Positional Root Motion for this action.
|
|
*/
|
|
public var rootMotionPos: Bool = false;
|
|
/**
|
|
* Rotational Root Motion for this action.
|
|
*/
|
|
public var rootMotionRot: Bool = false;
|
|
/**
|
|
* Action matrix from previous sample. Mainly used for root motion.
|
|
*/
|
|
var actionCache: Mat4 = Mat4.identity();
|
|
/**
|
|
* `actionCache` set this frame.
|
|
*/
|
|
public var cacheSet: Bool = false;
|
|
/**
|
|
* `actionCache` initialized. Set to false to force reset cache.
|
|
*/
|
|
public var cacheInit(default, null): Bool = false;
|
|
|
|
/**
|
|
* Create a new action sampler.
|
|
* @param action Name of the action.
|
|
* @param speed Speed of sampler.
|
|
* @param loop Loop after action ends.
|
|
* @param startPaused Do not start sample on init.
|
|
* @param onComplete Callback functions after action completes.
|
|
*/
|
|
public inline function new(action: String, speed: FastFloat = 1.0, loop: Bool = true, startPaused: Bool = false, onComplete: Array<Void -> Void> = null) {
|
|
this.action = action;
|
|
this.speed = speed;
|
|
this.loop = loop;
|
|
this.onComplete = onComplete;
|
|
this.paused = startPaused;
|
|
}
|
|
|
|
/**
|
|
* Set current frame of the sampler. Time is calculated.
|
|
* @param frameOffset Frame.
|
|
*/
|
|
public inline function setFrameOffset(frameOffset: Int){
|
|
this.offset = frameOffset;
|
|
this.time = Scene.active.raw.frame_time * offset;
|
|
cacheInit = false;
|
|
}
|
|
|
|
/**
|
|
* Set current time of the sampler. Frame is calculated.
|
|
* @param timeOffset Time.
|
|
*/
|
|
public inline function setTimeOffset(timeOffset: FastFloat){
|
|
this.time = timeOffset;
|
|
var ftime: FastFloat = Scene.active.raw.frame_time;
|
|
this.offset = Std.int(time / ftime);
|
|
cacheInit = false;
|
|
}
|
|
|
|
/**
|
|
* Restart action.
|
|
*/
|
|
public inline function restartAction() {
|
|
this.setFrameOffset(0);
|
|
paused = false;
|
|
cacheInit = false;
|
|
}
|
|
|
|
/**
|
|
* Add a callback function when action completes.
|
|
* @param onComplete Callback
|
|
*/
|
|
public function notifyOnComplete(onComplete: Void -> Void) {
|
|
if(this.onComplete == null) this.onComplete = [];
|
|
this.onComplete.push(onComplete);
|
|
}
|
|
|
|
/**
|
|
* Remove callback function
|
|
* @param onComplete Callback
|
|
*/
|
|
public function removeOnComplete(onComplete: Void -> Void) {
|
|
this.onComplete.remove(onComplete);
|
|
}
|
|
|
|
/**
|
|
* Set time offset only. Frame will not be set.
|
|
* @param time Time.
|
|
*/
|
|
public inline function setTimeOnly(time: FastFloat) {
|
|
this.time = time;
|
|
}
|
|
|
|
/**
|
|
* Set frame offset only. Time will not be set.
|
|
* @param frame Frame
|
|
*/
|
|
public inline function setFrameOffsetOnly(frame: Int) {
|
|
this.offset = frame;
|
|
}
|
|
|
|
/**
|
|
* Get raw bones data for bone animation.
|
|
* @return Null<Array<TObj>> Raw bone action data.
|
|
*/
|
|
public inline function getBoneAction(): Null<Array<TObj>> {
|
|
return actionData;
|
|
}
|
|
|
|
/**
|
|
* Get raw object data for object animation.
|
|
* @return Null<TObj> Raw object action data.
|
|
*/
|
|
public inline function getObjectAction(): Null<TObj> {
|
|
if(actionData != null) return actionData[0];
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Cache raw bones data for bone animation.
|
|
* @param actionData Raw bone data.
|
|
*/
|
|
public inline function setBoneAction(actionData: Array<TObj>) {
|
|
this.actionData = actionData;
|
|
this.totalFrames = actionData[0].anim.tracks[0].frames.length;
|
|
if(actionData[0].anim.root_motion_pos) this.rootMotionPos = true;
|
|
if(actionData[0].anim.root_motion_rot) this.rootMotionRot = true;
|
|
actionDataInit = true;
|
|
}
|
|
|
|
/**
|
|
* Cache raw object data for object animation.
|
|
* @param actionData Raw object data.
|
|
*/
|
|
public inline function setObjectAction(actionData: TObj) {
|
|
this.actionData = [actionData];
|
|
this.totalFrames = actionData.anim.tracks[0].frames.length;
|
|
actionDataInit = true;
|
|
}
|
|
|
|
/**
|
|
* Temporary cache of action matrix from previous frame.
|
|
* @param m Matrix to cache.
|
|
*/
|
|
public inline function setActionCache(m: Mat4) {
|
|
if(! cacheSet) actionCache.setFrom(m);
|
|
cacheSet = true;
|
|
cacheInit = true;
|
|
}
|
|
|
|
/**
|
|
* Copy cahced action matrix and to the matrix.
|
|
* @param m Matrix to copy the cache to.
|
|
*/
|
|
public inline function getActionCache(m: Mat4) {
|
|
m.setFrom(actionCache);
|
|
}
|
|
}
|