UNPKG

mdx-m3-viewer

Version:

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

346 lines (295 loc) 10.6 kB
import Extent from './extent'; /** * A geoset. */ export default class Geoset { /** * */ constructor() { /** @member {Float32Array} */ this.vertices = new Float32Array(0); /** @member {Float32Array} */ this.normals = new Float32Array(0); /** @member {Uint32Array} */ this.faceTypeGroups = new Uint32Array(0); /** @member {Uint32Array} */ this.faceGroups = new Uint32Array(0); /** @member {Uint16Array} */ this.faces = new Uint16Array(0); /** @member {Uint8Array} */ this.vertexGroups = new Uint8Array(0); /** @member {Uint32Array} */ this.matrixGroups = new Uint32Array(0); /** @member {Uint32Array} */ this.matrixIndices = new Uint32Array(0); /** @member {number} */ this.materialId = 0; /** @member {number} */ this.selectionGroup = 0; /** @member {number} */ this.selectionFlags = 0; /** @member {Extent} */ /** * @since 900 * @member {number} */ this.lod = 0; /** * @since 900 * @member {string} */ this.lodName = ''; this.extent = new Extent(); /** @member {Array<Extent>} */ this.sequenceExtents = []; /** * @since 900 * @member {Float32Array} */ this.tangents = new Float32Array(0); /** * @since 900 * @member {Float32Array} */ this.weights = new Uint8Array(0); /** @member {Array<Float32Array>} */ this.uvSets = []; } /** * @param {BinaryStream} stream * @param {number} version */ readMdx(stream, version) { stream.readUint32(); // Don't care about the size. stream.skip(4); // VRTX this.vertices = stream.readFloat32Array(stream.readUint32() * 3); stream.skip(4); // NRMS this.normals = stream.readFloat32Array(stream.readUint32() * 3); stream.skip(4); // PTYP this.faceTypeGroups = stream.readUint32Array(stream.readUint32()); stream.skip(4); // PCNT this.faceGroups = stream.readUint32Array(stream.readUint32()); stream.skip(4); // PVTX this.faces = stream.readUint16Array(stream.readUint32()); stream.skip(4); // GNDX this.vertexGroups = stream.readUint8Array(stream.readUint32()); stream.skip(4); // MTGC this.matrixGroups = stream.readUint32Array(stream.readUint32()); stream.skip(4); // MATS this.matrixIndices = stream.readUint32Array(stream.readUint32()); this.materialId = stream.readUint32(); this.selectionGroup = stream.readUint32(); this.selectionFlags = stream.readUint32(); if (version === 900) { this.lod = stream.readUint32(); this.lodName = stream.read(80); } this.extent.readMdx(stream); for (let i = 0, l = stream.readUint32(); i < l; i++) { let extent = new Extent(); extent.readMdx(stream); this.sequenceExtents.push(extent); } // Non-reforged models that come with reforged are saved with version 900, however they don't have TANG and SKIN. if (version === 900 && stream.peek(4) === 'TANG') { stream.skip(4); // TANG this.tangents = stream.readFloat32Array(stream.readUint32() * 4); stream.skip(4); // SKIN this.weights = stream.readUint8Array(stream.readUint32()); } stream.skip(4); // UVAS for (let i = 0, l = stream.readUint32(); i < l; i++) { stream.skip(4); // UVBS this.uvSets.push(stream.readFloat32Array(stream.readUint32() * 2)); } } /** * @param {BinaryStream} stream * @param {number} version */ writeMdx(stream, version) { stream.writeUint32(this.getByteLength()); stream.write('VRTX'); stream.writeUint32(this.vertices.length / 3); stream.writeFloat32Array(this.vertices); stream.write('NRMS'); stream.writeUint32(this.normals.length / 3); stream.writeFloat32Array(this.normals); stream.write('PTYP'); stream.writeUint32(this.faceTypeGroups.length); stream.writeUint32Array(this.faceTypeGroups); stream.write('PCNT'); stream.writeUint32(this.faceGroups.length); stream.writeUint32Array(this.faceGroups); stream.write('PVTX'); stream.writeUint32(this.faces.length); stream.writeUint16Array(this.faces); stream.write('GNDX'); stream.writeUint32(this.vertexGroups.length); stream.writeUint8Array(this.vertexGroups); stream.write('MTGC'); stream.writeUint32(this.matrixGroups.length); stream.writeUint32Array(this.matrixGroups); stream.write('MATS'); stream.writeUint32(this.matrixIndices.length); stream.writeUint32Array(this.matrixIndices); stream.writeUint32(this.materialId); stream.writeUint32(this.selectionGroup); stream.writeUint32(this.selectionFlags); if (version === 900) { stream.writeUint32(this.lod); stream.write(this.lodName); stream.skip(80 - this.lodName.length); } this.extent.writeMdx(stream); stream.writeUint32(this.sequenceExtents.length); for (let sequenceExtent of this.sequenceExtents) { sequenceExtent.writeMdx(stream); } if (version === 900 && this.tangents.length) { stream.write('TANG'); stream.writeFloat32Array(this.tangents); stream.write('SKIN'); stream.writeUint8Array(this.weights); } stream.write('UVAS'); stream.writeUint32(this.uvSets.length); for (let uvSet of this.uvSets) { stream.write('UVBS'); stream.writeUint32(uvSet.length / 2); stream.writeFloat32Array(uvSet); } } /** * @param {TokenStream} stream */ readMdl(stream) { for (let token of stream.readBlock()) { if (token === 'Vertices') { this.vertices = stream.readVectorArray(new Float32Array(stream.readInt() * 3), 3); } else if (token === 'Normals') { this.normals = stream.readVectorArray(new Float32Array(stream.readInt() * 3), 3); } else if (token === 'TVertices') { this.uvSets.push(stream.readVectorArray(new Float32Array(stream.readInt() * 2), 2)); } else if (token === 'VertexGroup') { // Vertex groups are stored in a block with no count, can't allocate the buffer yet. let vertexGroups = []; for (let vertexGroup of stream.readBlock()) { vertexGroups.push(parseInt(vertexGroup)); } this.vertexGroups = new Uint8Array(vertexGroups); } else if (token === 'Faces') { // For now hardcoded for triangles, until I see a model with something different. this.faceTypeGroups = new Uint32Array([4]); stream.readInt(); // number of groups, irrelevant for now let count = stream.readInt(); stream.read(); // { stream.read(); // Triangles stream.read(); // { this.faces = stream.readIntArray(new Uint16Array(count)); stream.read(); // } stream.read(); // } } else if (token === 'Groups') { let indices = []; let groups = []; stream.readInt(); // matrices count stream.readInt(); // total indices // eslint-disable-next-line no-unused-vars for (let matrix of stream.readBlock()) { let size = 0; for (let index of stream.readBlock()) { indices.push(parseInt(index)); size += 1; } groups.push(size); } this.matrixIndices = new Uint32Array(indices); this.matrixGroups = new Uint32Array(groups); } else if (token === 'MinimumExtent') { stream.readFloatArray(this.extent.min); } else if (token === 'MaximumExtent') { stream.readFloatArray(this.extent.max); } else if (token === 'BoundsRadius') { this.extent.boundsRadius = stream.readFloat(); } else if (token === 'Anim') { let extent = new Extent(); for (token of stream.readBlock()) { if (token === 'MinimumExtent') { stream.readFloatArray(extent.min); } else if (token === 'MaximumExtent') { stream.readFloatArray(extent.max); } else if (token === 'BoundsRadius') { extent.boundsRadius = stream.readFloat(); } } this.sequenceExtents.push(extent); } else if (token === 'MaterialID') { this.materialId = stream.readInt(); } else if (token === 'SelectionGroup') { this.selectionGroup = stream.readInt(); } else if (token === 'Unselectable') { this.selectionFlags = 4; } else { throw new Error(`Unknown token in Geoset: "${token}"`); } } } /** * @param {TokenStream} stream */ writeMdl(stream) { stream.startBlock('Geoset'); stream.writeVectorArray('Vertices', this.vertices, 3); stream.writeVectorArray('Normals', this.normals, 3); for (let uvSet of this.uvSets) { stream.writeVectorArray('TVertices', uvSet, 2); } stream.startBlock('VertexGroup'); for (let i = 0, l = this.vertexGroups.length; i < l; i++) { stream.writeLine(`${this.vertexGroups[i]},`); } stream.endBlock(); // For now hardcoded for triangles, until I see a model with something different. stream.startBlock('Faces', 1, this.faces.length); stream.startBlock('Triangles'); stream.writeLine(`{ ${this.faces.join(', ')} },`); stream.endBlock(); stream.endBlock(); stream.startBlock('Groups', this.matrixGroups.length, this.matrixIndices.length); let index = 0; for (let groupSize of this.matrixGroups) { stream.writeArrayAttrib('Matrices', this.matrixIndices.subarray(index, index + groupSize)); index += groupSize; } stream.endBlock(); this.extent.writeMdl(stream); for (let sequenceExtent of this.sequenceExtents) { stream.startBlock('Anim'); sequenceExtent.writeMdl(stream); stream.endBlock(); } stream.writeAttrib('MaterialID', this.materialId); stream.writeAttrib('SelectionGroup', this.selectionGroup); if (this.selectionFlags === 4) { stream.writeFlag('Unselectable'); } stream.endBlock(); } /** * @param {number} version * @return {number} */ getByteLength(version) { let size = 92 + this.vertices.byteLength + this.normals.byteLength + this.faceTypeGroups.byteLength + this.faceGroups.byteLength + this.faces.byteLength + this.vertexGroups.byteLength + this.matrixGroups.byteLength + this.matrixIndices.byteLength; if (version === 900) { size += 132 + this.tangents.byteLength + this.weights.byteLength; } else { size += 28 + this.sequenceExtents.length * 28; } for (let uvSet of this.uvSets) { size += 8 + uvSet.byteLength; } return size; } }