minecraft-renderer 0.1.0

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.
Files changed (187) hide show
  1. package/README.md +297 -0
  2. package/dist/index.html +83 -0
  3. package/dist/static/image/arrow.6f27b59f.png +0 -0
  4. package/dist/static/image/blocksAtlasLatest.7850afa3.png +0 -0
  5. package/dist/static/image/blocksAtlasLegacy.5c76823d.png +0 -0
  6. package/dist/static/image/itemsAtlasLatest.36036f95.png +0 -0
  7. package/dist/static/image/itemsAtlasLegacy.dcb1b58d.png +0 -0
  8. package/dist/static/image/tipped_arrow.6f27b59f.png +0 -0
  9. package/dist/static/js/365.f05233ab.js +8462 -0
  10. package/dist/static/js/365.f05233ab.js.LICENSE.txt +52 -0
  11. package/dist/static/js/async/738.efa27644.js +1 -0
  12. package/dist/static/js/index.092ec5be.js +56 -0
  13. package/dist/static/js/lib-polyfill.98986ac5.js +1 -0
  14. package/dist/static/js/lib-react.5c9129e0.js +2 -0
  15. package/dist/static/js/lib-react.5c9129e0.js.LICENSE.txt +39 -0
  16. package/package.json +104 -0
  17. package/src/assets/destroy_stage_0.png +0 -0
  18. package/src/assets/destroy_stage_1.png +0 -0
  19. package/src/assets/destroy_stage_2.png +0 -0
  20. package/src/assets/destroy_stage_3.png +0 -0
  21. package/src/assets/destroy_stage_4.png +0 -0
  22. package/src/assets/destroy_stage_5.png +0 -0
  23. package/src/assets/destroy_stage_6.png +0 -0
  24. package/src/assets/destroy_stage_7.png +0 -0
  25. package/src/assets/destroy_stage_8.png +0 -0
  26. package/src/assets/destroy_stage_9.png +0 -0
  27. package/src/examples/README.md +146 -0
  28. package/src/examples/appViewerExample.ts +205 -0
  29. package/src/examples/initialMenuStart.ts +161 -0
  30. package/src/graphicsBackend/appViewer.ts +297 -0
  31. package/src/graphicsBackend/config.ts +119 -0
  32. package/src/graphicsBackend/index.ts +10 -0
  33. package/src/graphicsBackend/playerState.ts +61 -0
  34. package/src/graphicsBackend/types.ts +143 -0
  35. package/src/index.ts +97 -0
  36. package/src/lib/DebugGui.ts +190 -0
  37. package/src/lib/animationController.ts +85 -0
  38. package/src/lib/buildSharedConfig.mjs +1 -0
  39. package/src/lib/cameraBobbing.ts +94 -0
  40. package/src/lib/canvas2DOverlay.example.ts +361 -0
  41. package/src/lib/canvas2DOverlay.quickstart.ts +242 -0
  42. package/src/lib/canvas2DOverlay.ts +381 -0
  43. package/src/lib/cleanupDecorator.ts +29 -0
  44. package/src/lib/createPlayerObject.ts +55 -0
  45. package/src/lib/frameTimingCollector.ts +164 -0
  46. package/src/lib/guiRenderer.ts +283 -0
  47. package/src/lib/items.ts +140 -0
  48. package/src/lib/mesherlogReader.ts +131 -0
  49. package/src/lib/moreBlockDataGenerated.json +714 -0
  50. package/src/lib/preflatMap.json +1741 -0
  51. package/src/lib/simpleUtils.ts +40 -0
  52. package/src/lib/smoothSwitcher.ts +168 -0
  53. package/src/lib/spiral.ts +29 -0
  54. package/src/lib/ui/newStats.ts +120 -0
  55. package/src/lib/utils/proxy.ts +23 -0
  56. package/src/lib/utils/skins.ts +63 -0
  57. package/src/lib/utils.ts +76 -0
  58. package/src/lib/workerProxy.ts +342 -0
  59. package/src/lib/worldrendererCommon.ts +1088 -0
  60. package/src/mesher/mesher.ts +253 -0
  61. package/src/mesher/models.ts +769 -0
  62. package/src/mesher/modelsGeometryCommon.ts +142 -0
  63. package/src/mesher/shared.ts +80 -0
  64. package/src/mesher/standaloneRenderer.ts +270 -0
  65. package/src/mesher/test/a.ts +3 -0
  66. package/src/mesher/test/mesherTester.ts +76 -0
  67. package/src/mesher/test/playground.ts +19 -0
  68. package/src/mesher/test/test-perf.ts +74 -0
  69. package/src/mesher/test/tests.test.ts +56 -0
  70. package/src/mesher/world.ts +294 -0
  71. package/src/mesher/worldConstants.ts +1 -0
  72. package/src/modules/index.ts +11 -0
  73. package/src/modules/starfield.ts +313 -0
  74. package/src/modules/types.ts +110 -0
  75. package/src/playerState/playerState.ts +78 -0
  76. package/src/playerState/types.ts +36 -0
  77. package/src/playground/allEntitiesDebug.ts +170 -0
  78. package/src/playground/baseScene.ts +587 -0
  79. package/src/playground/mobileControls.tsx +268 -0
  80. package/src/playground/playground.html +83 -0
  81. package/src/playground/playground.ts +11 -0
  82. package/src/playground/playgroundUi.tsx +140 -0
  83. package/src/playground/reactUtils.ts +71 -0
  84. package/src/playground/scenes/allEntities.ts +13 -0
  85. package/src/playground/scenes/entities.ts +37 -0
  86. package/src/playground/scenes/floorRandom.ts +33 -0
  87. package/src/playground/scenes/frequentUpdates.ts +148 -0
  88. package/src/playground/scenes/geometryExport.ts +142 -0
  89. package/src/playground/scenes/index.ts +12 -0
  90. package/src/playground/scenes/lightingStarfield.ts +40 -0
  91. package/src/playground/scenes/main.ts +313 -0
  92. package/src/playground/scenes/railsCobweb.ts +14 -0
  93. package/src/playground/scenes/rotationIssue.ts +7 -0
  94. package/src/playground/scenes/slabsOptimization.ts +16 -0
  95. package/src/playground/scenes/transparencyIssue.ts +11 -0
  96. package/src/playground/shared.ts +79 -0
  97. package/src/resourcesManager/index.ts +5 -0
  98. package/src/resourcesManager/resourcesManager.ts +314 -0
  99. package/src/shims/minecraftData.ts +41 -0
  100. package/src/sign-renderer/index.html +21 -0
  101. package/src/sign-renderer/index.ts +216 -0
  102. package/src/sign-renderer/noop.js +1 -0
  103. package/src/sign-renderer/playground.ts +38 -0
  104. package/src/sign-renderer/tests.test.ts +69 -0
  105. package/src/sign-renderer/vite.config.ts +10 -0
  106. package/src/three/appShared.ts +75 -0
  107. package/src/three/bannerRenderer.ts +275 -0
  108. package/src/three/cameraShake.ts +120 -0
  109. package/src/three/cinimaticScript.ts +350 -0
  110. package/src/three/documentRenderer.ts +491 -0
  111. package/src/three/entities.ts +1580 -0
  112. package/src/three/entity/EntityMesh.ts +707 -0
  113. package/src/three/entity/animations.js +171 -0
  114. package/src/three/entity/armorModels.json +204 -0
  115. package/src/three/entity/armorModels.ts +36 -0
  116. package/src/three/entity/entities.json +6230 -0
  117. package/src/three/entity/exportedModels.js +38 -0
  118. package/src/three/entity/externalTextures.json +1 -0
  119. package/src/three/entity/models/allay.obj +325 -0
  120. package/src/three/entity/models/arrow.obj +60 -0
  121. package/src/three/entity/models/axolotl.obj +509 -0
  122. package/src/three/entity/models/blaze.obj +601 -0
  123. package/src/three/entity/models/boat.obj +417 -0
  124. package/src/three/entity/models/camel.obj +1061 -0
  125. package/src/three/entity/models/cat.obj +509 -0
  126. package/src/three/entity/models/chicken.obj +371 -0
  127. package/src/three/entity/models/cod.obj +371 -0
  128. package/src/three/entity/models/creeper.obj +279 -0
  129. package/src/three/entity/models/dolphin.obj +371 -0
  130. package/src/three/entity/models/ender_dragon.obj +2993 -0
  131. package/src/three/entity/models/enderman.obj +325 -0
  132. package/src/three/entity/models/endermite.obj +187 -0
  133. package/src/three/entity/models/fox.obj +463 -0
  134. package/src/three/entity/models/frog.obj +739 -0
  135. package/src/three/entity/models/ghast.obj +463 -0
  136. package/src/three/entity/models/goat.obj +601 -0
  137. package/src/three/entity/models/guardian.obj +1015 -0
  138. package/src/three/entity/models/horse.obj +1061 -0
  139. package/src/three/entity/models/llama.obj +509 -0
  140. package/src/three/entity/models/minecart.obj +233 -0
  141. package/src/three/entity/models/parrot.obj +509 -0
  142. package/src/three/entity/models/piglin.obj +739 -0
  143. package/src/three/entity/models/pillager.obj +371 -0
  144. package/src/three/entity/models/rabbit.obj +555 -0
  145. package/src/three/entity/models/sheep.obj +555 -0
  146. package/src/three/entity/models/shulker.obj +141 -0
  147. package/src/three/entity/models/sniffer.obj +693 -0
  148. package/src/three/entity/models/spider.obj +509 -0
  149. package/src/three/entity/models/tadpole.obj +95 -0
  150. package/src/three/entity/models/turtle.obj +371 -0
  151. package/src/three/entity/models/vex.obj +325 -0
  152. package/src/three/entity/models/villager.obj +509 -0
  153. package/src/three/entity/models/warden.obj +463 -0
  154. package/src/three/entity/models/witch.obj +647 -0
  155. package/src/three/entity/models/wolf.obj +509 -0
  156. package/src/three/entity/models/zombie_villager.obj +463 -0
  157. package/src/three/entity/objModels.js +1 -0
  158. package/src/three/fireworks.ts +661 -0
  159. package/src/three/fireworksRenderer.ts +434 -0
  160. package/src/three/globals.d.ts +7 -0
  161. package/src/three/graphicsBackend.ts +274 -0
  162. package/src/three/graphicsBackendOffThread.ts +107 -0
  163. package/src/three/hand.ts +89 -0
  164. package/src/three/holdingBlock.ts +926 -0
  165. package/src/three/index.ts +20 -0
  166. package/src/three/itemMesh.ts +427 -0
  167. package/src/three/modules.d.ts +14 -0
  168. package/src/three/panorama.ts +308 -0
  169. package/src/three/panoramaShared.ts +1 -0
  170. package/src/three/renderSlot.ts +82 -0
  171. package/src/three/skyboxRenderer.ts +406 -0
  172. package/src/three/starField.ts +13 -0
  173. package/src/three/threeJsMedia.ts +731 -0
  174. package/src/three/threeJsMethods.ts +15 -0
  175. package/src/three/threeJsParticles.ts +160 -0
  176. package/src/three/threeJsSound.ts +95 -0
  177. package/src/three/threeJsUtils.ts +90 -0
  178. package/src/three/waypointSprite.ts +435 -0
  179. package/src/three/waypoints.ts +163 -0
  180. package/src/three/world/cursorBlock.ts +172 -0
  181. package/src/three/world/vr.ts +257 -0
  182. package/src/three/worldGeometryExport.ts +259 -0
  183. package/src/three/worldGeometryHandler.ts +279 -0
  184. package/src/three/worldRendererThree.ts +1381 -0
  185. package/src/worldView/index.ts +6 -0
  186. package/src/worldView/types.ts +66 -0
  187. package/src/worldView/worldView.ts +424 -0
