mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
359 lines • 18 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.renderEmitter = exports.SETTING_PARTICLES_HIGH = exports.EMITTER_EVENT_TEXTURE_OFFSET = exports.EMITTER_PARTICLE2_TEXTURE_OFFSET = exports.EMITTER_UBERSPLAT = exports.EMITTER_SPLAT = exports.EMITTER_RIBBON = exports.EMITTER_PARTICLE2 = exports.BYTE_OFFSET_TEAM_COLOR = exports.FLOAT_OFFSET_HEALTH = exports.FLOAT_OFFSET_P3 = exports.FLOAT_OFFSET_P2 = exports.FLOAT_OFFSET_P1 = exports.FLOAT_OFFSET_P0 = exports.BYTE_OFFSET_LEFT_RIGHT_TOP = exports.BYTE_OFFSET_TAIL = exports.BYTE_OFFSET_COLOR = exports.BYTE_OFFSET_HEALTH = exports.BYTE_OFFSET_P3 = exports.BYTE_OFFSET_P2 = exports.BYTE_OFFSET_P1 = exports.BYTE_OFFSET_P0 = exports.FLOATS_PER_OBJECT = exports.BYTES_PER_OBJECT = void 0;
const gl_matrix_1 = require("gl-matrix");
const locationHeap = gl_matrix_1.vec3.create();
const startHeap = gl_matrix_1.vec3.create();
const endHeap = gl_matrix_1.vec3.create();
// The total storage that emitted objects can use.
// This is enough to support all of the MDX geometry emitters.
// The memory layout is the same as this C struct:
//
// struct {
// float p0[3]
// float p1[3]
// float p2[3]
// float p3[3]
// float health
// byte color[4]
// byte tail
// byte leftRightTop[3]
// }
//
exports.BYTES_PER_OBJECT = 60;
exports.FLOATS_PER_OBJECT = exports.BYTES_PER_OBJECT >> 2;
// Offsets into the emitted object structure.
exports.BYTE_OFFSET_P0 = 0;
exports.BYTE_OFFSET_P1 = 12;
exports.BYTE_OFFSET_P2 = 24;
exports.BYTE_OFFSET_P3 = 36;
exports.BYTE_OFFSET_HEALTH = 48;
exports.BYTE_OFFSET_COLOR = 52;
exports.BYTE_OFFSET_TAIL = 56;
exports.BYTE_OFFSET_LEFT_RIGHT_TOP = 57;
// Offset aliases.
exports.FLOAT_OFFSET_P0 = exports.BYTE_OFFSET_P0 >> 2;
exports.FLOAT_OFFSET_P1 = exports.BYTE_OFFSET_P1 >> 2;
exports.FLOAT_OFFSET_P2 = exports.BYTE_OFFSET_P2 >> 2;
exports.FLOAT_OFFSET_P3 = exports.BYTE_OFFSET_P3 >> 2;
exports.FLOAT_OFFSET_HEALTH = exports.BYTE_OFFSET_HEALTH >> 2;
exports.BYTE_OFFSET_TEAM_COLOR = exports.BYTE_OFFSET_LEFT_RIGHT_TOP;
// Emitter types
exports.EMITTER_PARTICLE2 = 0;
exports.EMITTER_RIBBON = 1;
exports.EMITTER_SPLAT = 2;
exports.EMITTER_UBERSPLAT = 3;
// Offsets for overriding emitter textures using setResource().
exports.EMITTER_PARTICLE2_TEXTURE_OFFSET = 1000;
exports.EMITTER_EVENT_TEXTURE_OFFSET = 10000;
// The game scales the emission rate of particle emitters depending on the particles setting.
// High seems to double the emission.
exports.SETTING_PARTICLES_HIGH = 2;
function bindParticleEmitter2Buffer(emitter, buffer) {
const instance = emitter.instance;
const objects = emitter.objects;
const byteView = buffer.byteView;
const floatView = buffer.floatView;
const emitterObject = emitter.emitterObject;
const modelSpace = emitterObject.modelSpace;
const tailLength = emitterObject.tailLength;
const node = emitter.node;
const teamColor = instance.teamColor;
let offset = 0;
for (const object of objects) {
const byteOffset = offset * exports.BYTES_PER_OBJECT;
const floatOffset = offset * exports.FLOATS_PER_OBJECT;
const p0Offset = floatOffset + exports.FLOAT_OFFSET_P0;
let location = object.location;
const scale = object.scale;
const tail = object.tail;
if (tail === 0 /* Head */) {
// If this is a model space emitter, the location is in local space, so convert it to world space.
if (modelSpace) {
location = gl_matrix_1.vec3.transformMat4(locationHeap, location, node.worldMatrix);
}
floatView[p0Offset + 0] = location[0];
floatView[p0Offset + 1] = location[1];
floatView[p0Offset + 2] = location[2];
// Used to rotate XY particles to face their velocity on the XY plane.
floatView[p0Offset + 3] = object.facing;
}
else {
const velocity = object.velocity;
let start = startHeap;
let end = location;
start[0] = end[0] - tailLength * velocity[0];
start[1] = end[1] - tailLength * velocity[1];
start[2] = end[2] - tailLength * velocity[2];
// If this is a model space emitter, the start and end are in local space, so convert them to world space.
if (modelSpace) {
start = gl_matrix_1.vec3.transformMat4(start, start, node.worldMatrix);
end = gl_matrix_1.vec3.transformMat4(endHeap, end, node.worldMatrix);
}
floatView[p0Offset + 0] = start[0];
floatView[p0Offset + 1] = start[1];
floatView[p0Offset + 2] = start[2];
floatView[p0Offset + 3] = end[0];
floatView[p0Offset + 4] = end[1];
floatView[p0Offset + 5] = end[2];
}
floatView[p0Offset + 6] = scale[0];
floatView[p0Offset + 7] = scale[0];
floatView[p0Offset + 8] = scale[0];
floatView[floatOffset + exports.FLOAT_OFFSET_HEALTH] = object.health;
byteView[byteOffset + exports.BYTE_OFFSET_TAIL] = tail;
byteView[byteOffset + exports.BYTE_OFFSET_TEAM_COLOR] = teamColor;
offset += 1;
}
}
function bindParticleEmitter2Shader(emitter, shader) {
const instance = emitter.instance;
const textureOverrides = instance.textureOverrides;
const scene = instance.scene;
const camera = scene.camera;
const emitterObject = emitter.emitterObject;
const model = emitterObject.model;
const viewer = model.viewer;
const gl = viewer.gl;
const mdxCache = viewer.sharedCache.get('mdx');
const uniforms = shader.uniforms;
const colors = emitterObject.colors;
const intervals = emitterObject.intervals;
const replaceable = emitterObject.replaceableId;
let vectors;
let mdxTexture = emitterObject.internalTexture;
gl.blendFunc(emitterObject.blendSrc, emitterObject.blendDst);
gl.uniform1f(uniforms['u_filterMode'], emitterObject.filterMode);
// Determine where this texture is coming from.
// This is to get the texture wrap modes.
// The texture is either a replaceable in which case it's stored internally, a team color in which case it's stored in the handler, or a reference to one of the model textures.
if (emitterObject.internalTexture) {
mdxTexture = emitterObject.internalTexture;
}
else if (replaceable === 1) {
mdxTexture = mdxCache.teamColors[instance.teamColor];
}
else if (replaceable === 2) {
mdxTexture = mdxCache.teamGlows[instance.teamColor];
}
else {
mdxTexture = model.textures[emitterObject.textureId];
}
// Now get the actual texture itself.
// First check if there is an override for this particle emitter.
let texture = textureOverrides.get(exports.EMITTER_PARTICLE2_TEXTURE_OFFSET + emitterObject.index);
if (!texture) {
// Next check if there is an override for model textures if this is one.
if (replaceable === 0) {
texture = textureOverrides.get(emitterObject.textureId);
}
// If there is still no override, get it from the existing texture object.
if (!texture) {
texture = mdxTexture.texture;
}
}
viewer.webgl.bindTextureAndWrap(texture, 0, mdxTexture.wrapS, mdxTexture.wrapT);
// Choose between a default rectangle or a billboarded one
if (emitterObject.xYQuad) {
vectors = camera.vectors;
}
else {
vectors = camera.billboardedVectors;
}
gl.uniform1f(uniforms['u_lifeSpan'], emitterObject.lifeSpan);
gl.uniform1f(uniforms['u_timeMiddle'], emitterObject.timeMiddle);
gl.uniform1f(uniforms['u_columns'], emitterObject.columns);
gl.uniform1f(uniforms['u_rows'], emitterObject.rows);
gl.uniform1f(uniforms['u_teamColored'], emitterObject.teamColored);
gl.uniform3fv(uniforms['u_intervals[0]'], intervals[0]);
gl.uniform3fv(uniforms['u_intervals[1]'], intervals[1]);
gl.uniform3fv(uniforms['u_intervals[2]'], intervals[2]);
gl.uniform3fv(uniforms['u_intervals[3]'], intervals[3]);
gl.uniform4fv(uniforms['u_colors[0]'], colors[0]);
gl.uniform4fv(uniforms['u_colors[1]'], colors[1]);
gl.uniform4fv(uniforms['u_colors[2]'], colors[2]);
gl.uniform3fv(uniforms['u_scaling'], emitterObject.scaling);
if (emitterObject.head) {
gl.uniform3fv(uniforms['u_vertices[0]'], vectors[0]);
gl.uniform3fv(uniforms['u_vertices[1]'], vectors[1]);
gl.uniform3fv(uniforms['u_vertices[2]'], vectors[2]);
gl.uniform3fv(uniforms['u_vertices[3]'], vectors[3]);
}
if (emitterObject.tail) {
gl.uniform3fv(uniforms['u_cameraZ'], camera.directionZ);
}
}
function bindRibbonEmitterBuffer(emitter, buffer) {
let object = emitter.first;
const byteView = buffer.byteView;
const floatView = buffer.floatView;
const emitterObject = emitter.emitterObject;
const columns = emitterObject.columns;
const alive = emitter.alive;
const chainLengthFactor = 1 / (alive - 1);
let offset = 0;
while (object.next) {
const next = object.next.vertices;
const byteOffset = offset * exports.BYTES_PER_OBJECT;
const floatOffset = offset * exports.FLOATS_PER_OBJECT;
const p0Offset = floatOffset + exports.FLOAT_OFFSET_P0;
const colorOffset = byteOffset + exports.BYTE_OFFSET_COLOR;
const leftRightTopOffset = byteOffset + exports.BYTE_OFFSET_LEFT_RIGHT_TOP;
const left = ((object.slot % columns) + (1 - (offset * chainLengthFactor) - chainLengthFactor)) / columns;
const top = object.slot / columns;
const right = left + chainLengthFactor;
const vertices = object.vertices;
const color = object.color;
floatView[p0Offset + 0] = vertices[0];
floatView[p0Offset + 1] = vertices[1];
floatView[p0Offset + 2] = vertices[2];
floatView[p0Offset + 3] = vertices[3];
floatView[p0Offset + 4] = vertices[4];
floatView[p0Offset + 5] = vertices[5];
floatView[p0Offset + 6] = next[3];
floatView[p0Offset + 7] = next[4];
floatView[p0Offset + 8] = next[5];
floatView[p0Offset + 9] = next[0];
floatView[p0Offset + 10] = next[1];
floatView[p0Offset + 11] = next[2];
byteView[colorOffset + 0] = color[0];
byteView[colorOffset + 1] = color[1];
byteView[colorOffset + 2] = color[2];
byteView[colorOffset + 3] = color[3];
byteView[leftRightTopOffset + 0] = left * 255;
byteView[leftRightTopOffset + 1] = right * 255;
byteView[leftRightTopOffset + 2] = top * 255;
object = object.next;
offset += 1;
}
}
function bindRibbonEmitterShader(emitter, shader) {
const textureOverrides = emitter.instance.textureOverrides;
const emitterObject = emitter.emitterObject;
const layer = emitterObject.layer;
const model = emitterObject.model;
const gl = model.viewer.gl;
const uniforms = shader.uniforms;
const texture = model.textures[layer.textureId];
const actualTexture = textureOverrides.get(layer.textureId) || texture.texture;
layer.bind(shader);
gl.uniform1f(uniforms['u_filterMode'], layer.filterMode);
model.viewer.webgl.bindTextureAndWrap(actualTexture, 0, texture.wrapS, texture.wrapT);
gl.uniform1f(uniforms['u_columns'], emitterObject.columns);
gl.uniform1f(uniforms['u_rows'], emitterObject.rows);
}
function bindEventObjectEmitterBuffer(emitter, buffer) {
const objects = emitter.objects;
const floatView = buffer.floatView;
let offset = 0;
for (const object of objects) {
const floatOffset = offset * exports.FLOATS_PER_OBJECT;
const p0Offset = floatOffset + exports.FLOAT_OFFSET_P0;
const vertices = object.vertices;
floatView[p0Offset + 0] = vertices[0];
floatView[p0Offset + 1] = vertices[1];
floatView[p0Offset + 2] = vertices[2];
floatView[p0Offset + 3] = vertices[3];
floatView[p0Offset + 4] = vertices[4];
floatView[p0Offset + 5] = vertices[5];
floatView[p0Offset + 6] = vertices[6];
floatView[p0Offset + 7] = vertices[7];
floatView[p0Offset + 8] = vertices[8];
floatView[p0Offset + 9] = vertices[9];
floatView[p0Offset + 10] = vertices[10];
floatView[p0Offset + 11] = vertices[11];
floatView[floatOffset + exports.FLOAT_OFFSET_HEALTH] = object.health;
offset += 1;
}
}
function bindEventObjectSplEmitterShader(emitter, shader) {
const textureOverrides = emitter.instance.textureOverrides;
const emitterObject = emitter.emitterObject;
const intervalTimes = emitterObject.intervalTimes;
const intervals = emitterObject.intervals;
const colors = emitterObject.colors;
const model = emitterObject.model;
const gl = model.viewer.gl;
const uniforms = shader.uniforms;
const texture = emitterObject.internalTexture;
const actualTexture = textureOverrides.get(exports.EMITTER_EVENT_TEXTURE_OFFSET + emitterObject.index) || texture.texture;
gl.blendFunc(emitterObject.blendSrc, emitterObject.blendDst);
model.viewer.webgl.bindTextureAndWrap(actualTexture, 0, texture.wrapS, texture.wrapT);
gl.uniform1f(uniforms['u_lifeSpan'], emitterObject.lifeSpan);
gl.uniform1f(uniforms['u_columns'], emitterObject.columns);
gl.uniform1f(uniforms['u_rows'], emitterObject.rows);
// 3 because the uniform is shared with UBR, which has 3 values.
gl.uniform3f(uniforms['u_intervalTimes'], intervalTimes[0], intervalTimes[1], 0);
gl.uniform3fv(uniforms['u_intervals[0]'], intervals[0]);
gl.uniform3fv(uniforms['u_intervals[1]'], intervals[1]);
gl.uniform4fv(uniforms['u_colors[0]'], colors[0]);
gl.uniform4fv(uniforms['u_colors[1]'], colors[1]);
gl.uniform4fv(uniforms['u_colors[2]'], colors[2]);
}
function bindEventObjectUbrEmitterShader(emitter, shader) {
const textureOverrides = emitter.instance.textureOverrides;
const emitterObject = emitter.emitterObject;
const intervalTimes = emitterObject.intervalTimes;
const colors = emitterObject.colors;
const model = emitterObject.model;
const viewer = model.viewer;
const gl = viewer.gl;
const uniforms = shader.uniforms;
const texture = emitterObject.internalTexture;
const actualTexture = textureOverrides.get(exports.EMITTER_EVENT_TEXTURE_OFFSET + emitterObject.index) || texture.texture;
gl.blendFunc(emitterObject.blendSrc, emitterObject.blendDst);
model.viewer.webgl.bindTextureAndWrap(actualTexture, 0, texture.wrapS, texture.wrapT);
gl.uniform1f(uniforms['u_lifeSpan'], emitterObject.lifeSpan);
gl.uniform1f(uniforms['u_columns'], emitterObject.columns);
gl.uniform1f(uniforms['u_rows'], emitterObject.rows);
gl.uniform3fv(uniforms['u_intervalTimes'], intervalTimes);
gl.uniform4fv(uniforms['u_colors[0]'], colors[0]);
gl.uniform4fv(uniforms['u_colors[1]'], colors[1]);
gl.uniform4fv(uniforms['u_colors[2]'], colors[2]);
}
function renderEmitter(emitter, shader) {
let alive = emitter.alive;
const emitterObject = emitter.emitterObject;
const emitterType = emitterObject.geometryEmitterType;
if (emitterType === exports.EMITTER_RIBBON) {
alive -= 1;
}
if (alive > 0) {
const viewer = emitter.instance.model.viewer;
const buffer = viewer.buffer;
const gl = viewer.gl;
const instancedArrays = viewer.webgl.extensions['ANGLE_instanced_arrays'];
const size = alive * exports.BYTES_PER_OBJECT;
const attribs = shader.attribs;
buffer.reserve(size);
if (emitterType === exports.EMITTER_PARTICLE2) {
bindParticleEmitter2Buffer(emitter, buffer);
bindParticleEmitter2Shader(emitter, shader);
}
else if (emitterType === exports.EMITTER_RIBBON) {
bindRibbonEmitterBuffer(emitter, buffer);
bindRibbonEmitterShader(emitter, shader);
}
else if (emitterType === exports.EMITTER_SPLAT) {
bindEventObjectEmitterBuffer(emitter, buffer);
bindEventObjectSplEmitterShader(emitter, shader);
}
else {
bindEventObjectEmitterBuffer(emitter, buffer);
bindEventObjectUbrEmitterShader(emitter, shader);
}
buffer.bindAndUpdate(size);
gl.uniform1i(shader.uniforms['u_emitter'], emitterType);
gl.vertexAttribPointer(attribs['a_p0'], 3, gl.FLOAT, false, exports.BYTES_PER_OBJECT, exports.BYTE_OFFSET_P0);
gl.vertexAttribPointer(attribs['a_p1'], 3, gl.FLOAT, false, exports.BYTES_PER_OBJECT, exports.BYTE_OFFSET_P1);
gl.vertexAttribPointer(attribs['a_p2'], 3, gl.FLOAT, false, exports.BYTES_PER_OBJECT, exports.BYTE_OFFSET_P2);
gl.vertexAttribPointer(attribs['a_p3'], 3, gl.FLOAT, false, exports.BYTES_PER_OBJECT, exports.BYTE_OFFSET_P3);
gl.vertexAttribPointer(attribs['a_health'], 1, gl.FLOAT, false, exports.BYTES_PER_OBJECT, exports.BYTE_OFFSET_HEALTH);
gl.vertexAttribPointer(attribs['a_color'], 4, gl.UNSIGNED_BYTE, true, exports.BYTES_PER_OBJECT, exports.BYTE_OFFSET_COLOR);
gl.vertexAttribPointer(attribs['a_tail'], 1, gl.UNSIGNED_BYTE, false, exports.BYTES_PER_OBJECT, exports.BYTE_OFFSET_TAIL);
gl.vertexAttribPointer(attribs['a_leftRightTop'], 3, gl.UNSIGNED_BYTE, false, exports.BYTES_PER_OBJECT, exports.BYTE_OFFSET_LEFT_RIGHT_TOP);
instancedArrays.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, alive);
}
}
exports.renderEmitter = renderEmitter;
//# sourceMappingURL=geometryemitterfuncs.js.map