@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
228 lines (178 loc) • 5.67 kB
JavaScript
import { LoopOnce, LoopRepeat } from "three";
import { combine_hash } from "../../../../../core/collection/array/combine_hash.js";
import { min2 } from "../../../../../core/math/min2.js";
import { computeHashFloat } from "../../../../../core/primitives/numbers/computeHashFloat.js";
import { AnimationClipFlag } from "./AnimationClipFlag.js";
import { AnimationEventTypes } from "./AnimationEventTypes.js";
export class AnimationClip {
constructor() {
/**
*
* @type {AnimationClipDefinition}
*/
this.def = null;
/**
*
* @type {number}
*/
this.weight = 1;
/**
*
* @type {number}
*/
this.timeScale = 1;
/**
*
* @type {number|AnimationClipFlag}
*/
this.flags = 0;
}
/**
*
* @param {number} entity
* @param {EntityComponentDataset} ecd
* @param {number} time0
* @param {number} time1
*/
dispatchNotifications(entity, ecd, time0, time1) {
if (time0 === time1) {
// time interval 0
return;
}
const repeating = this.getFlag(AnimationClipFlag.Repeat);
/**
*
* @type {AnimationClipDefinition}
*/
const clipDefinition = this.def;
/**
*
* @type {AnimationNotification[]}
*/
const notifications = clipDefinition.notifications;
const notificationCount = notifications.length;
const clipDuration = clipDefinition.duration;
if (notificationCount > 0) {
let t = time0;
let time_end = time1;
let event_cursor = -1;
if (!repeating) {
time_end = min2(clipDuration, time1);
}
replay:while (t < time_end) {
const cycle_index = (t / clipDuration) | 0;
const cycle_start_time = cycle_index * clipDuration;
const event_cursor_before = event_cursor;
for (let i = 0; i < notificationCount; i++) {
const event_index = cycle_index * notificationCount + i;
if (event_index <= event_cursor) {
// event has already been processed
continue;
}
const animationNotification = notifications[i];
const notificationTime = animationNotification.time;
const event_time = notificationTime + cycle_start_time;
if (event_time > time_end) {
// event is past the end time of the interval
break replay;
} else if (event_time < t) {
// event is in the past, skip
continue;
}
//crossing notification boundary
const notificationDefinition = animationNotification.def;
ecd.sendEvent(entity, notificationDefinition.event, notificationDefinition.data);
t = event_time;
event_cursor = event_index;
}
if (event_cursor_before === event_cursor) {
// nothing dispatched
t += clipDuration;
}
}
}
if (!repeating && time0 < clipDuration && time1 > clipDuration) {
// Dispatch end of clip event
ecd.sendEvent(entity, AnimationEventTypes.ClipEnded, this);
}
}
/**
*
* @param {AnimationAction} action
*/
initializeThreeAnimationAction(action) {
const repeat = this.getFlag(AnimationClipFlag.Repeat);
action.reset();
if (repeat) {
action.loop = LoopRepeat;
action.repetitions = Infinity;
} else {
action.loop = LoopOnce;
action.repetitions = 1;
action.clampWhenFinished = true;
}
action.play();
}
/**
*
* @param {number|AnimationClipFlag} flag
* @returns {void}
*/
setFlag(flag) {
this.flags |= flag;
}
/**
*
* @param {number|AnimationClipFlag} flag
* @returns {void}
*/
clearFlag(flag) {
this.flags &= ~flag;
}
/**
*
* @param {number|AnimationClipFlag} flag
* @param {boolean} value
*/
writeFlag(flag, value) {
if (value) {
this.setFlag(flag);
} else {
this.clearFlag(flag);
}
}
/**
*
* @param {number|AnimationClipFlag} flag
* @returns {boolean}
*/
getFlag(flag) {
return (this.flags & flag) === flag;
}
/**
*
* @param {AnimationClip} other
* @returns {boolean}
*/
equals(other) {
if (this === other) {
return true;
}
return this.weight === other.weight
&& this.timeScale === other.timeScale
&& this.flags === other.flags
&& this.def.equals(other.def)
;
}
/**
* @returns {number}
*/
hash() {
return combine_hash(
this.flags,
computeHashFloat(this.weight),
computeHashFloat(this.timeScale),
this.def.hash()
);
}
}