@@ -0,0 +1,435 @@
1
+ import * as THREE from 'three'
2
+ import { createCanvas } from '../lib/utils'
3
+
4
+ // Centralized visual configuration (in screen pixels)
5
+ export const WAYPOINT_CONFIG = {
6
+ // Target size in screen pixels (this controls the final sprite size)
7
+ TARGET_SCREEN_PX: 150,
8
+ // Canvas size for internal rendering (keep power of 2 for textures)
9
+ CANVAS_SIZE: 256,
10
+ // Relative positions in canvas (0-1)
11
+ LAYOUT: {
12
+ DOT_Y: 0.3,
13
+ NAME_Y: 0.45,
14
+ DISTANCE_Y: 0.55,
15
+ },
16
+ // Multiplier for canvas internal resolution to keep text crisp
17
+ CANVAS_SCALE: 2,
18
+ ARROW: {
19
+ enabledDefault: false,
20
+ pixelSize: 50,
21
+ paddingPx: 50,
22
+ },
23
+ }
24
+
25
+ export type WaypointSprite = {
26
+ group: THREE.Group
27
+ sprite: THREE.Sprite
28
+ // Offscreen arrow controls
29
+ enableOffscreenArrow: (enabled: boolean) => void
30
+ setArrowParent: (parent: THREE.Object3D | null) => void
31
+ // Convenience combined updater
32
+ updateForCamera: (
33
+ cameraPosition: THREE.Vector3,
34
+ camera: THREE.PerspectiveCamera,
35
+ viewportWidthPx: number,
36
+ viewportHeightPx: number
37
+ ) => boolean
38
+ // Utilities
39
+ setColor: (color: number) => void
40
+ setLabel: (label?: string) => void
41
+ updateDistanceText: (label: string, distanceText: string) => void
42
+ setVisible: (visible: boolean) => void
43
+ setPosition: (x: number, y: number, z: number) => void
44
+ dispose: () => void
45
+ }
46
+
47
+ export function createWaypointSprite (options: {
48
+ position: THREE.Vector3 | { x: number, y: number, z: number },
49
+ color?: number,
50
+ label?: string,
51
+ depthTest?: boolean,
52
+ // Y offset in world units used by updateScaleWorld only (screen-pixel API ignores this)
53
+ labelYOffset?: number,
54
+ metadata?: any,
55
+ }): WaypointSprite {
56
+ const color = options.color ?? 0xFF_00_00
57
+ const depthTest = options.depthTest ?? false
58
+ const labelYOffset = options.labelYOffset ?? 1.5
59
+
60
+ // Build combined sprite
61
+ const sprite = createCombinedSprite(color, options.label ?? '', '0m', depthTest)
62
+ sprite.renderOrder = 10
63
+ let currentLabel = options.label ?? ''
64
+
65
+ // Performance optimization: cache distance text to avoid unnecessary updates
66
+ let lastDistanceText = '0m'
67
+ let lastDistance = 0
68
+
69
+ // Offscreen arrow (detached by default)
70
+ let arrowSprite: THREE.Sprite | undefined
71
+ let arrowParent: THREE.Object3D | null = null
72
+ let arrowEnabled = WAYPOINT_CONFIG.ARROW.enabledDefault
73
+
74
+ // Group for easy add/remove
75
+ const group = new THREE.Group()
76
+ group.add(sprite)
77
+
78
+ // Initial position
79
+ const { x, y, z } = options.position
80
+ group.position.set(x, y, z)
81
+
82
+ function setColor (newColor: number) {
83
+ const canvas = drawCombinedCanvas(newColor, currentLabel, '0m')
84
+ const texture = new THREE.CanvasTexture(canvas)
85
+ const mat = sprite.material
86
+ mat.map?.dispose()
87
+ mat.map = texture
88
+ mat.needsUpdate = true
89
+ }
90
+
91
+ function setLabel (newLabel?: string) {
92
+ currentLabel = newLabel ?? ''
93
+ const canvas = drawCombinedCanvas(color, currentLabel, '0m')
94
+ const texture = new THREE.CanvasTexture(canvas)
95
+ const mat = sprite.material
96
+ mat.map?.dispose()
97
+ mat.map = texture
98
+ mat.needsUpdate = true
99
+ }
100
+
101
+ function updateDistanceText (label: string, distanceText: string) {
102
+ // Performance optimization: only update if distance text actually changed
103
+ if (distanceText === lastDistanceText) {
104
+ return
105
+ }
106
+ lastDistanceText = distanceText
107
+
108
+ const canvas = drawCombinedCanvas(color, label, distanceText)
109
+ const texture = new THREE.CanvasTexture(canvas)
110
+ const mat = sprite.material
111
+ mat.map?.dispose()
112
+ mat.map = texture
113
+ mat.needsUpdate = true
114
+ }
115
+
116
+ function setVisible (visible: boolean) {
117
+ sprite.visible = visible
118
+ }
119
+
120
+ function setPosition (nx: number, ny: number, nz: number) {
121
+ group.position.set(nx, ny, nz)
122
+ }
123
+
124
+ // Keep constant pixel size on screen using global config
125
+ function updateScaleScreenPixels (
126
+ cameraPosition: THREE.Vector3,
127
+ cameraFov: number,
128
+ distance: number,
129
+ viewportHeightPx: number
130
+ ) {
131
+ const vFovRad = cameraFov * Math.PI / 180
132
+ const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * distance
133
+ // Use configured target screen size
134
+ const scale = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.TARGET_SCREEN_PX / viewportHeightPx)
135
+ sprite.scale.set(scale, scale, 1)
136
+ }
137
+
138
+ function ensureArrow () {
139
+ if (arrowSprite) return
140
+ const size = 128
141
+ const canvas = createCanvas(size, size)
142
+ const ctx = canvas.getContext('2d')!
143
+ ctx.clearRect(0, 0, size, size)
144
+
145
+ // Draw arrow shape
146
+ ctx.beginPath()
147
+ ctx.moveTo(size * 0.15, size * 0.5)
148
+ ctx.lineTo(size * 0.85, size * 0.5)
149
+ ctx.lineTo(size * 0.5, size * 0.15)
150
+ ctx.closePath()
151
+
152
+ // Use waypoint color for arrow
153
+ const colorHex = `#${color.toString(16).padStart(6, '0')}`
154
+ ctx.lineWidth = 6
155
+ ctx.strokeStyle = 'black'
156
+ ctx.stroke()
157
+ ctx.fillStyle = colorHex
158
+ ctx.fill()
159
+
160
+ const texture = new THREE.CanvasTexture(canvas)
161
+ const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false })
162
+ arrowSprite = new THREE.Sprite(material)
163
+ arrowSprite.renderOrder = 12
164
+ arrowSprite.visible = false
165
+ if (arrowParent) arrowParent.add(arrowSprite)
166
+ }
167
+
168
+ function enableOffscreenArrow (enabled: boolean) {
169
+ arrowEnabled = enabled
170
+ if (!enabled && arrowSprite) arrowSprite.visible = false
171
+ }
172
+
173
+ function setArrowParent (parent: THREE.Object3D | null) {
174
+ if (arrowSprite?.parent) arrowSprite.parent.remove(arrowSprite)
175
+ arrowParent = parent
176
+ if (arrowSprite && parent) parent.add(arrowSprite)
177
+ }
178
+
179
+ function updateOffscreenArrow (
180
+ camera: THREE.PerspectiveCamera,
181
+ viewportWidthPx: number,
182
+ viewportHeightPx: number
183
+ ): boolean {
184
+ if (!arrowEnabled) return true
185
+ ensureArrow()
186
+ if (!arrowSprite) return true
187
+
188
+ // Check if onlyLeftRight is enabled in metadata
189
+ const onlyLeftRight = options.metadata?.onlyLeftRight === true
190
+
191
+ // Build camera basis using camera.up to respect custom orientations
192
+ const forward = new THREE.Vector3()
193
+ camera.getWorldDirection(forward) // camera look direction
194
+ const upWorld = camera.up.clone().normalize()
195
+ const right = new THREE.Vector3().copy(forward).cross(upWorld).normalize()
196
+ const upCam = new THREE.Vector3().copy(right).cross(forward).normalize()
197
+
198
+ // Vector from camera to waypoint
199
+ const camPos = new THREE.Vector3().setFromMatrixPosition(camera.matrixWorld)
200
+ const toWp = new THREE.Vector3(group.position.x, group.position.y, group.position.z).sub(camPos)
201
+
202
+ // Components in camera basis
203
+ const z = toWp.dot(forward)
204
+ const x = toWp.dot(right)
205
+ const y = toWp.dot(upCam)
206
+
207
+ const aspect = viewportWidthPx / viewportHeightPx
208
+ const vFovRad = camera.fov * Math.PI / 180
209
+ const hFovRad = 2 * Math.atan(Math.tan(vFovRad / 2) * aspect)
210
+
211
+ // Determine if waypoint is inside view frustum using angular checks
212
+ const thetaX = Math.atan2(x, z)
213
+ const thetaY = Math.atan2(y, z)
214
+ const visible = z > 0 && Math.abs(thetaX) <= hFovRad / 2 && Math.abs(thetaY) <= vFovRad / 2
215
+ if (visible) {
216
+ arrowSprite.visible = false
217
+ return true
218
+ }
219
+
220
+ // Direction on screen in normalized frustum units
221
+ let rx = thetaX / (hFovRad / 2)
222
+ let ry = thetaY / (vFovRad / 2)
223
+
224
+ // If behind the camera, snap to dominant axis to avoid confusing directions
225
+ if (z <= 0) {
226
+ if (Math.abs(rx) > Math.abs(ry)) {
227
+ rx = Math.sign(rx)
228
+ ry = 0
229
+ } else {
230
+ rx = 0
231
+ ry = Math.sign(ry)
232
+ }
233
+ }
234
+
235
+ // Apply onlyLeftRight logic - restrict arrows to left/right edges only
236
+ if (onlyLeftRight) {
237
+ // Force the arrow to appear only on left or right edges
238
+ if (Math.abs(rx) > Math.abs(ry)) {
239
+ // Horizontal direction is dominant, keep it
240
+ ry = 0
241
+ } else {
242
+ // Vertical direction is dominant, but we want only left/right
243
+ // So choose left or right based on the sign of rx
244
+ rx = rx >= 0 ? 1 : -1
245
+ ry = 0
246
+ }
247
+ }
248
+
249
+ // Place on the rectangle border [-1,1]x[-1,1]
250
+ const s = Math.max(Math.abs(rx), Math.abs(ry)) || 1
251
+ let ndcX = rx / s
252
+ let ndcY = ry / s
253
+
254
+ // Apply padding in pixel space by clamping
255
+ const padding = WAYPOINT_CONFIG.ARROW.paddingPx
256
+ const pxX = ((ndcX + 1) * 0.5) * viewportWidthPx
257
+ const pxY = ((1 - ndcY) * 0.5) * viewportHeightPx
258
+ const clampedPxX = Math.min(Math.max(pxX, padding), viewportWidthPx - padding)
259
+ const clampedPxY = Math.min(Math.max(pxY, padding), viewportHeightPx - padding)
260
+ ndcX = (clampedPxX / viewportWidthPx) * 2 - 1
261
+ ndcY = -(clampedPxY / viewportHeightPx) * 2 + 1
262
+
263
+ // Compute world position at a fixed distance in front of the camera using camera basis
264
+ const placeDist = Math.max(2, camera.near * 4)
265
+ const halfPlaneHeight = Math.tan(vFovRad / 2) * placeDist
266
+ const halfPlaneWidth = halfPlaneHeight * aspect
267
+ const pos = camPos.clone()
268
+ .add(forward.clone().multiplyScalar(placeDist))
269
+ .add(right.clone().multiplyScalar(ndcX * halfPlaneWidth))
270
+ .add(upCam.clone().multiplyScalar(ndcY * halfPlaneHeight))
271
+
272
+ // Update arrow sprite
273
+ arrowSprite.visible = true
274
+ arrowSprite.position.copy(pos)
275
+
276
+ // Angle for rotation relative to screen right/up (derived from camera up vector)
277
+ const angle = Math.atan2(ry, rx)
278
+ arrowSprite.material.rotation = angle - Math.PI / 2
279
+
280
+ // Constant pixel size for arrow (use fixed placement distance)
281
+ const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * placeDist
282
+ const sPx = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.ARROW.pixelSize / viewportHeightPx)
283
+ arrowSprite.scale.set(sPx, sPx, 1)
284
+ return false
285
+ }
286
+
287
+ function computeDistance (cameraPosition: THREE.Vector3): number {
288
+ return cameraPosition.distanceTo(group.position)
289
+ }
290
+
291
+ function updateForCamera (
292
+ cameraPosition: THREE.Vector3,
293
+ camera: THREE.PerspectiveCamera,
294
+ viewportWidthPx: number,
295
+ viewportHeightPx: number
296
+ ): boolean {
297
+ const distance = computeDistance(cameraPosition)
298
+ // Keep constant pixel size
299
+ updateScaleScreenPixels(cameraPosition, camera.fov, distance, viewportHeightPx)
300
+
301
+ // Performance optimization: only update distance text if distance changed significantly
302
+ const roundedDistance = Math.round(distance)
303
+ if (Math.abs(roundedDistance - lastDistance) >= 1) {
304
+ lastDistance = roundedDistance
305
+ updateDistanceText(currentLabel, `${roundedDistance}m`)
306
+ }
307
+
308
+ // Update arrow and visibility
309
+ const onScreen = updateOffscreenArrow(camera, viewportWidthPx, viewportHeightPx)
310
+ setVisible(onScreen)
311
+ return onScreen
312
+ }
313
+
314
+ function dispose () {
315
+ const mat = sprite.material
316
+ mat.map?.dispose()
317
+ mat.dispose()
318
+ if (arrowSprite) {
319
+ // Remove arrow from parent before disposing
320
+ if (arrowSprite.parent) {
321
+ arrowSprite.parent.remove(arrowSprite)
322
+ }
323
+ const am = arrowSprite.material
324
+ am.map?.dispose()
325
+ am.dispose()
326
+ }
327
+ }
328
+
329
+ return {
330
+ group,
331
+ sprite,
332
+ enableOffscreenArrow,
333
+ setArrowParent,
334
+ updateForCamera,
335
+ setColor,
336
+ setLabel,
337
+ updateDistanceText,
338
+ setVisible,
339
+ setPosition,
340
+ dispose,
341
+ }
342
+ }
343
+
344
+ // Internal helpers
345
+ function drawCombinedCanvas (color: number, id: string, distance: string): OffscreenCanvas {
346
+ const scale = WAYPOINT_CONFIG.CANVAS_SCALE * (globalThis.devicePixelRatio || 1)
347
+ const size = WAYPOINT_CONFIG.CANVAS_SIZE * scale
348
+ const canvas = createCanvas(size, size)
349
+ const ctx = canvas.getContext('2d')!
350
+
351
+ // Clear canvas
352
+ ctx.clearRect(0, 0, size, size)
353
+
354
+ // Draw dot
355
+ const centerX = size / 2
356
+ const dotY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.DOT_Y)
357
+ const radius = Math.round(size * 0.05) // Dot takes up ~12% of canvas height
358
+ const borderWidth = Math.max(2, Math.round(4 * scale))
359
+
360
+ // Outer border (black)
361
+ ctx.beginPath()
362
+ ctx.arc(centerX, dotY, radius + borderWidth, 0, Math.PI * 2)
363
+ ctx.fillStyle = 'black'
364
+ ctx.fill()
365
+
366
+ // Inner circle (colored)
367
+ ctx.beginPath()
368
+ ctx.arc(centerX, dotY, radius, 0, Math.PI * 2)
369
+ ctx.fillStyle = `#${color.toString(16).padStart(6, '0')}`
370
+ ctx.fill()
371
+
372
+ // Text properties
373
+ ctx.textAlign = 'center'
374
+ ctx.textBaseline = 'middle'
375
+
376
+ // Title
377
+ const nameFontPx = Math.round(size * 0.08) // ~8% of canvas height
378
+ const distanceFontPx = Math.round(size * 0.06) // ~6% of canvas height
379
+ ctx.font = `bold ${nameFontPx}px mojangles`
380
+ ctx.lineWidth = Math.max(2, Math.round(3 * scale))
381
+ const nameY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.NAME_Y)
382
+
383
+ ctx.strokeStyle = 'black'
384
+ ctx.strokeText(id, centerX, nameY)
385
+ ctx.fillStyle = 'white'
386
+ ctx.fillText(id, centerX, nameY)
387
+
388
+ // Distance
389
+ ctx.font = `bold ${distanceFontPx}px mojangles`
390
+ ctx.lineWidth = Math.max(2, Math.round(2 * scale))
391
+ const distanceY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.DISTANCE_Y)
392
+
393
+ ctx.strokeStyle = 'black'
394
+ ctx.strokeText(distance, centerX, distanceY)
395
+ ctx.fillStyle = '#CCCCCC'
396
+ ctx.fillText(distance, centerX, distanceY)
397
+
398
+ return canvas
399
+ }
400
+
401
+ function createCombinedSprite (color: number, id: string, distance: string, depthTest: boolean): THREE.Sprite {
402
+ const canvas = drawCombinedCanvas(color, id, distance)
403
+ const texture = new THREE.CanvasTexture(canvas)
404
+ texture.anisotropy = 1
405
+ texture.magFilter = THREE.LinearFilter
406
+ texture.minFilter = THREE.LinearFilter
407
+ const material = new THREE.SpriteMaterial({
408
+ map: texture,
409
+ transparent: true,
410
+ opacity: 1,
411
+ depthTest,
412
+ depthWrite: false,
413
+ })
414
+ const sprite = new THREE.Sprite(material)
415
+ sprite.position.set(0, 0, 0)
416
+ return sprite
417
+ }
418
+
419
+ export const WaypointHelpers = {
420
+ // World-scale constant size helper
421
+ computeWorldScale (distance: number, fixedReference = 10) {
422
+ return Math.max(0.0001, distance / fixedReference)
423
+ },
424
+ // Screen-pixel constant size helper
425
+ computeScreenPixelScale (
426
+ camera: THREE.PerspectiveCamera,
427
+ distance: number,
428
+ pixelSize: number,
429
+ viewportHeightPx: number
430
+ ) {
431
+ const vFovRad = camera.fov * Math.PI / 180
432
+ const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * distance
433
+ return worldUnitsPerScreenHeightAtDist * (pixelSize / viewportHeightPx)
434
+ }
435
+ }
@@ -0,0 +1,163 @@
1
+ import * as THREE from 'three'
2
+ import { WorldRendererThree } from './worldRendererThree'
3
+ import { createWaypointSprite, type WaypointSprite } from './waypointSprite'
4
+
5
+ interface Waypoint {
6
+ id: string
7
+ x: number
8
+ y: number
9
+ z: number
10
+ minDistance: number
11
+ color: number
12
+ label?: string
13
+ sprite: WaypointSprite
14
+ }
15
+
16
+ interface WaypointOptions {
17
+ color?: number
18
+ label?: string
19
+ minDistance?: number
20
+ metadata?: any
21
+ }
22
+
23
+ export class WaypointsRenderer {
24
+ private readonly waypoints = new Map<string, Waypoint>()
25
+ private readonly waypointScene = new THREE.Scene()
26
+
27
+ // Performance optimization: cache camera position to reduce update frequency
28
+ private readonly lastCameraPosition = new THREE.Vector3()
29
+ private lastUpdateTime = 0
30
+ private readonly UPDATE_THROTTLE_MS = 16 // ~60fps max update rate
31
+
32
+ constructor(
33
+ private readonly worldRenderer: WorldRendererThree
34
+ ) {
35
+ if (process.env.NODE_ENV !== 'production') {
36
+ // this.addWaypoint('spawn', 0, 0, 0, { })
37
+ }
38
+ }
39
+
40
+ private updateWaypoints() {
41
+ const currentTime = performance.now()
42
+ const playerPos = this.worldRenderer.cameraObject.position
43
+
44
+ // Performance optimization: throttle updates and check for significant camera movement
45
+ const cameraMovedSignificantly = this.lastCameraPosition.distanceTo(playerPos) > 0.5
46
+ const timeToUpdate = currentTime - this.lastUpdateTime > this.UPDATE_THROTTLE_MS
47
+
48
+ if (!cameraMovedSignificantly && !timeToUpdate) {
49
+ return // Skip update if camera hasn't moved much and not enough time passed
50
+ }
51
+
52
+ this.lastCameraPosition.copy(playerPos)
53
+ this.lastUpdateTime = currentTime
54
+
55
+ const sizeVec = this.worldRenderer.renderer.getSize(new THREE.Vector2())
56
+
57
+ for (const waypoint of this.waypoints.values()) {
58
+ const waypointPos = new THREE.Vector3(waypoint.x, waypoint.y, waypoint.z)
59
+ const distance = playerPos.distanceTo(waypointPos)
60
+ const visible = !waypoint.minDistance || distance >= waypoint.minDistance
61
+
62
+ waypoint.sprite.setVisible(visible)
63
+
64
+ if (visible) {
65
+ // Update position
66
+ waypoint.sprite.setPosition(waypoint.x, waypoint.y, waypoint.z)
67
+ // Ensure camera-based update each frame
68
+ waypoint.sprite.updateForCamera(this.worldRenderer.getCameraPosition(), this.worldRenderer.camera, sizeVec.width, sizeVec.height)
69
+ }
70
+ }
71
+ }
72
+
73
+ render() {
74
+ if (this.waypoints.size === 0) return
75
+
76
+ // Update waypoint scaling
77
+ this.updateWaypoints()
78
+
79
+ // Render waypoints scene with the world camera
80
+ this.worldRenderer.renderer.render(this.waypointScene, this.worldRenderer.camera)
81
+ }
82
+
83
+ // Removed sprite/label texture creation. Use utils/waypointSprite.ts
84
+
85
+ addWaypoint(
86
+ id: string,
87
+ x: number,
88
+ y: number,
89
+ z: number,
90
+ options: WaypointOptions = {}
91
+ ) {
92
+ // Remove existing waypoint if it exists
93
+ this.removeWaypoint(id)
94
+
95
+ const color = options.color ?? 0xFF_00_00
96
+ const { label, metadata } = options
97
+ const minDistance = options.minDistance ?? 0
98
+
99
+ const sprite = createWaypointSprite({
100
+ position: new THREE.Vector3(x, y, z),
101
+ color,
102
+ label: (label || id),
103
+ metadata,
104
+ })
105
+ sprite.enableOffscreenArrow(true)
106
+ sprite.setArrowParent(this.waypointScene)
107
+
108
+ this.waypointScene.add(sprite.group)
109
+
110
+ this.waypoints.set(id, {
111
+ id, x: x + 0.5, y: y + 0.5, z: z + 0.5, minDistance,
112
+ color, label,
113
+ sprite,
114
+ })
115
+ }
116
+
117
+ removeWaypoint(id: string) {
118
+ const waypoint = this.waypoints.get(id)
119
+ if (waypoint) {
120
+ this.waypointScene.remove(waypoint.sprite.group)
121
+ waypoint.sprite.dispose()
122
+ this.waypoints.delete(id)
123
+ }
124
+ }
125
+
126
+ clear() {
127
+ for (const id of this.waypoints.keys()) {
128
+ this.removeWaypoint(id)
129
+ }
130
+ }
131
+
132
+ testWaypoint() {
133
+ this.addWaypoint('Test Point', 0, 70, 0, { color: 0x00_FF_00, label: 'Test Point' })
134
+ this.addWaypoint('Spawn', 0, 64, 0, { color: 0xFF_FF_00, label: 'Spawn' })
135
+ this.addWaypoint('Far Point', 100, 70, 100, { color: 0x00_00_FF, label: 'Far Point' })
136
+ this.addWaypoint('Far Point 2', 180, 170, 100, { color: 0x00_00_FF, label: 'Far Point 2' })
137
+ this.addWaypoint('Far Point 3', 1000, 100, 1000, { color: 0x00_00_FF, label: 'Far Point 3' })
138
+ }
139
+
140
+ getWaypoint(id: string): Waypoint | undefined {
141
+ return this.waypoints.get(id)
142
+ }
143
+
144
+ getAllWaypoints(): Waypoint[] {
145
+ return [...this.waypoints.values()]
146
+ }
147
+
148
+ setWaypointColor(id: string, color: number) {
149
+ const waypoint = this.waypoints.get(id)
150
+ if (waypoint) {
151
+ waypoint.sprite.setColor(color)
152
+ waypoint.color = color
153
+ }
154
+ }
155
+
156
+ setWaypointLabel(id: string, label?: string) {
157
+ const waypoint = this.waypoints.get(id)
158
+ if (waypoint) {
159
+ waypoint.label = label
160
+ waypoint.sprite.setLabel(label)
161
+ }
162
+ }
163
+ }