minecraft-renderer 0.1.36 → 0.1.37
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 +52 -52
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +393 -393
- package/package.json +1 -1
- package/src/three/chunkMeshManager.ts +1 -1
- package/src/three/holdingBlock.ts +39 -35
- package/src/three/holdingBlockItemIdentity.test.ts +43 -0
- package/src/three/holdingBlockItemIdentity.ts +30 -0
- package/src/three/holdingBlockLegacy.ts +22 -25
- package/src/three/modules/sciFiWorldReveal.ts +2 -2
- package/src/three/sceneOrigin.ts +2 -2
- package/src/three/worldRendererThree.ts +103 -13
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import * as THREE from 'three'
|
|
3
3
|
import * as tweenJs from '@tweenjs/tween.js'
|
|
4
|
-
import PrismarineItem from 'prismarine-item'
|
|
5
4
|
import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
|
6
5
|
import { BlockModel } from 'mc-assets'
|
|
7
6
|
import { DebugGui } from '../lib/DebugGui'
|
|
@@ -17,8 +16,14 @@ import { getThreeBlockModelGroup } from '../mesher/standaloneRenderer'
|
|
|
17
16
|
import { IndexedData } from 'minecraft-data'
|
|
18
17
|
import { WorldRendererConfig } from '../graphicsBackend'
|
|
19
18
|
import { computeCameraBob, type CameraBobInput } from '../lib/cameraBobbing'
|
|
19
|
+
import { getFirstPersonItemSpecificProps, getHandItemRenderKey } from './holdingBlockItemIdentity'
|
|
20
20
|
|
|
21
21
|
const _tempMat = new THREE.Matrix4()
|
|
22
|
+
const wrapPi = (a: number) => {
|
|
23
|
+
a = (a + Math.PI) % (Math.PI * 2)
|
|
24
|
+
if (a < 0) a += Math.PI * 2
|
|
25
|
+
return a - Math.PI
|
|
26
|
+
}
|
|
22
27
|
|
|
23
28
|
// Vanilla renderPlayerArm transform chain
|
|
24
29
|
function buildBareHandMatrix(swingProgress: number, equipProgress: number): THREE.Matrix4 {
|
|
@@ -94,6 +99,7 @@ export default class HoldingBlock implements IHoldingBlock {
|
|
|
94
99
|
equipProgress = 0 // 0 = fully visible, 1 = hidden
|
|
95
100
|
stopUpdate = false
|
|
96
101
|
lastHeldItem: HandItemBlock | undefined
|
|
102
|
+
lastHeldItemRenderKey: string | undefined
|
|
97
103
|
currentDisplayType: 'hand' | 'item' | 'block' = 'hand'
|
|
98
104
|
isSwinging = false
|
|
99
105
|
nextIterStopCallbacks: Array<() => void> | undefined
|
|
@@ -116,6 +122,7 @@ export default class HoldingBlock implements IHoldingBlock {
|
|
|
116
122
|
|
|
117
123
|
constructor(public worldRenderer: WorldRendererThree, public offHand = false) {
|
|
118
124
|
this.initCameraGroup()
|
|
125
|
+
this.swingAnimator = new HandSwingAnimator()
|
|
119
126
|
this.unsubs.push(
|
|
120
127
|
this.worldRenderer.onReactivePlayerStateUpdated('heldItemMain', () => {
|
|
121
128
|
if (!this.offHand) {
|
|
@@ -332,44 +339,32 @@ export default class HoldingBlock implements IHoldingBlock {
|
|
|
332
339
|
}
|
|
333
340
|
|
|
334
341
|
isDifferentItem(block: HandItemBlock | undefined) {
|
|
335
|
-
|
|
336
|
-
if (!this.lastHeldItem) {
|
|
337
|
-
return true
|
|
338
|
-
}
|
|
339
|
-
if (this.lastHeldItem.name !== block?.name) {
|
|
340
|
-
return true
|
|
341
|
-
}
|
|
342
|
-
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
|
|
343
|
-
if (!Item.equal(this.lastHeldItem.fullItem, block?.fullItem ?? {}) || JSON.stringify(this.lastHeldItem.fullItem.components) !== JSON.stringify(block?.fullItem?.components)) {
|
|
344
|
-
return true
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return false
|
|
342
|
+
return this.lastHeldItemRenderKey !== getHandItemRenderKey(this.worldRenderer, block)
|
|
348
343
|
}
|
|
349
344
|
|
|
350
345
|
updateCameraGroup() {
|
|
351
346
|
if (this.stopUpdate) return
|
|
352
347
|
const { camera } = this
|
|
353
348
|
|
|
354
|
-
// Hand rotation momentum (xBob/yBob) — vanilla Minecraft inertia effect
|
|
355
|
-
// Use base rotation from CameraShake (actual player view angles)
|
|
356
349
|
const now = performance.now()
|
|
357
350
|
const baseRotation = this.worldRenderer.cameraShake.getBaseRotation()
|
|
358
351
|
const actualPitch = baseRotation.pitch
|
|
359
352
|
const actualYaw = baseRotation.yaw
|
|
353
|
+
|
|
360
354
|
if (this.lastBobUpdateTime === 0) {
|
|
361
355
|
this.xBob = actualPitch
|
|
362
356
|
this.yBob = actualYaw
|
|
363
|
-
this.lastBobUpdateTime = now
|
|
364
357
|
} else {
|
|
365
358
|
const dt = Math.min((now - this.lastBobUpdateTime) / 1000, 0.1)
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
this.xBob += (actualPitch - this.xBob) *
|
|
369
|
-
this.yBob += (actualYaw - this.yBob) *
|
|
359
|
+
const pitchFactor = 1 - Math.pow(0.5, dt * 28)
|
|
360
|
+
const yawFactor = 1 - Math.pow(0.5, dt * 36)
|
|
361
|
+
this.xBob += (actualPitch - this.xBob) * pitchFactor
|
|
362
|
+
this.yBob += wrapPi(actualYaw - this.yBob) * yawFactor
|
|
370
363
|
}
|
|
371
|
-
|
|
372
|
-
|
|
364
|
+
|
|
365
|
+
this.lastBobUpdateTime = now
|
|
366
|
+
const pitchOffset = (actualPitch - this.xBob) * -0.05
|
|
367
|
+
const yawOffset = wrapPi(actualYaw - this.yBob) * -0.035
|
|
373
368
|
|
|
374
369
|
this.cameraGroup.position.copy(camera.position)
|
|
375
370
|
this.cameraGroup.rotation.copy(camera.rotation)
|
|
@@ -431,11 +426,7 @@ export default class HoldingBlock implements IHoldingBlock {
|
|
|
431
426
|
const result = this.worldRenderer.entities.getItemMesh({
|
|
432
427
|
...handItem.fullItem,
|
|
433
428
|
itemId: handItem.id,
|
|
434
|
-
},
|
|
435
|
-
'minecraft:display_context': 'firstperson',
|
|
436
|
-
'minecraft:use_duration': this.worldRenderer.playerStateReactive.itemUsageTicks,
|
|
437
|
-
'minecraft:using_item': !!this.worldRenderer.playerStateReactive.itemUsageTicks,
|
|
438
|
-
}, false, this.lastItemModelName)
|
|
429
|
+
}, getFirstPersonItemSpecificProps(this.worldRenderer), false, this.lastItemModelName)
|
|
439
430
|
if (result) {
|
|
440
431
|
const { mesh: itemMesh, isBlock, modelName } = result
|
|
441
432
|
if (isBlock) {
|
|
@@ -487,8 +478,11 @@ export default class HoldingBlock implements IHoldingBlock {
|
|
|
487
478
|
this.holdingBlock?.removeFromParent()
|
|
488
479
|
this.holdingBlock = undefined
|
|
489
480
|
this.currentDisplayType = 'hand'
|
|
490
|
-
this.swingAnimator
|
|
491
|
-
|
|
481
|
+
const swingAnimator = this.swingAnimator
|
|
482
|
+
swingAnimator?.stopSwing()
|
|
483
|
+
if (swingAnimator) {
|
|
484
|
+
swingAnimator.type = 'hand'
|
|
485
|
+
}
|
|
492
486
|
this.idleAnimator = undefined
|
|
493
487
|
return
|
|
494
488
|
}
|
|
@@ -516,10 +510,14 @@ export default class HoldingBlock implements IHoldingBlock {
|
|
|
516
510
|
|
|
517
511
|
switchRequest = 0
|
|
518
512
|
async setNewItem(handItem?: HandItemBlock) {
|
|
519
|
-
|
|
513
|
+
const nextRenderKey = getHandItemRenderKey(this.worldRenderer, handItem)
|
|
514
|
+
const itemChanged = this.lastHeldItemRenderKey !== nextRenderKey
|
|
515
|
+
this.lastHeldItem = handItem
|
|
516
|
+
if (!itemChanged) return
|
|
517
|
+
|
|
518
|
+
this.lastHeldItemRenderKey = nextRenderKey
|
|
520
519
|
this.lastItemModelName = undefined
|
|
521
520
|
const switchRequest = ++this.switchRequest
|
|
522
|
-
this.lastHeldItem = handItem
|
|
523
521
|
|
|
524
522
|
let playAppearAnimation = false
|
|
525
523
|
if (this.holdingBlock) {
|
|
@@ -535,7 +533,11 @@ export default class HoldingBlock implements IHoldingBlock {
|
|
|
535
533
|
|
|
536
534
|
if (!handItem) {
|
|
537
535
|
this.currentDisplayType = 'hand'
|
|
538
|
-
|
|
536
|
+
const swingAnimator = this.swingAnimator
|
|
537
|
+
swingAnimator?.stopSwing()
|
|
538
|
+
if (swingAnimator) {
|
|
539
|
+
swingAnimator.type = 'hand'
|
|
540
|
+
}
|
|
539
541
|
this.idleAnimator = undefined
|
|
540
542
|
this.blockSwapAnimation = undefined
|
|
541
543
|
return
|
|
@@ -553,8 +555,10 @@ export default class HoldingBlock implements IHoldingBlock {
|
|
|
553
555
|
await this.playBlockSwapAnimation('appeared')
|
|
554
556
|
}
|
|
555
557
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
+
const swingAnimator = this.swingAnimator
|
|
559
|
+
if (swingAnimator) {
|
|
560
|
+
swingAnimator.type = result.type
|
|
561
|
+
}
|
|
558
562
|
// Idle animation disabled — walking bob is handled by vanilla bobView applied to cameraGroup
|
|
559
563
|
this.idleAnimator = undefined
|
|
560
564
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { expect, test, vi } from 'vitest'
|
|
3
|
+
import { getHandItemRenderKey } from './holdingBlockItemIdentity'
|
|
4
|
+
|
|
5
|
+
test('hand item render key ignores live use state but keeps first-person display context', () => {
|
|
6
|
+
const getItemRenderData = vi.fn(() => ({ modelName: 'item/bow' }))
|
|
7
|
+
const worldRenderer = {
|
|
8
|
+
playerStateReactive: {
|
|
9
|
+
itemUsageTicks: 20,
|
|
10
|
+
},
|
|
11
|
+
resourcesManager: {
|
|
12
|
+
currentResources: {},
|
|
13
|
+
},
|
|
14
|
+
getItemRenderData,
|
|
15
|
+
} as any
|
|
16
|
+
|
|
17
|
+
const handItem = {
|
|
18
|
+
type: 'item',
|
|
19
|
+
id: 261,
|
|
20
|
+
name: 'minecraft:bow',
|
|
21
|
+
fullItem: {
|
|
22
|
+
count: 1,
|
|
23
|
+
},
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
const activeKey = getHandItemRenderKey(worldRenderer, handItem)
|
|
27
|
+
worldRenderer.playerStateReactive.itemUsageTicks = 0
|
|
28
|
+
const idleKey = getHandItemRenderKey(worldRenderer, handItem)
|
|
29
|
+
|
|
30
|
+
expect(activeKey).toBe(idleKey)
|
|
31
|
+
expect(getItemRenderData).toHaveBeenNthCalledWith(1, {
|
|
32
|
+
...handItem.fullItem,
|
|
33
|
+
itemId: handItem.id,
|
|
34
|
+
}, {
|
|
35
|
+
'minecraft:display_context': 'firstperson',
|
|
36
|
+
})
|
|
37
|
+
expect(getItemRenderData).toHaveBeenNthCalledWith(2, {
|
|
38
|
+
...handItem.fullItem,
|
|
39
|
+
itemId: handItem.id,
|
|
40
|
+
}, {
|
|
41
|
+
'minecraft:display_context': 'firstperson',
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import type { HandItemBlock, ItemSpecificContextProperties } from '../playerState/types'
|
|
3
|
+
import type { WorldRendererThree } from './worldRendererThree'
|
|
4
|
+
|
|
5
|
+
export const getFirstPersonItemSpecificProps = (worldRenderer: WorldRendererThree): ItemSpecificContextProperties => ({
|
|
6
|
+
'minecraft:display_context': 'firstperson',
|
|
7
|
+
'minecraft:use_duration': worldRenderer.playerStateReactive.itemUsageTicks,
|
|
8
|
+
'minecraft:using_item': !!worldRenderer.playerStateReactive.itemUsageTicks,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const getFirstPersonItemIdentityProps = (): ItemSpecificContextProperties => ({
|
|
12
|
+
'minecraft:display_context': 'firstperson',
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export const getHandItemRenderKey = (worldRenderer: WorldRendererThree, handItem?: HandItemBlock) => {
|
|
16
|
+
if (!handItem) return 'empty'
|
|
17
|
+
if (handItem.type === 'hand') return 'hand'
|
|
18
|
+
|
|
19
|
+
const itemIdentifier = handItem.name ?? (handItem.id !== undefined ? `#${handItem.id}` : 'unknown')
|
|
20
|
+
if (!worldRenderer.resourcesManager.currentResources) {
|
|
21
|
+
return `${handItem.type}:${itemIdentifier}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const renderData = worldRenderer.getItemRenderData({
|
|
25
|
+
...handItem.fullItem,
|
|
26
|
+
itemId: handItem.id,
|
|
27
|
+
}, getFirstPersonItemIdentityProps())
|
|
28
|
+
|
|
29
|
+
return `${handItem.type}:${itemIdentifier}:${renderData.modelName}`
|
|
30
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import * as THREE from 'three'
|
|
3
3
|
import * as tweenJs from '@tweenjs/tween.js'
|
|
4
|
-
import PrismarineItem from 'prismarine-item'
|
|
5
4
|
import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
|
6
5
|
import { BlockModel } from 'mc-assets'
|
|
7
6
|
import { DebugGui } from '../lib/DebugGui'
|
|
@@ -17,6 +16,7 @@ import { getThreeBlockModelGroup } from '../mesher/standaloneRenderer'
|
|
|
17
16
|
import { IndexedData } from 'minecraft-data'
|
|
18
17
|
import { WorldRendererConfig } from '../graphicsBackend'
|
|
19
18
|
import { IHoldingBlock } from './holdingBlockTypes'
|
|
19
|
+
import { getFirstPersonItemSpecificProps, getHandItemRenderKey } from './holdingBlockItemIdentity'
|
|
20
20
|
|
|
21
21
|
const rotationPositionData = {
|
|
22
22
|
itemRight: {
|
|
@@ -103,6 +103,7 @@ export default class HoldingBlockLegacy implements IHoldingBlock {
|
|
|
103
103
|
camera = new THREE.PerspectiveCamera(75, 1, 0.1, 100)
|
|
104
104
|
stopUpdate = false
|
|
105
105
|
lastHeldItem: HandItemBlock | undefined
|
|
106
|
+
lastHeldItemRenderKey: string | undefined
|
|
106
107
|
isSwinging = false
|
|
107
108
|
nextIterStopCallbacks: Array<() => void> | undefined
|
|
108
109
|
idleAnimator: HandIdleAnimator | undefined
|
|
@@ -304,19 +305,7 @@ export default class HoldingBlockLegacy implements IHoldingBlock {
|
|
|
304
305
|
}
|
|
305
306
|
|
|
306
307
|
isDifferentItem(block: HandItemBlock | undefined) {
|
|
307
|
-
|
|
308
|
-
if (!this.lastHeldItem) {
|
|
309
|
-
return true
|
|
310
|
-
}
|
|
311
|
-
if (this.lastHeldItem.name !== block?.name) {
|
|
312
|
-
return true
|
|
313
|
-
}
|
|
314
|
-
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
|
|
315
|
-
if (!Item.equal(this.lastHeldItem.fullItem, block?.fullItem ?? {}) || JSON.stringify(this.lastHeldItem.fullItem.components) !== JSON.stringify(block?.fullItem?.components)) {
|
|
316
|
-
return true
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return false
|
|
308
|
+
return this.lastHeldItemRenderKey !== getHandItemRenderKey(this.worldRenderer, block)
|
|
320
309
|
}
|
|
321
310
|
|
|
322
311
|
updateCameraGroup() {
|
|
@@ -355,11 +344,7 @@ export default class HoldingBlockLegacy implements IHoldingBlock {
|
|
|
355
344
|
const result = this.worldRenderer.entities.getItemMesh({
|
|
356
345
|
...handItem.fullItem,
|
|
357
346
|
itemId: handItem.id,
|
|
358
|
-
},
|
|
359
|
-
'minecraft:display_context': 'firstperson',
|
|
360
|
-
'minecraft:use_duration': this.worldRenderer.playerStateReactive.itemUsageTicks,
|
|
361
|
-
'minecraft:using_item': !!this.worldRenderer.playerStateReactive.itemUsageTicks,
|
|
362
|
-
}, false, this.lastItemModelName)
|
|
347
|
+
}, getFirstPersonItemSpecificProps(this.worldRenderer), false, this.lastItemModelName)
|
|
363
348
|
if (result) {
|
|
364
349
|
const { mesh: itemMesh, isBlock, modelName } = result
|
|
365
350
|
if (isBlock) {
|
|
@@ -421,10 +406,14 @@ export default class HoldingBlockLegacy implements IHoldingBlock {
|
|
|
421
406
|
|
|
422
407
|
switchRequest = 0
|
|
423
408
|
async setNewItem(handItem?: HandItemBlock) {
|
|
424
|
-
|
|
409
|
+
const nextRenderKey = getHandItemRenderKey(this.worldRenderer, handItem)
|
|
410
|
+
const itemChanged = this.lastHeldItemRenderKey !== nextRenderKey
|
|
411
|
+
this.lastHeldItem = handItem
|
|
412
|
+
if (!itemChanged) return
|
|
413
|
+
|
|
414
|
+
this.lastHeldItemRenderKey = nextRenderKey
|
|
425
415
|
this.lastItemModelName = undefined
|
|
426
416
|
const switchRequest = ++this.switchRequest
|
|
427
|
-
this.lastHeldItem = handItem
|
|
428
417
|
let playAppearAnimation = false
|
|
429
418
|
if (this.holdingBlock) {
|
|
430
419
|
// play disappear animation
|
|
@@ -472,7 +461,8 @@ export default class HoldingBlockLegacy implements IHoldingBlock {
|
|
|
472
461
|
await this.playBlockSwapAnimation('appeared')
|
|
473
462
|
}
|
|
474
463
|
|
|
475
|
-
this.swingAnimator
|
|
464
|
+
this.swingAnimator ??= new HandSwingAnimator(this.holdingBlockInnerGroup)
|
|
465
|
+
this.swingAnimator.setHandMesh(this.holdingBlockInnerGroup)
|
|
476
466
|
this.swingAnimator.type = result.type
|
|
477
467
|
if (this.config.viewBobbing) {
|
|
478
468
|
this.idleAnimator = new HandIdleAnimator(this.holdingBlockInnerGroup, this.worldRenderer.playerStateReactive)
|
|
@@ -783,9 +773,9 @@ class HandSwingAnimator {
|
|
|
783
773
|
private lastTime = 0
|
|
784
774
|
private isAnimating = false
|
|
785
775
|
private stopRequested = false
|
|
786
|
-
private
|
|
787
|
-
private
|
|
788
|
-
private
|
|
776
|
+
private originalRotation: THREE.Euler
|
|
777
|
+
private originalPosition: THREE.Vector3
|
|
778
|
+
private originalScale: THREE.Vector3
|
|
789
779
|
|
|
790
780
|
readonly debugParams = {
|
|
791
781
|
// Animation timing
|
|
@@ -849,6 +839,13 @@ class HandSwingAnimator {
|
|
|
849
839
|
// this.debugGui.activate()
|
|
850
840
|
}
|
|
851
841
|
|
|
842
|
+
setHandMesh(handMesh: THREE.Object3D) {
|
|
843
|
+
this.handMesh = handMesh
|
|
844
|
+
this.originalRotation.copy(handMesh.rotation)
|
|
845
|
+
this.originalPosition.copy(handMesh.position)
|
|
846
|
+
this.originalScale.copy(handMesh.scale)
|
|
847
|
+
}
|
|
848
|
+
|
|
852
849
|
update() {
|
|
853
850
|
if (!this.isAnimating && !this.debugParams.animationStage) {
|
|
854
851
|
// If not animating, ensure we're at original position
|
|
@@ -58,7 +58,7 @@ 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.Scene) | null = null
|
|
62
62
|
private originalHandleWorkerMessage: ((data: { geometry: MesherGeometryOutput; key: string; type: string }) => void) | null = null
|
|
63
63
|
|
|
64
64
|
private configEnabled = true
|
|
@@ -163,7 +163,7 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
163
163
|
|
|
164
164
|
// Patch scene.add to intercept mesh additions
|
|
165
165
|
this.originalSceneAdd = wr.scene.add.bind(wr.scene)
|
|
166
|
-
wr.scene.add = (...objects: THREE.Object3D[]): THREE.
|
|
166
|
+
wr.scene.add = (...objects: THREE.Object3D[]): THREE.Scene => {
|
|
167
167
|
// Call original add first
|
|
168
168
|
const result = this.originalSceneAdd!(...objects)
|
|
169
169
|
|
package/src/three/sceneOrigin.ts
CHANGED
|
@@ -174,7 +174,7 @@ export class SceneOrigin {
|
|
|
174
174
|
/** Untrack an Object3D and remove it from the scene */
|
|
175
175
|
removeAndUntrack(obj: Object3D): void {
|
|
176
176
|
this.untrack(obj)
|
|
177
|
-
|
|
177
|
+
obj.removeFromParent()
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
/** Untrack an Object3D and all its descendants, then remove from the scene */
|
|
@@ -182,7 +182,7 @@ export class SceneOrigin {
|
|
|
182
182
|
obj.traverse((child) => {
|
|
183
183
|
this.untrack(child)
|
|
184
184
|
})
|
|
185
|
-
|
|
185
|
+
obj.removeFromParent()
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
/** Get stored world position for a tracked object */
|
|
@@ -50,8 +50,10 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
50
50
|
cameraSectionPos: Vec3 = new Vec3(0, 0, 0)
|
|
51
51
|
holdingBlock: IHoldingBlock
|
|
52
52
|
holdingBlockLeft: IHoldingBlock
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
scene = new THREE.Scene()
|
|
54
|
+
get realScene() {
|
|
55
|
+
return this.scene
|
|
56
|
+
}
|
|
55
57
|
ambientLight = new THREE.AmbientLight(0xcc_cc_cc)
|
|
56
58
|
directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5)
|
|
57
59
|
entities = new Entities(this, (globalThis as any).mcData)
|
|
@@ -79,6 +81,9 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
79
81
|
*/
|
|
80
82
|
camera!: THREE.PerspectiveCamera
|
|
81
83
|
renderTimeAvg = 0
|
|
84
|
+
private pendingSectionUpdates = new Map<string, { geometry: MesherGeometryOutput, key: string, type: string }>()
|
|
85
|
+
private pendingSectionBufferStartTime: number | null = null
|
|
86
|
+
private static readonly MAX_SECTION_UPDATE_BUFFER_MS = 500
|
|
82
87
|
// Memory usage tracking (in bytes)
|
|
83
88
|
get estimatedMemoryUsage() {
|
|
84
89
|
return this.chunkMeshManager.getEstimatedMemoryUsage().total
|
|
@@ -107,7 +112,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
107
112
|
DEBUG_RAYCAST = false
|
|
108
113
|
skyboxRenderer: SkyboxRenderer
|
|
109
114
|
fireworks: FireworksManager
|
|
110
|
-
sceneOrigin = new SceneOrigin(this.
|
|
115
|
+
sceneOrigin = new SceneOrigin(this.scene)
|
|
111
116
|
/** Camera world position stored in float64 (JS number) for precision */
|
|
112
117
|
cameraWorldPos = { x: 0, y: 0, z: 0 }
|
|
113
118
|
|
|
@@ -450,20 +455,19 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
450
455
|
this.cameraWorldPos.y = 0
|
|
451
456
|
this.cameraWorldPos.z = 0
|
|
452
457
|
|
|
453
|
-
this.
|
|
454
|
-
this.
|
|
455
|
-
this.
|
|
458
|
+
this.scene.matrixAutoUpdate = false // for perf
|
|
459
|
+
this.scene.background = new THREE.Color(this.initOptions.config.sceneBackground)
|
|
460
|
+
this.scene.add(this.ambientLight)
|
|
456
461
|
this.directionalLight.position.set(1, 1, 0.5).normalize()
|
|
457
462
|
this.directionalLight.castShadow = true
|
|
458
|
-
this.
|
|
463
|
+
this.scene.add(this.directionalLight)
|
|
459
464
|
|
|
460
465
|
const size = this.renderer.getSize(new THREE.Vector2())
|
|
461
466
|
this.camera = new THREE.PerspectiveCamera(75, size.x / size.y, 0.1, 1000)
|
|
462
467
|
this._wrapCameraPositionWithWarning()
|
|
463
468
|
this.cameraContainer = new THREE.Object3D()
|
|
464
469
|
this.cameraContainer.add(this.camera)
|
|
465
|
-
this.
|
|
466
|
-
this.realScene.add(this.scene)
|
|
470
|
+
this.scene.add(this.cameraContainer)
|
|
467
471
|
}
|
|
468
472
|
|
|
469
473
|
override watchReactivePlayerState() {
|
|
@@ -743,13 +747,94 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
743
747
|
}
|
|
744
748
|
|
|
745
749
|
finishChunk(chunkKey: string) {
|
|
746
|
-
//
|
|
750
|
+
// Existing sections are buffered and flushed from applyPendingSectionUpdates().
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
private applyPendingSectionUpdates() {
|
|
754
|
+
if (this.pendingSectionUpdates.size === 0) return
|
|
755
|
+
|
|
756
|
+
const now = performance.now()
|
|
757
|
+
const sinceFirst = now - (this.pendingSectionBufferStartTime ?? now)
|
|
758
|
+
|
|
759
|
+
if (sinceFirst < WorldRendererThree.MAX_SECTION_UPDATE_BUFFER_MS) {
|
|
760
|
+
const sectionHeight = this.getSectionHeight()
|
|
761
|
+
for (const key of this.pendingSectionUpdates.keys()) {
|
|
762
|
+
const [sx, sy, sz] = key.split(',').map(Number)
|
|
763
|
+
const neighborKeys = [
|
|
764
|
+
`${sx - 16},${sy},${sz}`, `${sx + 16},${sy},${sz}`,
|
|
765
|
+
`${sx},${sy - sectionHeight},${sz}`, `${sx},${sy + sectionHeight},${sz}`,
|
|
766
|
+
`${sx},${sy},${sz - 16}`, `${sx},${sy},${sz + 16}`,
|
|
767
|
+
]
|
|
768
|
+
|
|
769
|
+
for (const neighborKey of neighborKeys) {
|
|
770
|
+
if (
|
|
771
|
+
this.sectionsWaiting.has(neighborKey) &&
|
|
772
|
+
!this.pendingSectionUpdates.has(neighborKey) &&
|
|
773
|
+
this.sectionObjects[neighborKey]
|
|
774
|
+
) {
|
|
775
|
+
return
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const updates = [...this.pendingSectionUpdates.values()]
|
|
782
|
+
this.pendingSectionUpdates.clear()
|
|
783
|
+
this.pendingSectionBufferStartTime = null
|
|
784
|
+
|
|
785
|
+
for (const update of updates) {
|
|
786
|
+
const chunkCoords = update.key.split(',')
|
|
787
|
+
const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
|
|
788
|
+
|
|
789
|
+
if (!this.loadedChunks[chunkKey] || !this.active) {
|
|
790
|
+
this.chunkMeshManager.releaseSection(update.key)
|
|
791
|
+
continue
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (!update.geometry.positions.length) {
|
|
795
|
+
this.chunkMeshManager.releaseSection(update.key)
|
|
796
|
+
continue
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
this.chunkMeshManager.updateSection(update.key, update.geometry)
|
|
800
|
+
this.updatePosDataChunk(update.key)
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
private clearPendingSectionUpdatesForChunk(x: number, z: number) {
|
|
805
|
+
for (const key of [...this.pendingSectionUpdates.keys()]) {
|
|
806
|
+
if (key.startsWith(`${x},`) && key.endsWith(`,${z}`)) {
|
|
807
|
+
this.pendingSectionUpdates.delete(key)
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (this.pendingSectionUpdates.size === 0) {
|
|
812
|
+
this.pendingSectionBufferStartTime = null
|
|
813
|
+
}
|
|
747
814
|
}
|
|
748
815
|
|
|
749
816
|
handleWorkerMessage(data: { geometry: MesherGeometryOutput, key, type }): void {
|
|
750
817
|
if (data.type === 'geometry') {
|
|
751
818
|
const chunkCoords = data.key.split(',')
|
|
752
|
-
|
|
819
|
+
const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
|
|
820
|
+
if (!this.loadedChunks[chunkKey] || !this.active) {
|
|
821
|
+
this.pendingSectionUpdates.delete(data.key)
|
|
822
|
+
if (this.pendingSectionUpdates.size === 0) {
|
|
823
|
+
this.pendingSectionBufferStartTime = null
|
|
824
|
+
}
|
|
825
|
+
return
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (this.sectionObjects[data.key]) {
|
|
829
|
+
this.pendingSectionUpdates.set(data.key, data)
|
|
830
|
+
this.pendingSectionBufferStartTime ??= performance.now()
|
|
831
|
+
return
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (!data.geometry.positions.length) {
|
|
835
|
+
this.chunkMeshManager.releaseSection(data.key)
|
|
836
|
+
return
|
|
837
|
+
}
|
|
753
838
|
this.chunkMeshManager.updateSection(data.key, data.geometry)
|
|
754
839
|
this.updatePosDataChunk(data.key)
|
|
755
840
|
}
|
|
@@ -1101,8 +1186,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1101
1186
|
|
|
1102
1187
|
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
|
1103
1188
|
const cam = this.cameraGroupVr instanceof THREE.Group ? this.cameraGroupVr.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
|
|
1104
|
-
|
|
1105
|
-
this.renderer.render(this.
|
|
1189
|
+
this.applyPendingSectionUpdates()
|
|
1190
|
+
this.renderer.render(this.scene, cam)
|
|
1106
1191
|
|
|
1107
1192
|
if (
|
|
1108
1193
|
this.displayOptions.inWorldRenderingConfig.showHand &&
|
|
@@ -1249,6 +1334,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1249
1334
|
resetWorld() {
|
|
1250
1335
|
super.resetWorld()
|
|
1251
1336
|
|
|
1337
|
+
this.pendingSectionUpdates.clear()
|
|
1338
|
+
this.pendingSectionBufferStartTime = null
|
|
1252
1339
|
this.chunkMeshManager.dispose()
|
|
1253
1340
|
this.chunkMeshManager = new ChunkMeshManager(this, this.scene, this.material, this.worldSizeParams.worldHeight, this.viewDistance)
|
|
1254
1341
|
|
|
@@ -1303,6 +1390,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1303
1390
|
super.removeColumn(x, z)
|
|
1304
1391
|
|
|
1305
1392
|
this.cleanChunkTextures(x, z)
|
|
1393
|
+
this.clearPendingSectionUpdatesForChunk(x, z)
|
|
1306
1394
|
const sectionHeight = this.getSectionHeight()
|
|
1307
1395
|
const worldMinY = this.worldMinYRender
|
|
1308
1396
|
for (let y = worldMinY; y < this.worldSizeParams.worldHeight; y += sectionHeight) {
|
|
@@ -1333,6 +1421,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1333
1421
|
}
|
|
1334
1422
|
|
|
1335
1423
|
destroy(): void {
|
|
1424
|
+
this.pendingSectionUpdates.clear()
|
|
1425
|
+
this.pendingSectionBufferStartTime = null
|
|
1336
1426
|
this.chunkMeshManager.dispose()
|
|
1337
1427
|
this.disposeModules()
|
|
1338
1428
|
this.fireworksLegacy.destroy()
|