UNPKG

mdx-m3-viewer

Version:

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

284 lines (231 loc) 8.98 kB
import Parser from '../../../parsers/mdlx/model'; import Sequence from '../../../parsers/mdlx/sequence'; import Model from '../../model'; import Texture from '../../texture'; import TextureAnimation from './textureanimation'; import Layer from './layer'; import Material from './material'; import GeosetAnimation from './geosetanimation'; import replaceableIds from './replaceableids'; import Bone from './bone'; import Light from './light'; import Helper from './helper'; import Attachment from './attachment'; import ParticleEmitterObject from './particleemitterobject'; import ParticleEmitter2Object from './particleemitter2object'; import RibbonEmitterObject from './ribbonemitterobject'; import Camera from './camera'; import EventObjectEmitterObject from './eventobjectemitterobject'; import CollisionShape from './collisionshape'; import setupGeosets from './setupgeosets'; import setupGroups from './setupgroups'; import BatchGroup from './batchgroup'; import EmitterGroup from './emittergroup'; import GenericObject from './genericobject'; import Batch from './batch'; import Geoset from './geoset'; import MdxModelInstance from './modelinstance'; /** * An MDX model. */ export default class MdxModel extends Model { reforged: boolean = false; hd: boolean = false; solverParams: { reforged?: boolean, hd?: boolean } = {}; name: string = ''; sequences: Sequence[] = []; globalSequences: number[] = []; materials: Material[] = []; layers: Layer[] = []; replaceables: number[] = []; textures: Texture[] = []; textureAnimations: TextureAnimation[] = []; geosets: Geoset[] = []; geosetAnimations: GeosetAnimation[] = []; bones: Bone[] = []; lights: Light[] = []; helpers: Helper[] = []; attachments: Attachment[] = []; pivotPoints: Float32Array[] = []; particleEmitters: ParticleEmitterObject[] = []; particleEmitters2: ParticleEmitter2Object[] = []; ribbonEmitters: RibbonEmitterObject[] = []; cameras: Camera[] = []; eventObjects: EventObjectEmitterObject[] = []; collisionShapes: CollisionShape[] = []; hasLayerAnims: boolean = false; hasGeosetAnims: boolean = false; batches: Batch[] = []; genericObjects: GenericObject[] = []; sortedGenericObjects: GenericObject[] = []; hierarchy: number[] = []; opaqueGroups: BatchGroup[] = []; translucentGroups: (BatchGroup | EmitterGroup)[] = []; arrayBuffer: WebGLBuffer | null = null; elementBuffer: WebGLBuffer | null = null; skinDataType: number = 0; bytesPerSkinElement: number = 1; createInstance(): MdxModelInstance { return new MdxModelInstance(this); } load(bufferOrParser: ArrayBuffer | string | Parser) { let parser; if (bufferOrParser instanceof Parser) { parser = bufferOrParser; } else { parser = new Parser(bufferOrParser); } let viewer = this.viewer; let pathSolver = this.pathSolver; let solverParams = this.solverParams; let reforged = parser.version > 800; let texturesExt = reforged ? '.dds' : '.blp'; this.reforged = reforged; this.name = parser.name; // Initialize the bounds. let extent = parser.extent; this.bounds.fromExtents(extent.min, extent.max); // Sequences for (let sequence of parser.sequences) { this.sequences.push(sequence); } // Global sequences for (let globalSequence of parser.globalSequences) { this.globalSequences.push(globalSequence); } // Texture animations for (let textureAnimation of parser.textureAnimations) { this.textureAnimations.push(new TextureAnimation(this, textureAnimation)); } // Materials let layerId = 0; for (let material of parser.materials) { let layers = []; for (let layer of material.layers) { let vLayer = new Layer(this, layer, layerId++, material.priorityPlane); layers.push(vLayer); this.layers.push(vLayer); } this.materials.push(new Material(this, material.shader, layers)); if (material.shader !== '') { this.hd = true; } } if (reforged) { solverParams.reforged = true; } if (this.hd) { solverParams.hd = true; } let gl = viewer.gl; let usingTeamTextures = false; // Textures. for (let texture of parser.textures) { let path = texture.path; let replaceableId = texture.replaceableId; let flags = texture.flags; if (replaceableId !== 0) { path = `ReplaceableTextures\\${replaceableIds[replaceableId]}${texturesExt}`; if (replaceableId === 1 || replaceableId === 2) { usingTeamTextures = true; } } if (reforged && !path.endsWith('.dds')) { path = `${path.slice(0, -4)}.dds`; } let viewerTexture = <Texture>viewer.load(path, pathSolver, solverParams); // When the texture will load, it will apply its wrap modes. if (!viewerTexture.loaded) { if (flags & 0x1) { viewerTexture.wrapS = gl.REPEAT; } if (flags & 0x2) { viewerTexture.wrapT = gl.REPEAT; } } this.replaceables.push(replaceableId); this.textures.push(viewerTexture); } // Start loading the team color and glow textures if this model uses them and they weren't loaded previously. if (usingTeamTextures) { let mdxCache = viewer.sharedCache.get('mdx'); let teamColors = reforged ? mdxCache.reforgedTeamColors : mdxCache.teamColors; let teamGlows = reforged ? mdxCache.reforgedTeamGlows : mdxCache.teamGlows; if (!teamColors.length) { for (let i = 0; i < 28; i++) { let id = ('' + i).padStart(2, '0'); teamColors[i] = <Texture>viewer.load(`ReplaceableTextures\\TeamColor\\TeamColor${id}${texturesExt}`, pathSolver, solverParams); teamGlows[i] = <Texture>viewer.load(`ReplaceableTextures\\TeamGlow\\TeamGlow${id}${texturesExt}`, pathSolver, solverParams); } } } // Geoset animations for (let geosetAnimation of parser.geosetAnimations) { this.geosetAnimations.push(new GeosetAnimation(this, geosetAnimation)); } this.pivotPoints = parser.pivotPoints; // Tracks the IDs of all generic objects. let objectId = 0; // Bones for (let bone of parser.bones) { this.bones.push(new Bone(this, bone, objectId++)); } // Lights for (let light of parser.lights) { this.lights.push(new Light(this, light, objectId++)); } // Helpers for (let helper of parser.helpers) { this.helpers.push(new Helper(this, helper, objectId++)); } // Attachments for (let attachment of parser.attachments) { this.attachments.push(new Attachment(this, attachment, objectId++)); } // Particle emitters for (let particleEmitter of parser.particleEmitters) { this.particleEmitters.push(new ParticleEmitterObject(this, particleEmitter, objectId++)); } // Particle emitters 2 for (let particleEmitter2 of parser.particleEmitters2) { this.particleEmitters2.push(new ParticleEmitter2Object(this, particleEmitter2, objectId++)); } // Ribbon emitters for (let ribbonEmitter of parser.ribbonEmitters) { this.ribbonEmitters.push(new RibbonEmitterObject(this, ribbonEmitter, objectId++)); } // Cameras for (let camera of parser.cameras) { this.cameras.push(new Camera(this, camera)); } // Event objects for (let eventObject of parser.eventObjects) { this.eventObjects.push(new EventObjectEmitterObject(this, eventObject, objectId++)); } // Collision shapes for (let collisionShape of parser.collisionShapes) { this.collisionShapes.push(new CollisionShape(this, collisionShape, objectId++)); } // One array for all generic objects. this.genericObjects.push(...this.bones, ...this.lights, ...this.helpers, ...this.attachments, ...this.particleEmitters, ...this.particleEmitters2, ...this.ribbonEmitters, ...this.eventObjects, ...this.collisionShapes); // Geosets setupGeosets(this, parser.geosets); // Render groups. setupGroups(this); // Creates the sorted indices array of the generic objects. this.setupHierarchy(-1); // Keep a sorted array. for (let i = 0, l = this.genericObjects.length; i < l; i++) { this.sortedGenericObjects[i] = this.genericObjects[this.hierarchy[i]]; } } setupHierarchy(parent: number) { for (let i = 0, l = this.genericObjects.length; i < l; i++) { let object = this.genericObjects[i]; if (object.parentId === parent) { this.hierarchy.push(i); this.setupHierarchy(object.objectId); } } } }