minecraft-renderer 0.1.32 → 0.1.34
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 +55 -55
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +431 -431
- package/package.json +1 -1
- package/src/three/graphicsBackendBase.ts +8 -0
- package/src/three/holdingBlock.ts +3 -2
- package/src/three/modules/blockBreakParticles.ts +438 -0
- package/src/three/modules/index.ts +2 -0
package/package.json
CHANGED
|
@@ -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')
|
|
@@ -386,9 +386,10 @@ export default class HoldingBlock implements IHoldingBlock {
|
|
|
386
386
|
}
|
|
387
387
|
const partialTick = Math.min((now - this.lastBobTickTime) / 50, 1)
|
|
388
388
|
|
|
389
|
+
const handBobSpeedMultiplier = 1.8
|
|
389
390
|
const bob = computeCameraBob({
|
|
390
|
-
walkDist: ps.walkDist,
|
|
391
|
-
prevWalkDist: ps.prevWalkDist,
|
|
391
|
+
walkDist: ps.walkDist * handBobSpeedMultiplier,
|
|
392
|
+
prevWalkDist: ps.prevWalkDist * handBobSpeedMultiplier,
|
|
392
393
|
bob: ps.bob,
|
|
393
394
|
prevBob: ps.prevBob,
|
|
394
395
|
partialTick
|
|
@@ -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
|
}
|