UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

384 lines (381 loc) 17.7 kB
import { math } from '../../core/math/math.js'; import { Mat4 } from '../../core/math/mat4.js'; import { Vec3 } from '../../core/math/vec3.js'; import { EMITTERSHAPE_BOX, EMITTERSHAPE_SPHERE, PARTICLESORT_NONE } from '../constants.js'; var nonUniformScale; var uniformScale = 1; var particleTexChannels = 4; var rotMat = new Mat4(); var rotMatInv = new Mat4(); var randomPosTformed = new Vec3(); var randomPos = new Vec3(); var rndFactor3Vec = new Vec3(); var particlePosPrev = new Vec3(); var velocityVec = new Vec3(); var localVelocityVec = new Vec3(); var velocityVec2 = new Vec3(); var localVelocityVec2 = new Vec3(); var radialVelocityVec = new Vec3(); var particlePos = new Vec3(); var particleFinalPos = new Vec3(); var moveDirVec = new Vec3(); var tmpVec3 = new Vec3(); function frac(f) { return f - Math.floor(f); } function saturate(x) { return Math.max(Math.min(x, 1), 0); } function glMod(x, y) { return x - y * Math.floor(x / y); } function encodeFloatRGBA(v) { var encX = frac(v); var encY = frac(255.0 * v); var encZ = frac(65025.0 * v); var encW = frac(160581375.0 * v); encX -= encY / 255.0; encY -= encZ / 255.0; encZ -= encW / 255.0; encW -= encW / 255.0; return [ encX, encY, encZ, encW ]; } function encodeFloatRG(v) { var encX = frac(v); var encY = frac(255.0 * v); encX -= encY / 255.0; encY -= encY / 255.0; return [ encX, encY ]; } class ParticleCPUUpdater { calcSpawnPosition(particleTex, spawnMatrix, extentsInnerRatioUniform, emitterPos, i) { var emitter = this._emitter; var rX = Math.random(); var rY = Math.random(); var rZ = Math.random(); var rW = Math.random(); if (emitter.useCpu) { particleTex[i * particleTexChannels + 0 + emitter.numParticlesPot * 2 * particleTexChannels] = rX; particleTex[i * particleTexChannels + 1 + emitter.numParticlesPot * 2 * particleTexChannels] = rY; particleTex[i * particleTexChannels + 2 + emitter.numParticlesPot * 2 * particleTexChannels] = rZ; } randomPos.x = rX - 0.5; randomPos.y = rY - 0.5; randomPos.z = rZ - 0.5; if (emitter.emitterShape === EMITTERSHAPE_BOX) { var max = Math.max(Math.abs(randomPos.x), Math.max(Math.abs(randomPos.y), Math.abs(randomPos.z))); var edgeX = max + (0.5 - max) * extentsInnerRatioUniform[0]; var edgeY = max + (0.5 - max) * extentsInnerRatioUniform[1]; var edgeZ = max + (0.5 - max) * extentsInnerRatioUniform[2]; randomPos.x = edgeX * (max === Math.abs(randomPos.x) ? Math.sign(randomPos.x) : 2 * randomPos.x); randomPos.y = edgeY * (max === Math.abs(randomPos.y) ? Math.sign(randomPos.y) : 2 * randomPos.y); randomPos.z = edgeZ * (max === Math.abs(randomPos.z) ? Math.sign(randomPos.z) : 2 * randomPos.z); if (!emitter.localSpace) { randomPosTformed.copy(emitterPos).add(spawnMatrix.transformPoint(randomPos)); } else { randomPosTformed.copy(spawnMatrix.transformPoint(randomPos)); } } else { randomPos.normalize(); var spawnBoundsSphereInnerRatio = emitter.emitterRadius === 0 ? 0 : emitter.emitterRadiusInner / emitter.emitterRadius; var r = rW * (1.0 - spawnBoundsSphereInnerRatio) + spawnBoundsSphereInnerRatio; if (!emitter.localSpace) { randomPosTformed.copy(emitterPos).add(randomPos.mulScalar(r * emitter.emitterRadius)); } else { randomPosTformed.copy(randomPos.mulScalar(r * emitter.emitterRadius)); } } var particleRate = math.lerp(emitter.rate, emitter.rate2, rX); var startSpawnTime = -particleRate * i; if (emitter.pack8) { var packX = (randomPosTformed.x - emitter.worldBounds.center.x) / emitter.worldBoundsSize.x + 0.5; var packY = (randomPosTformed.y - emitter.worldBounds.center.y) / emitter.worldBoundsSize.y + 0.5; var packZ = (randomPosTformed.z - emitter.worldBounds.center.z) / emitter.worldBoundsSize.z + 0.5; var packA = math.lerp(emitter.startAngle * math.DEG_TO_RAD, emitter.startAngle2 * math.DEG_TO_RAD, rX); packA = packA % (Math.PI * 2) / (Math.PI * 2); var rg0 = encodeFloatRG(packX); particleTex[i * particleTexChannels] = rg0[0]; particleTex[i * particleTexChannels + 1] = rg0[1]; var ba0 = encodeFloatRG(packY); particleTex[i * particleTexChannels + 2] = ba0[0]; particleTex[i * particleTexChannels + 3] = ba0[1]; var rg1 = encodeFloatRG(packZ); particleTex[i * particleTexChannels + 0 + emitter.numParticlesPot * particleTexChannels] = rg1[0]; particleTex[i * particleTexChannels + 1 + emitter.numParticlesPot * particleTexChannels] = rg1[1]; var ba1 = encodeFloatRG(packA); particleTex[i * particleTexChannels + 2 + emitter.numParticlesPot * particleTexChannels] = ba1[0]; particleTex[i * particleTexChannels + 3 + emitter.numParticlesPot * particleTexChannels] = ba1[1]; var a2 = 1.0; particleTex[i * particleTexChannels + 3 + emitter.numParticlesPot * particleTexChannels * 2] = a2; var maxNegLife = Math.max(emitter.lifetime, (emitter.numParticles - 1.0) * Math.max(emitter.rate, emitter.rate2)); var maxPosLife = emitter.lifetime + 1.0; startSpawnTime = (startSpawnTime + maxNegLife) / (maxNegLife + maxPosLife); var rgba3 = encodeFloatRGBA(startSpawnTime); particleTex[i * particleTexChannels + 0 + emitter.numParticlesPot * particleTexChannels * 3] = rgba3[0]; particleTex[i * particleTexChannels + 1 + emitter.numParticlesPot * particleTexChannels * 3] = rgba3[1]; particleTex[i * particleTexChannels + 2 + emitter.numParticlesPot * particleTexChannels * 3] = rgba3[2]; particleTex[i * particleTexChannels + 3 + emitter.numParticlesPot * particleTexChannels * 3] = rgba3[3]; } else { particleTex[i * particleTexChannels] = randomPosTformed.x; particleTex[i * particleTexChannels + 1] = randomPosTformed.y; particleTex[i * particleTexChannels + 2] = randomPosTformed.z; particleTex[i * particleTexChannels + 3] = math.lerp(emitter.startAngle * math.DEG_TO_RAD, emitter.startAngle2 * math.DEG_TO_RAD, rX); particleTex[i * particleTexChannels + 3 + emitter.numParticlesPot * particleTexChannels] = startSpawnTime; } } update(data, vbToSort, particleTex, spawnMatrix, extentsInnerRatioUniform, emitterPos, delta, isOnStop) { var a, b, c; var emitter = this._emitter; if (emitter.meshInstance.node) { var fullMat = emitter.meshInstance.node.worldTransform; for(var j = 0; j < 12; j++){ rotMat.data[j] = fullMat.data[j]; } rotMatInv.copy(rotMat); rotMatInv.invert(); nonUniformScale = emitter.meshInstance.node.localScale; uniformScale = Math.max(Math.max(nonUniformScale.x, nonUniformScale.y), nonUniformScale.z); } emitterPos = emitter.meshInstance.node === null || emitter.localSpace ? Vec3.ZERO : emitter.meshInstance.node.getPosition(); var posCam = emitter.camera ? emitter.camera._node.getPosition() : Vec3.ZERO; var vertSize = !emitter.useMesh ? 15 : 17; var cf, cc; var rotSpeed, rotSpeed2, scale2, alpha, alpha2, radialSpeed, radialSpeed2; var precision1 = emitter.precision - 1; for(var i = 0; i < emitter.numParticles; i++){ var id = Math.floor(emitter.vbCPU[i * emitter.numParticleVerts * (emitter.useMesh ? 6 : 4) + 3]); var rndFactor = particleTex[id * particleTexChannels + 0 + emitter.numParticlesPot * 2 * particleTexChannels]; rndFactor3Vec.x = rndFactor; rndFactor3Vec.y = particleTex[id * particleTexChannels + 1 + emitter.numParticlesPot * 2 * particleTexChannels]; rndFactor3Vec.z = particleTex[id * particleTexChannels + 2 + emitter.numParticlesPot * 2 * particleTexChannels]; var particleRate = emitter.rate + (emitter.rate2 - emitter.rate) * rndFactor; var particleLifetime = emitter.lifetime; var life = particleTex[id * particleTexChannels + 3 + emitter.numParticlesPot * particleTexChannels] + delta; var nlife = saturate(life / particleLifetime); var scale = 0; var alphaDiv = 0; var angle = 0; var respawn = life - delta <= 0.0 || life >= particleLifetime; if (respawn) { this.calcSpawnPosition(particleTex, spawnMatrix, extentsInnerRatioUniform, emitterPos, id); } var particleEnabled = life > 0.0 && life < particleLifetime; if (particleEnabled) { c = nlife * precision1; cf = Math.floor(c); cc = Math.ceil(c); c %= 1; a = emitter.qRotSpeed[cf]; b = emitter.qRotSpeed[cc]; rotSpeed = a + (b - a) * c; a = emitter.qRotSpeed2[cf]; b = emitter.qRotSpeed2[cc]; rotSpeed2 = a + (b - a) * c; a = emitter.qScale[cf]; b = emitter.qScale[cc]; scale = a + (b - a) * c; a = emitter.qScale2[cf]; b = emitter.qScale2[cc]; scale2 = a + (b - a) * c; a = emitter.qAlpha[cf]; b = emitter.qAlpha[cc]; alpha = a + (b - a) * c; a = emitter.qAlpha2[cf]; b = emitter.qAlpha2[cc]; alpha2 = a + (b - a) * c; a = emitter.qRadialSpeed[cf]; b = emitter.qRadialSpeed[cc]; radialSpeed = a + (b - a) * c; a = emitter.qRadialSpeed2[cf]; b = emitter.qRadialSpeed2[cc]; radialSpeed2 = a + (b - a) * c; radialSpeed += (radialSpeed2 - radialSpeed) * (rndFactor * 100.0 % 1.0); particlePosPrev.x = particleTex[id * particleTexChannels]; particlePosPrev.y = particleTex[id * particleTexChannels + 1]; particlePosPrev.z = particleTex[id * particleTexChannels + 2]; if (!emitter.localSpace) { radialVelocityVec.copy(particlePosPrev).sub(emitterPos); } else { radialVelocityVec.copy(particlePosPrev); } radialVelocityVec.normalize().mulScalar(radialSpeed); cf *= 3; cc *= 3; a = emitter.qLocalVelocity[cf]; b = emitter.qLocalVelocity[cc]; localVelocityVec.x = a + (b - a) * c; a = emitter.qLocalVelocity[cf + 1]; b = emitter.qLocalVelocity[cc + 1]; localVelocityVec.y = a + (b - a) * c; a = emitter.qLocalVelocity[cf + 2]; b = emitter.qLocalVelocity[cc + 2]; localVelocityVec.z = a + (b - a) * c; a = emitter.qLocalVelocity2[cf]; b = emitter.qLocalVelocity2[cc]; localVelocityVec2.x = a + (b - a) * c; a = emitter.qLocalVelocity2[cf + 1]; b = emitter.qLocalVelocity2[cc + 1]; localVelocityVec2.y = a + (b - a) * c; a = emitter.qLocalVelocity2[cf + 2]; b = emitter.qLocalVelocity2[cc + 2]; localVelocityVec2.z = a + (b - a) * c; a = emitter.qVelocity[cf]; b = emitter.qVelocity[cc]; velocityVec.x = a + (b - a) * c; a = emitter.qVelocity[cf + 1]; b = emitter.qVelocity[cc + 1]; velocityVec.y = a + (b - a) * c; a = emitter.qVelocity[cf + 2]; b = emitter.qVelocity[cc + 2]; velocityVec.z = a + (b - a) * c; a = emitter.qVelocity2[cf]; b = emitter.qVelocity2[cc]; velocityVec2.x = a + (b - a) * c; a = emitter.qVelocity2[cf + 1]; b = emitter.qVelocity2[cc + 1]; velocityVec2.y = a + (b - a) * c; a = emitter.qVelocity2[cf + 2]; b = emitter.qVelocity2[cc + 2]; velocityVec2.z = a + (b - a) * c; localVelocityVec.x += (localVelocityVec2.x - localVelocityVec.x) * rndFactor3Vec.x; localVelocityVec.y += (localVelocityVec2.y - localVelocityVec.y) * rndFactor3Vec.y; localVelocityVec.z += (localVelocityVec2.z - localVelocityVec.z) * rndFactor3Vec.z; if (emitter.initialVelocity > 0) { if (emitter.emitterShape === EMITTERSHAPE_SPHERE) { randomPos.copy(rndFactor3Vec).mulScalar(2).sub(Vec3.ONE).normalize(); localVelocityVec.add(randomPos.mulScalar(emitter.initialVelocity)); } else { localVelocityVec.add(Vec3.FORWARD.mulScalar(emitter.initialVelocity)); } } velocityVec.x += (velocityVec2.x - velocityVec.x) * rndFactor3Vec.x; velocityVec.y += (velocityVec2.y - velocityVec.y) * rndFactor3Vec.y; velocityVec.z += (velocityVec2.z - velocityVec.z) * rndFactor3Vec.z; rotSpeed += (rotSpeed2 - rotSpeed) * rndFactor3Vec.y; scale = (scale + (scale2 - scale) * (rndFactor * 10000.0 % 1.0)) * uniformScale; alphaDiv = (alpha2 - alpha) * (rndFactor * 1000.0 % 1.0); if (emitter.meshInstance.node) { if (!emitter.localSpace) { rotMat.transformPoint(localVelocityVec, localVelocityVec); } else { localVelocityVec.x /= nonUniformScale.x; localVelocityVec.y /= nonUniformScale.y; localVelocityVec.z /= nonUniformScale.z; } } if (!emitter.localSpace) { localVelocityVec.add(velocityVec.mul(nonUniformScale)); localVelocityVec.add(radialVelocityVec.mul(nonUniformScale)); } else { rotMatInv.transformPoint(velocityVec, velocityVec); localVelocityVec.add(velocityVec).add(radialVelocityVec); } moveDirVec.copy(localVelocityVec); particlePos.copy(particlePosPrev).add(localVelocityVec.mulScalar(delta)); particleFinalPos.copy(particlePos); particleTex[id * particleTexChannels] = particleFinalPos.x; particleTex[id * particleTexChannels + 1] = particleFinalPos.y; particleTex[id * particleTexChannels + 2] = particleFinalPos.z; particleTex[id * particleTexChannels + 3] += rotSpeed * delta; if (emitter.wrap && emitter.wrapBounds) { if (!emitter.localSpace) { particleFinalPos.sub(emitterPos); } particleFinalPos.x = glMod(particleFinalPos.x, emitter.wrapBounds.x) - emitter.wrapBounds.x * 0.5; particleFinalPos.y = glMod(particleFinalPos.y, emitter.wrapBounds.y) - emitter.wrapBounds.y * 0.5; particleFinalPos.z = glMod(particleFinalPos.z, emitter.wrapBounds.z) - emitter.wrapBounds.z * 0.5; if (!emitter.localSpace) { particleFinalPos.add(emitterPos); } } if (emitter.sort > 0) { if (emitter.sort === 1) { tmpVec3.copy(particleFinalPos).sub(posCam); emitter.particleDistance[id] = -(tmpVec3.x * tmpVec3.x + tmpVec3.y * tmpVec3.y + tmpVec3.z * tmpVec3.z); } else if (emitter.sort === 2) { emitter.particleDistance[id] = life; } else if (emitter.sort === 3) { emitter.particleDistance[id] = -life; } } } if (isOnStop) { if (life < 0) { particleTex[id * particleTexChannels + 3 + emitter.numParticlesPot * 2 * particleTexChannels] = -1; } } else { if (life >= particleLifetime) { life -= Math.max(particleLifetime, (emitter.numParticles - 1) * particleRate); particleTex[id * particleTexChannels + 3 + emitter.numParticlesPot * 2 * particleTexChannels] = emitter.loop ? 1 : -1; } if (life < 0 && emitter.loop) { particleTex[id * particleTexChannels + 3 + emitter.numParticlesPot * 2 * particleTexChannels] = 1; } } if (particleTex[id * particleTexChannels + 3 + emitter.numParticlesPot * 2 * particleTexChannels] < 0) { particleEnabled = false; } particleTex[id * particleTexChannels + 3 + emitter.numParticlesPot * particleTexChannels] = life; for(var v = 0; v < emitter.numParticleVerts; v++){ var vbOffset = (i * emitter.numParticleVerts + v) * (emitter.useMesh ? 6 : 4); var quadX = emitter.vbCPU[vbOffset]; var quadY = emitter.vbCPU[vbOffset + 1]; var quadZ = emitter.vbCPU[vbOffset + 2]; if (!particleEnabled) { quadX = quadY = quadZ = 0; } var w = i * emitter.numParticleVerts * vertSize + v * vertSize; data[w] = particleFinalPos.x; data[w + 1] = particleFinalPos.y; data[w + 2] = particleFinalPos.z; data[w + 3] = nlife; data[w + 4] = emitter.alignToMotion ? angle : particleTex[id * particleTexChannels + 3]; data[w + 5] = scale; data[w + 6] = alphaDiv; data[w + 7] = moveDirVec.x; data[w + 8] = quadX; data[w + 9] = quadY; data[w + 10] = quadZ; data[w + 11] = moveDirVec.y; data[w + 12] = id; data[w + 13] = moveDirVec.z; data[w + 14] = emitter.vbCPU[vbOffset + 3]; if (emitter.useMesh) { data[w + 15] = emitter.vbCPU[vbOffset + 4]; data[w + 16] = emitter.vbCPU[vbOffset + 5]; } } } if (emitter.sort > PARTICLESORT_NONE && emitter.camera) { var vbStride = emitter.useMesh ? 6 : 4; var particleDistance = emitter.particleDistance; for(var i1 = 0; i1 < emitter.numParticles; i1++){ vbToSort[i1][0] = i1; vbToSort[i1][1] = particleDistance[Math.floor(emitter.vbCPU[i1 * emitter.numParticleVerts * vbStride + 3])]; } emitter.vbOld.set(emitter.vbCPU); vbToSort.sort((p1, p2)=>{ return p1[1] - p2[1]; }); for(var i2 = 0; i2 < emitter.numParticles; i2++){ var src = vbToSort[i2][0] * emitter.numParticleVerts * vbStride; var dest = i2 * emitter.numParticleVerts * vbStride; for(var j1 = 0; j1 < emitter.numParticleVerts * vbStride; j1++){ emitter.vbCPU[dest + j1] = emitter.vbOld[src + j1]; } } } } constructor(emitter){ this._emitter = emitter; } } export { ParticleCPUUpdater };