@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
196 lines (158 loc) • 5.86 kB
JavaScript
import { assert } from "../../../core/assert.js";
import { isTypedArray } from "../../../core/collection/array/typed/isTypedArray.js";
/**
* Optimize multi-variable animation descriptor
* Consider using {@link AnimationCurve} instead if you're starting a new project as it's simpler to use and is more streamlined
*/
class AnimationTrack {
/**
*
* @param {String[]} properties
* @constructor
*/
constructor(properties) {
/**
*
* @type {String[]}
*/
this.properties = properties;
/**
*
* @type {number}
*/
this.propertyCount = properties.length;
/**
*
* @type {number[]}
*/
this.keyValues = [];
/**
*
* @type {number[]}
*/
this.keyTimes = [];
this.timeStart = Number.POSITIVE_INFINITY;
this.timeEnd = Number.NEGATIVE_INFINITY;
/**
* Indices of keys from which transition takes place
* @type {number[]}
*/
this.transitionKeys = [];
/**
* Transition function associated with a key
* @type {function[]}
*/
this.transitionFunctions = [];
}
optimize() {
const key_value_count = this.keyValues.length;
const key_count = this.keyTimes.length;
// use a common buffer to get better cache locality
const buffer = new ArrayBuffer(key_count * 4 + key_value_count * 8);
const keyTimesTyped = new Float32Array(buffer, 0, key_count);
const keysTyped = new Float64Array(buffer, key_count * 4, key_value_count);
//transfer original keys into typed array
keysTyped.set(this.keyValues, 0);
//overwrite the original array
this.keyValues = keysTyped;
keyTimesTyped.set(this.keyTimes, 0);
this.keyTimes = keyTimesTyped;
}
/**
*
* @param {number} keyTime
* @param {number[]} values
*/
addKey(keyTime, values) {
if (values.length !== this.propertyCount) {
throw new Error("Number of supplied values(" + values.length + ") does not match number of track properties(" + this.propertyCount + ")");
}
const keyCount = this.keyTimes.length;
if (keyCount === 0) {
//this is the very first key
this.timeStart = keyTime;
} else if (keyTime < this.timeEnd) {
//TODO we can do some sorting here. Have to account for possible existing transitions
throw new Error("Attempted to insert key in the past. Keys must be sequential");
} else if (keyTime === this.timeEnd) {
throw new Error("Attempted to insert key at the same time as already existing key");
}
//update end time of the track
this.timeEnd = keyTime;
Array.prototype.push.apply(this.keyValues, values);
this.keyTimes.push(keyTime);
}
/**
*
* @param {number} startKeyIndex
* @param {function(number):number} transitionFunction
*/
addTransition(startKeyIndex, transitionFunction) {
this.transitionKeys.push(startKeyIndex);
this.transitionFunctions.push(transitionFunction);
}
/**
*
* @param {number} time
* @returns {number}
*/
keyLowerBoundIndexAt(time) {
const keyTimes = this.keyTimes;
const keyCount = keyTimes.length;
for (let i = 0, l = keyCount; i < l; i++) {
const keyTime = keyTimes[i];
if (keyTime > time) {
return i - 1;
}
}
//nothing found, return index of very last key (if no keys exist it will be -1)
return keyCount - 1;
}
/**
*
* @param {number} time
* @returns {number}
*/
transitionIndexAt(time) {
const transitionKeys = this.transitionKeys;
const numTransitions = transitionKeys.length;
const keyTimes = this.keyTimes;
for (let i = 0, l = numTransitions; i < l; i++) {
const startKeyIndex = transitionKeys[i];
const endTime = keyTimes[startKeyIndex + 1];
if (endTime < time) {
//transition ends before time in question
continue;
}
const startTime = keyTimes[startKeyIndex];
if (startTime <= time) {
//match
return i;
} else {
//went too far, there is no transition that covers this time
return -1;
}
}
//no transitions exist, return -1
return -1;
}
/**
*
* @param {number} keyIndex
* @param {number[]} result
*/
readKeyValues(keyIndex, result) {
assert.equal(typeof keyIndex, 'number', `keyIndex must be a number, instead was '${typeof keyIndex}'`);
assert.ok(Number.isInteger(keyIndex), `keyIndex must be an integer, instead was ${keyIndex}`);
assert.ok(keyIndex >= 0, `keyIndex must be non-negative, was ${keyIndex}`);
assert.equal(typeof result, 'object', `result argument must be an object, instead was '${typeof result}'`);
assert.ok(Array.isArray(result) || isTypedArray(result), `result argument must be an array, instead was something else (${result})`);
const propertyCount = this.propertyCount;
const offset = propertyCount * keyIndex;
const keyValues = this.keyValues;
for (let i = 0; i < propertyCount; i++) {
result[i] = keyValues[i + offset];
}
}
}
export default AnimationTrack;