minecraft-renderer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +297 -0
  2. package/dist/index.html +83 -0
  3. package/dist/static/image/arrow.6f27b59f.png +0 -0
  4. package/dist/static/image/blocksAtlasLatest.7850afa3.png +0 -0
  5. package/dist/static/image/blocksAtlasLegacy.5c76823d.png +0 -0
  6. package/dist/static/image/itemsAtlasLatest.36036f95.png +0 -0
  7. package/dist/static/image/itemsAtlasLegacy.dcb1b58d.png +0 -0
  8. package/dist/static/image/tipped_arrow.6f27b59f.png +0 -0
  9. package/dist/static/js/365.f05233ab.js +8462 -0
  10. package/dist/static/js/365.f05233ab.js.LICENSE.txt +52 -0
  11. package/dist/static/js/async/738.efa27644.js +1 -0
  12. package/dist/static/js/index.092ec5be.js +56 -0
  13. package/dist/static/js/lib-polyfill.98986ac5.js +1 -0
  14. package/dist/static/js/lib-react.5c9129e0.js +2 -0
  15. package/dist/static/js/lib-react.5c9129e0.js.LICENSE.txt +39 -0
  16. package/package.json +104 -0
  17. package/src/assets/destroy_stage_0.png +0 -0
  18. package/src/assets/destroy_stage_1.png +0 -0
  19. package/src/assets/destroy_stage_2.png +0 -0
  20. package/src/assets/destroy_stage_3.png +0 -0
  21. package/src/assets/destroy_stage_4.png +0 -0
  22. package/src/assets/destroy_stage_5.png +0 -0
  23. package/src/assets/destroy_stage_6.png +0 -0
  24. package/src/assets/destroy_stage_7.png +0 -0
  25. package/src/assets/destroy_stage_8.png +0 -0
  26. package/src/assets/destroy_stage_9.png +0 -0
  27. package/src/examples/README.md +146 -0
  28. package/src/examples/appViewerExample.ts +205 -0
  29. package/src/examples/initialMenuStart.ts +161 -0
  30. package/src/graphicsBackend/appViewer.ts +297 -0
  31. package/src/graphicsBackend/config.ts +119 -0
  32. package/src/graphicsBackend/index.ts +10 -0
  33. package/src/graphicsBackend/playerState.ts +61 -0
  34. package/src/graphicsBackend/types.ts +143 -0
  35. package/src/index.ts +97 -0
  36. package/src/lib/DebugGui.ts +190 -0
  37. package/src/lib/animationController.ts +85 -0
  38. package/src/lib/buildSharedConfig.mjs +1 -0
  39. package/src/lib/cameraBobbing.ts +94 -0
  40. package/src/lib/canvas2DOverlay.example.ts +361 -0
  41. package/src/lib/canvas2DOverlay.quickstart.ts +242 -0
  42. package/src/lib/canvas2DOverlay.ts +381 -0
  43. package/src/lib/cleanupDecorator.ts +29 -0
  44. package/src/lib/createPlayerObject.ts +55 -0
  45. package/src/lib/frameTimingCollector.ts +164 -0
  46. package/src/lib/guiRenderer.ts +283 -0
  47. package/src/lib/items.ts +140 -0
  48. package/src/lib/mesherlogReader.ts +131 -0
  49. package/src/lib/moreBlockDataGenerated.json +714 -0
  50. package/src/lib/preflatMap.json +1741 -0
  51. package/src/lib/simpleUtils.ts +40 -0
  52. package/src/lib/smoothSwitcher.ts +168 -0
  53. package/src/lib/spiral.ts +29 -0
  54. package/src/lib/ui/newStats.ts +120 -0
  55. package/src/lib/utils/proxy.ts +23 -0
  56. package/src/lib/utils/skins.ts +63 -0
  57. package/src/lib/utils.ts +76 -0
  58. package/src/lib/workerProxy.ts +342 -0
  59. package/src/lib/worldrendererCommon.ts +1088 -0
  60. package/src/mesher/mesher.ts +253 -0
  61. package/src/mesher/models.ts +769 -0
  62. package/src/mesher/modelsGeometryCommon.ts +142 -0
  63. package/src/mesher/shared.ts +80 -0
  64. package/src/mesher/standaloneRenderer.ts +270 -0
  65. package/src/mesher/test/a.ts +3 -0
  66. package/src/mesher/test/mesherTester.ts +76 -0
  67. package/src/mesher/test/playground.ts +19 -0
  68. package/src/mesher/test/test-perf.ts +74 -0
  69. package/src/mesher/test/tests.test.ts +56 -0
  70. package/src/mesher/world.ts +294 -0
  71. package/src/mesher/worldConstants.ts +1 -0
  72. package/src/modules/index.ts +11 -0
  73. package/src/modules/starfield.ts +313 -0
  74. package/src/modules/types.ts +110 -0
  75. package/src/playerState/playerState.ts +78 -0
  76. package/src/playerState/types.ts +36 -0
  77. package/src/playground/allEntitiesDebug.ts +170 -0
  78. package/src/playground/baseScene.ts +587 -0
  79. package/src/playground/mobileControls.tsx +268 -0
  80. package/src/playground/playground.html +83 -0
  81. package/src/playground/playground.ts +11 -0
  82. package/src/playground/playgroundUi.tsx +140 -0
  83. package/src/playground/reactUtils.ts +71 -0
  84. package/src/playground/scenes/allEntities.ts +13 -0
  85. package/src/playground/scenes/entities.ts +37 -0
  86. package/src/playground/scenes/floorRandom.ts +33 -0
  87. package/src/playground/scenes/frequentUpdates.ts +148 -0
  88. package/src/playground/scenes/geometryExport.ts +142 -0
  89. package/src/playground/scenes/index.ts +12 -0
  90. package/src/playground/scenes/lightingStarfield.ts +40 -0
  91. package/src/playground/scenes/main.ts +313 -0
  92. package/src/playground/scenes/railsCobweb.ts +14 -0
  93. package/src/playground/scenes/rotationIssue.ts +7 -0
  94. package/src/playground/scenes/slabsOptimization.ts +16 -0
  95. package/src/playground/scenes/transparencyIssue.ts +11 -0
  96. package/src/playground/shared.ts +79 -0
  97. package/src/resourcesManager/index.ts +5 -0
  98. package/src/resourcesManager/resourcesManager.ts +314 -0
  99. package/src/shims/minecraftData.ts +41 -0
  100. package/src/sign-renderer/index.html +21 -0
  101. package/src/sign-renderer/index.ts +216 -0
  102. package/src/sign-renderer/noop.js +1 -0
  103. package/src/sign-renderer/playground.ts +38 -0
  104. package/src/sign-renderer/tests.test.ts +69 -0
  105. package/src/sign-renderer/vite.config.ts +10 -0
  106. package/src/three/appShared.ts +75 -0
  107. package/src/three/bannerRenderer.ts +275 -0
  108. package/src/three/cameraShake.ts +120 -0
  109. package/src/three/cinimaticScript.ts +350 -0
  110. package/src/three/documentRenderer.ts +491 -0
  111. package/src/three/entities.ts +1580 -0
  112. package/src/three/entity/EntityMesh.ts +707 -0
  113. package/src/three/entity/animations.js +171 -0
  114. package/src/three/entity/armorModels.json +204 -0
  115. package/src/three/entity/armorModels.ts +36 -0
  116. package/src/three/entity/entities.json +6230 -0
  117. package/src/three/entity/exportedModels.js +38 -0
  118. package/src/three/entity/externalTextures.json +1 -0
  119. package/src/three/entity/models/allay.obj +325 -0
  120. package/src/three/entity/models/arrow.obj +60 -0
  121. package/src/three/entity/models/axolotl.obj +509 -0
  122. package/src/three/entity/models/blaze.obj +601 -0
  123. package/src/three/entity/models/boat.obj +417 -0
  124. package/src/three/entity/models/camel.obj +1061 -0
  125. package/src/three/entity/models/cat.obj +509 -0
  126. package/src/three/entity/models/chicken.obj +371 -0
  127. package/src/three/entity/models/cod.obj +371 -0
  128. package/src/three/entity/models/creeper.obj +279 -0
  129. package/src/three/entity/models/dolphin.obj +371 -0
  130. package/src/three/entity/models/ender_dragon.obj +2993 -0
  131. package/src/three/entity/models/enderman.obj +325 -0
  132. package/src/three/entity/models/endermite.obj +187 -0
  133. package/src/three/entity/models/fox.obj +463 -0
  134. package/src/three/entity/models/frog.obj +739 -0
  135. package/src/three/entity/models/ghast.obj +463 -0
  136. package/src/three/entity/models/goat.obj +601 -0
  137. package/src/three/entity/models/guardian.obj +1015 -0
  138. package/src/three/entity/models/horse.obj +1061 -0
  139. package/src/three/entity/models/llama.obj +509 -0
  140. package/src/three/entity/models/minecart.obj +233 -0
  141. package/src/three/entity/models/parrot.obj +509 -0
  142. package/src/three/entity/models/piglin.obj +739 -0
  143. package/src/three/entity/models/pillager.obj +371 -0
  144. package/src/three/entity/models/rabbit.obj +555 -0
  145. package/src/three/entity/models/sheep.obj +555 -0
  146. package/src/three/entity/models/shulker.obj +141 -0
  147. package/src/three/entity/models/sniffer.obj +693 -0
  148. package/src/three/entity/models/spider.obj +509 -0
  149. package/src/three/entity/models/tadpole.obj +95 -0
  150. package/src/three/entity/models/turtle.obj +371 -0
  151. package/src/three/entity/models/vex.obj +325 -0
  152. package/src/three/entity/models/villager.obj +509 -0
  153. package/src/three/entity/models/warden.obj +463 -0
  154. package/src/three/entity/models/witch.obj +647 -0
  155. package/src/three/entity/models/wolf.obj +509 -0
  156. package/src/three/entity/models/zombie_villager.obj +463 -0
  157. package/src/three/entity/objModels.js +1 -0
  158. package/src/three/fireworks.ts +661 -0
  159. package/src/three/fireworksRenderer.ts +434 -0
  160. package/src/three/globals.d.ts +7 -0
  161. package/src/three/graphicsBackend.ts +274 -0
  162. package/src/three/graphicsBackendOffThread.ts +107 -0
  163. package/src/three/hand.ts +89 -0
  164. package/src/three/holdingBlock.ts +926 -0
  165. package/src/three/index.ts +20 -0
  166. package/src/three/itemMesh.ts +427 -0
  167. package/src/three/modules.d.ts +14 -0
  168. package/src/three/panorama.ts +308 -0
  169. package/src/three/panoramaShared.ts +1 -0
  170. package/src/three/renderSlot.ts +82 -0
  171. package/src/three/skyboxRenderer.ts +406 -0
  172. package/src/three/starField.ts +13 -0
  173. package/src/three/threeJsMedia.ts +731 -0
  174. package/src/three/threeJsMethods.ts +15 -0
  175. package/src/three/threeJsParticles.ts +160 -0
  176. package/src/three/threeJsSound.ts +95 -0
  177. package/src/three/threeJsUtils.ts +90 -0
  178. package/src/three/waypointSprite.ts +435 -0
  179. package/src/three/waypoints.ts +163 -0
  180. package/src/three/world/cursorBlock.ts +172 -0
  181. package/src/three/world/vr.ts +257 -0
  182. package/src/three/worldGeometryExport.ts +259 -0
  183. package/src/three/worldGeometryHandler.ts +279 -0
  184. package/src/three/worldRendererThree.ts +1381 -0
  185. package/src/worldView/index.ts +6 -0
  186. package/src/worldView/types.ts +66 -0
  187. package/src/worldView/worldView.ts +424 -0
