UNPKG

mdx-m3-viewer

Version:

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

224 lines (188 loc) 6.62 kB
import {decodeAudioData} from '../../../common/audio'; import MappedData from '../../../utils/mappeddata'; import GenericObject from './genericobject'; import {emitterFilterMode} from './filtermode'; const mappedDataCallback = (text) => new MappedData(text); const decodedDataCallback = (arrayBuffer) => decodeAudioData(arrayBuffer); /** * An event object. */ export default class EventObject extends GenericObject { /** * @param {MdxModel} model * @param {MdxParserEventObjectEmitter} eventObject * @param {number} index */ constructor(model, eventObject, index) { super(model, eventObject, index); let viewer = model.viewer; let name = eventObject.name; let type = name.substring(0, 3); let id = name.substring(4); // Same thing if (type === 'FPT') { type = 'SPL'; } this.ok = false; this.type = type; this.id = id; this.tracks = eventObject.tracks; this.globalSequence = null; this.defval = new Uint32Array(1); // SPN - Model // SPL & UBR - Texture this.internalResource = null; // SPL & UBR this.colors = null; this.intervalTimes = null; this.scale = 0; this.columns = 0; this.rows = 0; this.lifeSpan = 0; this.blendSrc = 0; this.blendDst = 0; // SPL this.intervals = null; // SND this.distanceCutoff = 0; this.maxDistance = 0; this.minDistance = 0; this.pitch = 0; this.pitchVariance = 0; this.volume = 0; this.decodedBuffers = []; let globalSequenceId = eventObject.globalSequenceId; if (globalSequenceId !== -1) { this.globalSequence = model.globalSequences[globalSequenceId]; } let tables = []; let pathSolver = model.pathSolver; if (type === 'SPN') { tables[0] = viewer.loadGeneric(pathSolver('Splats\\SpawnData.slk')[0], 'text', mappedDataCallback); } else if (type === 'SPL') { tables[0] = viewer.loadGeneric(pathSolver('Splats\\SplatData.slk')[0], 'text', mappedDataCallback); } else if (type === 'UBR') { tables[0] = viewer.loadGeneric(pathSolver('Splats\\UberSplatData.slk')[0], 'text', mappedDataCallback); } else if (type === 'SND') { tables[0] = viewer.loadGeneric(pathSolver('UI\\SoundInfo\\AnimLookups.slk')[0], 'text', mappedDataCallback); tables[1] = viewer.loadGeneric(pathSolver('UI\\SoundInfo\\AnimSounds.slk')[0], 'text', mappedDataCallback); } else { // Units\Critters\BlackStagMale\BlackStagMale.mdx has an event object named "Point01". return; } let promise = viewer.promise(); viewer.whenLoaded(tables) .then((tables) => { for (let table of tables) { if (!table.ok) { promise.resolve(); return; } } this.load(tables); promise.resolve(); }); } /** * @param {Array<SlkFile>} tables */ load(tables) { let row = tables[0].data.getRow(this.id); let type = this.type; if (row) { let model = this.model; let viewer = model.viewer; let pathSolver = model.pathSolver; if (type === 'SPN') { this.internalResource = viewer.load(row.Model.replace('.mdl', '.mdx'), pathSolver); } else if (type === 'SPL' || type === 'UBR') { this.internalResource = viewer.load('replaceabletextures/splats/' + row.file + '.blp', pathSolver); this.colors = [[row.StartR, row.StartG, row.StartB, row.StartA], [row.MiddleR, row.MiddleG, row.MiddleB, row.MiddleA], [row.EndR, row.EndG, row.EndB, row.EndA]]; this.scale = row.Scale; if (type === 'SPL') { this.columns = row.Columns; this.rows = row.Rows; this.intervals = [[row.UVLifespanStart, row.UVLifespanEnd, row.LifespanRepeat], [row.UVDecayStart, row.UVDecayEnd, row.DecayRepeat]]; this.intervalTimes = [row.Lifespan, row.Decay]; this.lifeSpan = row.Lifespan + row.Decay; } else { this.columns = 1; this.rows = 1; this.intervalTimes = [row.BirthTime, row.PauseTime, row.Decay]; this.lifeSpan = row.BirthTime + row.PauseTime + row.Decay; } [this.blendSrc, this.blendDst] = emitterFilterMode(row.BlendMode, viewer.gl); } else if (type === 'SND') { // Only load sounds if audio is enabled. // This is mostly to save on bandwidth and loading time, especially when loading full maps. if (viewer.enableAudio) { row = tables[1].data.getRow(row.SoundLabel); if (row) { this.distanceCutoff = row.DistanceCutoff; this.maxDistance = row.MaxDistance; this.minDistance = row.MinDistance; this.pitch = row.Pitch; this.pitchVariance = row.PitchVariance; this.volume = row.Volume; let fileNames = row.FileNames.split(','); viewer.whenLoaded(fileNames.map((fileName) => viewer.loadGeneric(pathSolver(row.DirectoryBase + fileName)[0], 'arrayBuffer', decodedDataCallback))) .then((resources) => { for (let resource of resources) { this.decodedBuffers.push(resource.data); } this.ok = true; }); } } } if (this.internalResource) { this.internalResource.whenLoaded() .then(() => this.ok = true); } } else { console.warn('Unknown event object ID', type, this.id); } } /** * @param {Uint32Array} out * @param {ModelInstance} instance * @return {number} */ getValue(out, instance) { if (this.globalSequence) { let globalSequence = this.globalSequence; return this.getValueAtTime(out, instance.counter % globalSequence, 0, globalSequence); } else if (instance.sequence !== -1) { let interval = this.model.sequences[instance.sequence].interval; return this.getValueAtTime(out, instance.frame, interval[0], interval[1]); } else { out[0] = this.defval[0]; return -1; } } /** * @param {Uint32Array} out * @param {number} frame * @param {number} start * @param {number} end * @return {number} */ getValueAtTime(out, frame, start, end) { let tracks = this.tracks; if (frame < start || frame > end) { out[0] = 0; return -1; } for (let i = tracks.length - 1; i > -1; i--) { if (tracks[i] < start) { out[0] = 0; return i; } else if (tracks[i] <= frame) { out[0] = 1; return i; } } out[0] = 0; return -1; } }