@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
143 lines (131 loc) • 4.29 kB
text/typescript
import {ChannelData, KeyframeData, ChannelInterpolation, SetCurveCallback, GetValueCallback} from './KeyframeCommon';
import {setCurveFromKeyframePairCubic, getValueCubic} from './channel/Cubic';
import {setCurveFromKeyframePairLinear, getValueLinear} from './channel/Linear';
import {mix} from '../math/_Module';
import {pushOnArrayAtEntry} from '../MapUtils';
import {copyKeyframeData, createKeyframeData} from './KeyframeSerialize';
interface KeyframePair {
start: KeyframeData;
end: KeyframeData;
}
// const _v2 = new Vector2();
export class Channel {
private _valuesByPos: Map<number, number> = new Map();
protected _setCurveCallback: SetCurveCallback = setCurveFromKeyframePairLinear;
protected _getValueCallback: GetValueCallback = getValueLinear;
constructor(public readonly data: ChannelData) {
Channel.validate(this.data);
this.compute();
}
static fromJSON(data: ChannelData) {
return new Channel(data);
}
value(t: number): number {
if (t == Math.floor(t)) {
const value = this._valuesByPos.get(t);
if (value != null) {
return value;
}
const keyframes = this.data.keyframes;
if (keyframes.length == 0) {
return 0;
}
const firstPos = keyframes[0].pos;
const lastPos = keyframes[keyframes.length - 1].pos;
if (t < firstPos) {
return this._valuesByPos.get(firstPos) || 0;
}
if (t > lastPos) {
return this._valuesByPos.get(lastPos) || 0;
}
}
const t0 = Math.floor(t);
const t1 = Math.ceil(t);
const v0 = this._valuesByPos.get(t0) || 0;
const v1 = this._valuesByPos.get(t1) || 0;
return mix(v0, v1, t - t0);
}
static validate(data: ChannelData) {
let keyframes = data.keyframes;
const keyframesByPos: Map<number, KeyframeData[]> = new Map();
for (const keyframe of keyframes) {
pushOnArrayAtEntry(keyframesByPos, keyframe.pos, keyframe);
}
// if we have keyframes that need to be merged, adjust the sizes of the arrays
if (keyframes.length != keyframesByPos.size) {
const expectedSize = keyframesByPos.size;
const keptKeyframes: KeyframeData[] = new Array(expectedSize);
let i = 0;
keyframesByPos.forEach((keyframes, pos) => {
keptKeyframes[i] = keyframes[0];
i++;
});
keyframes = keptKeyframes;
data.keyframes.splice(expectedSize, data.keyframes.length - expectedSize);
}
// sort by replacing the values, not just swapping the keyframe data objects
const sortedKeyframes = keyframes
.sort((k1, k2) => k1.pos - k2.pos)
.map((k) => {
const target = createKeyframeData();
copyKeyframeData(k, target);
return target;
});
let i = 0;
for (const keyframe of sortedKeyframes) {
copyKeyframeData(keyframe, data.keyframes[i]);
i++;
}
}
private _setCallbacks() {
switch (this.data.interpolation) {
case ChannelInterpolation.CUBIC: {
this._setCurveCallback = setCurveFromKeyframePairCubic;
this._getValueCallback = getValueCubic;
return;
}
case ChannelInterpolation.LINEAR: {
this._setCurveCallback = setCurveFromKeyframePairLinear;
this._getValueCallback = getValueLinear;
return;
}
}
}
compute() {
Channel.validate(this.data);
const keyframes = this.data.keyframes;
if (keyframes.length == 0) {
return;
}
this._setCallbacks();
const firstPos = keyframes[0].pos;
const lastPos = keyframes[keyframes.length - 1].pos;
this._valuesByPos.clear();
if (keyframes.length == 0) {
return;
}
if (keyframes.length == 1) {
this._valuesByPos.set(keyframes[0].pos, keyframes[0].value);
return;
}
let segmentIndex = 0;
const keyframePair: KeyframePair = {start: keyframes[0], end: keyframes[1]};
this._setCurveCallback(keyframePair.start, keyframePair.end);
for (let pos = firstPos; pos <= lastPos; pos++) {
if (pos > keyframePair.end.pos) {
segmentIndex++;
keyframePair.start = keyframePair.end;
keyframePair.end = keyframes[segmentIndex + 1];
this._setCurveCallback(keyframePair.start, keyframePair.end);
}
const value = this._getValueCallback(pos);
this._valuesByPos.set(pos, value);
}
}
// private _computeValue(pos: number, curve: CubicBezierCurve): number {
// const t = this._findTForXCallback(pos, curve);
// curve.getPoint(t, _v2);
// console.log(pos, t, _v2.x, _v2.y);
// return _v2.y;
// }
}