minecraft-renderer 0.1.33 → 0.1.35
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.
- package/dist/minecraft-renderer.js +56 -56
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +422 -422
- package/package.json +1 -1
- package/src/graphicsBackend/config.ts +2 -1
- package/src/lib/worldrendererCommon.ts +2 -0
- package/src/playground/scenes/main.ts +1 -1
- package/src/three/chunkMeshManager.ts +808 -0
- package/src/three/entities.ts +42 -36
- package/src/three/graphicsBackendBase.ts +9 -1
- package/src/three/modules/blockBreakParticles.ts +438 -0
- package/src/three/modules/index.ts +2 -0
- package/src/three/modules/sciFiWorldReveal.ts +10 -21
- package/src/three/panorama.ts +1 -1
- package/src/three/worldRendererThree.ts +60 -39
- package/src/three/worldBlockGeometry.ts +0 -355
package/src/three/entities.ts
CHANGED
|
@@ -149,43 +149,49 @@ const addNametag = (entity, options: { fontFamily: string }, mesh, version: stri
|
|
|
149
149
|
c.removeFromParent()
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
-
if (entity.username
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
nameTag.
|
|
171
|
-
nameTag.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
nameTag.name = 'nametag'
|
|
185
|
-
|
|
186
|
-
mesh.add(nameTag)
|
|
187
|
-
return nameTag
|
|
152
|
+
if (entity.username === undefined || entity.username === null) return
|
|
153
|
+
|
|
154
|
+
const plainUsername =
|
|
155
|
+
typeof entity.username === 'string'
|
|
156
|
+
? entity.username
|
|
157
|
+
: new (PrismarineChatLoader(version))(entity.username).toString()
|
|
158
|
+
if (plainUsername.startsWith('EMPTY')) return
|
|
159
|
+
|
|
160
|
+
const canvas = getUsernameTexture(entity, options, version)
|
|
161
|
+
if (!canvas) return
|
|
162
|
+
const tex = new THREE.Texture(canvas)
|
|
163
|
+
tex.needsUpdate = true
|
|
164
|
+
let nameTag: THREE.Object3D
|
|
165
|
+
if (entity.nameTagFixed) {
|
|
166
|
+
const geometry = new THREE.PlaneGeometry()
|
|
167
|
+
const material = new THREE.MeshBasicMaterial({ map: tex })
|
|
168
|
+
material.transparent = true
|
|
169
|
+
nameTag = new THREE.Mesh(geometry, material)
|
|
170
|
+
nameTag.rotation.set(entity.pitch, THREE.MathUtils.degToRad(entity.yaw + 180), 0)
|
|
171
|
+
nameTag.position.y += entity.height + 0.3
|
|
172
|
+
} else {
|
|
173
|
+
const spriteMat = new THREE.SpriteMaterial({ map: tex })
|
|
174
|
+
nameTag = new THREE.Sprite(spriteMat)
|
|
175
|
+
nameTag.position.y += entity.height + 0.6
|
|
176
|
+
}
|
|
177
|
+
nameTag.renderOrder = 1000
|
|
178
|
+
nameTag.scale.set(canvas.width * 0.005, canvas.height * 0.005, 1)
|
|
179
|
+
if (entity.nameTagRotationRight) {
|
|
180
|
+
nameTag.applyQuaternion(entity.nameTagRotationRight)
|
|
181
|
+
}
|
|
182
|
+
if (entity.nameTagScale) {
|
|
183
|
+
nameTag.scale.multiply(entity.nameTagScale)
|
|
188
184
|
}
|
|
185
|
+
if (entity.nameTagRotationLeft) {
|
|
186
|
+
nameTag.applyQuaternion(entity.nameTagRotationLeft)
|
|
187
|
+
}
|
|
188
|
+
if (entity.nameTagTranslation) {
|
|
189
|
+
nameTag.position.add(entity.nameTagTranslation)
|
|
190
|
+
}
|
|
191
|
+
nameTag.name = 'nametag'
|
|
192
|
+
|
|
193
|
+
mesh.add(nameTag)
|
|
194
|
+
return nameTag
|
|
189
195
|
}
|
|
190
196
|
|
|
191
197
|
// todo cleanup
|
|
@@ -68,6 +68,14 @@ export const getBackendMethods = (worldRenderer: WorldRendererThree): any => {
|
|
|
68
68
|
setSkyboxImage: worldRenderer.skyboxRenderer.setSkyboxImage.bind(worldRenderer.skyboxRenderer),
|
|
69
69
|
// Rain methods
|
|
70
70
|
setRain: (newState: boolean) => worldRenderer.toggleModule('rain', newState),
|
|
71
|
+
spawnBlockBreakParticles(x: number, y: number, z: number, blockName: string, floorMap: number[], biomeName?: string) {
|
|
72
|
+
const module = worldRenderer.getModule<import('./modules/blockBreakParticles').BlockBreakParticlesModule>('blockBreakParticles')
|
|
73
|
+
module?.spawnBlockBreakParticles(x, y, z, blockName, floorMap, biomeName)
|
|
74
|
+
},
|
|
75
|
+
spawnBlockCrackParticle(x: number, y: number, z: number, face: number, blockName: string, floorMap: number[], biomeName?: string) {
|
|
76
|
+
const module = worldRenderer.getModule<import('./modules/blockBreakParticles').BlockBreakParticlesModule>('blockBreakParticles')
|
|
77
|
+
module?.spawnCrackParticle(x, y, z, face, blockName, floorMap, biomeName)
|
|
78
|
+
},
|
|
71
79
|
async loadGeometryExport(exportData: any) {
|
|
72
80
|
// Import dynamically to avoid circular dependencies
|
|
73
81
|
const { applyWorldGeometryExport } = await import('./worldGeometryExport')
|
|
@@ -217,7 +225,7 @@ export const createGraphicsBackendBase = () => {
|
|
|
217
225
|
},
|
|
218
226
|
get left() {
|
|
219
227
|
return {
|
|
220
|
-
'Geo Memory': worldRenderer?.
|
|
228
|
+
'Geo Memory': worldRenderer?.chunkMeshManager.getEstimatedMemoryUsage().total ?? '-'
|
|
221
229
|
}
|
|
222
230
|
},
|
|
223
231
|
}),
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
import type { WorldRendererThree } from '../worldRendererThree'
|
|
4
|
+
import type { RendererModuleController, RendererModuleManifest } from '../rendererModuleSystem'
|
|
5
|
+
|
|
6
|
+
// --- Tint data (lazy-loaded from globalThis.loadedData.tints at runtime) ---
|
|
7
|
+
const tints: Record<string, Record<string, [number, number, number]>> = {}
|
|
8
|
+
let tintsInitialized = false
|
|
9
|
+
|
|
10
|
+
function ensureTintsLoaded(): void {
|
|
11
|
+
if (tintsInitialized) return
|
|
12
|
+
const tintsData = (globalThis as any).loadedData?.tints
|
|
13
|
+
if (!tintsData) return
|
|
14
|
+
for (const key of Object.keys(tintsData)) {
|
|
15
|
+
tints[key] = prepareTints(tintsData[key])
|
|
16
|
+
}
|
|
17
|
+
tintsInitialized = true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function prepareTints(data: any): Record<string, [number, number, number]> {
|
|
21
|
+
const result: Record<string, [number, number, number]> = {}
|
|
22
|
+
const defaultColor = tintToGl(data.default ?? 0xFFFFFF)
|
|
23
|
+
if (data.data) {
|
|
24
|
+
for (const entry of data.data) {
|
|
25
|
+
const color = tintToGl(entry.color)
|
|
26
|
+
for (const key of entry.keys) {
|
|
27
|
+
result[key] = color
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return new Proxy(result, {
|
|
32
|
+
get(target, prop: string) {
|
|
33
|
+
return target[prop] ?? defaultColor
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function tintToGl(tint: number): [number, number, number] {
|
|
39
|
+
return [
|
|
40
|
+
((tint >> 16) & 0xFF) / 255,
|
|
41
|
+
((tint >> 8) & 0xFF) / 255,
|
|
42
|
+
(tint & 0xFF) / 255,
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveTintColor(blockName: string, biomeName: string): [number, number, number] {
|
|
47
|
+
ensureTintsLoaded()
|
|
48
|
+
if (blockName === 'grass_block') return [1, 1, 1]
|
|
49
|
+
if (blockName === 'redstone_wire') return [1, 1, 1]
|
|
50
|
+
if (blockName === 'birch_leaves' || blockName === 'spruce_leaves' || blockName === 'lily_pad') {
|
|
51
|
+
return tints.constant?.[blockName] ?? [1, 1, 1]
|
|
52
|
+
}
|
|
53
|
+
if (blockName.includes('leaves') || blockName === 'vine') {
|
|
54
|
+
return tints.foliage?.[biomeName] ?? [1, 1, 1]
|
|
55
|
+
}
|
|
56
|
+
const grassTintedBlocks = ['short_grass', 'tall_grass', 'fern', 'large_fern', 'sugar_cane', 'grass']
|
|
57
|
+
if (grassTintedBlocks.includes(blockName)) {
|
|
58
|
+
return tints.grass?.[biomeName] ?? [1, 1, 1]
|
|
59
|
+
}
|
|
60
|
+
return [1, 1, 1]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface BreakParticle {
|
|
64
|
+
mesh: THREE.Mesh
|
|
65
|
+
active: boolean
|
|
66
|
+
x: number; y: number; z: number
|
|
67
|
+
prevX: number; prevY: number; prevZ: number
|
|
68
|
+
xd: number; yd: number; zd: number
|
|
69
|
+
age: number
|
|
70
|
+
maxAge: number
|
|
71
|
+
onGround: boolean
|
|
72
|
+
floorMap: number[]
|
|
73
|
+
blockX: number
|
|
74
|
+
blockZ: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const MAX_PARTICLES = 512
|
|
78
|
+
const TICK_RATE = 1 / 20
|
|
79
|
+
|
|
80
|
+
export class BlockBreakParticlesModule implements RendererModuleController {
|
|
81
|
+
private particles: BreakParticle[] = []
|
|
82
|
+
private sharedMaterial?: THREE.MeshBasicMaterial
|
|
83
|
+
private enabled = false
|
|
84
|
+
private tickAccumulator = 0
|
|
85
|
+
private nextParticleIndex = 0
|
|
86
|
+
|
|
87
|
+
constructor(private readonly worldRenderer: WorldRendererThree) {}
|
|
88
|
+
|
|
89
|
+
enable(): void {
|
|
90
|
+
if (this.enabled) return
|
|
91
|
+
this.enabled = true
|
|
92
|
+
this.ensureMaterial()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
disable(): void {
|
|
96
|
+
if (!this.enabled) return
|
|
97
|
+
this.enabled = false
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
dispose(): void {
|
|
101
|
+
for (const p of this.particles) {
|
|
102
|
+
if (p.active) {
|
|
103
|
+
this.worldRenderer.sceneOrigin.removeAndUntrack(p.mesh)
|
|
104
|
+
}
|
|
105
|
+
p.mesh.geometry.dispose()
|
|
106
|
+
}
|
|
107
|
+
this.particles = []
|
|
108
|
+
this.sharedMaterial?.dispose()
|
|
109
|
+
this.sharedMaterial = undefined
|
|
110
|
+
this.nextParticleIndex = 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
render = (deltaTime: number): void => {
|
|
114
|
+
if (!this.enabled) return
|
|
115
|
+
|
|
116
|
+
this.tickAccumulator += deltaTime
|
|
117
|
+
while (this.tickAccumulator >= TICK_RATE) {
|
|
118
|
+
this.tickAccumulator -= TICK_RATE
|
|
119
|
+
this.tickPhysics()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const alpha = this.tickAccumulator / TICK_RATE
|
|
123
|
+
this.updateVisuals(alpha)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
spawnBlockBreakParticles(worldX: number, worldY: number, worldZ: number, blockName: string, floorMap: number[], biomeName = 'plains'): void {
|
|
127
|
+
if (!this.enabled) return
|
|
128
|
+
|
|
129
|
+
const texInfo = this.resolveBlockTexture(blockName)
|
|
130
|
+
if (!texInfo) return
|
|
131
|
+
|
|
132
|
+
const tintColor = resolveTintColor(blockName, biomeName)
|
|
133
|
+
|
|
134
|
+
for (let ox = 0; ox < 4; ox++) {
|
|
135
|
+
for (let oy = 0; oy < 4; oy++) {
|
|
136
|
+
for (let oz = 0; oz < 4; oz++) {
|
|
137
|
+
const px = worldX + (ox + 0.5) / 4
|
|
138
|
+
const py = worldY + (oy + 0.5) / 4
|
|
139
|
+
const pz = worldZ + (oz + 0.5) / 4
|
|
140
|
+
|
|
141
|
+
let motionX = px - worldX - 0.5
|
|
142
|
+
let motionY = py - worldY - 0.5
|
|
143
|
+
let motionZ = pz - worldZ - 0.5
|
|
144
|
+
|
|
145
|
+
motionX += (Math.random() * 2 - 1) * 0.4
|
|
146
|
+
motionY += (Math.random() * 2 - 1) * 0.4
|
|
147
|
+
motionZ += (Math.random() * 2 - 1) * 0.4
|
|
148
|
+
|
|
149
|
+
const strength = (Math.random() + Math.random() + 1) * 0.15
|
|
150
|
+
const len = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ)
|
|
151
|
+
const xd = (motionX / len) * strength * 0.4
|
|
152
|
+
const yd = (motionY / len) * strength * 0.4 + 0.1
|
|
153
|
+
const zd = (motionZ / len) * strength * 0.4
|
|
154
|
+
|
|
155
|
+
const maxAge = Math.floor(4 / (Math.random() * 0.9 + 0.1))
|
|
156
|
+
|
|
157
|
+
this.createParticle(px, py, pz, xd, yd, zd, maxAge, texInfo, floorMap, worldX, worldZ, 1.0, tintColor)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private tickPhysics(): void {
|
|
164
|
+
for (const p of this.particles) {
|
|
165
|
+
if (!p.active) continue
|
|
166
|
+
|
|
167
|
+
p.prevX = p.x
|
|
168
|
+
p.prevY = p.y
|
|
169
|
+
p.prevZ = p.z
|
|
170
|
+
|
|
171
|
+
p.age++
|
|
172
|
+
if (p.age >= p.maxAge) {
|
|
173
|
+
this.deactivateParticle(p)
|
|
174
|
+
continue
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
p.yd -= 0.04
|
|
178
|
+
p.x += p.xd
|
|
179
|
+
p.y += p.yd
|
|
180
|
+
p.z += p.zd
|
|
181
|
+
|
|
182
|
+
// Recalculate onGround each tick (particle may move to a different column)
|
|
183
|
+
const floorY = this.getFloorY(p)
|
|
184
|
+
if (p.y <= floorY) {
|
|
185
|
+
p.y = floorY
|
|
186
|
+
p.yd = 0
|
|
187
|
+
p.onGround = true
|
|
188
|
+
} else {
|
|
189
|
+
p.onGround = false
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
p.xd *= 0.98
|
|
193
|
+
p.yd *= 0.98
|
|
194
|
+
p.zd *= 0.98
|
|
195
|
+
|
|
196
|
+
if (p.onGround) {
|
|
197
|
+
p.xd *= 0.7
|
|
198
|
+
p.zd *= 0.7
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private updateVisuals(alpha: number): void {
|
|
204
|
+
// Camera is at ~(0,0,0) in scene space (sceneOrigin tracks camera)
|
|
205
|
+
const cameraPosScene = this.worldRenderer.camera.position
|
|
206
|
+
|
|
207
|
+
for (const p of this.particles) {
|
|
208
|
+
if (!p.active) continue
|
|
209
|
+
|
|
210
|
+
const displayX = p.prevX + (p.x - p.prevX) * alpha
|
|
211
|
+
const displayY = p.prevY + (p.y - p.prevY) * alpha
|
|
212
|
+
const displayZ = p.prevZ + (p.z - p.prevZ) * alpha
|
|
213
|
+
|
|
214
|
+
p.mesh.position.set(displayX, displayY, displayZ)
|
|
215
|
+
// lookAt operates in parent (scene) coords — use scene-local camera pos
|
|
216
|
+
p.mesh.lookAt(cameraPosScene.x, cameraPosScene.y, cameraPosScene.z)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
spawnCrackParticle(worldX: number, worldY: number, worldZ: number, face: number, blockName: string, floorMap: number[], biomeName = 'plains'): void {
|
|
221
|
+
if (!this.enabled) return
|
|
222
|
+
|
|
223
|
+
const texInfo = this.resolveBlockTexture(blockName)
|
|
224
|
+
if (!texInfo) return
|
|
225
|
+
|
|
226
|
+
const tintColor = resolveTintColor(blockName, biomeName)
|
|
227
|
+
|
|
228
|
+
// Random position within block, inset 0.1 on each axis
|
|
229
|
+
let px = worldX + Math.random() * 0.8 + 0.1
|
|
230
|
+
let py = worldY + Math.random() * 0.8 + 0.1
|
|
231
|
+
let pz = worldZ + Math.random() * 0.8 + 0.1
|
|
232
|
+
|
|
233
|
+
// Override position on the hit face axis to be at face + 0.1 offset outward
|
|
234
|
+
switch (face) {
|
|
235
|
+
case 0: py = worldY - 0.1; break
|
|
236
|
+
case 1: py = worldY + 1.0 + 0.1; break
|
|
237
|
+
case 2: pz = worldZ - 0.1; break
|
|
238
|
+
case 3: pz = worldZ + 1.0 + 0.1; break
|
|
239
|
+
case 4: px = worldX - 0.1; break
|
|
240
|
+
case 5: px = worldX + 1.0 + 0.1; break
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Small random velocity, heavily damped
|
|
244
|
+
const xd = (Math.random() * 2 - 1) * 0.4 * 0.2
|
|
245
|
+
const yd = (Math.random() * 2 - 1) * 0.4 * 0.2 + 0.1 * 0.2
|
|
246
|
+
const zd = (Math.random() * 2 - 1) * 0.4 * 0.2
|
|
247
|
+
|
|
248
|
+
const maxAge = Math.floor(4 / (Math.random() * 0.9 + 0.1))
|
|
249
|
+
|
|
250
|
+
this.createParticle(px, py, pz, xd, yd, zd, maxAge, texInfo, floorMap, worldX, worldZ, 0.6, tintColor)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private createParticle(
|
|
254
|
+
px: number, py: number, pz: number,
|
|
255
|
+
xd: number, yd: number, zd: number,
|
|
256
|
+
maxAge: number,
|
|
257
|
+
texInfo: { u: number; v: number; su: number; sv: number },
|
|
258
|
+
floorMap: number[],
|
|
259
|
+
blockX: number, blockZ: number,
|
|
260
|
+
scaleFactor = 1.0,
|
|
261
|
+
tintColor: [number, number, number] = [1, 1, 1]
|
|
262
|
+
): void {
|
|
263
|
+
this.ensureMaterial()
|
|
264
|
+
|
|
265
|
+
let particle = this.findInactiveParticle()
|
|
266
|
+
|
|
267
|
+
if (!particle) {
|
|
268
|
+
if (this.particles.length < MAX_PARTICLES) {
|
|
269
|
+
particle = this.allocateParticle()
|
|
270
|
+
} else {
|
|
271
|
+
particle = this.recycleOldest()
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const randomU = Math.floor(Math.random() * 4)
|
|
276
|
+
const randomV = Math.floor(Math.random() * 4)
|
|
277
|
+
const particleU = texInfo.u + (randomU / 4) * texInfo.su
|
|
278
|
+
const particleV = texInfo.v + (randomV / 4) * texInfo.sv
|
|
279
|
+
const particleSU = texInfo.su / 4
|
|
280
|
+
const particleSV = texInfo.sv / 4
|
|
281
|
+
|
|
282
|
+
this.setGeometryUVs(particle.mesh.geometry as THREE.PlaneGeometry, particleU, particleV, particleSU, particleSV)
|
|
283
|
+
|
|
284
|
+
particle.active = true
|
|
285
|
+
particle.x = px; particle.y = py; particle.z = pz
|
|
286
|
+
particle.prevX = px; particle.prevY = py; particle.prevZ = pz
|
|
287
|
+
particle.xd = xd; particle.yd = yd; particle.zd = zd
|
|
288
|
+
particle.age = 0
|
|
289
|
+
particle.maxAge = maxAge
|
|
290
|
+
particle.onGround = false
|
|
291
|
+
particle.floorMap = floorMap
|
|
292
|
+
particle.blockX = Math.floor(blockX)
|
|
293
|
+
particle.blockZ = Math.floor(blockZ)
|
|
294
|
+
|
|
295
|
+
const scale = 0.1 * (0.5 + Math.random() * 0.5) * 2 * scaleFactor
|
|
296
|
+
particle.mesh.scale.set(scale, scale, scale)
|
|
297
|
+
particle.mesh.position.set(px, py, pz)
|
|
298
|
+
particle.mesh.visible = true
|
|
299
|
+
|
|
300
|
+
// Apply tint: base darkening 0.6 × tint color
|
|
301
|
+
const r = 0.6 * tintColor[0]
|
|
302
|
+
const g = 0.6 * tintColor[1]
|
|
303
|
+
const b = 0.6 * tintColor[2]
|
|
304
|
+
const colorArray = new Float32Array([r, g, b, r, g, b, r, g, b, r, g, b])
|
|
305
|
+
const colorAttr = particle.mesh.geometry.getAttribute('color') as THREE.BufferAttribute
|
|
306
|
+
if (colorAttr) {
|
|
307
|
+
colorAttr.set(colorArray)
|
|
308
|
+
colorAttr.needsUpdate = true
|
|
309
|
+
} else {
|
|
310
|
+
particle.mesh.geometry.setAttribute('color', new THREE.Float32BufferAttribute(colorArray, 3))
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
this.worldRenderer.sceneOrigin.addAndTrack(particle.mesh)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private allocateParticle(): BreakParticle {
|
|
317
|
+
const geometry = new THREE.PlaneGeometry(1, 1)
|
|
318
|
+
const mesh = new THREE.Mesh(geometry, this.sharedMaterial!)
|
|
319
|
+
mesh.visible = false
|
|
320
|
+
|
|
321
|
+
const particle: BreakParticle = {
|
|
322
|
+
mesh,
|
|
323
|
+
active: false,
|
|
324
|
+
x: 0, y: 0, z: 0,
|
|
325
|
+
prevX: 0, prevY: 0, prevZ: 0,
|
|
326
|
+
xd: 0, yd: 0, zd: 0,
|
|
327
|
+
age: 0,
|
|
328
|
+
maxAge: 0,
|
|
329
|
+
onGround: false,
|
|
330
|
+
floorMap: [],
|
|
331
|
+
blockX: 0,
|
|
332
|
+
blockZ: 0,
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.particles.push(particle)
|
|
336
|
+
return particle
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private findInactiveParticle(): BreakParticle | undefined {
|
|
340
|
+
for (let i = 0; i < this.particles.length; i++) {
|
|
341
|
+
const idx = (this.nextParticleIndex + i) % this.particles.length
|
|
342
|
+
if (!this.particles[idx].active) {
|
|
343
|
+
this.nextParticleIndex = (idx + 1) % this.particles.length
|
|
344
|
+
return this.particles[idx]
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return undefined
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private recycleOldest(): BreakParticle {
|
|
351
|
+
let oldest: BreakParticle = this.particles[0]
|
|
352
|
+
for (const p of this.particles) {
|
|
353
|
+
if (p.age > oldest.age) {
|
|
354
|
+
oldest = p
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
this.deactivateParticle(oldest)
|
|
358
|
+
return oldest
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private deactivateParticle(p: BreakParticle): void {
|
|
362
|
+
if (!p.active) return
|
|
363
|
+
p.active = false
|
|
364
|
+
p.mesh.visible = false
|
|
365
|
+
this.worldRenderer.sceneOrigin.removeAndUntrack(p.mesh)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private getFloorY(particle: BreakParticle): number {
|
|
369
|
+
let dx = Math.floor(particle.x) - particle.blockX
|
|
370
|
+
let dz = Math.floor(particle.z) - particle.blockZ
|
|
371
|
+
dx = Math.max(-2, Math.min(2, dx))
|
|
372
|
+
dz = Math.max(-2, Math.min(2, dz))
|
|
373
|
+
return particle.floorMap[(dz + 2) * 5 + (dx + 2)]
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private resolveBlockTexture(blockName: string): { u: number; v: number; su: number; sv: number } | null {
|
|
377
|
+
const resources = this.worldRenderer.resourcesManager.currentResources
|
|
378
|
+
if (!resources) return null
|
|
379
|
+
|
|
380
|
+
const atlasJson = resources.blocksAtlasJson
|
|
381
|
+
const textures = atlasJson.textures
|
|
382
|
+
|
|
383
|
+
if (textures[blockName]) return this.extractUV(textures[blockName], atlasJson)
|
|
384
|
+
|
|
385
|
+
for (const suffix of ['_side', '_top', '_front', '_0', '']) {
|
|
386
|
+
const key = blockName + suffix
|
|
387
|
+
if (textures[key]) return this.extractUV(textures[key], atlasJson)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
for (const key of Object.keys(textures)) {
|
|
391
|
+
if (key.startsWith(blockName)) return this.extractUV(textures[key], atlasJson)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return null
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private extractUV(
|
|
398
|
+
texInfo: { u: number; v: number; su?: number; sv?: number },
|
|
399
|
+
atlasJson: { suSv: number }
|
|
400
|
+
): { u: number; v: number; su: number; sv: number } {
|
|
401
|
+
return {
|
|
402
|
+
u: texInfo.u,
|
|
403
|
+
v: texInfo.v,
|
|
404
|
+
su: texInfo.su ?? atlasJson.suSv,
|
|
405
|
+
sv: texInfo.sv ?? atlasJson.suSv,
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private setGeometryUVs(geometry: THREE.PlaneGeometry, u: number, v: number, su: number, sv: number): void {
|
|
410
|
+
const uvAttr = geometry.getAttribute('uv') as THREE.BufferAttribute
|
|
411
|
+
// PlaneGeometry UV layout: (0,1) (1,1) (0,0) (1,0)
|
|
412
|
+
uvAttr.setXY(0, u, v)
|
|
413
|
+
uvAttr.setXY(1, u + su, v)
|
|
414
|
+
uvAttr.setXY(2, u, v + sv)
|
|
415
|
+
uvAttr.setXY(3, u + su, v + sv)
|
|
416
|
+
uvAttr.needsUpdate = true
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private ensureMaterial(): void {
|
|
420
|
+
if (this.sharedMaterial) return
|
|
421
|
+
const atlasTexture = this.worldRenderer.material.map
|
|
422
|
+
if (!atlasTexture) return
|
|
423
|
+
this.sharedMaterial = new THREE.MeshBasicMaterial({
|
|
424
|
+
map: atlasTexture,
|
|
425
|
+
vertexColors: true,
|
|
426
|
+
transparent: true,
|
|
427
|
+
alphaTest: 0.1,
|
|
428
|
+
})
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export const blockBreakParticlesManifest: RendererModuleManifest = {
|
|
433
|
+
id: 'blockBreakParticles',
|
|
434
|
+
controller: BlockBreakParticlesModule,
|
|
435
|
+
enabledDefault: true,
|
|
436
|
+
cannotBeDisabled: true,
|
|
437
|
+
requiresHeightmap: false,
|
|
438
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
|
+
import { blockBreakParticlesManifest } from './blockBreakParticles'
|
|
2
3
|
import { cameraBobbingManifest } from './cameraBobbing'
|
|
3
4
|
import { rainManifest } from './rain'
|
|
4
5
|
import { sciFiWorldRevealManifest } from './sciFiWorldReveal'
|
|
@@ -9,4 +10,5 @@ export const BUILTIN_MODULES = {
|
|
|
9
10
|
futuristicReveal: sciFiWorldRevealManifest,
|
|
10
11
|
rain: rainManifest,
|
|
11
12
|
cameraBobbing: cameraBobbingManifest,
|
|
13
|
+
blockBreakParticles: blockBreakParticlesManifest,
|
|
12
14
|
}
|
|
@@ -58,11 +58,9 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
58
58
|
// Store original methods for patching
|
|
59
59
|
private originalFinishChunk: ((chunkKey: string) => void) | null = null
|
|
60
60
|
private originalDestroy: (() => void) | null = null
|
|
61
|
-
private originalSceneAdd: ((...object: THREE.Object3D[]) => THREE.
|
|
61
|
+
private originalSceneAdd: ((...object: THREE.Object3D[]) => THREE.Group) | null = null
|
|
62
62
|
private originalHandleWorkerMessage: ((data: { geometry: MesherGeometryOutput; key: string; type: string }) => void) | null = null
|
|
63
63
|
|
|
64
|
-
private originalWbgHandle: ((data: any) => void) | null = null
|
|
65
|
-
|
|
66
64
|
private configEnabled = true
|
|
67
65
|
|
|
68
66
|
constructor(private readonly worldRenderer: WorldRendererThree) {
|
|
@@ -146,12 +144,10 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
146
144
|
this.originalDestroy!()
|
|
147
145
|
}
|
|
148
146
|
|
|
149
|
-
// Patch handleWorkerMessage
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
wbg.handleWorkerGeometryMessage = (data: any) => {
|
|
154
|
-
const result = this.originalWbgHandle!(data)
|
|
147
|
+
// Patch handleWorkerMessage to intercept geometry
|
|
148
|
+
this.originalHandleWorkerMessage = wr.handleWorkerMessage.bind(wr)
|
|
149
|
+
wr.handleWorkerMessage = (data: any) => {
|
|
150
|
+
this.originalHandleWorkerMessage!(data)
|
|
155
151
|
|
|
156
152
|
if (this.enabled && data?.type === 'geometry') {
|
|
157
153
|
Promise.resolve().then(() => {
|
|
@@ -162,14 +158,12 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
162
158
|
}
|
|
163
159
|
})
|
|
164
160
|
}
|
|
165
|
-
|
|
166
|
-
return result
|
|
167
161
|
}
|
|
168
162
|
|
|
169
163
|
|
|
170
164
|
// Patch scene.add to intercept mesh additions
|
|
171
165
|
this.originalSceneAdd = wr.scene.add.bind(wr.scene)
|
|
172
|
-
wr.scene.add = (...objects: THREE.Object3D[]): THREE.
|
|
166
|
+
wr.scene.add = (...objects: THREE.Object3D[]): THREE.Group => {
|
|
173
167
|
// Call original add first
|
|
174
168
|
const result = this.originalSceneAdd!(...objects)
|
|
175
169
|
|
|
@@ -208,11 +202,6 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
208
202
|
this.originalSceneAdd = null
|
|
209
203
|
}
|
|
210
204
|
|
|
211
|
-
if (this.originalWbgHandle) {
|
|
212
|
-
wr.worldBlockGeometry.handleWorkerGeometryMessage = this.originalWbgHandle as any
|
|
213
|
-
this.originalWbgHandle = null
|
|
214
|
-
}
|
|
215
|
-
|
|
216
205
|
if (this.onWorldSwitchedCb) {
|
|
217
206
|
const i = wr.onWorldSwitched.indexOf(this.onWorldSwitchedCb)
|
|
218
207
|
if (i !== -1) wr.onWorldSwitched.splice(i, 1)
|
|
@@ -249,7 +238,7 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
249
238
|
let current: THREE.Object3D | null = mesh
|
|
250
239
|
while (current) {
|
|
251
240
|
const { sectionKey } = (current as any)
|
|
252
|
-
if (sectionKey && this.worldRenderer.
|
|
241
|
+
if (sectionKey && this.worldRenderer.chunkMeshManager.sectionObjects[sectionKey] === current) {
|
|
253
242
|
return sectionKey
|
|
254
243
|
}
|
|
255
244
|
current = current.parent
|
|
@@ -270,7 +259,7 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
270
259
|
const derivedKey = `${sectionX},${sectionY},${sectionZ}`
|
|
271
260
|
|
|
272
261
|
// Verify this key exists in sectionObjects
|
|
273
|
-
if (this.worldRenderer.
|
|
262
|
+
if (this.worldRenderer.chunkMeshManager.sectionObjects[derivedKey]) {
|
|
274
263
|
return derivedKey
|
|
275
264
|
}
|
|
276
265
|
|
|
@@ -281,7 +270,7 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
281
270
|
* Get the scene from world renderer
|
|
282
271
|
*/
|
|
283
272
|
private get scene(): THREE.Scene {
|
|
284
|
-
return this.worldRenderer.
|
|
273
|
+
return this.worldRenderer.realScene
|
|
285
274
|
}
|
|
286
275
|
|
|
287
276
|
/**
|
|
@@ -295,7 +284,7 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
295
284
|
* Get original mesh for a section key
|
|
296
285
|
*/
|
|
297
286
|
private getOriginalMesh(key: string): THREE.Mesh | null {
|
|
298
|
-
const sectionObject = this.worldRenderer.
|
|
287
|
+
const sectionObject = this.worldRenderer.chunkMeshManager.sectionObjects[key]
|
|
299
288
|
if (!sectionObject) return null
|
|
300
289
|
return sectionObject.children.find(child => child.name === 'mesh') as THREE.Mesh | null
|
|
301
290
|
}
|
package/src/three/panorama.ts
CHANGED