@@ -0,0 +1,661 @@
1
+ import * as THREE from 'three'
2
+ import { createCanvas } from '../lib/utils'
3
+
4
+ // Shader code
5
+ const vertexShader = `
6
+ precision highp float;
7
+ attribute vec3 position;
8
+ attribute vec4 color;
9
+ attribute vec3 velocity;
10
+ attribute float adjustSize;
11
+ attribute float mass;
12
+
13
+ uniform mat4 modelViewMatrix;
14
+ uniform mat4 projectionMatrix;
15
+ uniform float size;
16
+
17
+ varying vec4 vColor;
18
+
19
+ void main() {
20
+ vColor = color;
21
+ vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
22
+ gl_PointSize = size * adjustSize * (300.0 / length(mvPosition.xyz));
23
+ gl_Position = projectionMatrix * mvPosition;
24
+ }
25
+ `
26
+
27
+ const fragmentShader = `
28
+ precision highp float;
29
+ uniform sampler2D texture;
30
+ varying vec4 vColor;
31
+
32
+ void main() {
33
+ vec4 texColor = texture2D(texture, gl_PointCoord);
34
+ gl_FragColor = vColor * texColor;
35
+ }
36
+ `
37
+
38
+ // Configuration interfaces
39
+ export interface FireworkLaunchOptions {
40
+ /** Position to launch the firework from */
41
+ position?: THREE.Vector3
42
+ /** Particle size (100-600, default: 300) */
43
+ particleSize?: number
44
+ /** Force rich fireworks type (with trails) */
45
+ forceRich?: boolean
46
+ /** Force basic fireworks type (no trails) */
47
+ forceBasic?: boolean
48
+ }
49
+
50
+ export interface FireworksManagerConfig {
51
+ /** Maximum number of active fireworks at once */
52
+ maxActiveFireworks?: number
53
+ /** Default particle size for fireworks */
54
+ defaultParticleSize?: number
55
+ }
56
+
57
+ // Constants
58
+ export const FIREWORKS_CONFIG = {
59
+ textureSize: 128,
60
+ gravity: new THREE.Vector3(0, -0.005, 0),
61
+ friction: 0.998,
62
+ defaultParticleSize: 300,
63
+ maxActiveFireworks: 5,
64
+ }
65
+
66
+ // Utility functions
67
+ const getOffsetXYZ = (i: number) => {
68
+ const offset = 3
69
+ const index = i * offset
70
+ return { x: index, y: index + 1, z: index + 2 }
71
+ }
72
+
73
+ const getOffsetRGBA = (i: number) => {
74
+ const offset = 4
75
+ const index = i * offset
76
+ return { r: index, g: index + 1, b: index + 2, a: index + 3 }
77
+ }
78
+
79
+ const getRandomNum = (max = 0, min = 0) => Math.floor(Math.random() * (max + 1 - min)) + min
80
+
81
+ // Texture generation
82
+ const drawRadialGradation = (ctx: OffscreenCanvasRenderingContext2D, canvasRadius: number, canvasW: number, canvasH: number) => {
83
+ ctx.save()
84
+ const gradient = ctx.createRadialGradient(canvasRadius, canvasRadius, 0, canvasRadius, canvasRadius, canvasRadius)
85
+ gradient.addColorStop(0, 'rgba(255,255,255,1.0)')
86
+ gradient.addColorStop(0.5, 'rgba(255,255,255,0.5)')
87
+ gradient.addColorStop(1, 'rgba(255,255,255,0)')
88
+ ctx.fillStyle = gradient
89
+ ctx.fillRect(0, 0, canvasW, canvasH)
90
+ ctx.restore()
91
+ }
92
+
93
+ export const createFireworksTexture = () => {
94
+ const diameter = FIREWORKS_CONFIG.textureSize
95
+ const canvas = createCanvas(diameter, diameter)
96
+ const ctx = canvas.getContext('2d')!
97
+ const canvasRadius = diameter / 2
98
+
99
+ drawRadialGradation(ctx, canvasRadius, canvas.width, canvas.height)
100
+ const texture = new THREE.Texture(canvas)
101
+ texture.needsUpdate = true
102
+ return texture
103
+ }
104
+
105
+ // Point mesh creation
106
+ const getPointMesh = (num: number, vels: THREE.Vector3[], type: 'seed' | 'trail' | 'default', texture: THREE.Texture, particleSize = FIREWORKS_CONFIG.defaultParticleSize) => {
107
+ const bufferGeometry = new THREE.BufferGeometry()
108
+ const vertices: number[] = []
109
+ const velocities: number[] = []
110
+ const colors: number[] = []
111
+ const adjustSizes: number[] = []
112
+ const masses: number[] = []
113
+
114
+ const colorType = Math.random() > 0.3 ? 'single' : 'multiple'
115
+ const singleColor = getRandomNum(100, 20) * 0.01
116
+ const multipleColor = () => getRandomNum(100, 1) * 0.01
117
+
118
+ let rgbType: 'red' | 'green' | 'blue' = 'red'
119
+ const rgbTypeDice = Math.random()
120
+ if (rgbTypeDice > 0.66) {
121
+ rgbType = 'red'
122
+ } else if (rgbTypeDice > 0.33) {
123
+ rgbType = 'green'
124
+ } else {
125
+ rgbType = 'blue'
126
+ }
127
+
128
+ for (let i = 0; i < num; i++) {
129
+ const pos = new THREE.Vector3(0, 0, 0)
130
+ vertices.push(pos.x, pos.y, pos.z)
131
+ velocities.push(vels[i].x, vels[i].y, vels[i].z)
132
+
133
+ if (type === 'seed') {
134
+ let size = vels[i].y ** 2 * 0.04
135
+ if (i === 0) size *= 1.1
136
+ adjustSizes.push(size)
137
+ masses.push(size * 0.017)
138
+ colors.push(1, 1, 1, 1)
139
+ } else if (type === 'trail') {
140
+ const size = Math.random() * 0.1 + 0.1
141
+ adjustSizes.push(size)
142
+ masses.push(size * 0.017)
143
+ colors.push(1, 1, 1, 1)
144
+ } else {
145
+ const size = getRandomNum(particleSize, 10) * 0.001
146
+ adjustSizes.push(size)
147
+ masses.push(size * 0.017)
148
+
149
+ if (colorType === 'multiple') {
150
+ colors.push(multipleColor(), multipleColor(), multipleColor(), 1)
151
+ } else {
152
+ switch (rgbType) {
153
+ case 'red':
154
+ colors.push(singleColor, 0.1, 0.1, 1)
155
+ break
156
+ case 'green':
157
+ colors.push(0.1, singleColor, 0.1, 1)
158
+ break
159
+ case 'blue':
160
+ colors.push(0.1, 0.1, singleColor, 1)
161
+ break
162
+ }
163
+ }
164
+ }
165
+ }
166
+
167
+ bufferGeometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3))
168
+ bufferGeometry.setAttribute('velocity', new THREE.Float32BufferAttribute(velocities, 3))
169
+ bufferGeometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 4))
170
+ bufferGeometry.setAttribute('adjustSize', new THREE.Float32BufferAttribute(adjustSizes, 1))
171
+ bufferGeometry.setAttribute('mass', new THREE.Float32BufferAttribute(masses, 1))
172
+
173
+ const shaderMaterial = new THREE.RawShaderMaterial({
174
+ uniforms: {
175
+ size: { value: FIREWORKS_CONFIG.textureSize },
176
+ texture: { value: texture },
177
+ },
178
+ transparent: true,
179
+ depthWrite: false,
180
+ blending: THREE.AdditiveBlending,
181
+ vertexShader,
182
+ fragmentShader,
183
+ })
184
+
185
+ return new THREE.Points(bufferGeometry, shaderMaterial)
186
+ }
187
+
188
+ // Particle mesh classes
189
+ export class ParticleMesh {
190
+ particleNum: number
191
+ timerStartFading: number
192
+ mesh: THREE.Points
193
+
194
+ constructor (num: number, vels: THREE.Vector3[], type: 'seed' | 'trail' | 'default', texture: THREE.Texture, particleSize?: number) {
195
+ this.particleNum = num
196
+ this.timerStartFading = 10
197
+ this.mesh = getPointMesh(num, vels, type, texture, particleSize)
198
+ }
199
+
200
+ update (gravity: THREE.Vector3) {
201
+ if (this.timerStartFading > 0) this.timerStartFading -= 0.3
202
+
203
+ const position = this.mesh.geometry.attributes.position as THREE.BufferAttribute
204
+ const velocity = this.mesh.geometry.attributes.velocity as THREE.BufferAttribute
205
+ const color = this.mesh.geometry.attributes.color as THREE.BufferAttribute
206
+ const mass = this.mesh.geometry.attributes.mass as THREE.BufferAttribute
207
+
208
+ const decrementRandom = () => (Math.random() > 0.5 ? 0.98 : 0.96)
209
+ const decrementByVel = (v: number) => (Math.random() > 0.5 ? 0 : (1 - v) * 0.1)
210
+
211
+ for (let i = 0; i < this.particleNum; i++) {
212
+ const { x, y, z } = getOffsetXYZ(i)
213
+
214
+ velocity.array[y] += gravity.y - mass.array[i]
215
+ velocity.array[x] *= FIREWORKS_CONFIG.friction
216
+ velocity.array[z] *= FIREWORKS_CONFIG.friction
217
+ velocity.array[y] *= FIREWORKS_CONFIG.friction
218
+
219
+ position.array[x] += velocity.array[x]
220
+ position.array[y] += velocity.array[y]
221
+ position.array[z] += velocity.array[z]
222
+
223
+ const { a } = getOffsetRGBA(i)
224
+ if (this.timerStartFading <= 0) {
225
+ color.array[a] *= decrementRandom() - decrementByVel(color.array[a])
226
+ if (color.array[a] < 0.001) color.array[a] = 0
227
+ }
228
+ }
229
+
230
+ position.needsUpdate = true
231
+ velocity.needsUpdate = true
232
+ color.needsUpdate = true
233
+ }
234
+
235
+ disposeAll () {
236
+ this.mesh.geometry.dispose()
237
+ ;(this.mesh.material as THREE.Material).dispose()
238
+ }
239
+ }
240
+
241
+ export class ParticleSeedMesh extends ParticleMesh {
242
+ constructor (num: number, vels: THREE.Vector3[], texture: THREE.Texture) {
243
+ super(num, vels, 'seed', texture)
244
+ }
245
+
246
+ update (gravity: THREE.Vector3) {
247
+ const position = this.mesh.geometry.attributes.position as THREE.BufferAttribute
248
+ const velocity = this.mesh.geometry.attributes.velocity as THREE.BufferAttribute
249
+ const color = this.mesh.geometry.attributes.color as THREE.BufferAttribute
250
+ const mass = this.mesh.geometry.attributes.mass as THREE.BufferAttribute
251
+
252
+ const decrementRandom = () => (Math.random() > 0.3 ? 0.99 : 0.96)
253
+ const decrementByVel = (v: number) => (Math.random() > 0.3 ? 0 : (1 - v) * 0.1)
254
+ const shake = () => (Math.random() > 0.5 ? 0.05 : -0.05)
255
+ const dice = () => Math.random() > 0.1
256
+ const _f = FIREWORKS_CONFIG.friction * 0.98
257
+
258
+ for (let i = 0; i < this.particleNum; i++) {
259
+ const { x, y, z } = getOffsetXYZ(i)
260
+
261
+ velocity.array[y] += gravity.y - mass.array[i]
262
+ velocity.array[x] *= _f
263
+ velocity.array[z] *= _f
264
+ velocity.array[y] *= _f
265
+
266
+ position.array[x] += velocity.array[x]
267
+ position.array[y] += velocity.array[y]
268
+ position.array[z] += velocity.array[z]
269
+
270
+ if (dice()) position.array[x] += shake()
271
+ if (dice()) position.array[z] += shake()
272
+
273
+ const { a } = getOffsetRGBA(i)
274
+ color.array[a] *= decrementRandom() - decrementByVel(color.array[a])
275
+ if (color.array[a] < 0.001) color.array[a] = 0
276
+ }
277
+
278
+ position.needsUpdate = true
279
+ velocity.needsUpdate = true
280
+ color.needsUpdate = true
281
+ }
282
+ }
283
+
284
+ export class ParticleTailMesh extends ParticleMesh {
285
+ constructor (num: number, vels: THREE.Vector3[], texture: THREE.Texture) {
286
+ super(num, vels, 'trail', texture)
287
+ }
288
+
289
+ update (gravity: THREE.Vector3) {
290
+ const position = this.mesh.geometry.attributes.position as THREE.BufferAttribute
291
+ const velocity = this.mesh.geometry.attributes.velocity as THREE.BufferAttribute
292
+ const color = this.mesh.geometry.attributes.color as THREE.BufferAttribute
293
+ const mass = this.mesh.geometry.attributes.mass as THREE.BufferAttribute
294
+
295
+ const decrementRandom = () => (Math.random() > 0.3 ? 0.98 : 0.95)
296
+ const shake = () => (Math.random() > 0.5 ? 0.05 : -0.05)
297
+ const dice = () => Math.random() > 0.2
298
+
299
+ for (let i = 0; i < this.particleNum; i++) {
300
+ const { x, y, z } = getOffsetXYZ(i)
301
+
302
+ velocity.array[y] += gravity.y - mass.array[i]
303
+ velocity.array[x] *= FIREWORKS_CONFIG.friction
304
+ velocity.array[z] *= FIREWORKS_CONFIG.friction
305
+ velocity.array[y] *= FIREWORKS_CONFIG.friction
306
+
307
+ position.array[x] += velocity.array[x]
308
+ position.array[y] += velocity.array[y]
309
+ position.array[z] += velocity.array[z]
310
+
311
+ if (dice()) position.array[x] += shake()
312
+ if (dice()) position.array[z] += shake()
313
+
314
+ const { a } = getOffsetRGBA(i)
315
+ color.array[a] *= decrementRandom()
316
+ if (color.array[a] < 0.001) color.array[a] = 0
317
+ }
318
+
319
+ position.needsUpdate = true
320
+ velocity.needsUpdate = true
321
+ color.needsUpdate = true
322
+ }
323
+ }
324
+
325
+ // Fireworks classes
326
+ export class BasicFireworks {
327
+ meshGroup: THREE.Group
328
+ isExplode: boolean
329
+ petalsNum: number
330
+ life: number
331
+ seed: ParticleSeedMesh
332
+ flowerSizeRate: number
333
+ flower?: ParticleMesh
334
+ texture: THREE.Texture
335
+ particleSize: number
336
+
337
+ constructor (texture: THREE.Texture, particleSize = FIREWORKS_CONFIG.defaultParticleSize, startPosition?: THREE.Vector3) {
338
+ this.meshGroup = new THREE.Group()
339
+ this.isExplode = false
340
+ this.texture = texture
341
+ this.particleSize = particleSize
342
+
343
+ const max = 400
344
+ const min = 150
345
+ this.petalsNum = getRandomNum(max, min)
346
+ this.life = 150
347
+ this.seed = this.getSeed(startPosition)
348
+ this.meshGroup.add(this.seed.mesh)
349
+ this.flowerSizeRate = THREE.MathUtils.mapLinear(this.petalsNum, min, max, 0.4, 0.7)
350
+ }
351
+
352
+ getSeed (startPosition?: THREE.Vector3): ParticleSeedMesh {
353
+ const num = 40
354
+ const vels: THREE.Vector3[] = []
355
+
356
+ for (let i = 0; i < num; i++) {
357
+ const vx = 0
358
+ const vy = i === 0 ? Math.random() * 2.5 + 0.9 : Math.random() * 2 + 0.4
359
+ const vz = 0
360
+ vels.push(new THREE.Vector3(vx, vy, vz))
361
+ }
362
+
363
+ const pm = new ParticleSeedMesh(num, vels, this.texture)
364
+ if (startPosition) {
365
+ pm.mesh.position.set(startPosition.x, startPosition.y, startPosition.z)
366
+ } else {
367
+ const x = Math.random() * 80 - 40
368
+ const y = -50
369
+ const z = Math.random() * 80 - 40
370
+ pm.mesh.position.set(x, y, z)
371
+ }
372
+
373
+ return pm
374
+ }
375
+
376
+ explode (pos: THREE.Vector3) {
377
+ this.isExplode = true
378
+ this.flower = this.getFlower(pos)
379
+ this.meshGroup.add(this.flower.mesh)
380
+ this.meshGroup.remove(this.seed.mesh)
381
+ this.seed.disposeAll()
382
+ }
383
+
384
+ getFlower (pos: THREE.Vector3): ParticleMesh {
385
+ const num = this.petalsNum
386
+ const vels: THREE.Vector3[] = []
387
+ let radius: number
388
+ const dice = Math.random()
389
+
390
+ if (dice > 0.5) {
391
+ for (let i = 0; i < num; i++) {
392
+ radius = getRandomNum(120, 60) * 0.01
393
+ const theta = THREE.MathUtils.degToRad(Math.random() * 180)
394
+ const phi = THREE.MathUtils.degToRad(Math.random() * 360)
395
+ const vx = Math.sin(theta) * Math.cos(phi) * radius
396
+ const vy = Math.sin(theta) * Math.sin(phi) * radius
397
+ const vz = Math.cos(theta) * radius
398
+ const vel = new THREE.Vector3(vx, vy, vz)
399
+ vel.multiplyScalar(this.flowerSizeRate)
400
+ vels.push(vel)
401
+ }
402
+ } else {
403
+ const zStep = 180 / num
404
+ const trad = (360 * (Math.random() * 20 + 1)) / num
405
+ const xStep = trad
406
+ const yStep = trad
407
+ radius = getRandomNum(120, 60) * 0.01
408
+
409
+ for (let i = 0; i < num; i++) {
410
+ const sphereRate = Math.sin(THREE.MathUtils.degToRad(zStep * i))
411
+ const vz = Math.cos(THREE.MathUtils.degToRad(zStep * i)) * radius
412
+ const vx = Math.cos(THREE.MathUtils.degToRad(xStep * i)) * sphereRate * radius
413
+ const vy = Math.sin(THREE.MathUtils.degToRad(yStep * i)) * sphereRate * radius
414
+ const vel = new THREE.Vector3(vx, vy, vz)
415
+ vel.multiplyScalar(this.flowerSizeRate)
416
+ vels.push(vel)
417
+ }
418
+ }
419
+
420
+ const particleMesh = new ParticleMesh(num, vels, 'default', this.texture, this.particleSize)
421
+ particleMesh.mesh.position.set(pos.x, pos.y, pos.z)
422
+ return particleMesh
423
+ }
424
+
425
+ update (gravity: THREE.Vector3) {
426
+ if (this.isExplode) {
427
+ this.flower!.update(gravity)
428
+ if (this.life > 0) this.life -= 1
429
+ } else {
430
+ this.drawTail()
431
+ }
432
+ }
433
+
434
+ drawTail () {
435
+ this.seed.update(FIREWORKS_CONFIG.gravity)
436
+
437
+ const position = this.seed.mesh.geometry.attributes.position as THREE.BufferAttribute
438
+ const velocity = this.seed.mesh.geometry.attributes.velocity as THREE.BufferAttribute
439
+
440
+ let count = 0
441
+
442
+ // Check if the y-axis speed is down for all particles
443
+ for (let i = 0, l = velocity.array.length; i < l; i++) {
444
+ const v = velocity.array[i]
445
+ const index = i % 3
446
+ if (index === 1 && v > 0) {
447
+ count++
448
+ }
449
+ }
450
+
451
+ const isComplete = count === 0
452
+ if (!isComplete) return
453
+
454
+ const { x, y, z } = this.seed.mesh.position
455
+ const flowerPos = new THREE.Vector3(x, y, z)
456
+ let highestPos = 0
457
+ let offsetPos: THREE.Vector3 | undefined
458
+
459
+ for (let i = 0, l = position.array.length; i < l; i++) {
460
+ const p = position.array[i]
461
+ const index = i % 3
462
+ if (index === 1 && p > highestPos) {
463
+ highestPos = p
464
+ offsetPos = new THREE.Vector3(position.array[i - 1], p, position.array[i + 2])
465
+ }
466
+ }
467
+
468
+ if (offsetPos) {
469
+ flowerPos.add(offsetPos)
470
+ this.explode(flowerPos)
471
+ }
472
+ }
473
+ }
474
+
475
+ export class RichFireworks extends BasicFireworks {
476
+ tailMeshGroup: THREE.Group
477
+ tails: ParticleTailMesh[]
478
+
479
+ constructor (texture: THREE.Texture, particleSize = FIREWORKS_CONFIG.defaultParticleSize, startPosition?: THREE.Vector3) {
480
+ super(texture, particleSize, startPosition)
481
+
482
+ const max = 150
483
+ const min = 100
484
+ this.petalsNum = getRandomNum(max, min)
485
+ this.flowerSizeRate = THREE.MathUtils.mapLinear(this.petalsNum, min, max, 0.4, 0.7)
486
+ this.tailMeshGroup = new THREE.Group()
487
+ this.tails = []
488
+ }
489
+
490
+ explode (pos: THREE.Vector3) {
491
+ this.isExplode = true
492
+ this.flower = this.getFlower(pos)
493
+ this.tails = this.getTail()
494
+ this.meshGroup.add(this.flower.mesh)
495
+ this.meshGroup.add(this.tailMeshGroup)
496
+ }
497
+
498
+ getTail (): ParticleTailMesh[] {
499
+ const tails: ParticleTailMesh[] = []
500
+ const num = 20
501
+ const petalColor = this.flower!.mesh.geometry.attributes.color as THREE.BufferAttribute
502
+
503
+ for (let i = 0; i < this.petalsNum; i++) {
504
+ const vels: THREE.Vector3[] = []
505
+ for (let j = 0; j < num; j++) {
506
+ vels.push(new THREE.Vector3(0, 0, 0))
507
+ }
508
+
509
+ const tail = new ParticleTailMesh(num, vels, this.texture)
510
+ const { r, g, b, a } = getOffsetRGBA(i)
511
+
512
+ const petalR = petalColor.array[r]
513
+ const petalG = petalColor.array[g]
514
+ const petalB = petalColor.array[b]
515
+ const petalA = petalColor.array[a]
516
+
517
+ const position = tail.mesh.geometry.attributes.position as THREE.BufferAttribute
518
+ const color = tail.mesh.geometry.attributes.color as THREE.BufferAttribute
519
+
520
+ for (let k = 0; k < position.count; k++) {
521
+ const rgba = getOffsetRGBA(k)
522
+ color.array[rgba.r] = petalR
523
+ color.array[rgba.g] = petalG
524
+ color.array[rgba.b] = petalB
525
+ color.array[rgba.a] = petalA
526
+ }
527
+
528
+ const { x, y, z } = this.flower!.mesh.position
529
+ tail.mesh.position.set(x, y, z)
530
+ tails.push(tail)
531
+ this.tailMeshGroup.add(tail.mesh)
532
+ }
533
+
534
+ return tails
535
+ }
536
+
537
+ update (gravity: THREE.Vector3) {
538
+ if (this.isExplode) {
539
+ this.flower!.update(gravity)
540
+
541
+ const flowerGeometry = this.flower!.mesh.geometry.attributes.position as THREE.BufferAttribute
542
+
543
+ for (let i = 0, l = this.tails.length; i < l; i++) {
544
+ const tail = this.tails[i]
545
+ tail.update(gravity)
546
+
547
+ const { x, y, z } = getOffsetXYZ(i)
548
+ const flowerPos = new THREE.Vector3(
549
+ flowerGeometry.array[x],
550
+ flowerGeometry.array[y],
551
+ flowerGeometry.array[z]
552
+ )
553
+
554
+ const position = tail.mesh.geometry.attributes.position as THREE.BufferAttribute
555
+ const velocity = tail.mesh.geometry.attributes.velocity as THREE.BufferAttribute
556
+
557
+ for (let k = 0; k < position.count; k++) {
558
+ const offset = getOffsetXYZ(k)
559
+ const desiredVelocity = new THREE.Vector3()
560
+ const tailPos = new THREE.Vector3(position.array[offset.x], position.array[offset.y], position.array[offset.z])
561
+ const tailVel = new THREE.Vector3(velocity.array[offset.x], velocity.array[offset.y], velocity.array[offset.z])
562
+ desiredVelocity.subVectors(flowerPos, tailPos)
563
+ const steer = desiredVelocity.sub(tailVel)
564
+ steer.normalize()
565
+ steer.multiplyScalar(Math.random() * 0.0003 * this.life)
566
+ velocity.array[offset.x] += steer.x
567
+ velocity.array[offset.y] += steer.y
568
+ velocity.array[offset.z] += steer.z
569
+ }
570
+ velocity.needsUpdate = true
571
+ }
572
+
573
+ if (this.life > 0) this.life -= 1.2
574
+ } else {
575
+ this.drawTail()
576
+ }
577
+ }
578
+ }
579
+
580
+ // Manager class for handling multiple fireworks
581
+ export class FireworksManager {
582
+ fireworksInstances: Array<BasicFireworks | RichFireworks>
583
+ scene: THREE.Scene
584
+ texture: THREE.Texture
585
+ particleSize: number
586
+ maxFireworks: number
587
+
588
+ constructor (scene: THREE.Scene, config?: FireworksManagerConfig) {
589
+ this.fireworksInstances = []
590
+ this.scene = scene
591
+ this.texture = createFireworksTexture()
592
+ this.particleSize = config?.defaultParticleSize ?? FIREWORKS_CONFIG.defaultParticleSize
593
+ this.maxFireworks = config?.maxActiveFireworks ?? FIREWORKS_CONFIG.maxActiveFireworks
594
+ }
595
+
596
+ launchFirework (options?: FireworkLaunchOptions) {
597
+ if (this.fireworksInstances.length >= this.maxFireworks) return
598
+
599
+ const particleSize = options?.particleSize ?? this.particleSize
600
+ const position = options?.position
601
+
602
+ let fw: BasicFireworks | RichFireworks
603
+ if (options?.forceRich) {
604
+ fw = new RichFireworks(this.texture, particleSize, position)
605
+ } else if (options?.forceBasic) {
606
+ fw = new BasicFireworks(this.texture, particleSize, position)
607
+ } else {
608
+ fw = Math.random() > 0.5 ? new BasicFireworks(this.texture, particleSize, position) : new RichFireworks(this.texture, particleSize, position)
609
+ }
610
+
611
+ this.fireworksInstances.push(fw)
612
+ this.scene.add(fw.meshGroup)
613
+ }
614
+
615
+ update () {
616
+ const explodedIndexList: number[] = []
617
+
618
+ for (let i = this.fireworksInstances.length - 1; i >= 0; i--) {
619
+ const instance = this.fireworksInstances[i]
620
+ instance.update(FIREWORKS_CONFIG.gravity)
621
+ if (instance.isExplode) explodedIndexList.push(i)
622
+ }
623
+
624
+ for (let i = 0, l = explodedIndexList.length; i < l; i++) {
625
+ const index = explodedIndexList[i]
626
+ const instance = this.fireworksInstances[index]
627
+ if (!instance) continue
628
+
629
+ instance.meshGroup.remove(instance.seed.mesh)
630
+ instance.seed.disposeAll()
631
+
632
+ if (instance.life <= 0) {
633
+ this.scene.remove(instance.meshGroup)
634
+ if (instance instanceof RichFireworks && instance.tailMeshGroup) {
635
+ for (const v of instance.tails) {
636
+ v.disposeAll()
637
+ }
638
+ }
639
+ instance.flower?.disposeAll()
640
+ this.fireworksInstances.splice(index, 1)
641
+ }
642
+ }
643
+ }
644
+
645
+ clear () {
646
+ for (const instance of this.fireworksInstances) {
647
+ this.scene.remove(instance.meshGroup)
648
+ instance.seed.disposeAll()
649
+ if (instance.flower) instance.flower.disposeAll()
650
+ if (instance instanceof RichFireworks) {
651
+ for (const v of instance.tails) v.disposeAll()
652
+ }
653
+ }
654
+ this.fireworksInstances = []
655
+ }
656
+
657
+ dispose () {
658
+ this.clear()
659
+ this.texture.dispose()
660
+ }
661
+ }