minecraft-renderer 0.1.36 → 0.1.38
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 +58 -58
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +408 -408
- package/package.json +1 -1
- package/src/graphicsBackend/config.ts +1 -1
- package/src/three/chunkMeshManager.ts +167 -6
- 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 +139 -24
|
@@ -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)
|
|
@@ -65,7 +67,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
65
67
|
cameraContainer!: THREE.Object3D
|
|
66
68
|
media: ThreeJsMedia
|
|
67
69
|
get waitingChunksToDisplay() {
|
|
68
|
-
return
|
|
70
|
+
return this.chunkMeshManager.waitingChunksToDisplay
|
|
69
71
|
}
|
|
70
72
|
waypoints: WaypointsRenderer
|
|
71
73
|
cinimaticScript: CinimaticScriptRunner
|
|
@@ -79,6 +81,16 @@ 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
|
+
/**
|
|
86
|
+
* Per-section buffering timestamps for `applyPendingSectionUpdates`.
|
|
87
|
+
* Each section gets its own deadline so a continuous stream of updates
|
|
88
|
+
* (e.g. server-side block changes from explosions, pistons, fluid ticks)
|
|
89
|
+
* does not flush freshly added sections together with stale ones via a
|
|
90
|
+
* single global timer.
|
|
91
|
+
*/
|
|
92
|
+
private pendingSectionBufferStartTimes = new Map<string, number>()
|
|
93
|
+
private static readonly MAX_SECTION_UPDATE_BUFFER_MS = 500
|
|
82
94
|
// Memory usage tracking (in bytes)
|
|
83
95
|
get estimatedMemoryUsage() {
|
|
84
96
|
return this.chunkMeshManager.getEstimatedMemoryUsage().total
|
|
@@ -107,7 +119,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
107
119
|
DEBUG_RAYCAST = false
|
|
108
120
|
skyboxRenderer: SkyboxRenderer
|
|
109
121
|
fireworks: FireworksManager
|
|
110
|
-
sceneOrigin = new SceneOrigin(this.
|
|
122
|
+
sceneOrigin = new SceneOrigin(this.scene)
|
|
111
123
|
/** Camera world position stored in float64 (JS number) for precision */
|
|
112
124
|
cameraWorldPos = { x: 0, y: 0, z: 0 }
|
|
113
125
|
|
|
@@ -450,20 +462,19 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
450
462
|
this.cameraWorldPos.y = 0
|
|
451
463
|
this.cameraWorldPos.z = 0
|
|
452
464
|
|
|
453
|
-
this.
|
|
454
|
-
this.
|
|
455
|
-
this.
|
|
465
|
+
this.scene.matrixAutoUpdate = false // for perf
|
|
466
|
+
this.scene.background = new THREE.Color(this.initOptions.config.sceneBackground)
|
|
467
|
+
this.scene.add(this.ambientLight)
|
|
456
468
|
this.directionalLight.position.set(1, 1, 0.5).normalize()
|
|
457
469
|
this.directionalLight.castShadow = true
|
|
458
|
-
this.
|
|
470
|
+
this.scene.add(this.directionalLight)
|
|
459
471
|
|
|
460
472
|
const size = this.renderer.getSize(new THREE.Vector2())
|
|
461
473
|
this.camera = new THREE.PerspectiveCamera(75, size.x / size.y, 0.1, 1000)
|
|
462
474
|
this._wrapCameraPositionWithWarning()
|
|
463
475
|
this.cameraContainer = new THREE.Object3D()
|
|
464
476
|
this.cameraContainer.add(this.camera)
|
|
465
|
-
this.
|
|
466
|
-
this.realScene.add(this.scene)
|
|
477
|
+
this.scene.add(this.cameraContainer)
|
|
467
478
|
}
|
|
468
479
|
|
|
469
480
|
override watchReactivePlayerState() {
|
|
@@ -743,13 +754,108 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
743
754
|
}
|
|
744
755
|
|
|
745
756
|
finishChunk(chunkKey: string) {
|
|
746
|
-
//
|
|
757
|
+
// Reveal all sections of this chunk that were held invisible by the
|
|
758
|
+
// "Batch Chunks Display" (`_renderByChunks`) option. No-op when the
|
|
759
|
+
// option is off — `waitingChunksToDisplay` is empty in that case.
|
|
760
|
+
this.chunkMeshManager.finishChunkDisplay(chunkKey)
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
private applyPendingSectionUpdates() {
|
|
764
|
+
if (this.pendingSectionUpdates.size === 0) return
|
|
765
|
+
|
|
766
|
+
const now = performance.now()
|
|
767
|
+
const sectionHeight = this.getSectionHeight()
|
|
768
|
+
const ready: string[] = []
|
|
769
|
+
|
|
770
|
+
for (const key of this.pendingSectionUpdates.keys()) {
|
|
771
|
+
const startedAt = this.pendingSectionBufferStartTimes.get(key) ?? now
|
|
772
|
+
const sinceFirst = now - startedAt
|
|
773
|
+
|
|
774
|
+
if (sinceFirst < WorldRendererThree.MAX_SECTION_UPDATE_BUFFER_MS) {
|
|
775
|
+
// Still within this section's grace window — wait if any neighbor is
|
|
776
|
+
// currently being re-meshed so we don't briefly expose a hole between
|
|
777
|
+
// the just-updated section and a stale neighbor (sky-flicker bug).
|
|
778
|
+
const [sx, sy, sz] = key.split(',').map(Number)
|
|
779
|
+
const neighborKeys = [
|
|
780
|
+
`${sx - 16},${sy},${sz}`, `${sx + 16},${sy},${sz}`,
|
|
781
|
+
`${sx},${sy - sectionHeight},${sz}`, `${sx},${sy + sectionHeight},${sz}`,
|
|
782
|
+
`${sx},${sy},${sz - 16}`, `${sx},${sy},${sz + 16}`,
|
|
783
|
+
]
|
|
784
|
+
let neighborBusy = false
|
|
785
|
+
for (const neighborKey of neighborKeys) {
|
|
786
|
+
if (
|
|
787
|
+
this.sectionsWaiting.has(neighborKey) &&
|
|
788
|
+
!this.pendingSectionUpdates.has(neighborKey) &&
|
|
789
|
+
this.sectionObjects[neighborKey]
|
|
790
|
+
) {
|
|
791
|
+
neighborBusy = true
|
|
792
|
+
break
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (neighborBusy) continue
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
ready.push(key)
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (ready.length === 0) return
|
|
802
|
+
|
|
803
|
+
for (const key of ready) {
|
|
804
|
+
const update = this.pendingSectionUpdates.get(key)!
|
|
805
|
+
this.pendingSectionUpdates.delete(key)
|
|
806
|
+
this.pendingSectionBufferStartTimes.delete(key)
|
|
807
|
+
|
|
808
|
+
const chunkCoords = update.key.split(',')
|
|
809
|
+
const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
|
|
810
|
+
|
|
811
|
+
if (!this.loadedChunks[chunkKey] || !this.active) {
|
|
812
|
+
this.chunkMeshManager.releaseSection(update.key)
|
|
813
|
+
continue
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (!update.geometry.positions.length) {
|
|
817
|
+
this.chunkMeshManager.releaseSection(update.key)
|
|
818
|
+
continue
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
this.chunkMeshManager.updateSection(update.key, update.geometry)
|
|
822
|
+
this.updatePosDataChunk(update.key)
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
private clearPendingSectionUpdatesForChunk(x: number, z: number) {
|
|
827
|
+
for (const key of [...this.pendingSectionUpdates.keys()]) {
|
|
828
|
+
if (key.startsWith(`${x},`) && key.endsWith(`,${z}`)) {
|
|
829
|
+
this.pendingSectionUpdates.delete(key)
|
|
830
|
+
this.pendingSectionBufferStartTimes.delete(key)
|
|
831
|
+
}
|
|
832
|
+
}
|
|
747
833
|
}
|
|
748
834
|
|
|
749
835
|
handleWorkerMessage(data: { geometry: MesherGeometryOutput, key, type }): void {
|
|
750
836
|
if (data.type === 'geometry') {
|
|
751
837
|
const chunkCoords = data.key.split(',')
|
|
752
|
-
|
|
838
|
+
const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
|
|
839
|
+
if (!this.loadedChunks[chunkKey] || !this.active) {
|
|
840
|
+
this.pendingSectionUpdates.delete(data.key)
|
|
841
|
+
this.pendingSectionBufferStartTimes.delete(data.key)
|
|
842
|
+
return
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
if (this.sectionObjects[data.key]) {
|
|
846
|
+
this.pendingSectionUpdates.set(data.key, data)
|
|
847
|
+
// Per-section deadline: only set if we don't already have one, so
|
|
848
|
+
// repeated updates to the same section don't postpone its flush.
|
|
849
|
+
if (!this.pendingSectionBufferStartTimes.has(data.key)) {
|
|
850
|
+
this.pendingSectionBufferStartTimes.set(data.key, performance.now())
|
|
851
|
+
}
|
|
852
|
+
return
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (!data.geometry.positions.length) {
|
|
856
|
+
this.chunkMeshManager.releaseSection(data.key)
|
|
857
|
+
return
|
|
858
|
+
}
|
|
753
859
|
this.chunkMeshManager.updateSection(data.key, data.geometry)
|
|
754
860
|
this.updatePosDataChunk(data.key)
|
|
755
861
|
}
|
|
@@ -1052,6 +1158,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1052
1158
|
chunksRenderDistanceOverride !== undefined
|
|
1053
1159
|
) {
|
|
1054
1160
|
for (const [key, object] of Object.entries(this.sectionObjects)) {
|
|
1161
|
+
if (object._waitingForChunkDisplay) continue
|
|
1055
1162
|
const [x, y, z] = key.split(',').map(Number)
|
|
1056
1163
|
const isVisible =
|
|
1057
1164
|
// eslint-disable-next-line no-constant-binary-expression, sonarjs/no-redundant-boolean
|
|
@@ -1064,9 +1171,11 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1064
1171
|
object.visible = isVisible
|
|
1065
1172
|
}
|
|
1066
1173
|
} else {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1174
|
+
// No debug visibility override active — defer to the manager so the
|
|
1175
|
+
// performance-based override distance (set by `recordRenderTime` /
|
|
1176
|
+
// `autoLowerRenderDistance`) is honored, instead of force-showing every
|
|
1177
|
+
// section every frame and clobbering it.
|
|
1178
|
+
this.chunkMeshManager.updateSectionsVisibility()
|
|
1070
1179
|
}
|
|
1071
1180
|
}
|
|
1072
1181
|
|
|
@@ -1101,8 +1210,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1101
1210
|
|
|
1102
1211
|
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
|
1103
1212
|
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.
|
|
1213
|
+
this.applyPendingSectionUpdates()
|
|
1214
|
+
this.renderer.render(this.scene, cam)
|
|
1106
1215
|
|
|
1107
1216
|
if (
|
|
1108
1217
|
this.displayOptions.inWorldRenderingConfig.showHand &&
|
|
@@ -1237,18 +1346,16 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1237
1346
|
}
|
|
1238
1347
|
|
|
1239
1348
|
updateShowChunksBorder(value: boolean) {
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
child.visible = value
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1349
|
+
// Lazily create helpers on the first toggle (they are not created upfront
|
|
1350
|
+
// for sections streamed in while the option was off).
|
|
1351
|
+
this.chunkMeshManager.updateAllBoxHelpers(value)
|
|
1247
1352
|
}
|
|
1248
1353
|
|
|
1249
1354
|
resetWorld() {
|
|
1250
1355
|
super.resetWorld()
|
|
1251
1356
|
|
|
1357
|
+
this.pendingSectionUpdates.clear()
|
|
1358
|
+
this.pendingSectionBufferStartTimes.clear()
|
|
1252
1359
|
this.chunkMeshManager.dispose()
|
|
1253
1360
|
this.chunkMeshManager = new ChunkMeshManager(this, this.scene, this.material, this.worldSizeParams.worldHeight, this.viewDistance)
|
|
1254
1361
|
|
|
@@ -1279,6 +1386,11 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1279
1386
|
textures[key].dispose()
|
|
1280
1387
|
delete textures[key]
|
|
1281
1388
|
}
|
|
1389
|
+
// Sign / head textures moved to ChunkMeshManager.signHeadsRenderer in PR
|
|
1390
|
+
// #16; without invalidating that cache here, sign edits (and any other
|
|
1391
|
+
// block-entity NBT change picked up via setSectionDirty) would re-render
|
|
1392
|
+
// with the stale cached canvas until a full world reset.
|
|
1393
|
+
this.chunkMeshManager.cleanSignChunkTextures(x, z)
|
|
1282
1394
|
}
|
|
1283
1395
|
|
|
1284
1396
|
readdChunks() {
|
|
@@ -1303,6 +1415,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1303
1415
|
super.removeColumn(x, z)
|
|
1304
1416
|
|
|
1305
1417
|
this.cleanChunkTextures(x, z)
|
|
1418
|
+
this.clearPendingSectionUpdatesForChunk(x, z)
|
|
1306
1419
|
const sectionHeight = this.getSectionHeight()
|
|
1307
1420
|
const worldMinY = this.worldMinYRender
|
|
1308
1421
|
for (let y = worldMinY; y < this.worldSizeParams.worldHeight; y += sectionHeight) {
|
|
@@ -1333,6 +1446,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1333
1446
|
}
|
|
1334
1447
|
|
|
1335
1448
|
destroy(): void {
|
|
1449
|
+
this.pendingSectionUpdates.clear()
|
|
1450
|
+
this.pendingSectionBufferStartTimes.clear()
|
|
1336
1451
|
this.chunkMeshManager.dispose()
|
|
1337
1452
|
this.disposeModules()
|
|
1338
1453
|
this.fireworksLegacy.destroy()
|