448 lines
11 KiB
Haxe
Raw Normal View History

2025-01-22 16:18:30 +01:00
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);
}
}