import*as THREE from "../../../../libs/three.js/build/three.module.js"; import {vertexShader, fragmentShader} from './shader.js' import Tween from '../Tween.js' import Particle from './Particle.js' import {util} from './Util.js' import {Shape} from './const.js' let particleTexture const getTexture = ()=>{ if (!particleTexture) { particleTexture = new THREE.TextureLoader().load(Potree.resourcePath + '/textures/explode.png') } return particleTexture } const sphereGeo = new THREE.SphereBufferGeometry(1, 10,4); const sphereMat = new THREE.MeshBasicMaterial({wireframe:true, color:"#ffffff"}) const defaults = { position: new THREE.Vector3(0,0,1), positionShape: Shape.SPHERE, positionRange: new THREE.Vector3(1,1,1), //cube radius: 1.3, //sphere velocityShape: Shape.SPHERE, velocity: new THREE.Vector3(0,0,2), //cube velocityRange: new THREE.Vector3(0,0,3), //sphere speed: 0.4, speedRange: 1, size: 0.4, sizeRange: 2, //sizeTween: new Tween( [0, 0.05, 0.3, 0.45], [0, 1, 3, 0.1] ), sizeTween: [[0, 0.04, 0.2, 1],[0.1, 1, 6, 8]] , color: new THREE.Vector3(1.0,1.0,1.0), colorRange: new THREE.Vector3(0,0,0), colorTween: new Tween(), opacity: 1.0, opacityRange: 0.0, opacityTween: new Tween([0, 0.06, 0.3, 0.8, 1],[0, 1, 0.3, 0.05, 0]), blendMode: THREE.AdditiveBlending, acceleration: 0.5, accelerationRange: 0, angle: 0, angleRange: 0, angleVelocity: 0, angleVelocityRange: 0, angleAcceleration: 0, angleAccelerationRange: 0, strength:1, //particlesPerSecond: 8, particleDeathAge: 0.7 , recycleTimes : 3 , //每个粒子在一次爆炸后循环次数,循环完毕进入particleSpaceTime,等待下一次爆炸. //爆炸时长: particleDeathAge * (recycleTimes+1) particleSpaceTime: 3, //间隔 } class ExplodeParticle extends THREE.Points { constructor(params) { super() this.age = 0 this.alive = true //this.deathAge = 60 this.loop = true this.blendMode = THREE.NormalBlending this.setParameters(params) this.createParticles() this.frustumCulled = false//似乎是禁止相机裁剪,否则会在某些角度消失。但是会不会更耗性能呢? //------------------------------------ this.setSize({viewport:viewer.mainViewport}) this.setFov(viewer.fov) let setSize = (e)=>{ if(e.viewport.name != "MainView")return this.setSize(e) } let setFov = (e)=>{ this.setFov(e.fov) } /* let reStart = (e)=>{ if(e.v){//重新一个个放出粒子,否则会一股脑儿全部出来,因为同时大于粒子周期了一起重新生成出现。 setTimeout(()=>{//会先update一次delta为pageUnvisile的时间才触发 //console.log('归零') //this.reStart() },1) } } */ viewer.addEventListener('resize',setSize) viewer.addEventListener('fov_changed',setFov) //viewer.addEventListener('pageVisible', reStart) this.addEventListener('dispose',()=>{ viewer.removeEventListener('resize',setSize) viewer.removeEventListener('fov_changed',setFov) //viewer.removeEventListener('pageVisible', reStart) }) } computeParams(){ if(this.curve){ this.position.copy(this.curve.points[0]) } const minSize = 0.8, maxSize = 10, minRadiusBound = 0.2, maxRadiusBound = 20; let size = minSize + (maxSize - minSize) * THREE.Math.smoothstep(this.radius*this.strength , minRadiusBound, maxRadiusBound); this.sizeTween.values = this.defaultSizeTween.values.map(e=> e*size) this.particleCount = Math.ceil( this.strength * this.radius * 5 /* * this.radius * this.radius */ ) this.speed = defaults.speed * this.radius; this.speedRange = defaults.speedRange * this.radius; console.log(this.particleCount) { this.boundPoints = [] this.boundPoints.push(this.position.clone()) let maxSize = this.sizeTween.values.slice().sort((a,b)=>b-a)[0] let margin = maxSize * 0.35 + 0.5; let scale = this.radius+margin let sphere = new THREE.Sphere(this.position, scale)//加上防止剪裁 this.boundingSphere = sphere //虽然还是会有一些后续移动的会超出 this.boundingBox = new THREE.Box3().setFromCenterAndSize(this.position, new THREE.Vector3(scale*2,scale*2,scale*2)) /* if(!this.debugSphere){ this.debugSphere = new THREE.Mesh(sphereGeo, sphereMat) this.add(this.debugSphere) } this.debugSphere.scale.set(scale,scale,scale) */ } } getPointsForBound(){ return this.boundPoints; //可以用于expand实时bound的点, 不含particle的size等边距 } reStart(){ this.age = 0; this.createParticles() } setParameters(params) { params = $.extend({}, defaults, params) for (var key in params) { let value = params[key] if (key == 'position') this.position.copy(value) else if (value instanceof Array && value[0]instanceof Array){ this[key] = new Tween(...value) }else if(value instanceof THREE.Vector3 || value instanceof THREE.Color){ this[ key ] = value.clone() }else{ this[key] = value; } } this.defaultSizeTween = this.sizeTween.clone() //Object.assign(this, params) this.particles = [] this.age = 0.0 this.alive = true this.geometry = new THREE.BufferGeometry() this.computeParams() this.material = new THREE.ShaderMaterial({ uniforms: { u_sampler: { value: this.texture || getTexture() }, heightOfNearPlane: { type: "f", value: 0 }//相对far ,以确保画面缩放时点的大小也会缩放 }, vertexShader, fragmentShader, transparent: true, alphaTest: 0.5, depthTest: this.blendMode == THREE.NormalBlending, blending: this.blendMode }) } createParticles() { this.particles = [] const count = this.particleCount const positionArray = new Float32Array(count * 3) const colorArray = new Float32Array(count * 3) const sizeArray = new Float32Array(count) const angleArray = new Float32Array(count) const opacityArray = new Float32Array(count) const visibleArray = new Float32Array(count) for (let i = 0; i < count; i++) { const particle = this.createParticle() /* positionArray[i * 3] = particle.position.x positionArray[i * 3 + 1] = particle.position.y positionArray[i * 3 + 2] = particle.position.z colorArray[i * 3] = particle.color.r colorArray[i * 3 + 1] = particle.color.g colorArray[i * 3 + 2] = particle.color.b sizeArray[i] = particle.size angleArray[i] = particle.angel opacityArray[i] = particle.opacity visibleArray[i] = particle.alive */ this.particles[i] = particle } this.geometry.setAttribute('position', new THREE.BufferAttribute(positionArray,3)) this.geometry.setAttribute('color', new THREE.BufferAttribute(colorArray,3)) this.geometry.setAttribute('angle', new THREE.BufferAttribute(angleArray,1)) this.geometry.setAttribute('size', new THREE.BufferAttribute(sizeArray,1)) this.geometry.setAttribute('visible', new THREE.BufferAttribute(visibleArray,1)) this.geometry.setAttribute('opacity', new THREE.BufferAttribute(opacityArray,1)) } createParticle() { const particle = new Particle() particle.sizeTween = this.sizeTween particle.colorTween = this.colorTween particle.opacityTween = this.opacityTween particle.deathAge = this.particleDeathAge if (this.positionShape == Shape.CUBE) { particle.position = util.randomVector3(new THREE.Vector3, this.positionRange) } if (this.positionShape == Shape.SPHERE) { /* const z = 2 * Math.random() - 1 const t = Math.PI * 2 * Math.random() const r = Math.sqrt(1 - z*z) const vec3 = new THREE.Vector3(r * Math.cos(t), r * Math.sin(t), z) particle.position = vec3.multiplyScalar(this.radius) */ const y = 2 * Math.random() - 1 const t = Math.PI * 2 * Math.random(); const r = Math.sqrt(1 - y * y); const vec3 = new THREE.Vector3(r * Math.cos(t),y,r * Math.sin(t)); particle.position = vec3.multiplyScalar(this.radius) } if (this.velocityShape == Shape.CUBE) { particle.velocity = util.randomVector3(this.velocity, this.velocityRange) } if (this.velocityShape == Shape.SPHERE) { const direction = new THREE.Vector3().addVectors(particle.position, new THREE.Vector3(0,0,this.radius*2))//向上升? const speed = util.randomValue(this.speed, this.speedRange) particle.velocity = direction.normalize().multiplyScalar(speed) } particle.acceleration = util.randomValue(this.acceleration, this.accelerationRange) particle.angle = util.randomValue(this.angle, this.angleRange) particle.angleVelocity = util.randomValue(this.angleVelocity, this.angleVelocityRange) particle.angleAcceleration = util.randomValue(this.angleAcceleration, this.angleAccelerationRange) particle.size = util.randomValue(this.size, this.sizeRange) const color = util.randomVector3(this.color, this.colorRange) particle.color = new THREE.Color().setHSL(color.x, color.y, color.z) particle.opacity = util.randomValue(this.opacity, this.opacityRange) return particle } update(dt) { if(!viewer.getObjVisiByReason(this,'force')){//被手动隐藏了 return } if(this.delayStartTime>0){ // 爆炸延迟 return this.delayStartTime -= dt } if(!Potree.Utils.isInsideFrustum(this.boundingSphere, viewer.scene.getActiveCamera())){ viewer.updateVisible(this,'isInsideFrustum', false ) //不在视野范围 return }else{ viewer.updateVisible(this,'isInsideFrustum', true ) } //const timeRatio = 0.5 if(dt > 1){ console.log('update dt>1', dt) } //dt *= timeRatio let particleDeathAge = this.particleDeathAge/* * timeRatio */ let particleSpaceTime = this.particleSpaceTime /* * timeRatio */ const recycleIndices = [] const recycleAges = [] const recycleRebornCount = [] const positionArray = this.geometry.attributes.position.array const opacityArray = this.geometry.attributes.opacity.array const visibleArray = this.geometry.attributes.visible.array const colorArray = this.geometry.attributes.color.array const angleArray = this.geometry.attributes.angle.array const sizeArray = this.geometry.attributes.size.array for (let i = 0; i < this.particleCount; i++) { const particle = this.particles[i] if (particle.alive) { particle.update(dt) if (particle.age > particleDeathAge) { particle.alive = 0.0 if(particle.rebornCount >= this.recycleTimes){ particle.deadAge = particle.age - particleDeathAge //已死亡时间 }else{//直接循环 recycleIndices.push(i) recycleAges.push(/* ( */particle.age - particleDeathAge/* )%(this.particleDeathAge ) */) recycleRebornCount.push(particle.rebornCount+1) } } positionArray[i * 3] = particle.position.x positionArray[i * 3 + 1] = particle.position.y positionArray[i * 3 + 2] = particle.position.z colorArray[i * 3] = particle.color.r colorArray[i * 3 + 1] = particle.color.g colorArray[i * 3 + 2] = particle.color.b visibleArray[i] = particle.alive opacityArray[i] = particle.opacity angleArray[i] = particle.angle sizeArray[i] = particle.size }else{ if(particle.rebornCount >= this.recycleTimes){ if(particle.age > particleDeathAge) {//其他已经死亡的粒子的时间继续增加 particle.deadAge += dt } } } if (particle.rebornCount >= this.recycleTimes && particle.age > particleDeathAge) {//已经死亡 if(particle.deadAge >= particleSpaceTime){//死亡时间超过设定的间隔时间后重启 recycleIndices.push(i) let wholeTime = particleDeathAge * (this.recycleTimes+1) + particleSpaceTime recycleAges.push((particle.deadAge - particleSpaceTime)% wholeTime ) //剩余时间就是重生后的age recycleRebornCount.push(0) } } } this.geometry.attributes.size.needsUpdate = true this.geometry.attributes.color.needsUpdate = true this.geometry.attributes.angle.needsUpdate = true this.geometry.attributes.visible.needsUpdate = true this.geometry.attributes.opacity.needsUpdate = true this.geometry.attributes.position.needsUpdate = true if (!this.alive) return if (this.age < particleDeathAge) { let startIndex = Math.round(this.particleCount * (this.age + 0)/ particleDeathAge) let endIndex = Math.round(this.particleCount * (this.age + dt)/ particleDeathAge) if (endIndex > this.particleCount) { endIndex = this.particleCount } for (let i = startIndex; i < endIndex; i++) { this.particles[i].alive = 1.0 } } for (let j = 0; j < recycleIndices.length; j++) { let i = recycleIndices[j] this.particles[i] = this.createParticle() this.particles[i].alive = 1.0 //出生 this.particles[i].age = recycleAges[j] this.particles[i].rebornCount= recycleRebornCount[j] /* if(this.particles[i].age < particleDeathAge){ positionArray[i * 3] = this.particles[i].position.x positionArray[i * 3 + 1] = this.particles[i].position.y positionArray[i * 3 + 2] = this.particles[i].position.z visibleArray[i] = particle.alive? } */ } this.geometry.attributes.position.needsUpdate = true this.age += dt if (this.age > this.deathAge && !this.loop) { this.alive = false } } setSize(e) { let viewport = e.viewport this.screenHeight = viewport.resolution.y this.setPerspective(this.fov, this.screenHeight) } setFov(fov) { this.fov = fov this.setPerspective(this.fov, this.screenHeight) } setPerspective(fov, height) { //this.uniforms.heightOfNearPlane.value = Math.abs(height / (2 * Math.tan(THREE.Math.degToRad(fov * 0.5)))); let far = Math.abs(height / (2 * Math.tan(THREE.Math.degToRad(fov * 0.5)))); this.material.uniforms.heightOfNearPlane.value = far } updateGeometry(){ this.computeParams() this.reStart() } dispose(){ this.geometry.dispose(); this.material.dispose(); this.dispatchEvent('dispose') } } export default ExplodeParticle