diff --git a/docs/preview/preview.ts b/docs/preview/preview.ts index c239f40..ca6d3b8 100755 --- a/docs/preview/preview.ts +++ b/docs/preview/preview.ts @@ -594,7 +594,7 @@ function initDragDrop () { function setTextures (textures) { - let promises: Promise[] = []; + const promises: Promise[] = []; for (const texture of model.Textures) { if (texture.Image) { diff --git a/package.json b/package.json index 9b946ea..e1b7221 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "war3-model", - "version": "2.1.0", + "version": "2.2.0", "description": "Warcraft 3 model parser, generator, convertor and previewer", "keywords": [ "warcraft3", diff --git a/renderer/modelRenderer.ts b/renderer/modelRenderer.ts index ad0e02c..2f20e46 100644 --- a/renderer/modelRenderer.ts +++ b/renderer/modelRenderer.ts @@ -6,8 +6,8 @@ import {vec3, quat, mat3, mat4} from 'gl-matrix'; import parseDds from 'parse-dds'; import {mat4fromRotationOrigin, getShader, isWebGL2} from './util'; import {ModelInterp} from './modelInterp'; -import {ParticlesController} from './particles'; import {RendererData, NodeWrapper} from './rendererData'; +import {ParticlesController} from './particles'; import {RibbonsController} from './ribbons'; // actually, all is number @@ -338,6 +338,15 @@ export class ModelRenderer { } public destroy (): void { + if (this.particlesController) { + this.particlesController.destroy(); + this.particlesController = null; + } + if (this.ribbonsController) { + this.ribbonsController.destroy(); + this.ribbonsController = null; + } + if (this.skeletonShaderProgram) { if (this.skeletonVertexShader) { this.gl.detachShader(this.skeletonShaderProgram, this.skeletonVertexShader); @@ -369,7 +378,7 @@ export class ModelRenderer { } } - public initGL (glContext: WebGLRenderingContext): void { + public initGL (glContext: WebGL2RenderingContext | WebGLRenderingContext): void { this.gl = glContext; // Max bones + MV + P this.softwareSkinning = this.gl.getParameter(this.gl.MAX_VERTEX_UNIFORM_VECTORS) < 4 * (MAX_NODES + 2); @@ -380,8 +389,8 @@ export class ModelRenderer { ); this.initShaders(); this.initBuffers(); - ParticlesController.initGL(glContext); - RibbonsController.initGL(glContext); + this.particlesController.initGL(glContext); + this.ribbonsController.initGL(glContext); } public setTextureImage (path: string, img: HTMLImageElement, flags: TextureFlags): void { diff --git a/renderer/particles.ts b/renderer/particles.ts index a48fa3c..05b6b11 100755 --- a/renderer/particles.ts +++ b/renderer/particles.ts @@ -9,31 +9,6 @@ import {degToRad, rand, getShader} from './util'; import {RendererData} from './rendererData'; import {lerp} from './interp'; -let gl: WebGLRenderingContext; -let shaderProgram: WebGLProgram; -const shaderProgramLocations: { - vertexPositionAttribute: number | null; - textureCoordAttribute: number | null; - colorAttribute: number | null; - pMatrixUniform: WebGLUniformLocation | null; - mvMatrixUniform: WebGLUniformLocation | null; - samplerUniform: WebGLUniformLocation | null; - replaceableColorUniform: WebGLUniformLocation | null; - replaceableTypeUniform: WebGLUniformLocation | null; - discardAlphaLevelUniform: WebGLUniformLocation | null; -} = { - vertexPositionAttribute: null, - textureCoordAttribute: null, - colorAttribute: null, - pMatrixUniform: null, - mvMatrixUniform: null, - samplerUniform: null, - replaceableColorUniform: null, - replaceableTypeUniform: null, - discardAlphaLevelUniform: null -}; -const particleStorage: Particle[] = []; - const rotateCenter: vec3 = vec3.fromValues(0, 0, 0); const firstColor = vec4.create(); const secondColor = vec4.create(); @@ -146,46 +121,146 @@ const DISCARD_ALPHA_KEY_LEVEL = 0.83; const DISCARD_MODULATE_LEVEL = 0.01; export class ParticlesController { - public static initGL (glContext: WebGLRenderingContext): void { - gl = glContext; + private gl: WebGL2RenderingContext | WebGLRenderingContext; + private shaderProgram: WebGLProgram; + private vertexShader: WebGLShader; + private fragmentShader: WebGLShader; + + private shaderProgramLocations: { + vertexPositionAttribute: number | null; + textureCoordAttribute: number | null; + colorAttribute: number | null; + pMatrixUniform: WebGLUniformLocation | null; + mvMatrixUniform: WebGLUniformLocation | null; + samplerUniform: WebGLUniformLocation | null; + replaceableColorUniform: WebGLUniformLocation | null; + replaceableTypeUniform: WebGLUniformLocation | null; + discardAlphaLevelUniform: WebGLUniformLocation | null; + }; + + private particleStorage: Particle[]; + + private interp: ModelInterp; + private rendererData: RendererData; + private emitters: ParticleEmitterWrapper[]; + + private particleBaseVectors: vec3[]; + + constructor (interp: ModelInterp, rendererData: RendererData) { + this.shaderProgramLocations = { + vertexPositionAttribute: null, + textureCoordAttribute: null, + colorAttribute: null, + pMatrixUniform: null, + mvMatrixUniform: null, + samplerUniform: null, + replaceableColorUniform: null, + replaceableTypeUniform: null, + discardAlphaLevelUniform: null + }; + this.particleStorage = []; + this.interp = interp; + this.rendererData = rendererData; + this.emitters = []; + + if (rendererData.model.ParticleEmitters2.length) { + this.particleBaseVectors = [ + vec3.create(), + vec3.create(), + vec3.create(), + vec3.create() + ]; - ParticlesController.initShaders(); + for (const particleEmitter of rendererData.model.ParticleEmitters2) { + const emitter: ParticleEmitterWrapper = { + emission: 0, + squirtFrame: 0, + particles: [], + props: particleEmitter, + capacity: 0, + baseCapacity: 0, + type: particleEmitter.FrameFlags, + tailVertices: null, + tailVertexBuffer: null, + headVertices: null, + headVertexBuffer: null, + tailTexCoords: null, + tailTexCoordBuffer: null, + headTexCoords: null, + headTexCoordBuffer: null, + colors: null, + colorBuffer: null, + indices: null, + indexBuffer: null + }; + + emitter.baseCapacity = Math.ceil( + ModelInterp.maxAnimVectorVal(emitter.props.EmissionRate) * emitter.props.LifeSpan + ); + + this.emitters.push(emitter); + } + } } - private static initShaders (): void { - const vertex = getShader(gl, vertexShader, gl.VERTEX_SHADER); - const fragment = getShader(gl, fragmentShader, gl.FRAGMENT_SHADER); + public destroy (): void { + if (this.shaderProgram) { + if (this.vertexShader) { + this.gl.detachShader(this.shaderProgram, this.vertexShader); + this.gl.deleteShader(this.vertexShader); + this.vertexShader = null; + } + if (this.fragmentShader) { + this.gl.detachShader(this.shaderProgram, this.fragmentShader); + this.gl.deleteShader(this.fragmentShader); + this.fragmentShader = null; + } + this.gl.deleteProgram(this.shaderProgram); + this.shaderProgram = null; + } + this.particleStorage = []; + } - shaderProgram = gl.createProgram(); - gl.attachShader(shaderProgram, vertex); - gl.attachShader(shaderProgram, fragment); - gl.linkProgram(shaderProgram); + public initGL (glContext: WebGLRenderingContext): void { + this.gl = glContext; - if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { + this.initShaders(); + } + + private initShaders (): void { + const vertex = this.vertexShader = getShader(this.gl, vertexShader, this.gl.VERTEX_SHADER); + const fragment = this.fragmentShader = getShader(this.gl, fragmentShader, this.gl.FRAGMENT_SHADER); + + const shaderProgram = this.shaderProgram = this.gl.createProgram(); + this.gl.attachShader(shaderProgram, vertex); + this.gl.attachShader(shaderProgram, fragment); + this.gl.linkProgram(shaderProgram); + + if (!this.gl.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) { alert('Could not initialise shaders'); } - gl.useProgram(shaderProgram); - - shaderProgramLocations.vertexPositionAttribute = - gl.getAttribLocation(shaderProgram, 'aVertexPosition'); - shaderProgramLocations.textureCoordAttribute = - gl.getAttribLocation(shaderProgram, 'aTextureCoord'); - shaderProgramLocations.colorAttribute = - gl.getAttribLocation(shaderProgram, 'aColor'); - - shaderProgramLocations.pMatrixUniform = gl.getUniformLocation(shaderProgram, 'uPMatrix'); - shaderProgramLocations.mvMatrixUniform = gl.getUniformLocation(shaderProgram, 'uMVMatrix'); - shaderProgramLocations.samplerUniform = gl.getUniformLocation(shaderProgram, 'uSampler'); - shaderProgramLocations.replaceableColorUniform = - gl.getUniformLocation(shaderProgram, 'uReplaceableColor'); - shaderProgramLocations.replaceableTypeUniform = - gl.getUniformLocation(shaderProgram, 'uReplaceableType'); - shaderProgramLocations.discardAlphaLevelUniform = - gl.getUniformLocation(shaderProgram, 'uDiscardAlphaLevel'); + this.gl.useProgram(shaderProgram); + + this.shaderProgramLocations.vertexPositionAttribute = + this.gl.getAttribLocation(shaderProgram, 'aVertexPosition'); + this.shaderProgramLocations.textureCoordAttribute = + this.gl.getAttribLocation(shaderProgram, 'aTextureCoord'); + this.shaderProgramLocations.colorAttribute = + this.gl.getAttribLocation(shaderProgram, 'aColor'); + + this.shaderProgramLocations.pMatrixUniform = this.gl.getUniformLocation(shaderProgram, 'uPMatrix'); + this.shaderProgramLocations.mvMatrixUniform = this.gl.getUniformLocation(shaderProgram, 'uMVMatrix'); + this.shaderProgramLocations.samplerUniform = this.gl.getUniformLocation(shaderProgram, 'uSampler'); + this.shaderProgramLocations.replaceableColorUniform = + this.gl.getUniformLocation(shaderProgram, 'uReplaceableColor'); + this.shaderProgramLocations.replaceableTypeUniform = + this.gl.getUniformLocation(shaderProgram, 'uReplaceableType'); + this.shaderProgramLocations.discardAlphaLevelUniform = + this.gl.getUniformLocation(shaderProgram, 'uDiscardAlphaLevel'); } - private static updateParticle (particle: Particle, delta: number): void { + private updateParticle (particle: Particle, delta: number): void { delta /= 1000; particle.lifeSpan -= delta; @@ -199,7 +274,7 @@ export class ParticlesController { particle.pos[2] += particle.speed[2] * delta; } - private static resizeEmitterBuffers (emitter: ParticleEmitterWrapper, size: number): void { + private resizeEmitterBuffers (emitter: ParticleEmitterWrapper, size: number): void { if (size <= emitter.capacity) { return; } @@ -251,66 +326,15 @@ export class ParticlesController { if (!emitter.indexBuffer) { if (emitter.type & ParticleEmitter2FramesFlags.Tail) { - emitter.tailVertexBuffer = gl.createBuffer(); - emitter.tailTexCoordBuffer = gl.createBuffer(); + emitter.tailVertexBuffer = this.gl.createBuffer(); + emitter.tailTexCoordBuffer = this.gl.createBuffer(); } if (emitter.type & ParticleEmitter2FramesFlags.Head) { - emitter.headVertexBuffer = gl.createBuffer(); - emitter.headTexCoordBuffer = gl.createBuffer(); - } - emitter.colorBuffer = gl.createBuffer(); - emitter.indexBuffer = gl.createBuffer(); - } - } - - private interp: ModelInterp; - private rendererData: RendererData; - private emitters: ParticleEmitterWrapper[]; - - private particleBaseVectors: vec3[]; - - constructor (interp: ModelInterp, rendererData: RendererData) { - this.interp = interp; - this.rendererData = rendererData; - this.emitters = []; - - if (rendererData.model.ParticleEmitters2.length) { - this.particleBaseVectors = [ - vec3.create(), - vec3.create(), - vec3.create(), - vec3.create() - ]; - - for (const particleEmitter of rendererData.model.ParticleEmitters2) { - const emitter: ParticleEmitterWrapper = { - emission: 0, - squirtFrame: 0, - particles: [], - props: particleEmitter, - capacity: 0, - baseCapacity: 0, - type: particleEmitter.FrameFlags, - tailVertices: null, - tailVertexBuffer: null, - headVertices: null, - headVertexBuffer: null, - tailTexCoords: null, - tailTexCoordBuffer: null, - headTexCoords: null, - headTexCoordBuffer: null, - colors: null, - colorBuffer: null, - indices: null, - indexBuffer: null - }; - - emitter.baseCapacity = Math.ceil( - ModelInterp.maxAnimVectorVal(emitter.props.EmissionRate) * emitter.props.LifeSpan - ); - - this.emitters.push(emitter); + emitter.headVertexBuffer = this.gl.createBuffer(); + emitter.headTexCoordBuffer = this.gl.createBuffer(); } + emitter.colorBuffer = this.gl.createBuffer(); + emitter.indexBuffer = this.gl.createBuffer(); } } @@ -321,15 +345,15 @@ export class ParticlesController { } public render (mvMatrix: mat4, pMatrix: mat4): void { - gl.enable(gl.CULL_FACE); - gl.useProgram(shaderProgram); + this.gl.enable(this.gl.CULL_FACE); + this.gl.useProgram(this.shaderProgram); - gl.uniformMatrix4fv(shaderProgramLocations.pMatrixUniform, false, pMatrix); - gl.uniformMatrix4fv(shaderProgramLocations.mvMatrixUniform, false, mvMatrix); + this.gl.uniformMatrix4fv(this.shaderProgramLocations.pMatrixUniform, false, pMatrix); + this.gl.uniformMatrix4fv(this.shaderProgramLocations.mvMatrixUniform, false, mvMatrix); - gl.enableVertexAttribArray(shaderProgramLocations.vertexPositionAttribute); - gl.enableVertexAttribArray(shaderProgramLocations.textureCoordAttribute); - gl.enableVertexAttribArray(shaderProgramLocations.colorAttribute); + this.gl.enableVertexAttribArray(this.shaderProgramLocations.vertexPositionAttribute); + this.gl.enableVertexAttribArray(this.shaderProgramLocations.textureCoordAttribute); + this.gl.enableVertexAttribArray(this.shaderProgramLocations.colorAttribute); for (const emitter of this.emitters) { if (!emitter.particles.length) { @@ -347,9 +371,9 @@ export class ParticlesController { } } - gl.disableVertexAttribArray(shaderProgramLocations.vertexPositionAttribute); - gl.disableVertexAttribArray(shaderProgramLocations.textureCoordAttribute); - gl.disableVertexAttribArray(shaderProgramLocations.colorAttribute); + this.gl.disableVertexAttribArray(this.shaderProgramLocations.vertexPositionAttribute); + this.gl.disableVertexAttribArray(this.shaderProgramLocations.textureCoordAttribute); + this.gl.disableVertexAttribArray(this.shaderProgramLocations.colorAttribute); } private updateEmitter (emitter: ParticleEmitterWrapper, delta: number): void { @@ -382,11 +406,11 @@ export class ParticlesController { if (emitter.particles.length) { const updatedParticles = []; for (const particle of emitter.particles) { - ParticlesController.updateParticle(particle, delta); + this.updateParticle(particle, delta); if (particle.lifeSpan > 0) { updatedParticles.push(particle); } else { - particleStorage.push(particle); + this.particleStorage.push(particle); } } emitter.particles = updatedParticles; @@ -410,7 +434,7 @@ export class ParticlesController { } } - ParticlesController.resizeEmitterBuffers(emitter, emitter.particles.length); + this.resizeEmitterBuffers(emitter, emitter.particles.length); for (let i = 0; i < emitter.particles.length; ++i) { this.updateParticleBuffers(emitter.particles[i], i, emitter); } @@ -420,8 +444,8 @@ export class ParticlesController { private createParticle (emitter: ParticleEmitterWrapper, emitterMatrix: mat4) { let particle: Particle; - if (particleStorage.length) { - particle = particleStorage.pop(); + if (this.particleStorage.length) { + particle = this.particleStorage.pop(); } else { particle = { emitter: null, @@ -635,81 +659,81 @@ export class ParticlesController { private setLayerProps (emitter: ParticleEmitterWrapper): void { if (emitter.props.FilterMode === ParticleEmitter2FilterMode.AlphaKey) { - gl.uniform1f(shaderProgramLocations.discardAlphaLevelUniform, DISCARD_ALPHA_KEY_LEVEL); + this.gl.uniform1f(this.shaderProgramLocations.discardAlphaLevelUniform, DISCARD_ALPHA_KEY_LEVEL); } else if (emitter.props.FilterMode === ParticleEmitter2FilterMode.Modulate || emitter.props.FilterMode === ParticleEmitter2FilterMode.Modulate2x) { - gl.uniform1f(shaderProgramLocations.discardAlphaLevelUniform, DISCARD_MODULATE_LEVEL); + this.gl.uniform1f(this.shaderProgramLocations.discardAlphaLevelUniform, DISCARD_MODULATE_LEVEL); } else { - gl.uniform1f(shaderProgramLocations.discardAlphaLevelUniform, 0.); + this.gl.uniform1f(this.shaderProgramLocations.discardAlphaLevelUniform, 0.); } if (emitter.props.FilterMode === ParticleEmitter2FilterMode.Blend) { - gl.enable(gl.BLEND); - gl.enable(gl.DEPTH_TEST); - gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - gl.depthMask(false); + this.gl.enable(this.gl.BLEND); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA); + this.gl.depthMask(false); } else if (emitter.props.FilterMode === ParticleEmitter2FilterMode.Additive) { - gl.enable(gl.BLEND); - gl.enable(gl.DEPTH_TEST); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE); - gl.depthMask(false); + this.gl.enable(this.gl.BLEND); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); + this.gl.depthMask(false); } else if (emitter.props.FilterMode === ParticleEmitter2FilterMode.AlphaKey) { - gl.enable(gl.BLEND); - gl.enable(gl.DEPTH_TEST); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE); - gl.depthMask(false); + this.gl.enable(this.gl.BLEND); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); + this.gl.depthMask(false); } else if (emitter.props.FilterMode === ParticleEmitter2FilterMode.Modulate) { - gl.enable(gl.BLEND); - gl.enable(gl.DEPTH_TEST); - gl.blendFuncSeparate(gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.ONE); - gl.depthMask(false); + this.gl.enable(this.gl.BLEND); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.blendFuncSeparate(this.gl.ZERO, this.gl.SRC_COLOR, this.gl.ZERO, this.gl.ONE); + this.gl.depthMask(false); } else if (emitter.props.FilterMode === ParticleEmitter2FilterMode.Modulate2x) { - gl.enable(gl.BLEND); - gl.enable(gl.DEPTH_TEST); - gl.blendFuncSeparate(gl.DST_COLOR, gl.SRC_COLOR, gl.ZERO, gl.ONE); - gl.depthMask(false); + this.gl.enable(this.gl.BLEND); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.blendFuncSeparate(this.gl.DST_COLOR, this.gl.SRC_COLOR, this.gl.ZERO, this.gl.ONE); + this.gl.depthMask(false); } const texture = this.rendererData.model.Textures[emitter.props.TextureID]; if (texture.Image) { - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this.rendererData.textures[texture.Image]); - gl.uniform1i(shaderProgramLocations.samplerUniform, 0); - gl.uniform1f(shaderProgramLocations.replaceableTypeUniform, 0); + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.rendererData.textures[texture.Image]); + this.gl.uniform1i(this.shaderProgramLocations.samplerUniform, 0); + this.gl.uniform1f(this.shaderProgramLocations.replaceableTypeUniform, 0); } else if (texture.ReplaceableId === 1 || texture.ReplaceableId === 2) { - gl.uniform3fv(shaderProgramLocations.replaceableColorUniform, this.rendererData.teamColor); - gl.uniform1f(shaderProgramLocations.replaceableTypeUniform, texture.ReplaceableId); + this.gl.uniform3fv(this.shaderProgramLocations.replaceableColorUniform, this.rendererData.teamColor); + this.gl.uniform1f(this.shaderProgramLocations.replaceableTypeUniform, texture.ReplaceableId); } } private setGeneralBuffers (emitter: ParticleEmitterWrapper): void { - gl.bindBuffer(gl.ARRAY_BUFFER, emitter.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, emitter.colors, gl.DYNAMIC_DRAW); - gl.vertexAttribPointer(shaderProgramLocations.colorAttribute, 4, gl.FLOAT, false, 0, 0); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, emitter.colorBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, emitter.colors, this.gl.DYNAMIC_DRAW); + this.gl.vertexAttribPointer(this.shaderProgramLocations.colorAttribute, 4, this.gl.FLOAT, false, 0, 0); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, emitter.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, emitter.indices, gl.DYNAMIC_DRAW); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, emitter.indexBuffer); + this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, emitter.indices, this.gl.DYNAMIC_DRAW); } private renderEmitterType (emitter: ParticleEmitterWrapper, type: ParticleEmitter2FramesFlags): void { if (type === ParticleEmitter2FramesFlags.Tail) { - gl.bindBuffer(gl.ARRAY_BUFFER, emitter.tailTexCoordBuffer); - gl.bufferData(gl.ARRAY_BUFFER, emitter.tailTexCoords, gl.DYNAMIC_DRAW); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, emitter.tailTexCoordBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, emitter.tailTexCoords, this.gl.DYNAMIC_DRAW); } else { - gl.bindBuffer(gl.ARRAY_BUFFER, emitter.headTexCoordBuffer); - gl.bufferData(gl.ARRAY_BUFFER, emitter.headTexCoords, gl.DYNAMIC_DRAW); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, emitter.headTexCoordBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, emitter.headTexCoords, this.gl.DYNAMIC_DRAW); } - gl.vertexAttribPointer(shaderProgramLocations.textureCoordAttribute, 2, gl.FLOAT, false, 0, 0); + this.gl.vertexAttribPointer(this.shaderProgramLocations.textureCoordAttribute, 2, this.gl.FLOAT, false, 0, 0); if (type === ParticleEmitter2FramesFlags.Tail) { - gl.bindBuffer(gl.ARRAY_BUFFER, emitter.tailVertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, emitter.tailVertices, gl.DYNAMIC_DRAW); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, emitter.tailVertexBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, emitter.tailVertices, this.gl.DYNAMIC_DRAW); } else { - gl.bindBuffer(gl.ARRAY_BUFFER, emitter.headVertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, emitter.headVertices, gl.DYNAMIC_DRAW); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, emitter.headVertexBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, emitter.headVertices, this.gl.DYNAMIC_DRAW); } - gl.vertexAttribPointer(shaderProgramLocations.vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0); + this.gl.vertexAttribPointer(this.shaderProgramLocations.vertexPositionAttribute, 3, this.gl.FLOAT, false, 0, 0); - gl.drawElements(gl.TRIANGLES, emitter.particles.length * 6, gl.UNSIGNED_SHORT, 0); + this.gl.drawElements(this.gl.TRIANGLES, emitter.particles.length * 6, this.gl.UNSIGNED_SHORT, 0); } } diff --git a/renderer/ribbons.ts b/renderer/ribbons.ts index 1e2a164..ad58ebe 100644 --- a/renderer/ribbons.ts +++ b/renderer/ribbons.ts @@ -3,29 +3,6 @@ import {RendererData} from './rendererData'; import {ModelInterp} from './modelInterp'; import {FilterMode, Layer, LayerShading, Material, RibbonEmitter} from '../model'; import {mat4, vec3} from 'gl-matrix'; -let gl: WebGLRenderingContext; -let shaderProgram: WebGLProgram; -const shaderProgramLocations: { - vertexPositionAttribute: number, - textureCoordAttribute: number, - pMatrixUniform: WebGLUniformLocation | null, - mvMatrixUniform: WebGLUniformLocation | null, - samplerUniform: WebGLUniformLocation | null, - replaceableColorUniform: WebGLUniformLocation | null, - replaceableTypeUniform: WebGLUniformLocation | null, - discardAlphaLevelUniform: WebGLUniformLocation | null, - colorUniform: WebGLUniformLocation | null -} = { - vertexPositionAttribute: null, - textureCoordAttribute: null, - pMatrixUniform: null, - mvMatrixUniform: null, - samplerUniform: null, - replaceableColorUniform: null, - replaceableTypeUniform: null, - discardAlphaLevelUniform: null, - colorUniform: null -}; const vertexShader = ` attribute vec3 aVertexPosition; @@ -100,75 +77,40 @@ interface RibbonEmitterWrapper { } export class RibbonsController { - public static initGL (glContext: WebGLRenderingContext): void { - gl = glContext; - - RibbonsController.initShaders(); - } - - private static initShaders (): void { - const vertex = getShader(gl, vertexShader, gl.VERTEX_SHADER); - const fragment = getShader(gl, fragmentShader, gl.FRAGMENT_SHADER); - - shaderProgram = gl.createProgram(); - gl.attachShader(shaderProgram, vertex); - gl.attachShader(shaderProgram, fragment); - gl.linkProgram(shaderProgram); - - if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { - alert('Could not initialise shaders'); - } - - gl.useProgram(shaderProgram); - - shaderProgramLocations.vertexPositionAttribute = - gl.getAttribLocation(shaderProgram, 'aVertexPosition'); - shaderProgramLocations.textureCoordAttribute = - gl.getAttribLocation(shaderProgram, 'aTextureCoord'); - - shaderProgramLocations.pMatrixUniform = gl.getUniformLocation(shaderProgram, 'uPMatrix'); - shaderProgramLocations.mvMatrixUniform = gl.getUniformLocation(shaderProgram, 'uMVMatrix'); - shaderProgramLocations.samplerUniform = gl.getUniformLocation(shaderProgram, 'uSampler'); - shaderProgramLocations.replaceableColorUniform = - gl.getUniformLocation(shaderProgram, 'uReplaceableColor'); - shaderProgramLocations.replaceableTypeUniform = - gl.getUniformLocation(shaderProgram, 'uReplaceableType'); - shaderProgramLocations.discardAlphaLevelUniform = - gl.getUniformLocation(shaderProgram, 'uDiscardAlphaLevel'); - shaderProgramLocations.colorUniform = - gl.getUniformLocation(shaderProgram, 'uColor'); - } - - private static resizeEmitterBuffers (emitter: RibbonEmitterWrapper, size: number): void { - if (size <= emitter.capacity) { - return; - } - - size = Math.min(size, emitter.baseCapacity); - - const vertices = new Float32Array(size * 2 * 3); // 2 vertices * xyz - const texCoords = new Float32Array(size * 2 * 2); // 2 vertices * xy - - if (emitter.vertices) { - vertices.set(emitter.vertices); - } - - emitter.vertices = vertices; - emitter.texCoords = texCoords; - - emitter.capacity = size; - - if (!emitter.vertexBuffer) { - emitter.vertexBuffer = gl.createBuffer(); - emitter.texCoordBuffer = gl.createBuffer(); - } - } + private gl: WebGL2RenderingContext | WebGLRenderingContext; + private shaderProgram: WebGLProgram; + private vertexShader: WebGLShader; + private fragmentShader: WebGLShader; + + private shaderProgramLocations: { + vertexPositionAttribute: number, + textureCoordAttribute: number, + pMatrixUniform: WebGLUniformLocation | null, + mvMatrixUniform: WebGLUniformLocation | null, + samplerUniform: WebGLUniformLocation | null, + replaceableColorUniform: WebGLUniformLocation | null, + replaceableTypeUniform: WebGLUniformLocation | null, + discardAlphaLevelUniform: WebGLUniformLocation | null, + colorUniform: WebGLUniformLocation | null + }; private interp: ModelInterp; private rendererData: RendererData; private emitters: RibbonEmitterWrapper[]; constructor (interp: ModelInterp, rendererData: RendererData) { + this.shaderProgramLocations = { + vertexPositionAttribute: null, + textureCoordAttribute: null, + pMatrixUniform: null, + mvMatrixUniform: null, + samplerUniform: null, + replaceableColorUniform: null, + replaceableTypeUniform: null, + discardAlphaLevelUniform: null, + colorUniform: null + }; + this.interp = interp; this.rendererData = rendererData; this.emitters = []; @@ -196,6 +138,30 @@ export class RibbonsController { } } + public destroy (): void { + if (this.shaderProgram) { + if (this.vertexShader) { + this.gl.detachShader(this.shaderProgram, this.vertexShader); + this.gl.deleteShader(this.vertexShader); + this.vertexShader = null; + } + if (this.fragmentShader) { + this.gl.detachShader(this.shaderProgram, this.fragmentShader); + this.gl.deleteShader(this.fragmentShader); + this.fragmentShader = null; + } + this.gl.deleteProgram(this.shaderProgram); + this.shaderProgram = null; + } + this.emitters = []; + } + + public initGL (glContext: WebGLRenderingContext): void { + this.gl = glContext; + + this.initShaders(); + } + public update (delta: number): void { for (const emitter of this.emitters) { this.updateEmitter(emitter, delta); @@ -203,20 +169,20 @@ export class RibbonsController { } public render (mvMatrix: mat4, pMatrix: mat4): void { - gl.useProgram(shaderProgram); + this.gl.useProgram(this.shaderProgram); - gl.uniformMatrix4fv(shaderProgramLocations.pMatrixUniform, false, pMatrix); - gl.uniformMatrix4fv(shaderProgramLocations.mvMatrixUniform, false, mvMatrix); + this.gl.uniformMatrix4fv(this.shaderProgramLocations.pMatrixUniform, false, pMatrix); + this.gl.uniformMatrix4fv(this.shaderProgramLocations.mvMatrixUniform, false, mvMatrix); - gl.enableVertexAttribArray(shaderProgramLocations.vertexPositionAttribute); - gl.enableVertexAttribArray(shaderProgramLocations.textureCoordAttribute); + this.gl.enableVertexAttribArray(this.shaderProgramLocations.vertexPositionAttribute); + this.gl.enableVertexAttribArray(this.shaderProgramLocations.textureCoordAttribute); for (const emitter of this.emitters) { if (emitter.creationTimes.length < 2) { continue; } - gl.uniform4f(shaderProgramLocations.colorUniform, + this.gl.uniform4f(this.shaderProgramLocations.colorUniform, emitter.props.Color[0], emitter.props.Color[1], emitter.props.Color[2], this.interp.animVectorVal(emitter.props.Alpha, 1) ); @@ -230,8 +196,66 @@ export class RibbonsController { } } - gl.disableVertexAttribArray(shaderProgramLocations.vertexPositionAttribute); - gl.disableVertexAttribArray(shaderProgramLocations.textureCoordAttribute); + this.gl.disableVertexAttribArray(this.shaderProgramLocations.vertexPositionAttribute); + this.gl.disableVertexAttribArray(this.shaderProgramLocations.textureCoordAttribute); + } + + private initShaders (): void { + const vertex = this.vertexShader = getShader(this.gl, vertexShader, this.gl.VERTEX_SHADER); + const fragment = this.fragmentShader = getShader(this.gl, fragmentShader, this.gl.FRAGMENT_SHADER); + + const shaderProgram = this.shaderProgram = this.gl.createProgram(); + this.gl.attachShader(shaderProgram, vertex); + this.gl.attachShader(shaderProgram, fragment); + this.gl.linkProgram(shaderProgram); + + if (!this.gl.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) { + alert('Could not initialise shaders'); + } + + this.gl.useProgram(shaderProgram); + + this.shaderProgramLocations.vertexPositionAttribute = + this.gl.getAttribLocation(shaderProgram, 'aVertexPosition'); + this.shaderProgramLocations.textureCoordAttribute = + this.gl.getAttribLocation(shaderProgram, 'aTextureCoord'); + + this.shaderProgramLocations.pMatrixUniform = this.gl.getUniformLocation(shaderProgram, 'uPMatrix'); + this.shaderProgramLocations.mvMatrixUniform = this.gl.getUniformLocation(shaderProgram, 'uMVMatrix'); + this.shaderProgramLocations.samplerUniform = this.gl.getUniformLocation(shaderProgram, 'uSampler'); + this.shaderProgramLocations.replaceableColorUniform = + this.gl.getUniformLocation(shaderProgram, 'uReplaceableColor'); + this.shaderProgramLocations.replaceableTypeUniform = + this.gl.getUniformLocation(shaderProgram, 'uReplaceableType'); + this.shaderProgramLocations.discardAlphaLevelUniform = + this.gl.getUniformLocation(shaderProgram, 'uDiscardAlphaLevel'); + this.shaderProgramLocations.colorUniform = + this.gl.getUniformLocation(shaderProgram, 'uColor'); + } + + private resizeEmitterBuffers (emitter: RibbonEmitterWrapper, size: number): void { + if (size <= emitter.capacity) { + return; + } + + size = Math.min(size, emitter.baseCapacity); + + const vertices = new Float32Array(size * 2 * 3); // 2 vertices * xyz + const texCoords = new Float32Array(size * 2 * 2); // 2 vertices * xy + + if (emitter.vertices) { + vertices.set(emitter.vertices); + } + + emitter.vertices = vertices; + emitter.texCoords = texCoords; + + emitter.capacity = size; + + if (!emitter.vertexBuffer) { + emitter.vertexBuffer = this.gl.createBuffer(); + emitter.texCoordBuffer = this.gl.createBuffer(); + } } private updateEmitter (emitter: RibbonEmitterWrapper, delta: number): void { @@ -248,7 +272,7 @@ export class RibbonsController { emitter.emission = emitter.emission % 1000; if (emitter.creationTimes.length + 1 > emitter.capacity) { - RibbonsController.resizeEmitterBuffers(emitter, emitter.creationTimes.length + 1); + this.resizeEmitterBuffers(emitter, emitter.creationTimes.length + 1); } this.appendVertices(emitter); @@ -320,69 +344,69 @@ export class RibbonsController { const texture = this.rendererData.model.Textures[textureID]; if (layer.Shading & LayerShading.TwoSided) { - gl.disable(gl.CULL_FACE); + this.gl.disable(this.gl.CULL_FACE); } else { - gl.enable(gl.CULL_FACE); + this.gl.enable(this.gl.CULL_FACE); } if (layer.FilterMode === FilterMode.Transparent) { - gl.uniform1f(shaderProgramLocations.discardAlphaLevelUniform, 0.75); + this.gl.uniform1f(this.shaderProgramLocations.discardAlphaLevelUniform, 0.75); } else { - gl.uniform1f(shaderProgramLocations.discardAlphaLevelUniform, 0.); + this.gl.uniform1f(this.shaderProgramLocations.discardAlphaLevelUniform, 0.); } if (layer.FilterMode === FilterMode.None) { - gl.disable(gl.BLEND); - gl.enable(gl.DEPTH_TEST); - // gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - gl.depthMask(true); + this.gl.disable(this.gl.BLEND); + this.gl.enable(this.gl.DEPTH_TEST); + // this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); + this.gl.depthMask(true); } else if (layer.FilterMode === FilterMode.Transparent) { - gl.enable(gl.BLEND); - gl.enable(gl.DEPTH_TEST); - gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - gl.depthMask(true); + this.gl.enable(this.gl.BLEND); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA); + this.gl.depthMask(true); } else if (layer.FilterMode === FilterMode.Blend) { - gl.enable(gl.BLEND); - gl.enable(gl.DEPTH_TEST); - gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - gl.depthMask(false); + this.gl.enable(this.gl.BLEND); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA); + this.gl.depthMask(false); } else if (layer.FilterMode === FilterMode.Additive) { - gl.enable(gl.BLEND); - gl.enable(gl.DEPTH_TEST); - gl.blendFunc(gl.SRC_COLOR, gl.ONE); - gl.depthMask(false); + this.gl.enable(this.gl.BLEND); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.blendFunc(this.gl.SRC_COLOR, this.gl.ONE); + this.gl.depthMask(false); } else if (layer.FilterMode === FilterMode.AddAlpha) { - gl.enable(gl.BLEND); - gl.enable(gl.DEPTH_TEST); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE); - gl.depthMask(false); + this.gl.enable(this.gl.BLEND); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); + this.gl.depthMask(false); } else if (layer.FilterMode === FilterMode.Modulate) { - gl.enable(gl.BLEND); - gl.enable(gl.DEPTH_TEST); - gl.blendFuncSeparate(gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.ONE); - gl.depthMask(false); + this.gl.enable(this.gl.BLEND); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.blendFuncSeparate(this.gl.ZERO, this.gl.SRC_COLOR, this.gl.ZERO, this.gl.ONE); + this.gl.depthMask(false); } else if (layer.FilterMode === FilterMode.Modulate2x) { - gl.enable(gl.BLEND); - gl.enable(gl.DEPTH_TEST); - gl.blendFuncSeparate(gl.DST_COLOR, gl.SRC_COLOR, gl.ZERO, gl.ONE); - gl.depthMask(false); + this.gl.enable(this.gl.BLEND); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.blendFuncSeparate(this.gl.DST_COLOR, this.gl.SRC_COLOR, this.gl.ZERO, this.gl.ONE); + this.gl.depthMask(false); } if (texture.Image) { - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this.rendererData.textures[texture.Image]); - gl.uniform1i(shaderProgramLocations.samplerUniform, 0); - gl.uniform1f(shaderProgramLocations.replaceableTypeUniform, 0); + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, this.rendererData.textures[texture.Image]); + this.gl.uniform1i(this.shaderProgramLocations.samplerUniform, 0); + this.gl.uniform1f(this.shaderProgramLocations.replaceableTypeUniform, 0); } else if (texture.ReplaceableId === 1 || texture.ReplaceableId === 2) { - gl.uniform3fv(shaderProgramLocations.replaceableColorUniform, this.rendererData.teamColor); - gl.uniform1f(shaderProgramLocations.replaceableTypeUniform, texture.ReplaceableId); + this.gl.uniform3fv(this.shaderProgramLocations.replaceableColorUniform, this.rendererData.teamColor); + this.gl.uniform1f(this.shaderProgramLocations.replaceableTypeUniform, texture.ReplaceableId); } if (layer.Shading & LayerShading.NoDepthTest) { - gl.disable(gl.DEPTH_TEST); + this.gl.disable(this.gl.DEPTH_TEST); } if (layer.Shading & LayerShading.NoDepthSet) { - gl.depthMask(false); + this.gl.depthMask(false); } /*if (typeof layer.TVertexAnimId === 'number') { @@ -403,23 +427,23 @@ export class RibbonsController { texCoordMat4[12], texCoordMat4[13], 0 ); - gl.uniformMatrix3fv(shaderProgramLocations.tVertexAnimUniform, false, texCoordMat3); + this.gl.uniformMatrix3fv(this.shaderProgramLocations.tVertexAnimUniform, false, texCoordMat3); } else { - gl.uniformMatrix3fv(shaderProgramLocations.tVertexAnimUniform, false, identifyMat3); + this.gl.uniformMatrix3fv(this.shaderProgramLocations.tVertexAnimUniform, false, identifyMat3); }*/ } private setGeneralBuffers (emitter: RibbonEmitterWrapper): void { - gl.bindBuffer(gl.ARRAY_BUFFER, emitter.texCoordBuffer); - gl.bufferData(gl.ARRAY_BUFFER, emitter.texCoords, gl.DYNAMIC_DRAW); - gl.vertexAttribPointer(shaderProgramLocations.textureCoordAttribute, 2, gl.FLOAT, false, 0, 0); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, emitter.texCoordBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, emitter.texCoords, this.gl.DYNAMIC_DRAW); + this.gl.vertexAttribPointer(this.shaderProgramLocations.textureCoordAttribute, 2, this.gl.FLOAT, false, 0, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, emitter.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, emitter.vertices, gl.DYNAMIC_DRAW); - gl.vertexAttribPointer(shaderProgramLocations.vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, emitter.vertexBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, emitter.vertices, this.gl.DYNAMIC_DRAW); + this.gl.vertexAttribPointer(this.shaderProgramLocations.vertexPositionAttribute, 3, this.gl.FLOAT, false, 0, 0); } private renderEmitter (emitter: RibbonEmitterWrapper): void { - gl.drawArrays(gl.TRIANGLE_STRIP, 0, emitter.creationTimes.length * 2); + this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, emitter.creationTimes.length * 2); } }