minecraft-renderer 0.1.27 → 0.1.29
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/mesher.js +22 -22
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +17 -17
- package/dist/minecraft-renderer.js +65 -619
- package/dist/minecraft-renderer.js.meta.json +1 -0
- package/dist/threeWorker.js +453 -1007
- package/package.json +1 -1
- package/src/lib/guiRenderer.ts +0 -1
- package/src/lib/moreBlockDataGenerated.json +8 -0
- package/src/lib/skyLight.ts +125 -0
- package/src/lib/worldrendererCommon.ts +2 -12
- package/src/mesher/models.ts +28 -2
- package/src/mesher/test/mesherTester.ts +1 -1
- package/src/sign-renderer/index.ts +4 -1
- package/src/three/bannerRenderer.ts +5 -4
- package/src/three/cinimaticScript.ts +2 -2
- package/src/three/entities.ts +42 -11
- package/src/three/entity/EntityMesh.ts +1 -1
- package/src/three/entity/entities.json +108 -2
- package/src/three/entity/exportedModels.js +0 -1
- package/src/three/entity/externalTextures.json +1 -1
- package/src/three/fireworks.ts +18 -5
- package/src/three/fireworksRenderer.ts +15 -13
- package/src/three/modules/rain.ts +6 -1
- package/src/three/modules/sciFiWorldReveal.ts +14 -10
- package/src/three/modules/starfield.ts +1 -1
- package/src/three/sceneOrigin.ts +215 -0
- package/src/three/skyboxRenderer.ts +3 -3
- package/src/three/threeJsMedia.ts +12 -6
- package/src/three/threeJsParticles.ts +42 -14
- package/src/three/threeJsSound.ts +3 -3
- package/src/three/waypointSprite.ts +45 -23
- package/src/three/waypoints.ts +12 -4
- package/src/three/world/cursorBlock.ts +5 -5
- package/src/three/worldBlockGeometry.ts +14 -5
- package/src/three/worldGeometryExport.ts +4 -3
- package/src/three/worldRendererThree.ts +155 -30
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import type { Object3D, Scene, Vector3 } from 'three'
|
|
3
|
+
|
|
4
|
+
const IS_TRACKED_PROXY = Symbol('tracked-proxy')
|
|
5
|
+
|
|
6
|
+
const MUTATING_METHODS = new Set([
|
|
7
|
+
'add', 'addScalar', 'addScaledVector', 'addVectors',
|
|
8
|
+
'sub', 'subScalar', 'subVectors',
|
|
9
|
+
'multiply', 'multiplyScalar', 'multiplyVectors',
|
|
10
|
+
'divide', 'divideScalar',
|
|
11
|
+
'applyEuler', 'applyAxisAngle', 'applyMatrix3', 'applyMatrix4', 'applyNormalMatrix', 'applyQuaternion',
|
|
12
|
+
'negate', 'floor', 'ceil', 'round', 'roundToZero',
|
|
13
|
+
'min', 'max', 'clamp', 'clampLength', 'clampScalar',
|
|
14
|
+
'project', 'unproject', 'reflect',
|
|
15
|
+
'lerp', 'lerpVectors',
|
|
16
|
+
'cross', 'crossVectors',
|
|
17
|
+
'setFromMatrixPosition', 'setFromMatrixColumn', 'setFromMatrix3Column',
|
|
18
|
+
'setFromEuler', 'setFromSpherical', 'setFromSphericalCoords', 'setFromCylindrical',
|
|
19
|
+
'fromArray', 'fromBufferAttribute',
|
|
20
|
+
'setComponent', 'randomDirection', 'random',
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
interface TrackOptions {
|
|
24
|
+
updateMatrix?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class SceneOrigin {
|
|
28
|
+
private scene: Scene
|
|
29
|
+
|
|
30
|
+
// World coordinates of origin in float64 (JavaScript number)
|
|
31
|
+
private _x = 0
|
|
32
|
+
private _y = 0
|
|
33
|
+
private _z = 0
|
|
34
|
+
|
|
35
|
+
private readonly _tracked = new Set<Object3D>()
|
|
36
|
+
private readonly _worldCoords = new WeakMap<Object3D, { x: number; y: number; z: number }>()
|
|
37
|
+
private readonly _originalPositions = new WeakMap<Object3D, Vector3>()
|
|
38
|
+
private readonly _trackOptions = new WeakMap<Object3D, TrackOptions>()
|
|
39
|
+
|
|
40
|
+
constructor(scene: Scene) {
|
|
41
|
+
this.scene = scene
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get x(): number { return this._x }
|
|
45
|
+
get y(): number { return this._y }
|
|
46
|
+
get z(): number { return this._z }
|
|
47
|
+
|
|
48
|
+
/** Update origin (called each frame with camera world position) */
|
|
49
|
+
update(worldX: number, worldY: number, worldZ: number): void {
|
|
50
|
+
this._x = worldX
|
|
51
|
+
this._y = worldY
|
|
52
|
+
this._z = worldZ
|
|
53
|
+
|
|
54
|
+
for (const obj of this._tracked) {
|
|
55
|
+
const worldData = this._worldCoords.get(obj)!
|
|
56
|
+
const realPos = this._originalPositions.get(obj)!
|
|
57
|
+
realPos.set(worldData.x - worldX, worldData.y - worldY, worldData.z - worldZ)
|
|
58
|
+
const opts = this._trackOptions.get(obj)
|
|
59
|
+
if (opts?.updateMatrix) obj.updateMatrix()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Track an Object3D so its position is automatically adjusted on origin changes */
|
|
64
|
+
track(obj: Object3D, options?: TrackOptions): void {
|
|
65
|
+
// If already tracked, use the stored original position to avoid nesting Proxies
|
|
66
|
+
const realPosition = this._originalPositions.get(obj) ?? obj.position
|
|
67
|
+
const worldData = { x: 0, y: 0, z: 0 }
|
|
68
|
+
|
|
69
|
+
this._originalPositions.set(obj, realPosition)
|
|
70
|
+
this._worldCoords.set(obj, worldData)
|
|
71
|
+
if (options) {
|
|
72
|
+
this._trackOptions.set(obj, options)
|
|
73
|
+
} else {
|
|
74
|
+
this._trackOptions.delete(obj)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
78
|
+
const origin = this
|
|
79
|
+
const opts = options
|
|
80
|
+
|
|
81
|
+
const proxy = new Proxy(realPosition, {
|
|
82
|
+
get(target, prop, receiver) {
|
|
83
|
+
if (prop === IS_TRACKED_PROXY) return worldData
|
|
84
|
+
if (prop === 'set') {
|
|
85
|
+
return (x: number, y: number, z: number) => {
|
|
86
|
+
worldData.x = x; worldData.y = y; worldData.z = z
|
|
87
|
+
target.set(x - origin._x, y - origin._y, z - origin._z)
|
|
88
|
+
if (opts?.updateMatrix) obj.updateMatrix()
|
|
89
|
+
return receiver
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (prop === 'copy') {
|
|
93
|
+
return (v: Vector3) => {
|
|
94
|
+
const srcWorld = (v as any)[IS_TRACKED_PROXY] as { x: number; y: number; z: number } | undefined
|
|
95
|
+
const wx = srcWorld ? srcWorld.x : v.x
|
|
96
|
+
const wy = srcWorld ? srcWorld.y : v.y
|
|
97
|
+
const wz = srcWorld ? srcWorld.z : v.z
|
|
98
|
+
worldData.x = wx; worldData.y = wy; worldData.z = wz
|
|
99
|
+
target.set(wx - origin._x, wy - origin._y, wz - origin._z)
|
|
100
|
+
if (opts?.updateMatrix) obj.updateMatrix()
|
|
101
|
+
return receiver
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (prop === 'setX') {
|
|
105
|
+
return (val: number) => {
|
|
106
|
+
worldData.x = val; target.x = val - origin._x
|
|
107
|
+
if (opts?.updateMatrix) obj.updateMatrix()
|
|
108
|
+
return receiver
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (prop === 'setY') {
|
|
112
|
+
return (val: number) => {
|
|
113
|
+
worldData.y = val; target.y = val - origin._y
|
|
114
|
+
if (opts?.updateMatrix) obj.updateMatrix()
|
|
115
|
+
return receiver
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (prop === 'setZ') {
|
|
119
|
+
return (val: number) => {
|
|
120
|
+
worldData.z = val; target.z = val - origin._z
|
|
121
|
+
if (opts?.updateMatrix) obj.updateMatrix()
|
|
122
|
+
return receiver
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (typeof prop === 'string' && MUTATING_METHODS.has(prop)) {
|
|
126
|
+
return () => { throw new Error(`Cannot call position.${prop}() on a tracked object. Use position.set(x, y, z) instead.`) }
|
|
127
|
+
}
|
|
128
|
+
const value = (target as any)[prop]
|
|
129
|
+
if (typeof value === 'function') return value.bind(target)
|
|
130
|
+
return value
|
|
131
|
+
},
|
|
132
|
+
set(target, prop, value) {
|
|
133
|
+
if (prop === 'x') {
|
|
134
|
+
worldData.x = value; target.x = value - origin._x
|
|
135
|
+
if (opts?.updateMatrix) obj.updateMatrix()
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
if (prop === 'y') {
|
|
139
|
+
worldData.y = value; target.y = value - origin._y
|
|
140
|
+
if (opts?.updateMatrix) obj.updateMatrix()
|
|
141
|
+
return true
|
|
142
|
+
}
|
|
143
|
+
if (prop === 'z') {
|
|
144
|
+
worldData.z = value; target.z = value - origin._z
|
|
145
|
+
if (opts?.updateMatrix) obj.updateMatrix()
|
|
146
|
+
return true
|
|
147
|
+
}
|
|
148
|
+
;(target as any)[prop] = value
|
|
149
|
+
return true
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
Object.defineProperty(obj, 'position', { value: proxy, configurable: true, enumerable: true })
|
|
154
|
+
this._tracked.add(obj)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Stop tracking an Object3D, restoring its original position Vector3 */
|
|
158
|
+
untrack(obj: Object3D): void {
|
|
159
|
+
const originalPos = this._originalPositions.get(obj)
|
|
160
|
+
if (!originalPos) return
|
|
161
|
+
Object.defineProperty(obj, 'position', { value: originalPos, configurable: true, enumerable: true })
|
|
162
|
+
this._tracked.delete(obj)
|
|
163
|
+
this._worldCoords.delete(obj)
|
|
164
|
+
this._originalPositions.delete(obj)
|
|
165
|
+
this._trackOptions.delete(obj)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Track an Object3D and add it to the scene */
|
|
169
|
+
addAndTrack(obj: Object3D, options?: TrackOptions): void {
|
|
170
|
+
this.track(obj, options)
|
|
171
|
+
this.scene.add(obj)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Untrack an Object3D and remove it from the scene */
|
|
175
|
+
removeAndUntrack(obj: Object3D): void {
|
|
176
|
+
this.untrack(obj)
|
|
177
|
+
this.scene.remove(obj)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Untrack an Object3D and all its descendants, then remove from the scene */
|
|
181
|
+
removeAndUntrackAll(obj: Object3D): void {
|
|
182
|
+
obj.traverse((child) => {
|
|
183
|
+
this.untrack(child)
|
|
184
|
+
})
|
|
185
|
+
this.scene.remove(obj)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Get stored world position for a tracked object */
|
|
189
|
+
getWorldPosition(obj: Object3D): { x: number; y: number; z: number } | undefined {
|
|
190
|
+
const w = this._worldCoords.get(obj)
|
|
191
|
+
return w ? { x: w.x, y: w.y, z: w.z } : undefined
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Clear all tracked objects (call on scene reset) */
|
|
195
|
+
clear(): void {
|
|
196
|
+
for (const obj of this._tracked) {
|
|
197
|
+
this.untrack(obj)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Number of currently tracked objects (for debugging) */
|
|
202
|
+
get trackedCount(): number {
|
|
203
|
+
return this._tracked.size
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Convert world coordinates → scene coordinates */
|
|
207
|
+
toSceneX(worldX: number): number { return worldX - this._x }
|
|
208
|
+
toSceneY(worldY: number): number { return worldY - this._y }
|
|
209
|
+
toSceneZ(worldZ: number): number { return worldZ - this._z }
|
|
210
|
+
|
|
211
|
+
/** Convert scene coordinates → world coordinates */
|
|
212
|
+
toWorldX(sceneX: number): number { return sceneX + this._x }
|
|
213
|
+
toWorldY(sceneY: number): number { return sceneY + this._y }
|
|
214
|
+
toWorldZ(sceneZ: number): number { return sceneZ + this._z }
|
|
215
|
+
}
|
|
@@ -108,11 +108,11 @@ export class SkyboxRenderer {
|
|
|
108
108
|
|
|
109
109
|
if (this.mesh) {
|
|
110
110
|
// Update skybox position
|
|
111
|
-
this.mesh.position.
|
|
111
|
+
this.mesh.position.set(0, 0, 0)
|
|
112
112
|
} else if (this.skyMesh) {
|
|
113
113
|
// Update gradient sky position
|
|
114
|
-
this.skyMesh.position.
|
|
115
|
-
this.voidMesh?.position.
|
|
114
|
+
this.skyMesh.position.set(0, 0, 0)
|
|
115
|
+
this.voidMesh?.position.set(0, 0, 0)
|
|
116
116
|
this.updateSkyColors() // Update colors based on time of day
|
|
117
117
|
}
|
|
118
118
|
}
|
|
@@ -203,7 +203,7 @@ export class ThreeJsMedia {
|
|
|
203
203
|
positionalAudio = new THREE.PositionalAudio(soundSystem.audioListener)
|
|
204
204
|
positionalAudio.setRefDistance(6)
|
|
205
205
|
positionalAudio.setVolume(volume)
|
|
206
|
-
|
|
206
|
+
this.worldRenderer.sceneOrigin.addAndTrack(positionalAudio)
|
|
207
207
|
positionalAudio.position.set(props.position.x, props.position.y, props.position.z)
|
|
208
208
|
|
|
209
209
|
// Connect video to positional audio
|
|
@@ -461,9 +461,9 @@ export class ThreeJsMedia {
|
|
|
461
461
|
if (mediaData.positionalAudio) {
|
|
462
462
|
// mediaData.positionalAudio.stop()
|
|
463
463
|
// mediaData.positionalAudio.disconnect()
|
|
464
|
-
|
|
464
|
+
this.worldRenderer.sceneOrigin.removeAndUntrack(mediaData.positionalAudio)
|
|
465
465
|
}
|
|
466
|
-
|
|
466
|
+
this.worldRenderer.sceneOrigin.removeAndUntrack(mediaData.mesh)
|
|
467
467
|
mediaData.texture.dispose()
|
|
468
468
|
|
|
469
469
|
// Get the inner mesh from the group
|
|
@@ -549,7 +549,8 @@ export class ThreeJsMedia {
|
|
|
549
549
|
mesh.geometry.translate(0.5, 0.5, 0)
|
|
550
550
|
mesh.geometry.attributes.position.needsUpdate = true
|
|
551
551
|
|
|
552
|
-
// Now place the mesh at the start position
|
|
552
|
+
// Now place the mesh at the start position (convert world → scene coords)
|
|
553
|
+
this.worldRenderer.sceneOrigin.track(mesh)
|
|
553
554
|
mesh.position.set(startPosition.x, startPosition.y, startPosition.z)
|
|
554
555
|
|
|
555
556
|
// Create a group to hold our mesh and markers
|
|
@@ -561,7 +562,7 @@ export class ThreeJsMedia {
|
|
|
561
562
|
new THREE.BoxGeometry(0.1, 0.1, 0.1),
|
|
562
563
|
new THREE.MeshBasicMaterial({ color: 0xff_00_00 })
|
|
563
564
|
)
|
|
564
|
-
startMarker.position.
|
|
565
|
+
startMarker.position.set(startPosition.x, startPosition.y, startPosition.z)
|
|
565
566
|
debugGroup.add(startMarker)
|
|
566
567
|
|
|
567
568
|
// Add a marker at the end position (width units away in the rotated direction)
|
|
@@ -591,9 +592,14 @@ export class ThreeJsMedia {
|
|
|
591
592
|
debugGroup.add(endCornerMarker)
|
|
592
593
|
|
|
593
594
|
// Also add a visual helper to show the rotation direction
|
|
595
|
+
const sceneStartPos = new THREE.Vector3(
|
|
596
|
+
this.worldRenderer.sceneOrigin.toSceneX(startPosition.x),
|
|
597
|
+
this.worldRenderer.sceneOrigin.toSceneY(startPosition.y),
|
|
598
|
+
this.worldRenderer.sceneOrigin.toSceneZ(startPosition.z)
|
|
599
|
+
)
|
|
594
600
|
const directionHelper = new THREE.ArrowHelper(
|
|
595
601
|
new THREE.Vector3(Math.cos(rotation), 0, Math.sin(rotation)),
|
|
596
|
-
|
|
602
|
+
sceneStartPos,
|
|
597
603
|
1,
|
|
598
604
|
0xff_00_00
|
|
599
605
|
)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import * as THREE from 'three'
|
|
3
|
+
import type { SceneOrigin } from './sceneOrigin'
|
|
3
4
|
|
|
4
5
|
interface ParticleMesh extends THREE.Mesh {
|
|
5
6
|
velocity: THREE.Vector3;
|
|
7
|
+
worldPos: THREE.Vector3;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
interface ParticleConfig {
|
|
@@ -24,11 +26,13 @@ export class Fountain {
|
|
|
24
26
|
private readonly particles: ParticleMesh[] = []
|
|
25
27
|
private readonly config: { particleConfig: ParticleConfig }
|
|
26
28
|
private readonly position: THREE.Vector3
|
|
29
|
+
private readonly sceneOrigin: SceneOrigin | undefined
|
|
27
30
|
container: THREE.Object3D | undefined
|
|
28
31
|
|
|
29
|
-
constructor (public sectionId: string, options: FountainOptions = {}) {
|
|
32
|
+
constructor (public sectionId: string, options: FountainOptions = {}, sceneOrigin?: SceneOrigin) {
|
|
30
33
|
this.position = options.position ? new THREE.Vector3(options.position.x, options.position.y, options.position.z) : new THREE.Vector3(0, 0, 0)
|
|
31
34
|
this.config = this.createConfig(options.particleConfig)
|
|
35
|
+
this.sceneOrigin = sceneOrigin
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
private createConfig (
|
|
@@ -48,6 +52,18 @@ export class Fountain {
|
|
|
48
52
|
return { particleConfig }
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
private toSceneX (worldX: number): number {
|
|
56
|
+
return this.sceneOrigin ? this.sceneOrigin.toSceneX(worldX) : worldX
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private toSceneY (worldY: number): number {
|
|
60
|
+
return this.sceneOrigin ? this.sceneOrigin.toSceneY(worldY) : worldY
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private toSceneZ (worldZ: number): number {
|
|
64
|
+
return this.sceneOrigin ? this.sceneOrigin.toSceneZ(worldZ) : worldZ
|
|
65
|
+
}
|
|
66
|
+
|
|
51
67
|
|
|
52
68
|
createParticles (container: THREE.Object3D): void {
|
|
53
69
|
this.container = container
|
|
@@ -65,11 +81,12 @@ export class Fountain {
|
|
|
65
81
|
const mesh = new THREE.Mesh(geometry, material)
|
|
66
82
|
const particle = mesh as unknown as ParticleMesh
|
|
67
83
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
84
|
+
const worldX = this.position.x + (Math.random() - 0.5) * this.config.particleConfig.xVelocityRange * 2
|
|
85
|
+
const worldY = this.position.y + this.config.particleConfig.fountainHeight
|
|
86
|
+
const worldZ = this.position.z + (Math.random() - 0.5) * this.config.particleConfig.zVelocityRange * 2
|
|
87
|
+
|
|
88
|
+
particle.worldPos = new THREE.Vector3(worldX, worldY, worldZ)
|
|
89
|
+
particle.position.set(this.toSceneX(worldX), this.toSceneY(worldY), this.toSceneZ(worldZ))
|
|
73
90
|
|
|
74
91
|
particle.velocity = new THREE.Vector3(
|
|
75
92
|
(Math.random() - 0.5) * this.config.particleConfig.xVelocityRange,
|
|
@@ -89,20 +106,26 @@ export class Fountain {
|
|
|
89
106
|
render (): void {
|
|
90
107
|
for (const particle of this.particles) {
|
|
91
108
|
particle.velocity.y -= 0.01 + Math.random() * 0.1
|
|
92
|
-
particle.
|
|
109
|
+
particle.worldPos.add(particle.velocity)
|
|
93
110
|
|
|
94
|
-
if (particle.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
)
|
|
111
|
+
if (particle.worldPos.y < this.position.y + this.config.particleConfig.resetHeight) {
|
|
112
|
+
const worldX = this.position.x + (Math.random() - 0.5) * this.config.particleConfig.xVelocityRange * 2
|
|
113
|
+
const worldY = this.position.y + this.config.particleConfig.fountainHeight
|
|
114
|
+
const worldZ = this.position.z + (Math.random() - 0.5) * this.config.particleConfig.zVelocityRange * 2
|
|
115
|
+
|
|
116
|
+
particle.worldPos.set(worldX, worldY, worldZ)
|
|
100
117
|
particle.velocity.set(
|
|
101
118
|
(Math.random() - 0.5) * this.config.particleConfig.xVelocityRange,
|
|
102
119
|
-Math.random() * this.config.particleConfig.yVelocityRange.max,
|
|
103
120
|
(Math.random() - 0.5) * this.config.particleConfig.zVelocityRange
|
|
104
121
|
)
|
|
105
122
|
}
|
|
123
|
+
|
|
124
|
+
particle.position.set(
|
|
125
|
+
this.toSceneX(particle.worldPos.x),
|
|
126
|
+
this.toSceneY(particle.worldPos.y),
|
|
127
|
+
this.toSceneZ(particle.worldPos.z)
|
|
128
|
+
)
|
|
106
129
|
}
|
|
107
130
|
}
|
|
108
131
|
|
|
@@ -126,7 +149,12 @@ export class Fountain {
|
|
|
126
149
|
for (let i = 0; i < count; i++) {
|
|
127
150
|
const mesh = new THREE.Mesh(geometry, material)
|
|
128
151
|
const particle = mesh as unknown as ParticleMesh
|
|
129
|
-
particle.position.
|
|
152
|
+
particle.worldPos = this.position.clone()
|
|
153
|
+
particle.position.set(
|
|
154
|
+
this.toSceneX(this.position.x),
|
|
155
|
+
this.toSceneY(this.position.y),
|
|
156
|
+
this.toSceneZ(this.position.z)
|
|
157
|
+
)
|
|
130
158
|
particle.velocity = new THREE.Vector3(
|
|
131
159
|
Math.random() * this.config.particleConfig.xVelocityRange -
|
|
132
160
|
this.config.particleConfig.xVelocityRange / 2,
|
|
@@ -45,11 +45,11 @@ export class ThreeJsSound implements SoundSystem {
|
|
|
45
45
|
sound.setRefDistance(20)
|
|
46
46
|
sound.setVolume(volume * this.baseVolume)
|
|
47
47
|
sound.setPlaybackRate(pitch) // set the pitch
|
|
48
|
-
this.worldRenderer.scene.add(sound)
|
|
49
48
|
// set sound position
|
|
49
|
+
this.worldRenderer.sceneOrigin.addAndTrack(sound)
|
|
50
50
|
sound.position.set(position.x, position.y, position.z)
|
|
51
51
|
sound.onEnded = () => {
|
|
52
|
-
this.worldRenderer.
|
|
52
|
+
this.worldRenderer.sceneOrigin.removeAndUntrack(sound)
|
|
53
53
|
if (sound.source) {
|
|
54
54
|
sound.disconnect()
|
|
55
55
|
}
|
|
@@ -68,7 +68,7 @@ export class ThreeJsSound implements SoundSystem {
|
|
|
68
68
|
if (sound.source) {
|
|
69
69
|
sound.disconnect()
|
|
70
70
|
}
|
|
71
|
-
this.worldRenderer.
|
|
71
|
+
this.worldRenderer.sceneOrigin.removeAndUntrack(sound)
|
|
72
72
|
}
|
|
73
73
|
this.activeSounds.clear()
|
|
74
74
|
this.soundVolumes.clear()
|
|
@@ -21,6 +21,10 @@ export const WAYPOINT_CONFIG = {
|
|
|
21
21
|
pixelSize: 50,
|
|
22
22
|
paddingPx: 50,
|
|
23
23
|
},
|
|
24
|
+
// Default visual scale factor (can be overridden globally or per-waypoint)
|
|
25
|
+
DEFAULT_VISUAL_SCALE: 1,
|
|
26
|
+
// Default opacity (can be overridden globally or per-waypoint)
|
|
27
|
+
DEFAULT_OPACITY: 1,
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export type WaypointSprite = {
|
|
@@ -53,14 +57,31 @@ export function createWaypointSprite (options: {
|
|
|
53
57
|
// Y offset in world units used by updateScaleWorld only (screen-pixel API ignores this)
|
|
54
58
|
labelYOffset?: number,
|
|
55
59
|
metadata?: any,
|
|
60
|
+
visualScale?: number,
|
|
61
|
+
opacity?: number,
|
|
56
62
|
}): WaypointSprite {
|
|
57
63
|
const color = options.color ?? 0xFF_00_00
|
|
58
64
|
const depthTest = options.depthTest ?? false
|
|
59
65
|
const labelYOffset = options.labelYOffset ?? 1.5
|
|
60
66
|
|
|
67
|
+
// Get visual scale from options, metadata, server metadata, or default
|
|
68
|
+
// Priority: options.visualScale > metadata.visualScale > window.serverMetadata?.waypointVisualScale > DEFAULT
|
|
69
|
+
const visualScale = options.visualScale
|
|
70
|
+
?? options.metadata?.visualScale
|
|
71
|
+
?? (typeof window === 'undefined' ? undefined : (window as any).serverMetadata?.waypointVisualScale)
|
|
72
|
+
?? WAYPOINT_CONFIG.DEFAULT_VISUAL_SCALE
|
|
73
|
+
|
|
74
|
+
// Get opacity from options, metadata, server metadata, or default
|
|
75
|
+
// Priority: options.opacity > metadata.opacity > window.serverMetadata?.waypointOpacity > DEFAULT
|
|
76
|
+
const opacity = options.opacity
|
|
77
|
+
?? options.metadata?.opacity
|
|
78
|
+
?? (typeof window === 'undefined' ? undefined : (window as any).serverMetadata?.waypointOpacity)
|
|
79
|
+
?? WAYPOINT_CONFIG.DEFAULT_OPACITY
|
|
80
|
+
|
|
61
81
|
// Build combined sprite
|
|
62
|
-
const sprite = createCombinedSprite(color, options.label ?? '', '0m', depthTest)
|
|
82
|
+
const sprite = createCombinedSprite(color, options.label ?? '', '0m', depthTest, visualScale)
|
|
63
83
|
sprite.renderOrder = 10
|
|
84
|
+
sprite.material.opacity = opacity
|
|
64
85
|
let currentLabel = options.label ?? ''
|
|
65
86
|
|
|
66
87
|
// Performance optimization: cache distance text to avoid unnecessary updates
|
|
@@ -81,7 +102,7 @@ export function createWaypointSprite (options: {
|
|
|
81
102
|
group.position.set(x, y, z)
|
|
82
103
|
|
|
83
104
|
function setColor (newColor: number) {
|
|
84
|
-
const canvas = drawCombinedCanvas(newColor, currentLabel, '0m')
|
|
105
|
+
const canvas = drawCombinedCanvas(newColor, currentLabel, '0m', visualScale)
|
|
85
106
|
const texture = new THREE.CanvasTexture(canvas)
|
|
86
107
|
const mat = sprite.material
|
|
87
108
|
mat.map?.dispose()
|
|
@@ -91,7 +112,7 @@ export function createWaypointSprite (options: {
|
|
|
91
112
|
|
|
92
113
|
function setLabel (newLabel?: string) {
|
|
93
114
|
currentLabel = newLabel ?? ''
|
|
94
|
-
const canvas = drawCombinedCanvas(color, currentLabel, '0m')
|
|
115
|
+
const canvas = drawCombinedCanvas(color, currentLabel, '0m', visualScale)
|
|
95
116
|
const texture = new THREE.CanvasTexture(canvas)
|
|
96
117
|
const mat = sprite.material
|
|
97
118
|
mat.map?.dispose()
|
|
@@ -106,7 +127,7 @@ export function createWaypointSprite (options: {
|
|
|
106
127
|
}
|
|
107
128
|
lastDistanceText = distanceText
|
|
108
129
|
|
|
109
|
-
const canvas = drawCombinedCanvas(color, label, distanceText)
|
|
130
|
+
const canvas = drawCombinedCanvas(color, label, distanceText, visualScale)
|
|
110
131
|
const texture = new THREE.CanvasTexture(canvas)
|
|
111
132
|
const mat = sprite.material
|
|
112
133
|
mat.map?.dispose()
|
|
@@ -131,8 +152,8 @@ export function createWaypointSprite (options: {
|
|
|
131
152
|
) {
|
|
132
153
|
const vFovRad = cameraFov * Math.PI / 180
|
|
133
154
|
const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * distance
|
|
134
|
-
// Use configured target screen size
|
|
135
|
-
const scale = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.TARGET_SCREEN_PX / viewportHeightPx)
|
|
155
|
+
// Use configured target screen size with visual scale multiplier
|
|
156
|
+
const scale = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.TARGET_SCREEN_PX * visualScale / viewportHeightPx)
|
|
136
157
|
sprite.scale.set(scale, scale, 1)
|
|
137
158
|
}
|
|
138
159
|
|
|
@@ -159,7 +180,7 @@ export function createWaypointSprite (options: {
|
|
|
159
180
|
ctx.fill()
|
|
160
181
|
|
|
161
182
|
const texture = new THREE.CanvasTexture(canvas)
|
|
162
|
-
const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false })
|
|
183
|
+
const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false, opacity })
|
|
163
184
|
arrowSprite = new THREE.Sprite(material)
|
|
164
185
|
arrowSprite.renderOrder = 12
|
|
165
186
|
arrowSprite.visible = false
|
|
@@ -278,15 +299,16 @@ export function createWaypointSprite (options: {
|
|
|
278
299
|
const angle = Math.atan2(ry, rx)
|
|
279
300
|
arrowSprite.material.rotation = angle - Math.PI / 2
|
|
280
301
|
|
|
281
|
-
// Constant pixel size for arrow (use fixed placement distance)
|
|
302
|
+
// Constant pixel size for arrow (use fixed placement distance) with visual scale
|
|
282
303
|
const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * placeDist
|
|
283
|
-
const sPx = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.ARROW.pixelSize / viewportHeightPx)
|
|
304
|
+
const sPx = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.ARROW.pixelSize * visualScale / viewportHeightPx)
|
|
284
305
|
arrowSprite.scale.set(sPx, sPx, 1)
|
|
285
306
|
return false
|
|
286
307
|
}
|
|
287
308
|
|
|
288
|
-
function computeDistance (
|
|
289
|
-
|
|
309
|
+
function computeDistance (_cameraPosition: THREE.Vector3): number {
|
|
310
|
+
// group.position is in scene space; camera is at scene origin (0,0,0)
|
|
311
|
+
return group.position.length()
|
|
290
312
|
}
|
|
291
313
|
|
|
292
314
|
function updateForCamera (
|
|
@@ -343,7 +365,7 @@ export function createWaypointSprite (options: {
|
|
|
343
365
|
}
|
|
344
366
|
|
|
345
367
|
// Internal helpers
|
|
346
|
-
function drawCombinedCanvas (color: number, id: string, distance: string): OffscreenCanvas {
|
|
368
|
+
function drawCombinedCanvas (color: number, id: string, distance: string, visualScale = 1): OffscreenCanvas {
|
|
347
369
|
const scale = WAYPOINT_CONFIG.CANVAS_SCALE * (globalThis.devicePixelRatio || 1)
|
|
348
370
|
const size = WAYPOINT_CONFIG.CANVAS_SIZE * scale
|
|
349
371
|
const canvas = createCanvas(size, size)
|
|
@@ -352,11 +374,11 @@ function drawCombinedCanvas (color: number, id: string, distance: string): Offsc
|
|
|
352
374
|
// Clear canvas
|
|
353
375
|
ctx.clearRect(0, 0, size, size)
|
|
354
376
|
|
|
355
|
-
// Draw dot
|
|
377
|
+
// Draw dot with visual scale applied
|
|
356
378
|
const centerX = size / 2
|
|
357
379
|
const dotY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.DOT_Y)
|
|
358
|
-
const radius = Math.round(size * 0.05) // Dot takes up ~
|
|
359
|
-
const borderWidth = Math.max(2, Math.round(4 * scale))
|
|
380
|
+
const radius = Math.round(size * 0.05 * visualScale) // Dot takes up ~5% of canvas height, scaled
|
|
381
|
+
const borderWidth = Math.max(2, Math.round(4 * scale * visualScale))
|
|
360
382
|
|
|
361
383
|
// Outer border (black)
|
|
362
384
|
ctx.beginPath()
|
|
@@ -374,11 +396,11 @@ function drawCombinedCanvas (color: number, id: string, distance: string): Offsc
|
|
|
374
396
|
ctx.textAlign = 'center'
|
|
375
397
|
ctx.textBaseline = 'middle'
|
|
376
398
|
|
|
377
|
-
// Title
|
|
378
|
-
const nameFontPx = Math.round(size * 0.08) // ~8% of canvas height
|
|
379
|
-
const distanceFontPx = Math.round(size * 0.06) // ~6% of canvas height
|
|
399
|
+
// Title with visual scale applied
|
|
400
|
+
const nameFontPx = Math.round(size * 0.08 * visualScale) // ~8% of canvas height, scaled
|
|
401
|
+
const distanceFontPx = Math.round(size * 0.06 * visualScale) // ~6% of canvas height, scaled
|
|
380
402
|
ctx.font = `bold ${nameFontPx}px mojangles`
|
|
381
|
-
ctx.lineWidth = Math.max(2, Math.round(3 * scale))
|
|
403
|
+
ctx.lineWidth = Math.max(2, Math.round(3 * scale * visualScale))
|
|
382
404
|
const nameY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.NAME_Y)
|
|
383
405
|
|
|
384
406
|
ctx.strokeStyle = 'black'
|
|
@@ -386,9 +408,9 @@ function drawCombinedCanvas (color: number, id: string, distance: string): Offsc
|
|
|
386
408
|
ctx.fillStyle = 'white'
|
|
387
409
|
ctx.fillText(id, centerX, nameY)
|
|
388
410
|
|
|
389
|
-
// Distance
|
|
411
|
+
// Distance with visual scale applied
|
|
390
412
|
ctx.font = `bold ${distanceFontPx}px mojangles`
|
|
391
|
-
ctx.lineWidth = Math.max(2, Math.round(2 * scale))
|
|
413
|
+
ctx.lineWidth = Math.max(2, Math.round(2 * scale * visualScale))
|
|
392
414
|
const distanceY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.DISTANCE_Y)
|
|
393
415
|
|
|
394
416
|
ctx.strokeStyle = 'black'
|
|
@@ -399,8 +421,8 @@ function drawCombinedCanvas (color: number, id: string, distance: string): Offsc
|
|
|
399
421
|
return canvas
|
|
400
422
|
}
|
|
401
423
|
|
|
402
|
-
function createCombinedSprite (color: number, id: string, distance: string, depthTest: boolean): THREE.Sprite {
|
|
403
|
-
const canvas = drawCombinedCanvas(color, id, distance)
|
|
424
|
+
function createCombinedSprite (color: number, id: string, distance: string, depthTest: boolean, visualScale = 1): THREE.Sprite {
|
|
425
|
+
const canvas = drawCombinedCanvas(color, id, distance, visualScale)
|
|
404
426
|
const texture = new THREE.CanvasTexture(canvas)
|
|
405
427
|
texture.anisotropy = 1
|
|
406
428
|
texture.magFilter = THREE.LinearFilter
|
package/src/three/waypoints.ts
CHANGED
|
@@ -9,6 +9,7 @@ interface Waypoint {
|
|
|
9
9
|
y: number
|
|
10
10
|
z: number
|
|
11
11
|
minDistance: number
|
|
12
|
+
maxDistance: number
|
|
12
13
|
color: number
|
|
13
14
|
label?: string
|
|
14
15
|
sprite: WaypointSprite
|
|
@@ -18,6 +19,7 @@ interface WaypointOptions {
|
|
|
18
19
|
color?: number
|
|
19
20
|
label?: string
|
|
20
21
|
minDistance?: number
|
|
22
|
+
maxDistance?: number
|
|
21
23
|
metadata?: any
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -40,7 +42,7 @@ export class WaypointsRenderer {
|
|
|
40
42
|
|
|
41
43
|
private updateWaypoints() {
|
|
42
44
|
const currentTime = performance.now()
|
|
43
|
-
const playerPos = this.worldRenderer.
|
|
45
|
+
const playerPos = this.worldRenderer.getCameraPosition()
|
|
44
46
|
|
|
45
47
|
// Performance optimization: throttle updates and check for significant camera movement
|
|
46
48
|
const cameraMovedSignificantly = this.lastCameraPosition.distanceTo(playerPos) > 0.5
|
|
@@ -58,13 +60,18 @@ export class WaypointsRenderer {
|
|
|
58
60
|
for (const waypoint of this.waypoints.values()) {
|
|
59
61
|
const waypointPos = new THREE.Vector3(waypoint.x, waypoint.y, waypoint.z)
|
|
60
62
|
const distance = playerPos.distanceTo(waypointPos)
|
|
61
|
-
const visible = !waypoint.minDistance || distance >= waypoint.minDistance
|
|
63
|
+
const visible = (!waypoint.minDistance || distance >= waypoint.minDistance) &&
|
|
64
|
+
(waypoint.maxDistance === Infinity || distance <= waypoint.maxDistance)
|
|
62
65
|
|
|
63
66
|
waypoint.sprite.setVisible(visible)
|
|
64
67
|
|
|
65
68
|
if (visible) {
|
|
66
69
|
// Update position
|
|
67
|
-
waypoint.sprite.setPosition(
|
|
70
|
+
waypoint.sprite.setPosition(
|
|
71
|
+
this.worldRenderer.sceneOrigin.toSceneX(waypoint.x),
|
|
72
|
+
this.worldRenderer.sceneOrigin.toSceneY(waypoint.y),
|
|
73
|
+
this.worldRenderer.sceneOrigin.toSceneZ(waypoint.z)
|
|
74
|
+
)
|
|
68
75
|
// Ensure camera-based update each frame
|
|
69
76
|
waypoint.sprite.updateForCamera(this.worldRenderer.getCameraPosition(), this.worldRenderer.camera, sizeVec.width, sizeVec.height)
|
|
70
77
|
}
|
|
@@ -96,6 +103,7 @@ export class WaypointsRenderer {
|
|
|
96
103
|
const color = options.color ?? 0xFF_00_00
|
|
97
104
|
const { label, metadata } = options
|
|
98
105
|
const minDistance = options.minDistance ?? 0
|
|
106
|
+
const maxDistance = options.maxDistance ?? Infinity
|
|
99
107
|
|
|
100
108
|
const sprite = createWaypointSprite({
|
|
101
109
|
position: new THREE.Vector3(x, y, z),
|
|
@@ -109,7 +117,7 @@ export class WaypointsRenderer {
|
|
|
109
117
|
this.waypointScene.add(sprite.group)
|
|
110
118
|
|
|
111
119
|
this.waypoints.set(id, {
|
|
112
|
-
id, x: x + 0.5, y: y + 0.5, z: z + 0.5, minDistance,
|
|
120
|
+
id, x: x + 0.5, y: y + 0.5, z: z + 0.5, minDistance, maxDistance,
|
|
113
121
|
color, label,
|
|
114
122
|
sprite,
|
|
115
123
|
})
|