minecraft-renderer 0.1.29 → 0.1.31
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 +386 -386
- package/package.json +1 -1
- package/src/graphicsBackend/playerState.ts +4 -0
- package/src/lib/cameraBobbing.ts +29 -85
- package/src/playerState/playerState.ts +4 -0
- package/src/three/cameraShake.ts +26 -2
- package/src/three/modules/cameraBobbing.ts +59 -0
- package/src/three/modules/index.ts +2 -0
- package/src/three/worldBlockGeometry.ts +85 -16
- package/src/three/worldRendererThree.ts +4 -1
- package/src/three/entity/models/sheep.obj +0 -555
package/package.json
CHANGED
package/src/lib/cameraBobbing.ts
CHANGED
|
@@ -1,95 +1,39 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
private bobAmount = 0
|
|
6
|
-
private prevBobAmount = 0
|
|
7
|
-
private readonly gameTimer = new GameTimer()
|
|
8
|
-
|
|
9
|
-
// eslint-disable-next-line max-params
|
|
10
|
-
constructor (
|
|
11
|
-
private readonly BOB_FREQUENCY: number = Math.PI, // How fast the bob cycles
|
|
12
|
-
private readonly BOB_BASE_AMPLITUDE: number = 0.5, // Base amplitude of the bob
|
|
13
|
-
private readonly VERTICAL_MULTIPLIER: number = 1, // Vertical movement multiplier
|
|
14
|
-
private readonly ROTATION_MULTIPLIER_Z: number = 3, // Roll rotation multiplier
|
|
15
|
-
private readonly ROTATION_MULTIPLIER_X: number = 5 // Pitch rotation multiplier
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
// Call this when player is moving
|
|
19
|
-
public updateWalkDistance (distance: number): void {
|
|
20
|
-
this.prevWalkDistance = this.walkDistance
|
|
21
|
-
this.walkDistance = distance
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Call this when player is moving to update bob amount
|
|
25
|
-
public updateBobAmount (isMoving: boolean): void {
|
|
26
|
-
const targetBob = isMoving ? 1 : 0
|
|
27
|
-
this.prevBobAmount = this.bobAmount
|
|
28
|
-
|
|
29
|
-
// Update timing
|
|
30
|
-
const ticks = this.gameTimer.update()
|
|
31
|
-
const deltaTime = ticks / 20 // Convert ticks to seconds assuming 20 TPS
|
|
32
|
-
|
|
33
|
-
// Smooth transition for bob amount
|
|
34
|
-
const bobDelta = (targetBob - this.bobAmount) * Math.min(1, deltaTime * 10)
|
|
35
|
-
this.bobAmount += bobDelta
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Call this in your render/animation loop
|
|
39
|
-
public getBobbing (): { position: { x: number, y: number }, rotation: { x: number, z: number } } {
|
|
40
|
-
// Interpolate walk distance
|
|
41
|
-
const walkDist = this.prevWalkDistance +
|
|
42
|
-
(this.walkDistance - this.prevWalkDistance) * this.gameTimer.partialTick
|
|
43
|
-
|
|
44
|
-
// Interpolate bob amount
|
|
45
|
-
const bob = this.prevBobAmount +
|
|
46
|
-
(this.bobAmount - this.prevBobAmount) * this.gameTimer.partialTick
|
|
47
|
-
|
|
48
|
-
// Calculate total distance for bob cycle
|
|
49
|
-
const totalDist = -(walkDist * this.BOB_FREQUENCY)
|
|
50
|
-
|
|
51
|
-
// Calculate offsets
|
|
52
|
-
const xOffset = Math.sin(totalDist) * bob * this.BOB_BASE_AMPLITUDE
|
|
53
|
-
const yOffset = -Math.abs(Math.cos(totalDist) * bob) * this.VERTICAL_MULTIPLIER
|
|
54
|
-
|
|
55
|
-
// Calculate rotations (in radians)
|
|
56
|
-
const zRot = (Math.sin(totalDist) * bob * this.ROTATION_MULTIPLIER_Z) * (Math.PI / 180)
|
|
57
|
-
const xRot = (Math.abs(Math.cos(totalDist - 0.2) * bob) * this.ROTATION_MULTIPLIER_X) * (Math.PI / 180)
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
position: { x: xOffset, y: yOffset },
|
|
61
|
-
rotation: { x: xRot, z: zRot }
|
|
62
|
-
}
|
|
63
|
-
}
|
|
2
|
+
export interface CameraBobResult {
|
|
3
|
+
position: { x: number; y: number }
|
|
4
|
+
rotation: { x: number; z: number }
|
|
64
5
|
}
|
|
65
6
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
this.lastMs = performance.now()
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
update (): number {
|
|
77
|
-
const currentMs = performance.now()
|
|
78
|
-
const deltaSinceLastTick = currentMs - this.lastMs
|
|
7
|
+
export interface CameraBobInput {
|
|
8
|
+
walkDist: number
|
|
9
|
+
prevWalkDist: number
|
|
10
|
+
bob: number
|
|
11
|
+
prevBob: number
|
|
12
|
+
partialTick: number
|
|
13
|
+
}
|
|
79
14
|
|
|
80
|
-
|
|
81
|
-
const tickDelta = deltaSinceLastTick / this.msPerTick
|
|
82
|
-
this.lastMs = currentMs
|
|
15
|
+
const DEG_TO_RAD = Math.PI / 180
|
|
83
16
|
|
|
84
|
-
|
|
85
|
-
|
|
17
|
+
export function computeCameraBob (input: CameraBobInput): CameraBobResult {
|
|
18
|
+
const { walkDist, prevWalkDist, bob, prevBob, partialTick } = input
|
|
86
19
|
|
|
87
|
-
|
|
88
|
-
|
|
20
|
+
// Vanilla uses "backwards interpolation": -(walkDist + delta * partialTick)
|
|
21
|
+
// See ClientAvatarState.getBackwardsInterpolatedWalkDistance()
|
|
22
|
+
const walkDelta = walkDist - prevWalkDist
|
|
23
|
+
const interpolatedWalkDist = -(walkDist + walkDelta * partialTick)
|
|
24
|
+
const interpolatedBob = prevBob + (bob - prevBob) * partialTick
|
|
89
25
|
|
|
90
|
-
|
|
91
|
-
|
|
26
|
+
const sinWalk = Math.sin(interpolatedWalkDist * Math.PI)
|
|
27
|
+
const cosWalk = Math.cos(interpolatedWalkDist * Math.PI)
|
|
92
28
|
|
|
93
|
-
|
|
29
|
+
return {
|
|
30
|
+
position: {
|
|
31
|
+
x: sinWalk * interpolatedBob * 0.5,
|
|
32
|
+
y: -Math.abs(cosWalk * interpolatedBob)
|
|
33
|
+
},
|
|
34
|
+
rotation: {
|
|
35
|
+
x: Math.abs(Math.cos(interpolatedWalkDist * Math.PI - 0.2) * interpolatedBob) * 5 * DEG_TO_RAD,
|
|
36
|
+
z: sinWalk * interpolatedBob * 3 * DEG_TO_RAD
|
|
37
|
+
}
|
|
94
38
|
}
|
|
95
39
|
}
|
package/src/three/cameraShake.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import * as THREE from 'three'
|
|
3
|
+
import { computeCameraBob, type CameraBobInput } from '../lib/cameraBobbing'
|
|
3
4
|
import { WorldRendererThree } from './worldRendererThree'
|
|
4
5
|
|
|
5
6
|
export class CameraShake {
|
|
@@ -9,6 +10,11 @@ export class CameraShake {
|
|
|
9
10
|
private rollAnimation?: { startTime: number, startRoll: number, targetRoll: number, duration: number, returnToZero?: boolean }
|
|
10
11
|
private basePitch = 0
|
|
11
12
|
private baseYaw = 0
|
|
13
|
+
private cameraBobInput: CameraBobInput | null = null
|
|
14
|
+
|
|
15
|
+
setCameraBobInput(input: CameraBobInput | null) {
|
|
16
|
+
this.cameraBobInput = input
|
|
17
|
+
}
|
|
12
18
|
|
|
13
19
|
constructor(public worldRenderer: WorldRendererThree, public onRenderCallbacks: Array<(deltaTime: number) => void>) {
|
|
14
20
|
onRenderCallbacks.push(() => {
|
|
@@ -88,8 +94,26 @@ export class CameraShake {
|
|
|
88
94
|
const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), pitchOffset)
|
|
89
95
|
const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), yawOffset)
|
|
90
96
|
const rollQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(this.rollAngle))
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
|
|
98
|
+
// Camera bobbing rotation
|
|
99
|
+
let bobRollQuat = new THREE.Quaternion()
|
|
100
|
+
let bobPitchQuat = new THREE.Quaternion()
|
|
101
|
+
const perspective = this.worldRenderer.playerStateReactive.perspective
|
|
102
|
+
if (this.cameraBobInput) {
|
|
103
|
+
const bob = computeCameraBob(this.cameraBobInput)
|
|
104
|
+
bobRollQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), bob.rotation.z)
|
|
105
|
+
bobPitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), bob.rotation.x)
|
|
106
|
+
|
|
107
|
+
// Apply position bobbing to the camera child (not cameraContainer) in first-person only
|
|
108
|
+
if (perspective === 'first_person') {
|
|
109
|
+
this.worldRenderer.camera.position.set(bob.position.x, bob.position.y, 0)
|
|
110
|
+
}
|
|
111
|
+
} else if (perspective === 'first_person') {
|
|
112
|
+
this.worldRenderer.camera.position.set(0, 0, 0)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Combine: yaw * pitch * damageRoll * bobRoll(Z) * bobPitch(X) — vanilla applies Z then X
|
|
116
|
+
const finalQuat = yawQuat.multiply(pitchQuat).multiply(rollQuat).multiply(bobRollQuat).multiply(bobPitchQuat)
|
|
93
117
|
camera.setRotationFromQuaternion(finalQuat)
|
|
94
118
|
}
|
|
95
119
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import type { WorldRendererThree } from '../worldRendererThree'
|
|
3
|
+
import type { RendererModuleController, RendererModuleManifest } from '../rendererModuleSystem'
|
|
4
|
+
|
|
5
|
+
export class CameraBobbingModule implements RendererModuleController {
|
|
6
|
+
private enabled = false
|
|
7
|
+
private lastBobWalkDist = 0
|
|
8
|
+
private lastBobTickTime = 0
|
|
9
|
+
|
|
10
|
+
constructor(private readonly worldRenderer: WorldRendererThree) { }
|
|
11
|
+
|
|
12
|
+
enable(): void {
|
|
13
|
+
this.enabled = true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
disable(): void {
|
|
17
|
+
this.enabled = false
|
|
18
|
+
this.worldRenderer.cameraShake.setCameraBobInput(null)
|
|
19
|
+
const { perspective } = this.worldRenderer.playerStateReactive
|
|
20
|
+
if (perspective === 'first_person') {
|
|
21
|
+
this.worldRenderer.camera.position.set(0, 0, 0)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
render?: (deltaTime: number) => void = () => {
|
|
26
|
+
if (!this.enabled) return
|
|
27
|
+
const config = this.worldRenderer.displayOptions.inWorldRenderingConfig
|
|
28
|
+
const { perspective } = this.worldRenderer.playerStateReactive
|
|
29
|
+
|
|
30
|
+
if (config.viewBobbing && perspective === 'first_person') {
|
|
31
|
+
if (this.worldRenderer.playerStateReactive.walkDist !== this.lastBobWalkDist) {
|
|
32
|
+
this.lastBobTickTime = performance.now()
|
|
33
|
+
this.lastBobWalkDist = this.worldRenderer.playerStateReactive.walkDist
|
|
34
|
+
}
|
|
35
|
+
const partialTick = Math.min((performance.now() - this.lastBobTickTime) / 50, 1)
|
|
36
|
+
|
|
37
|
+
this.worldRenderer.cameraShake.setCameraBobInput({
|
|
38
|
+
walkDist: this.worldRenderer.playerStateReactive.walkDist,
|
|
39
|
+
prevWalkDist: this.worldRenderer.playerStateReactive.prevWalkDist,
|
|
40
|
+
bob: this.worldRenderer.playerStateReactive.bob,
|
|
41
|
+
prevBob: this.worldRenderer.playerStateReactive.prevBob,
|
|
42
|
+
partialTick
|
|
43
|
+
})
|
|
44
|
+
} else {
|
|
45
|
+
this.worldRenderer.cameraShake.setCameraBobInput(null)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
dispose(): void {
|
|
50
|
+
this.disable()
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const cameraBobbingManifest: RendererModuleManifest = {
|
|
55
|
+
id: 'cameraBobbing',
|
|
56
|
+
controller: CameraBobbingModule,
|
|
57
|
+
enabledDefault: true,
|
|
58
|
+
cannotBeDisabled: true,
|
|
59
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
|
+
import { cameraBobbingManifest } from './cameraBobbing'
|
|
2
3
|
import { rainManifest } from './rain'
|
|
3
4
|
import { sciFiWorldRevealManifest } from './sciFiWorldReveal'
|
|
4
5
|
import { starfieldManifest } from './starfield'
|
|
@@ -7,4 +8,5 @@ export const BUILTIN_MODULES = {
|
|
|
7
8
|
starfield: starfieldManifest,
|
|
8
9
|
futuristicReveal: sciFiWorldRevealManifest,
|
|
9
10
|
rain: rainManifest,
|
|
11
|
+
cameraBobbing: cameraBobbingManifest,
|
|
10
12
|
}
|
|
@@ -4,7 +4,6 @@ import { Vec3 } from 'vec3'
|
|
|
4
4
|
import nbt from 'prismarine-nbt'
|
|
5
5
|
import { MesherGeometryOutput, IS_FULL_WORLD_SECTION } from '../mesher/shared'
|
|
6
6
|
import { getBannerTexture, createBannerMesh, releaseBannerTexture } from './bannerRenderer'
|
|
7
|
-
import { disposeObject } from './threeJsUtils'
|
|
8
7
|
import type { WorldRendererThree } from './worldRendererThree'
|
|
9
8
|
|
|
10
9
|
export interface SectionObject extends THREE.Object3D {
|
|
@@ -17,6 +16,9 @@ export class WorldBlockGeometry {
|
|
|
17
16
|
sectionObjects: Record<string, SectionObject> = {}
|
|
18
17
|
waitingChunksToDisplay: { [chunkKey: string]: string[] } = {}
|
|
19
18
|
estimatedMemoryUsage = 0
|
|
19
|
+
private pendingUpdates: Map<string, { geometry: MesherGeometryOutput; key: string; type: string }> = new Map()
|
|
20
|
+
private pendingBufferStartTime: number | null = null
|
|
21
|
+
private static readonly MAX_BUFFER_MS = 500
|
|
20
22
|
|
|
21
23
|
constructor(
|
|
22
24
|
private readonly worldRenderer: WorldRendererThree,
|
|
@@ -26,21 +28,84 @@ export class WorldBlockGeometry {
|
|
|
26
28
|
) { }
|
|
27
29
|
|
|
28
30
|
handleWorkerGeometryMessage(data: { geometry: MesherGeometryOutput; key: string; type: string }): void {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
const isUpdate = !!this.sectionObjects[data.key]
|
|
32
|
+
|
|
33
|
+
if (isUpdate) {
|
|
34
|
+
// Buffer updates for existing sections — keep old mesh visible
|
|
35
|
+
this.pendingUpdates.set(data.key, data)
|
|
36
|
+
if (this.pendingBufferStartTime === null) {
|
|
37
|
+
this.pendingBufferStartTime = performance.now()
|
|
38
|
+
}
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Initial load — apply immediately
|
|
43
|
+
this._applySectionGeometry(data)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
applyPendingUpdates(): void {
|
|
47
|
+
if (this.pendingUpdates.size === 0) return
|
|
48
|
+
|
|
49
|
+
const now = performance.now()
|
|
50
|
+
const sinceFirst = now - (this.pendingBufferStartTime ?? now)
|
|
51
|
+
|
|
52
|
+
// Wait for neighboring sections still being meshed (unless max timeout reached)
|
|
53
|
+
if (sinceFirst < WorldBlockGeometry.MAX_BUFFER_MS) {
|
|
54
|
+
const sectionHeight = this.worldRenderer.getSectionHeight()
|
|
55
|
+
for (const key of this.pendingUpdates.keys()) {
|
|
56
|
+
const [sx, sy, sz] = key.split(',').map(Number)
|
|
57
|
+
const neighborKeys = [
|
|
58
|
+
`${sx - 16},${sy},${sz}`, `${sx + 16},${sy},${sz}`,
|
|
59
|
+
`${sx},${sy - sectionHeight},${sz}`, `${sx},${sy + sectionHeight},${sz}`,
|
|
60
|
+
`${sx},${sy},${sz - 16}`, `${sx},${sy},${sz + 16}`,
|
|
61
|
+
]
|
|
62
|
+
for (const neighborKey of neighborKeys) {
|
|
63
|
+
// Wait if neighbor is being meshed, hasn't arrived yet, and has a loaded mesh
|
|
64
|
+
if (this.worldRenderer.sectionsWaiting.has(neighborKey) &&
|
|
65
|
+
!this.pendingUpdates.has(neighborKey) &&
|
|
66
|
+
this.sectionObjects[neighborKey]) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
37
69
|
}
|
|
38
|
-
}
|
|
39
|
-
this.worldRenderer.sceneOrigin.removeAndUntrackAll(object)
|
|
40
|
-
disposeObject(object)
|
|
41
|
-
delete this.sectionObjects[data.key]
|
|
70
|
+
}
|
|
42
71
|
}
|
|
43
72
|
|
|
73
|
+
// Flush all pending updates atomically
|
|
74
|
+
for (const [key, data] of this.pendingUpdates) {
|
|
75
|
+
this._removeSectionSafely(key)
|
|
76
|
+
this._applySectionGeometry(data)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.pendingUpdates.clear()
|
|
80
|
+
this.pendingBufferStartTime = null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private disposeSectionObject(obj: THREE.Object3D): void {
|
|
84
|
+
if (obj instanceof THREE.Mesh) {
|
|
85
|
+
obj.geometry?.dispose?.()
|
|
86
|
+
// Don't dispose material - it's shared across all sections
|
|
87
|
+
}
|
|
88
|
+
if (obj.children) {
|
|
89
|
+
obj.children.forEach(child => this.disposeSectionObject(child))
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private _removeSectionSafely(key: string): void {
|
|
94
|
+
const object = this.sectionObjects[key]
|
|
95
|
+
if (!object) return
|
|
96
|
+
|
|
97
|
+
this.removeSectionMemoryUsage(object)
|
|
98
|
+
object.traverse((child) => {
|
|
99
|
+
if ((child as any).bannerTexture) {
|
|
100
|
+
releaseBannerTexture((child as any).bannerTexture)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
this.worldRenderer.sceneOrigin.removeAndUntrackAll(object)
|
|
104
|
+
this.disposeSectionObject(object)
|
|
105
|
+
delete this.sectionObjects[key]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private _applySectionGeometry(data: { geometry: MesherGeometryOutput; key: string; type: string }): void {
|
|
44
109
|
const chunkCoords = data.key.split(',')
|
|
45
110
|
if (
|
|
46
111
|
!this.worldRenderer.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] ||
|
|
@@ -69,7 +134,7 @@ export class WorldBlockGeometry {
|
|
|
69
134
|
this.worldRenderer.sceneOrigin.track(mesh, { updateMatrix: true })
|
|
70
135
|
mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz)
|
|
71
136
|
mesh.name = 'mesh'
|
|
72
|
-
object = new THREE.Group()
|
|
137
|
+
const object: THREE.Object3D = new THREE.Group()
|
|
73
138
|
object.add(mesh)
|
|
74
139
|
// mesh with static dimensions: 16x16xsectionHeight
|
|
75
140
|
const sectionHeight = data.geometry.sectionEndY - data.geometry.sectionStartY
|
|
@@ -238,6 +303,8 @@ export class WorldBlockGeometry {
|
|
|
238
303
|
|
|
239
304
|
// Reset memory tracking since all sections are cleared
|
|
240
305
|
this.estimatedMemoryUsage = 0
|
|
306
|
+
this.pendingUpdates.clear()
|
|
307
|
+
this.pendingBufferStartTime = null
|
|
241
308
|
}
|
|
242
309
|
|
|
243
310
|
removeColumn(x: number, z: number) {
|
|
@@ -259,9 +326,10 @@ export class WorldBlockGeometry {
|
|
|
259
326
|
}
|
|
260
327
|
})
|
|
261
328
|
this.worldRenderer.sceneOrigin.removeAndUntrackAll(mesh)
|
|
262
|
-
|
|
329
|
+
this.disposeSectionObject(mesh)
|
|
263
330
|
}
|
|
264
331
|
delete this.sectionObjects[key]
|
|
332
|
+
this.pendingUpdates.delete(key)
|
|
265
333
|
} else {
|
|
266
334
|
for (let y = worldMinY; y < this.worldRenderer.worldSizeParams.worldHeight; y += sectionHeight) {
|
|
267
335
|
this.worldRenderer.setSectionDirty(new Vec3(x, y, z), false)
|
|
@@ -277,9 +345,10 @@ export class WorldBlockGeometry {
|
|
|
277
345
|
}
|
|
278
346
|
})
|
|
279
347
|
this.worldRenderer.sceneOrigin.removeAndUntrackAll(mesh)
|
|
280
|
-
|
|
348
|
+
this.disposeSectionObject(mesh)
|
|
281
349
|
}
|
|
282
350
|
delete this.sectionObjects[key]
|
|
351
|
+
this.pendingUpdates.delete(key)
|
|
283
352
|
}
|
|
284
353
|
}
|
|
285
354
|
}
|
|
@@ -985,7 +985,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
985
985
|
this.camera.rotation.set(0, 0, 0)
|
|
986
986
|
}
|
|
987
987
|
} else {
|
|
988
|
-
|
|
988
|
+
// Only reset z (clears third-person offset); x/y are managed by CameraShake for bobbing
|
|
989
|
+
this.camera.position.z = 0
|
|
989
990
|
this.camera.rotation.set(0, 0, 0)
|
|
990
991
|
|
|
991
992
|
// remove any debug raycasting
|
|
@@ -1065,6 +1066,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1065
1066
|
|
|
1066
1067
|
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
|
1067
1068
|
const cam = this.cameraGroupVr instanceof THREE.Group ? this.cameraGroupVr.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
|
|
1069
|
+
// Flush buffered geometry updates atomically before rendering
|
|
1070
|
+
this.worldBlockGeometry.applyPendingUpdates()
|
|
1068
1071
|
this.renderer.render(this.scene, cam)
|
|
1069
1072
|
|
|
1070
1073
|
if (
|