ExplodeParticle.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. import*as THREE from "../../../../libs/three.js/build/three.module.js";
  2. import {vertexShader, fragmentShader} from './shader.js'
  3. import Tween from '../Tween.js'
  4. import Particle from './Particle.js'
  5. import {util} from './Util.js'
  6. import {Shape} from './const.js'
  7. let particleTexture
  8. const getTexture = ()=>{
  9. if (!particleTexture) {
  10. particleTexture = new THREE.TextureLoader().load(Potree.resourcePath + '/textures/explode.png')
  11. }
  12. return particleTexture
  13. }
  14. const sphereGeo = new THREE.SphereBufferGeometry(1, 10,4);
  15. const sphereMat = new THREE.MeshBasicMaterial({wireframe:true, color:"#ffffff"})
  16. const defaults = {
  17. position: new THREE.Vector3(0,0,1),
  18. positionShape: Shape.SPHERE,
  19. positionRange: new THREE.Vector3(1,1,1),
  20. //cube
  21. radius: 1.3,
  22. //sphere
  23. velocityShape: Shape.SPHERE,
  24. velocity: new THREE.Vector3(0,0,2),
  25. //cube
  26. velocityRange: new THREE.Vector3(0,0,3),
  27. //sphere
  28. speed: 0.4,
  29. speedRange: 1,
  30. size: 0.4,
  31. sizeRange: 2,
  32. //sizeTween: new Tween( [0, 0.05, 0.3, 0.45], [0, 1, 3, 0.1] ),
  33. sizeTween: [[0, 0.04, 0.2, 1],[0.1, 1, 6, 8]] ,
  34. color: new THREE.Vector3(1.0,1.0,1.0),
  35. colorRange: new THREE.Vector3(0,0,0),
  36. colorTween: new Tween(),
  37. opacity: 1.0,
  38. opacityRange: 0.0,
  39. opacityTween: new Tween([0, 0.06, 0.3, 0.8, 1],[0, 1, 0.3, 0.05, 0]),
  40. blendMode: THREE.AdditiveBlending,
  41. acceleration: 0.5,
  42. accelerationRange: 0,
  43. angle: 0,
  44. angleRange: 0,
  45. angleVelocity: 0,
  46. angleVelocityRange: 0,
  47. angleAcceleration: 0,
  48. angleAccelerationRange: 0,
  49. strength:1,
  50. //particlesPerSecond: 8,
  51. particleDeathAge: 0.7 ,
  52. recycleTimes : 3 , //每个粒子在一次爆炸后循环次数,循环完毕进入particleSpaceTime,等待下一次爆炸.
  53. //爆炸时长: particleDeathAge * (recycleTimes+1)
  54. particleSpaceTime: 3, //间隔
  55. }
  56. class ExplodeParticle extends THREE.Points {
  57. constructor(params) {
  58. super()
  59. this.age = 0
  60. this.alive = true
  61. //this.deathAge = 60
  62. this.loop = true
  63. this.blendMode = THREE.NormalBlending
  64. this.setParameters(params)
  65. this.createParticles()
  66. this.frustumCulled = false//似乎是禁止相机裁剪,否则会在某些角度消失。但是会不会更耗性能呢?
  67. //------------------------------------
  68. this.setSize({viewport:viewer.mainViewport})
  69. this.setFov(viewer.fov)
  70. let setSize = (e)=>{
  71. if(e.viewport.name != "MainView")return
  72. this.setSize(e)
  73. }
  74. let setFov = (e)=>{
  75. this.setFov(e.fov)
  76. }
  77. /* let reStart = (e)=>{
  78. if(e.v){//重新一个个放出粒子,否则会一股脑儿全部出来,因为同时大于粒子周期了一起重新生成出现。
  79. setTimeout(()=>{//会先update一次delta为pageUnvisile的时间才触发
  80. //console.log('归零')
  81. //this.reStart()
  82. },1)
  83. }
  84. } */
  85. viewer.addEventListener('resize',setSize)
  86. viewer.addEventListener('fov_changed',setFov)
  87. //viewer.addEventListener('pageVisible', reStart)
  88. this.addEventListener('dispose',()=>{
  89. viewer.removeEventListener('resize',setSize)
  90. viewer.removeEventListener('fov_changed',setFov)
  91. //viewer.removeEventListener('pageVisible', reStart)
  92. })
  93. }
  94. computeParams(){
  95. if(this.curve){
  96. this.position.copy(this.curve.points[0])
  97. }
  98. const minSize = 0.8, maxSize = 10, minRadiusBound = 0.2, maxRadiusBound = 20;
  99. let size = minSize + (maxSize - minSize) * THREE.Math.smoothstep(this.radius*this.strength , minRadiusBound, maxRadiusBound);
  100. this.sizeTween.values = this.defaultSizeTween.values.map(e=> e*size)
  101. this.particleCount = Math.ceil( this.strength * this.radius * 5 /* * this.radius * this.radius */ )
  102. this.speed = defaults.speed * this.radius;
  103. this.speedRange = defaults.speedRange * this.radius;
  104. console.log(this.particleCount)
  105. {
  106. this.boundPoints = []
  107. this.boundPoints.push(this.position.clone())
  108. let maxSize = this.sizeTween.values.slice().sort((a,b)=>b-a)[0]
  109. let margin = maxSize * 0.35 + 0.5;
  110. let scale = this.radius+margin
  111. let sphere = new THREE.Sphere(this.position, scale)//加上防止剪裁
  112. this.boundingSphere = sphere //虽然还是会有一些后续移动的会超出
  113. this.boundingBox = new THREE.Box3().setFromCenterAndSize(this.position, new THREE.Vector3(scale*2,scale*2,scale*2))
  114. /* if(!this.debugSphere){
  115. this.debugSphere = new THREE.Mesh(sphereGeo, sphereMat)
  116. this.add(this.debugSphere)
  117. }
  118. this.debugSphere.scale.set(scale,scale,scale) */
  119. }
  120. }
  121. getPointsForBound(){
  122. return this.boundPoints; //可以用于expand实时bound的点, 不含particle的size等边距
  123. }
  124. reStart(){
  125. this.age = 0;
  126. this.createParticles()
  127. }
  128. setParameters(params) {
  129. params = $.extend({}, defaults, params)
  130. for (var key in params) {
  131. let value = params[key]
  132. if (key == 'position')
  133. this.position.copy(value)
  134. else if (value instanceof Array && value[0]instanceof Array){
  135. this[key] = new Tween(...value)
  136. }else if(value instanceof THREE.Vector3 || value instanceof THREE.Color){
  137. this[ key ] = value.clone()
  138. }else{
  139. this[key] = value;
  140. }
  141. }
  142. this.defaultSizeTween = this.sizeTween.clone()
  143. //Object.assign(this, params)
  144. this.particles = []
  145. this.age = 0.0
  146. this.alive = true
  147. this.geometry = new THREE.BufferGeometry()
  148. this.computeParams()
  149. this.material = new THREE.ShaderMaterial({
  150. uniforms: {
  151. u_sampler: {
  152. value: this.texture || getTexture()
  153. },
  154. heightOfNearPlane: {
  155. type: "f",
  156. value: 0
  157. }//相对far ,以确保画面缩放时点的大小也会缩放
  158. },
  159. vertexShader,
  160. fragmentShader,
  161. transparent: true,
  162. alphaTest: 0.5,
  163. depthTest: this.blendMode == THREE.NormalBlending,
  164. blending: this.blendMode
  165. })
  166. }
  167. createParticles() {
  168. this.particles = []
  169. const count = this.particleCount
  170. const positionArray = new Float32Array(count * 3)
  171. const colorArray = new Float32Array(count * 3)
  172. const sizeArray = new Float32Array(count)
  173. const angleArray = new Float32Array(count)
  174. const opacityArray = new Float32Array(count)
  175. const visibleArray = new Float32Array(count)
  176. for (let i = 0; i < count; i++) {
  177. const particle = this.createParticle()
  178. /* positionArray[i * 3] = particle.position.x
  179. positionArray[i * 3 + 1] = particle.position.y
  180. positionArray[i * 3 + 2] = particle.position.z
  181. colorArray[i * 3] = particle.color.r
  182. colorArray[i * 3 + 1] = particle.color.g
  183. colorArray[i * 3 + 2] = particle.color.b
  184. sizeArray[i] = particle.size
  185. angleArray[i] = particle.angel
  186. opacityArray[i] = particle.opacity
  187. visibleArray[i] = particle.alive */
  188. this.particles[i] = particle
  189. }
  190. this.geometry.setAttribute('position', new THREE.BufferAttribute(positionArray,3))
  191. this.geometry.setAttribute('color', new THREE.BufferAttribute(colorArray,3))
  192. this.geometry.setAttribute('angle', new THREE.BufferAttribute(angleArray,1))
  193. this.geometry.setAttribute('size', new THREE.BufferAttribute(sizeArray,1))
  194. this.geometry.setAttribute('visible', new THREE.BufferAttribute(visibleArray,1))
  195. this.geometry.setAttribute('opacity', new THREE.BufferAttribute(opacityArray,1))
  196. }
  197. createParticle() {
  198. const particle = new Particle()
  199. particle.sizeTween = this.sizeTween
  200. particle.colorTween = this.colorTween
  201. particle.opacityTween = this.opacityTween
  202. particle.deathAge = this.particleDeathAge
  203. if (this.positionShape == Shape.CUBE) {
  204. particle.position = util.randomVector3(new THREE.Vector3, this.positionRange)
  205. }
  206. if (this.positionShape == Shape.SPHERE) {
  207. /* const z = 2 * Math.random() - 1
  208. const t = Math.PI * 2 * Math.random()
  209. const r = Math.sqrt(1 - z*z)
  210. const vec3 = new THREE.Vector3(r * Math.cos(t), r * Math.sin(t), z)
  211. particle.position = vec3.multiplyScalar(this.radius) */
  212. const y = 2 * Math.random() - 1
  213. const t = Math.PI * 2 * Math.random();
  214. const r = Math.sqrt(1 - y * y);
  215. const vec3 = new THREE.Vector3(r * Math.cos(t),y,r * Math.sin(t));
  216. particle.position = vec3.multiplyScalar(this.radius)
  217. }
  218. if (this.velocityShape == Shape.CUBE) {
  219. particle.velocity = util.randomVector3(this.velocity, this.velocityRange)
  220. }
  221. if (this.velocityShape == Shape.SPHERE) {
  222. const direction = new THREE.Vector3().addVectors(particle.position, new THREE.Vector3(0,0,this.radius*2))//向上升?
  223. const speed = util.randomValue(this.speed, this.speedRange)
  224. particle.velocity = direction.normalize().multiplyScalar(speed)
  225. }
  226. particle.acceleration = util.randomValue(this.acceleration, this.accelerationRange)
  227. particle.angle = util.randomValue(this.angle, this.angleRange)
  228. particle.angleVelocity = util.randomValue(this.angleVelocity, this.angleVelocityRange)
  229. particle.angleAcceleration = util.randomValue(this.angleAcceleration, this.angleAccelerationRange)
  230. particle.size = util.randomValue(this.size, this.sizeRange)
  231. const color = util.randomVector3(this.color, this.colorRange)
  232. particle.color = new THREE.Color().setHSL(color.x, color.y, color.z)
  233. particle.opacity = util.randomValue(this.opacity, this.opacityRange)
  234. return particle
  235. }
  236. update(dt) {
  237. if(!viewer.getObjVisiByReason(this,'force')){//被手动隐藏了
  238. return
  239. }
  240. if(this.delayStartTime>0){ // 爆炸延迟
  241. return this.delayStartTime -= dt
  242. }
  243. if(!Potree.Utils.isInsideFrustum(this.boundingSphere, viewer.scene.getActiveCamera())){
  244. viewer.updateVisible(this,'isInsideFrustum', false ) //不在视野范围
  245. return
  246. }else{
  247. viewer.updateVisible(this,'isInsideFrustum', true )
  248. }
  249. //const timeRatio = 0.5
  250. if(dt > 1){
  251. console.log('update dt>1', dt)
  252. }
  253. //dt *= timeRatio
  254. let particleDeathAge = this.particleDeathAge/* * timeRatio */
  255. let particleSpaceTime = this.particleSpaceTime /* * timeRatio */
  256. const recycleIndices = []
  257. const recycleAges = []
  258. const recycleRebornCount = []
  259. const positionArray = this.geometry.attributes.position.array
  260. const opacityArray = this.geometry.attributes.opacity.array
  261. const visibleArray = this.geometry.attributes.visible.array
  262. const colorArray = this.geometry.attributes.color.array
  263. const angleArray = this.geometry.attributes.angle.array
  264. const sizeArray = this.geometry.attributes.size.array
  265. for (let i = 0; i < this.particleCount; i++) {
  266. const particle = this.particles[i]
  267. if (particle.alive) {
  268. particle.update(dt)
  269. if (particle.age > particleDeathAge) {
  270. particle.alive = 0.0
  271. if(particle.rebornCount >= this.recycleTimes){
  272. particle.deadAge = particle.age - particleDeathAge //已死亡时间
  273. }else{//直接循环
  274. recycleIndices.push(i)
  275. recycleAges.push(/* ( */particle.age - particleDeathAge/* )%(this.particleDeathAge ) */)
  276. recycleRebornCount.push(particle.rebornCount+1)
  277. }
  278. }
  279. positionArray[i * 3] = particle.position.x
  280. positionArray[i * 3 + 1] = particle.position.y
  281. positionArray[i * 3 + 2] = particle.position.z
  282. colorArray[i * 3] = particle.color.r
  283. colorArray[i * 3 + 1] = particle.color.g
  284. colorArray[i * 3 + 2] = particle.color.b
  285. visibleArray[i] = particle.alive
  286. opacityArray[i] = particle.opacity
  287. angleArray[i] = particle.angle
  288. sizeArray[i] = particle.size
  289. }else{
  290. if(particle.rebornCount >= this.recycleTimes){
  291. if(particle.age > particleDeathAge) {//其他已经死亡的粒子的时间继续增加
  292. particle.deadAge += dt
  293. }
  294. }
  295. }
  296. if (particle.rebornCount >= this.recycleTimes && particle.age > particleDeathAge) {//已经死亡
  297. if(particle.deadAge >= particleSpaceTime){//死亡时间超过设定的间隔时间后重启
  298. recycleIndices.push(i)
  299. let wholeTime = particleDeathAge * (this.recycleTimes+1) + particleSpaceTime
  300. recycleAges.push((particle.deadAge - particleSpaceTime)% wholeTime ) //剩余时间就是重生后的age
  301. recycleRebornCount.push(0)
  302. }
  303. }
  304. }
  305. this.geometry.attributes.size.needsUpdate = true
  306. this.geometry.attributes.color.needsUpdate = true
  307. this.geometry.attributes.angle.needsUpdate = true
  308. this.geometry.attributes.visible.needsUpdate = true
  309. this.geometry.attributes.opacity.needsUpdate = true
  310. this.geometry.attributes.position.needsUpdate = true
  311. if (!this.alive)
  312. return
  313. if (this.age < particleDeathAge) {
  314. let startIndex = Math.round(this.particleCount * (this.age + 0)/ particleDeathAge)
  315. let endIndex = Math.round(this.particleCount * (this.age + dt)/ particleDeathAge)
  316. if (endIndex > this.particleCount) {
  317. endIndex = this.particleCount
  318. }
  319. for (let i = startIndex; i < endIndex; i++) {
  320. this.particles[i].alive = 1.0
  321. }
  322. }
  323. for (let j = 0; j < recycleIndices.length; j++) {
  324. let i = recycleIndices[j]
  325. this.particles[i] = this.createParticle()
  326. this.particles[i].alive = 1.0 //出生
  327. this.particles[i].age = recycleAges[j]
  328. this.particles[i].rebornCount= recycleRebornCount[j]
  329. /* if(this.particles[i].age < particleDeathAge){
  330. positionArray[i * 3] = this.particles[i].position.x
  331. positionArray[i * 3 + 1] = this.particles[i].position.y
  332. positionArray[i * 3 + 2] = this.particles[i].position.z
  333. visibleArray[i] = particle.alive?
  334. } */
  335. }
  336. this.geometry.attributes.position.needsUpdate = true
  337. this.age += dt
  338. if (this.age > this.deathAge && !this.loop) {
  339. this.alive = false
  340. }
  341. }
  342. setSize(e) {
  343. let viewport = e.viewport
  344. this.screenHeight = viewport.resolution.y
  345. this.setPerspective(this.fov, this.screenHeight)
  346. }
  347. setFov(fov) {
  348. this.fov = fov
  349. this.setPerspective(this.fov, this.screenHeight)
  350. }
  351. setPerspective(fov, height) {
  352. //this.uniforms.heightOfNearPlane.value = Math.abs(height / (2 * Math.tan(THREE.Math.degToRad(fov * 0.5))));
  353. let far = Math.abs(height / (2 * Math.tan(THREE.Math.degToRad(fov * 0.5))));
  354. this.material.uniforms.heightOfNearPlane.value = far
  355. }
  356. updateGeometry(){
  357. this.computeParams()
  358. this.reStart()
  359. }
  360. dispose(){
  361. this.geometry.dispose();
  362. this.material.dispose();
  363. this.dispatchEvent('dispose')
  364. }
  365. }
  366. export default ExplodeParticle