UNPKG

mdx-m3-viewer

Version:

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

244 lines (200 loc) 8.72 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 GenericResource from '../../genericresource'; import Texture from '../../texture'; import MdxModel from './model'; import GenericObject from './genericobject'; import { emitterFilterMode } from './filtermode'; import { EMITTER_SPLAT, EMITTER_UBERSPLAT } from './geometryemitterfuncs'; import MdxModelInstance from './modelinstance'; 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: Texture | null = null; colors: Float32Array[] | null = null; intervalTimes: Float32Array | null = null; scale: number = 0; columns: number = 0; rows: number = 0; lifeSpan: number = 0; blendSrc: number = 0; blendDst: number = 0; intervals: Float32Array[] | null = null; distanceCutoff: number = 0; maxDistance: number = 0; minDistance: number = 0; pitch: number = 0; pitchVariance: number = 0; volume: number = 0; decodedBuffers: AudioBuffer[] = []; /** * If this is an SPL/UBR emitter object, ok will be set to true if the tables are loaded. * * This is because, like the other geometry emitters, it is fine to use them even if the textures don't load. * * The particles will simply be black. */ 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; let solverParams = model.solverParams; if (type === 'SPN') { tables[0] = viewer.loadGeneric(urlWithParams(pathSolver('Splats\\SpawnData.slk')[0], solverParams), 'text', mappedDataCallback); } else if (type === 'SPL') { tables[0] = viewer.loadGeneric(urlWithParams(pathSolver('Splats\\SplatData.slk')[0], solverParams), 'text', mappedDataCallback); } else if (type === 'UBR') { tables[0] = viewer.loadGeneric(urlWithParams(pathSolver('Splats\\UberSplatData.slk')[0], solverParams), 'text', mappedDataCallback); } else if (type === 'SND') { if (!model.reforged) { tables.push(viewer.loadGeneric(urlWithParams(pathSolver('UI\\SoundInfo\\AnimLookups.slk')[0], solverParams), 'text', mappedDataCallback)); } tables.push(viewer.loadGeneric(urlWithParams(pathSolver('UI\\SoundInfo\\AnimSounds.slk')[0], solverParams), 'text', mappedDataCallback)); } else { // Units\Critters\BlackStagMale\BlackStagMale.mdx has an event object named "Point01". return; } let promise = viewer.promise(); viewer.whenLoaded(tables, (tables) => { for (let table of tables) { if (!table.ok) { promise.resolve(); return; } } this.load(<GenericResource[]>tables); promise.resolve(); }) } load(tables: GenericResource[]) { let firstTable = <MappedData>tables[0].data; let row = firstTable.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.internalModel = <MdxModel>viewer.load((<string>row.Model).replace('.mdl', '.mdx'), pathSolver, model.solverParams); if (this.internalModel) { this.internalModel.whenLoaded((model) => this.ok = model.ok); } } else if (type === 'SPL' || type === 'UBR') { let texturesExt = model.reforged ? '.dds' : '.blp'; this.internalTexture = <Texture>viewer.load(`replaceabletextures/splats/${row.file}${texturesExt}`, pathSolver, model.solverParams); this.scale = <number>row.Scale; this.colors = [ new Float32Array([<number>row.StartR, <number>row.StartG, <number>row.StartB, <number>row.StartA]), new Float32Array([<number>row.MiddleR, <number>row.MiddleG, <number>row.MiddleB, <number>row.MiddleA]), 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 = new Float32Array([<number>row.Lifespan, <number>row.Decay]); this.intervals = [ new Float32Array([<number>row.UVLifespanStart, <number>row.UVLifespanEnd, <number>row.LifespanRepeat]), 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 = new Float32Array([<number>row.BirthTime, <number>row.PauseTime, <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>tables[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(pathSolver(row.DirectoryBase + fileName)[0], model.solverParams), 'arrayBuffer', decodedDataCallback)); viewer.whenLoaded(resources, (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); } } 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; } }