mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
165 lines (129 loc) • 5.13 kB
text/typescript
import { Animation } from '../../parsers/mdlx/animations';
import SanityTestData from './data';
import EventObject from '../../parsers/mdlx/eventobject';
function getSequenceFromFrame(data: SanityTestData, frame: number, globalSequenceId: number) {
if (globalSequenceId === -1) {
let sequences = data.model.sequences;
for (let i = 0, l = sequences.length; i < l; i++) {
let interval = sequences[i].interval;
if (frame >= interval[0] && frame <= interval[1]) {
return i;
}
}
} else {
let end = data.model.globalSequences[globalSequenceId];
if (frame >= 0 && frame <= end) {
return globalSequenceId;
}
}
return -1;
}
function seprateTracks(data: SanityTestData, object: Animation | EventObject, separated: number[][]) {
let frames;
let globalSequenceId = object.globalSequenceId;
if (object instanceof Animation) {
frames = object.frames;
} else {
frames = object.tracks;
}
for (let i = 0, l = frames.length; i < l; i++) {
let frame = frames[i];
data.assertWarning(frame >= 0, `Track ${i}: Negative frame`);
let sequence = getSequenceFromFrame(data, frame, globalSequenceId);
if (sequence !== -1) {
separated[sequence].push(i);
} else {
// Frame 0 seems to be special.
// Or maybe it's the first keyframe regardless of the frame.
// Who knows.
if (frame !== 0 && frames.length > 1) {
if (globalSequenceId === -1) {
data.addUnused(`Track ${i}: Frame ${frame} is not in any sequence`);
} else {
data.addUnused(`Track ${i}: Frame ${frame} is not in global sequence ${globalSequenceId}`);
}
}
}
}
}
function getSequenceName(data: SanityTestData, sequence: number, globalSequenceId: number) {
if (globalSequenceId === -1) {
return `sequence "${data.model.sequences[sequence].name}"`;
} else {
return `global sequence ${globalSequenceId}`;
}
}
const EPSILON = 0.001;
function compareValues(a: Uint32Array | Float32Array, b: Uint32Array | Float32Array, c: Uint32Array | Float32Array) {
for (let i = 0, l = a.length; i < l; i++) {
let ai = a[i];
if (Math.abs(ai - b[i]) > EPSILON || Math.abs(ai - c[i]) > EPSILON) {
return false;
}
}
return true;
}
function testSequenceTracks(data: SanityTestData, object: Animation | EventObject, indices: number[], sequence: number) {
let frames;
let interpolationType = 0;
let globalSequenceId = object.globalSequenceId;
if (object instanceof Animation) {
frames = object.frames;
interpolationType = object.interpolationType;
} else {
frames = object.tracks;
}
let start = 0;
let end;
if (globalSequenceId === -1) {
let interval = data.model.sequences[sequence].interval;
start = interval[0];
end = interval[1];
} else {
end = data.model.globalSequences[globalSequenceId];
}
let first = frames[indices[0]];
let last = frames[indices[indices.length - 1]];
if (object instanceof Animation) {
// Missing the opening/closing tracks for a specific sequence can sometimes cause weird animations in the game.
// Generally speaking these warnings can be ignored though.
data.assertWarning(first === start, `No opening track for ${getSequenceName(data, sequence, globalSequenceId)} at frame ${start}`);
// If there is no interpolation, then it doesn't matter if there's a closing track or not.
data.assertWarning(last === end || interpolationType === 0, `No closing track for ${getSequenceName(data, sequence, globalSequenceId)} at frame ${end}`);
// Check for consecutive tracks with the same values.
if (indices.length > 2) {
let values = object.values;
let a = values[indices[0]];
let b = values[indices[1]];
for (let i = 2, l = indices.length; i < l; i++) {
let c = values[indices[i]];
if (compareValues(a, b, c)) {
let index = indices[i - 1];
data.addUnused(`Track ${index} at frame ${frames[index]} has the same-ish value as tracks ${index - 1} and ${index + 1}`);
}
a = b;
b = c;
}
}
}
}
export default function testTracks(data: SanityTestData, object: Animation | EventObject) {
let globalSequenceId = object.globalSequenceId;
let separated: number[][] = [];
if (globalSequenceId === -1) {
let sequences = data.model.sequences;
data.assertWarning(frames.length === 0 || sequences.length !== 0, 'Tracks used without sequences');
for (let i = 0, l = sequences.length; i < l; i++) {
separated.push([]);
}
} else if (globalSequenceId >= 0 && globalSequenceId < data.model.globalSequences.length) {
separated[globalSequenceId] = [];
}
seprateTracks(data, object, separated);
for (let i = 0, l = separated.length; i < l; i++) {
let indices = separated[i];
if (indices && indices.length > 1) {
testSequenceTracks(data, object, indices, i);
}
}
}