UNPKG

mdx-m3-viewer

Version:

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

252 lines (206 loc) 9.24 kB
import urlWithParams from '../../../common/urlwithparams'; import { decodeAudioData } from '../../../common/audio'; import { FetchDataType } from '../../../common/fetchdatatype'; import { MappedData } from '../../../utils/mappeddata'; import EventObject from '../../../parsers/mdlx/eventobject'; import Texture from '../../texture'; import GenericResource from '../../genericresource'; import MdxModel from './model'; import GenericObject from './genericobject'; import { emitterFilterMode } from './filtermode'; import { EMITTER_SPLAT, EMITTER_UBERSPLAT } from './geometryemitterfuncs'; import MdxModelInstance from './modelinstance'; import MdxTexture from './texture'; import { PathSolver } from '../../handlerresource'; const mappedDataCallback = (data: FetchDataType) => new MappedData(<string>data); const decodedDataCallback = (data: FetchDataType) => decodeAudioData(<ArrayBuffer>data); /** * An event object. */ export default class EventObjectEmitterObject extends GenericObject { geometryEmitterType: number = -1; type: string; id: string; tracks: Uint32Array; globalSequence: number = -1; defval: Uint32Array = new Uint32Array(1); internalModel: MdxModel | null = null; internalTexture: MdxTexture | null = null; colors: Float32Array[] = []; intervalTimes: Float32Array = new Float32Array(3); scale: number = 0; columns: number = 0; rows: number = 0; lifeSpan: number = 0; blendSrc: number = 0; blendDst: number = 0; intervals: Float32Array[] = []; distanceCutoff: number = 0; maxDistance: number = 0; minDistance: number = 0; pitch: number = 0; pitchVariance: number = 0; volume: number = 0; decodedBuffers: AudioBuffer[] = []; ok: boolean = false; constructor(model: MdxModel, eventObject: EventObject, index: number) { 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'; } if (type === 'SPL') { this.geometryEmitterType = EMITTER_SPLAT; } else if (type === 'UBR') { this.geometryEmitterType = EMITTER_UBERSPLAT; } this.type = type; this.id = id; this.tracks = eventObject.tracks; let globalSequenceId = eventObject.globalSequenceId; if (globalSequenceId !== -1) { this.globalSequence = model.globalSequences[globalSequenceId]; } let tables = []; let pathSolver = model.pathSolver; // Nothing to do without a path solver. if (!pathSolver) { return; } // Sometimes TS isn't the brightest. let pathSolverAsPathSolver = <PathSolver>pathSolver; let solverParams = model.solverParams; if (type === 'SPN') { tables[0] = viewer.loadGeneric(urlWithParams(pathSolver('Splats\\SpawnData.slk'), solverParams), 'text', mappedDataCallback); } else if (type === 'SPL') { tables[0] = viewer.loadGeneric(urlWithParams(pathSolver('Splats\\SplatData.slk'), solverParams), 'text', mappedDataCallback); } else if (type === 'UBR') { tables[0] = viewer.loadGeneric(urlWithParams(pathSolver('Splats\\UberSplatData.slk'), solverParams), 'text', mappedDataCallback); } else if (type === 'SND') { if (!model.reforged) { tables.push(viewer.loadGeneric(urlWithParams(pathSolver('UI\\SoundInfo\\AnimLookups.slk'), solverParams), 'text', mappedDataCallback)); } tables.push(viewer.loadGeneric(urlWithParams(pathSolver('UI\\SoundInfo\\AnimSounds.slk'), solverParams), 'text', mappedDataCallback)); } else { // Units\Critters\BlackStagMale\BlackStagMale.mdx has an event object named "Point01". return; } let resolve = viewer.promise(); Promise.all(tables) .then((tables) => { for (let table of tables) { if (!table) { resolve(); return; } } let tablesAsGeneric = <GenericResource[]>tables; let firstTable = <MappedData>tablesAsGeneric[0].data; let row = firstTable.getRow(this.id); if (row) { if (type === 'SPN') { viewer.load((<string>row.Model).replace('.mdl', '.mdx'), pathSolverAsPathSolver, model.solverParams) .then((model) => { if (model) { this.internalModel = <MdxModel>model; this.ok = true; } }); } else if (type === 'SPL' || type === 'UBR') { let texturesExt = model.reforged ? '.dds' : '.blp'; this.internalTexture = new MdxTexture(0, true, true); viewer.load(`replaceabletextures/splats/${row.file}${texturesExt}`, pathSolverAsPathSolver, model.solverParams) .then((texture) => { if (texture) { (<MdxTexture>this.internalTexture).texture = <Texture>texture; this.ok = true; } }); this.scale = <number>row.Scale; this.colors[0] = new Float32Array([<number>row.StartR, <number>row.StartG, <number>row.StartB, <number>row.StartA]); this.colors[1] = new Float32Array([<number>row.MiddleR, <number>row.MiddleG, <number>row.MiddleB, <number>row.MiddleA]); this.colors[2] = new Float32Array([<number>row.EndR, <number>row.EndG, <number>row.EndB, <number>row.EndA]); if (type === 'SPL') { this.columns = <number>row.Columns; this.rows = <number>row.Rows; this.lifeSpan = <number>row.Lifespan + <number>row.Decay; this.intervalTimes[0] = <number>row.Lifespan; this.intervalTimes[1] = <number>row.Decay; this.intervals[0] = new Float32Array([<number>row.UVLifespanStart, <number>row.UVLifespanEnd, <number>row.LifespanRepeat]); this.intervals[1] = new Float32Array([<number>row.UVDecayStart, <number>row.UVDecayEnd, <number>row.DecayRepeat]); } else { this.columns = 1; this.rows = 1; this.lifeSpan = <number>row.BirthTime + <number>row.PauseTime + <number>row.Decay; this.intervalTimes[0] = <number>row.BirthTime; this.intervalTimes[1] = <number>row.PauseTime; this.intervalTimes[2] = <number>row.Decay; } let blendModes = emitterFilterMode(<number>row.BlendMode, viewer.gl); this.blendSrc = blendModes[0]; this.blendDst = blendModes[1]; this.ok = true; } 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.audioEnabled) { let animSounds = <MappedData>tablesAsGeneric[1].data; row = animSounds.getRow(<string>row.SoundLabel); if (row) { this.distanceCutoff = <number>row.DistanceCutoff; this.maxDistance = <number>row.MaxDistance; this.minDistance = <number>row.MinDistance; this.pitch = <number>row.Pitch; this.pitchVariance = <number>row.PitchVariance; this.volume = <number>row.Volume; let fileNames = (<string>row.FileNames).split(','); let resources = fileNames.map((fileName) => viewer.loadGeneric(urlWithParams(pathSolverAsPathSolver(row.DirectoryBase + fileName), model.solverParams), 'arrayBuffer', decodedDataCallback)); Promise.all(resources) .then((resources) => { for (let resource of resources) { this.decodedBuffers.push((<GenericResource>resource).data); } this.ok = true; }); } } } } else { console.warn('Unknown event object ID', type, this.id); } resolve(); }); } getValue(out: Uint32Array, instance: MdxModelInstance) { if (this.globalSequence !== -1) { 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; } } getValueAtTime(out: Uint32Array, frame: number, start: number, end: number) { let tracks = this.tracks; if (frame >= start && frame <= end) { 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; } }