UNPKG

mdx-m3-viewer

Version:

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

196 lines (167 loc) 5.83 kB
import {vec3} from 'gl-matrix'; import {VEC3_ONE} from '../../../common/gl-matrix-addon'; /** * A shallow geoset. */ export class ShallowGeoset { /** * @param {MdxModel} model * @param {Array<number>} offsets * @param {number} uvSetSize * @param {Uint16Array} elements */ constructor(model, offsets, uvSetSize, elements) { this.model = model; this.offsets = offsets; this.uvSetSize = uvSetSize; this.elements = elements; } /** * @param {ShaderProgram} shader * @param {number} coordId */ bind(shader, coordId) { let gl = this.model.viewer.gl; let offsets = this.offsets; let attribs = shader.attribs; gl.vertexAttribPointer(attribs.a_position, 3, gl.FLOAT, false, 12, offsets[0]); gl.vertexAttribPointer(attribs.a_normal, 3, gl.FLOAT, false, 12, offsets[1]); gl.vertexAttribPointer(attribs.a_uv, 2, gl.FLOAT, false, 8, offsets[2] + coordId * this.uvSetSize); gl.vertexAttribPointer(attribs.a_bones, 4, gl.UNSIGNED_BYTE, false, 4, offsets[3]); gl.vertexAttribPointer(attribs.a_boneNumber, 1, gl.UNSIGNED_BYTE, false, 4, offsets[4]); } /** * @param {number} instances */ render(instances) { let gl = this.model.viewer.gl; gl.extensions.instancedArrays.drawElementsInstancedANGLE(gl.TRIANGLES, this.elements, gl.UNSIGNED_SHORT, this.offsets[5], instances); } } /** * A geoset. */ export class Geoset { /** * @param {MdxModel} model * @param {MdxParserGeoset} geoset * @param {number} index */ constructor(model, geoset, index) { let positions = geoset.vertices; let normals = geoset.normals; let textureCoordinateSets = geoset.uvSets; let uvsetSize = textureCoordinateSets[0].length; let vertices = positions.length / 3; let uvs; let boneIndices = new Uint8Array(vertices * 4); // A note for the future, since I keep coming back to this. // The reason bone numbers are stored as 32 bits instead of 8 are because of offset rules. // WebGL complains if the bind() method of ShallowGeoset above uses offset 1 instead of 4, even if the shader gets one float. // Why? heck if I know. // I don't see the alignment issues, but WebGL does. let boneNumbers = new Uint32Array(vertices); let vertexGroups = geoset.vertexGroups; let matrixGroups = geoset.matrixGroups; let matrixIndices = geoset.matrixIndices; let slices = []; // Make one typed array for the texture coordinates, in case there are multiple ones if (textureCoordinateSets.length > 1) { uvs = new Float32Array(textureCoordinateSets.length * uvsetSize); for (let i = 0, l = textureCoordinateSets.length; i < l; i++) { uvs.set(textureCoordinateSets[i], i * uvsetSize); } } else { uvs = textureCoordinateSets[0]; } // Parse the bone indices by slicing the matrix groups for (let i = 0, l = matrixGroups.length, k = 0; i < l; i++) { slices.push(matrixIndices.subarray(k, k + matrixGroups[i])); k += matrixGroups[i]; } // Construct the final bone arrays for (let i = 0; i < vertices; i++) { let slice = slices[vertexGroups[i]]; // Somehow in some bad models a vertex group index refers to an invalid matrix group. // Such models are still loaded by the game. if (slice) { let bones = slices[vertexGroups[i]]; let boneCount = Math.min(bones.length, 4); // The viewer supports up to 4 bones per vertex, the game handles any(?) amount. for (let j = 0; j < boneCount; j++) { // 1 is added to every index for shader optimization (index 0 is a zero matrix) boneIndices[i * 4 + j] = bones[j] + 1; } boneNumbers[i] = boneCount; } } this.index = index; this.materialId = geoset.materialId; this.locationArray = positions; this.normalArray = normals; this.uvsArray = uvs; this.boneIndexArray = boneIndices; this.boneNumberArray = boneNumbers; this.faceArray = geoset.faces; this.uvSetSize = uvsetSize * 4; let geosetAnimations = model.geosetAnimations; for (let i = 0, l = geosetAnimations.length; i < l; i++) { if (geosetAnimations[i].geosetId === index) { this.geosetAnimation = geosetAnimations[i]; } } let variants = { alpha: [], color: [], }; let hasAnim = false; for (let i = 0, l = model.sequences.length; i < l; i++) { let alpha = this.isAlphaVariant(i); let color = this.isColorVariant(i); if (alpha || color) { hasAnim = true; } variants.alpha[i] = alpha; variants.color[i] = color; } this.variants = variants; this.hasAnim = hasAnim; } /** * @param {Float32Array} out * @param {ModelInstance} instance * @return {number} */ getAlpha(out, instance) { if (this.geosetAnimation) { return this.geosetAnimation.getAlpha(out, instance); } out[0] = 1; return -1; } /** * @param {vec3} out * @param {ModelInstance} instance * @return {number} */ getColor(out, instance) { if (this.geosetAnimation) { return this.geosetAnimation.getColor(out, instance); } vec3.copy(out, VEC3_ONE); return -1; } /** * @param {number} sequence * @return {boolean} */ isAlphaVariant(sequence) { return this.geosetAnimation && this.geosetAnimation.isAlphaVariant(sequence); } /** * @param {number} sequence * @return {boolean} */ isColorVariant(sequence) { return this.geosetAnimation && this.geosetAnimation.isColorVariant(sequence); } }