UNPKG

mdx-m3-viewer

Version:

A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.

336 lines (290 loc) 10.8 kB
import { vec3, quat } from 'gl-matrix'; import { clamp, lerp, hermite, bezier } from '../../../common/math'; import { Animation, UintAnimation, FloatAnimation, Vector3Animation } from '../../../parsers/mdlx/animations'; import MdxModel from './model'; /** * Animated data for a specific sequence. */ class SdSequence { sd: Sd; start: number; end: number; frames: number[] = []; values: (Uint32Array | Float32Array)[] = []; inTans: (Uint32Array | Float32Array)[] = []; outTans: (Uint32Array | Float32Array)[] = []; constant: boolean = false; constructor(sd: Sd, start: number, end: number, animation: Animation, isGlobal: boolean) { this.sd = sd; this.start = start; this.end = end; let interpolationType = sd.interpolationType; let frames = animation.frames; let values = animation.values; let inTans = animation.inTans; let outTans = animation.outTans; let defval = sd.defval; // When using a global sequence, where the first key is outside of the sequence's length, it becomes its constant value. // When having one key in the sequence's range, and one key outside of it, results seem to be non-deterministic. // Sometimes the second key is used too, sometimes not. // It also differs depending where the model is viewed - the WE previewer, the WE itself, or the game. // All three show different results, none of them make sense. // Therefore, only handle the case where the first key is outside. // This fixes problems spread over many models, e.g. HeroMountainKing (compare in WE and in Magos). if (isGlobal && frames[0] > end) { this.frames[0] = frames[0]; this.values[0] = values[0]; } // Go over the keyframes, and add all of the ones that are in this sequence (start <= frame <= end). for (let i = 0, l = frames.length; i < l; i++) { let frame = frames[i]; if (frame >= start && frame <= end) { this.frames.push(frame); this.values.push(values[i]); if (interpolationType > 1) { this.inTans.push(inTans[i]); this.outTans.push(outTans[i]); } } } let tracksCount = this.frames.length; if (tracksCount === 0) { // If there are no keys, use the default value directly. this.constant = true; this.frames[0] = start; this.values[0] = defval; } else if (tracksCount === 1) { // If there's only one key, use it directly. this.constant = true; } else { let firstValue = this.values[0]; // If all of the values in this sequence are the same, might as well make it constant. this.constant = this.values.every((value) => firstValue.every((element: number, index: number) => element === value[index])); if (!this.constant) { // If there is no opening keyframe for this sequence, inject one with the default value. if (this.frames[0] !== start) { this.frames.unshift(start); this.values.unshift(defval); if (interpolationType > 1) { this.inTans.unshift(defval); this.outTans.unshift(defval); } } // If there is no closing keyframe for this sequence, inject one with the default value. if (this.frames[this.frames.length - 1] !== end) { this.frames.push(end); this.values.push(this.values[0]); if (interpolationType > 1) { this.inTans.push(this.inTans[0]); this.outTans.push(this.outTans[0]); } } } } } getValue(out: Uint32Array | Float32Array, frame: number) { let frames = this.frames; let l = frames.length; if (this.constant || frame < this.start) { this.sd.copy(out, this.values[0]); return -1; } else if (frame >= this.end) { this.sd.copy(out, this.values[l - 1]); return l - 1; } else { for (let i = 1; i < l; i++) { if (frames[i] > frame) { let start = frames[i - 1]; let end = frames[i]; let t = clamp((frame - start) / (end - start), 0, 1); this.sd.interpolate(out, this.values, this.inTans, this.outTans, i - 1, i, t); return i; } } return -1; } } } const forcedInterpMap = { KLAV: 0, KATV: 0, KPEV: 0, KP2V: 0, KRVS: 0, }; const floatDefval = new Float32Array(1); const uintDefval = new Uint32Array(1); const visibilityDefval = new Float32Array([1]); const translationDefval = vec3.create(); const rotationDefval = quat.create(); const scaleDefval = vec3.fromValues(1, 1, 1); const alphaDefval = visibilityDefval; const colorDefval = translationDefval; const defVals = { // LAYS KMTF: floatDefval, KMTA: alphaDefval, // TXAN KTAT: translationDefval, KTAR: rotationDefval, KTAS: scaleDefval, // GEOA KGAO: alphaDefval, KGAC: colorDefval, // LITE KLAS: floatDefval, KLAE: floatDefval, KLAC: colorDefval, KLAI: floatDefval, KLBI: floatDefval, KLBC: colorDefval, KLAV: visibilityDefval, // ATCH KATV: visibilityDefval, // PREM KPEE: floatDefval, KPEG: floatDefval, KPLN: floatDefval, KPLT: floatDefval, KPEL: floatDefval, KPES: floatDefval, KPEV: visibilityDefval, // PRE2 KP2S: floatDefval, KP2R: floatDefval, KP2L: floatDefval, KP2G: floatDefval, KP2E: floatDefval, KP2N: floatDefval, KP2W: floatDefval, KP2V: visibilityDefval, // RIBB KRHA: floatDefval, KRHB: floatDefval, KRAL: alphaDefval, KRCO: colorDefval, KRTX: floatDefval, KRVS: visibilityDefval, // CAMS KCTR: translationDefval, KTTR: translationDefval, KCRL: uintDefval, // NODE KGTR: translationDefval, KGRT: rotationDefval, KGSC: scaleDefval, }; /** * Animated data. */ export abstract class Sd { defval: Float32Array | Uint32Array; model: MdxModel; name: string; globalSequence: SdSequence | null = null; sequences: SdSequence[] = []; interpolationType: number; abstract copy(out: Uint32Array | Float32Array | vec3 | quat, value: Uint32Array | Float32Array | vec3 | quat): void; abstract interpolate(out: Uint32Array | Float32Array | vec3 | quat, values: (Uint32Array | Float32Array | vec3 | quat)[], inTans: (Uint32Array | Float32Array | vec3 | quat)[], outTans: (Uint32Array | Float32Array | vec3 | quat)[], start: number, end: number, t: number): void; constructor(model: MdxModel, animation: Animation) { let globalSequences = model.globalSequences; let globalSequenceId = animation.globalSequenceId; let forcedInterp = forcedInterpMap[animation.name]; this.model = model; this.name = animation.name; this.defval = defVals[animation.name]; // Allow to force an interpolation type. // The game seems to do this with visibility tracks, where the type is forced to None. // It came up as a bug report by a user who used the wrong interpolation type. this.interpolationType = forcedInterp !== undefined ? forcedInterp : animation.interpolationType; if (globalSequenceId !== -1 && globalSequences) { this.globalSequence = new SdSequence(this, 0, globalSequences[globalSequenceId], animation, true); } else { for (let sequence of model.sequences) { let interval = sequence.interval; this.sequences.push(new SdSequence(this, interval[0], interval[1], animation, false)); } } } getValue(out: Uint32Array | Float32Array, sequence: number, frame: number, counter: number) { if (this.globalSequence) { return this.globalSequence.getValue(out, counter % this.globalSequence.end); } return this.sequences[sequence].getValue(out, frame); } isVariant(sequence: number) { if (this.globalSequence) { return !this.globalSequence.constant; } else { return !this.sequences[sequence].constant; } } } /** * A scalar animation. */ export class ScalarSd extends Sd { copy(out: Uint32Array | Float32Array, value: Uint32Array | Float32Array) { out[0] = value[0]; } interpolate(out: Uint32Array | Float32Array, values: (Uint32Array | Float32Array)[], inTans: (Uint32Array | Float32Array)[], outTans: (Uint32Array | Float32Array)[], start: number, end: number, t: number) { let interpolationType = this.interpolationType; let startValue = values[start][0]; if (interpolationType === 0) { out[0] = startValue; } else if (interpolationType === 1) { out[0] = lerp(startValue, values[end][0], t); } else if (interpolationType === 2) { out[0] = hermite(startValue, outTans[start][0], inTans[end][0], values[end][0], t); } else if (interpolationType === 3) { out[0] = bezier(startValue, outTans[start][0], inTans[end][0], values[end][0], t); } } } /** * A vector animation. */ export class VectorSd extends Sd { copy(out: vec3, value: vec3) { vec3.copy(out, value); } interpolate(out: vec3, values: vec3[], inTans: vec3[], outTans: vec3[], start: number, end: number, t: number) { let interpolationType = this.interpolationType; if (interpolationType === 0) { vec3.copy(out, values[start]); } else if (interpolationType === 1) { vec3.lerp(out, values[start], values[end], t); } else if (interpolationType === 2) { vec3.hermite(out, values[start], outTans[start], inTans[end], values[end], t); } else if (interpolationType === 3) { vec3.bezier(out, values[start], outTans[start], inTans[end], values[end], t); } } } /** * A quaternion animation. */ export class QuatSd extends Sd { copy(out: quat, value: quat) { quat.copy(out, value); } interpolate(out: quat, values: quat[], inTans: quat[], outTans: quat[], start: number, end: number, t: number) { let interpolationType = this.interpolationType; if (interpolationType === 0) { quat.copy(out, values[start]); } else if (interpolationType === 1) { quat.slerp(out, values[start], values[end], t); } else if (interpolationType === 2 || interpolationType === 3) { quat.sqlerp(out, values[start], outTans[start], inTans[end], values[end], t); } } } export function createTypedSd(model: MdxModel, animation: Animation) { if (animation instanceof UintAnimation || animation instanceof FloatAnimation) { return new ScalarSd(model, animation); } else if (animation instanceof Vector3Animation) { return new VectorSd(model, animation); } else { return new QuatSd(model, animation); } }