UNPKG

mdx-m3-viewer

Version:

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

400 lines (332 loc) 15.6 kB
import { vec3 } from 'gl-matrix'; import Scene from '../../scene'; import ShaderProgram from '../../gl/program'; import ClientBuffer from '../../gl/clientbuffer'; import ParticleEmitter2Object from './particleemitter2object'; import RibbonEmitterObject from './ribbonemitterobject'; import EventObjectEmitterObject from './eventobjectemitterobject'; import MdxModelInstance from './modelinstance'; import ParticleEmitter2 from './particleemitter2'; import RibbonEmitter from './ribbonemitter'; import EventObjectSplEmitter from './eventobjectsplemitter'; import EventObjectUbrEmitter from './eventobjectubremitter'; import Particle2 from './particle2'; import Ribbon from './ribbon'; import EventObjectSplUbr from './eventobjectsplubr'; const locationHeap = vec3.create(); const startHeap = vec3.create(); const endHeap = 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] // } // export const BYTES_PER_OBJECT = 60; export const FLOATS_PER_OBJECT = BYTES_PER_OBJECT >> 2; // Offsets into the emitted object structure. export const BYTE_OFFSET_P0 = 0; export const BYTE_OFFSET_P1 = 12; export const BYTE_OFFSET_P2 = 24; export const BYTE_OFFSET_P3 = 36; export const BYTE_OFFSET_HEALTH = 48; export const BYTE_OFFSET_COLOR = 52; export const BYTE_OFFSET_TAIL = 56; export const BYTE_OFFSET_LEFT_RIGHT_TOP = 57; // Offset aliases. export const FLOAT_OFFSET_P0 = BYTE_OFFSET_P0 >> 2; export const FLOAT_OFFSET_P1 = BYTE_OFFSET_P1 >> 2; export const FLOAT_OFFSET_P2 = BYTE_OFFSET_P2 >> 2; export const FLOAT_OFFSET_P3 = BYTE_OFFSET_P3 >> 2; export const FLOAT_OFFSET_HEALTH = BYTE_OFFSET_HEALTH >> 2; export const BYTE_OFFSET_TEAM_COLOR = BYTE_OFFSET_LEFT_RIGHT_TOP; // Head or tail. export const HEAD = 0; export const TAIL = 1; // Emitter types export const EMITTER_PARTICLE2 = 0; export const EMITTER_RIBBON = 1; export const EMITTER_SPLAT = 2; export const EMITTER_UBERSPLAT = 3; function bindParticleEmitter2Buffer(emitter: ParticleEmitter2, buffer: ClientBuffer) { let instance = <MdxModelInstance>emitter.instance; let objects = <Particle2[]>emitter.objects; let byteView = <Uint8Array>buffer.byteView; let floatView = <Float32Array>buffer.floatView; let emitterObject = <ParticleEmitter2Object>emitter.emitterObject; let modelSpace = emitterObject.modelSpace; let tailLength = emitterObject.tailLength; let teamColor = instance.teamColor; let offset = 0; for (let object of objects) { let byteOffset = offset * BYTES_PER_OBJECT; let floatOffset = offset * FLOATS_PER_OBJECT; let p0Offset = floatOffset + FLOAT_OFFSET_P0; let location = object.location; let scale = object.scale; let tail = object.tail; if (tail === HEAD) { // If this is a model space emitter, the location is in local space, so convert it to world space. if (modelSpace) { location = vec3.transformMat4(locationHeap, location, emitter.node.worldMatrix); } floatView[p0Offset + 0] = location[0]; floatView[p0Offset + 1] = location[1]; floatView[p0Offset + 2] = location[2]; } else { let 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 = vec3.transformMat4(start, start, emitter.node.worldMatrix); end = vec3.transformMat4(endHeap, end, emitter.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 + FLOAT_OFFSET_HEALTH] = object.health; byteView[byteOffset + BYTE_OFFSET_TAIL] = tail; byteView[byteOffset + BYTE_OFFSET_TEAM_COLOR] = teamColor; offset += 1; } } function bindParticleEmitter2Shader(emitter: ParticleEmitter2, shader: ShaderProgram) { let instance = <MdxModelInstance>emitter.instance; let scene = <Scene>instance.scene; let camera = scene.camera; let emitterObject = <ParticleEmitter2Object>emitter.emitterObject; let model = emitterObject.model; let viewer = model.viewer; let gl = viewer.gl; let mdxCache = viewer.sharedCache.get('mdx'); let uniforms = shader.uniforms; let colors = emitterObject.colors; let intervals = emitterObject.intervals; let replaceable = emitterObject.replaceableId; let vectors; let texture; gl.blendFunc(emitterObject.blendSrc, emitterObject.blendDst); if (replaceable === 1) { let teamColors = model.reforged ? mdxCache.reforgedTeamColors : mdxCache.teamColors; texture = teamColors[instance.teamColor]; } else if (replaceable === 2) { let teamGlows = model.reforged ? mdxCache.reforgedTeamGlows : mdxCache.teamGlows; texture = teamGlows[instance.teamColor]; } else { texture = emitterObject.internalTexture; } viewer.webgl.bindTexture(texture, 0); // 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: RibbonEmitter, buffer: ClientBuffer) { let object = <Ribbon>emitter.first; let byteView = <Uint8Array>buffer.byteView; let floatView = <Float32Array>buffer.floatView; let emitterObject = <RibbonEmitterObject>emitter.emitterObject; let columns = emitterObject.columns; let alive = emitter.alive; let chainLengthFactor = 1 / (alive - 1); let offset = 0; while (object.next) { let next = object.next.vertices; let byteOffset = offset * BYTES_PER_OBJECT; let floatOffset = offset * FLOATS_PER_OBJECT; let p0Offset = floatOffset + FLOAT_OFFSET_P0; let colorOffset = byteOffset + BYTE_OFFSET_COLOR; let leftRightTopOffset = byteOffset + BYTE_OFFSET_LEFT_RIGHT_TOP; let left = ((object.slot % columns) + (1 - (offset * chainLengthFactor) - chainLengthFactor)) / columns; let top = object.slot / columns; let right = left + chainLengthFactor; let vertices = object.vertices; let 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: RibbonEmitter, shader: ShaderProgram) { let textureMapper = emitter.instance.textureMapper; let emitterObject = <RibbonEmitterObject>emitter.emitterObject; let layer = emitterObject.layer; let model = emitterObject.model; let gl = model.viewer.gl; let uniforms = shader.uniforms; let texture = model.textures[layer.textureId]; layer.bind(shader); model.viewer.webgl.bindTexture(textureMapper.get(texture) || texture, 0); gl.uniform1f(uniforms.u_columns, emitterObject.columns); gl.uniform1f(uniforms.u_rows, emitterObject.rows); } function bindEventObjectEmitterBuffer(emitter: EventObjectSplEmitter | EventObjectUbrEmitter, buffer: ClientBuffer) { let objects = <EventObjectSplUbr[]>emitter.objects; let floatView = <Float32Array>buffer.floatView; let offset = 0; for (let object of objects) { let floatOffset = offset * FLOATS_PER_OBJECT; let p0Offset = floatOffset + FLOAT_OFFSET_P0; let 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 + FLOAT_OFFSET_HEALTH] = object.health; offset += 1; } } function bindEventObjectSplEmitterShader(emitter: EventObjectSplEmitter, shader: ShaderProgram) { let textureMapper = emitter.instance.textureMapper; let emitterObject = <EventObjectEmitterObject>emitter.emitterObject; let intervalTimes = <Float32Array>emitterObject.intervalTimes; let intervals = <Float32Array[]>emitterObject.intervals; let colors = <Float32Array[]>emitterObject.colors; let model = emitterObject.model; let gl = model.viewer.gl; let uniforms = shader.uniforms; let texture = emitterObject.internalTexture; gl.blendFunc(emitterObject.blendSrc, emitterObject.blendDst); model.viewer.webgl.bindTexture(textureMapper.get(texture) || texture, 0); 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: EventObjectUbrEmitter, shader: ShaderProgram) { let textureMapper = emitter.instance.textureMapper; let emitterObject = <EventObjectEmitterObject>emitter.emitterObject; let intervalTimes = <Float32Array>emitterObject.intervalTimes; let colors = <Float32Array[]>emitterObject.colors; let model = emitterObject.model; let viewer = model.viewer; let gl = viewer.gl; let uniforms = shader.uniforms; let texture = emitterObject.internalTexture; gl.blendFunc(emitterObject.blendSrc, emitterObject.blendDst); viewer.webgl.bindTexture(textureMapper.get(texture) || texture, 0); 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]); } export function renderEmitter(emitter: ParticleEmitter2 | RibbonEmitter | EventObjectSplEmitter | EventObjectUbrEmitter, shader: ShaderProgram) { let alive = emitter.alive; let emitterObject = <ParticleEmitter2Object | RibbonEmitterObject | EventObjectEmitterObject>emitter.emitterObject; let emitterType = emitterObject.geometryEmitterType; if (emitterType === EMITTER_RIBBON) { alive -= 1; } if (alive > 0) { let viewer = emitter.instance.model.viewer; let buffer = viewer.buffer; let gl = viewer.gl; let instancedArrays = <ANGLE_instanced_arrays>viewer.webgl.extensions.ANGLE_instanced_arrays let size = alive * BYTES_PER_OBJECT; let attribs = shader.attribs; buffer.reserve(size); if (emitterType === EMITTER_PARTICLE2) { bindParticleEmitter2Buffer(<ParticleEmitter2>emitter, buffer); bindParticleEmitter2Shader(<ParticleEmitter2>emitter, shader); } else if (emitterType === EMITTER_RIBBON) { bindRibbonEmitterBuffer(<RibbonEmitter>emitter, buffer); bindRibbonEmitterShader(<RibbonEmitter>emitter, shader); } else if (emitterType === EMITTER_SPLAT) { bindEventObjectEmitterBuffer(<EventObjectSplEmitter>emitter, buffer); bindEventObjectSplEmitterShader(<EventObjectSplEmitter>emitter, shader); } else { bindEventObjectEmitterBuffer(<EventObjectUbrEmitter>emitter, buffer); bindEventObjectUbrEmitterShader(<EventObjectUbrEmitter>emitter, shader); } buffer.bindAndUpdate(size); gl.uniform1i(shader.uniforms.u_emitter, emitterType); gl.vertexAttribPointer(attribs.a_p0, 3, gl.FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P0); gl.vertexAttribPointer(attribs.a_p1, 3, gl.FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P1); gl.vertexAttribPointer(attribs.a_p2, 3, gl.FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P2); gl.vertexAttribPointer(attribs.a_p3, 3, gl.FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P3); gl.vertexAttribPointer(attribs.a_health, 1, gl.FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_HEALTH); gl.vertexAttribPointer(attribs.a_color, 4, gl.UNSIGNED_BYTE, true, BYTES_PER_OBJECT, BYTE_OFFSET_COLOR); gl.vertexAttribPointer(attribs.a_tail, 1, gl.UNSIGNED_BYTE, false, BYTES_PER_OBJECT, BYTE_OFFSET_TAIL); gl.vertexAttribPointer(attribs.a_leftRightTop, 3, gl.UNSIGNED_BYTE, false, BYTES_PER_OBJECT, BYTE_OFFSET_LEFT_RIGHT_TOP); instancedArrays.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, alive); } }