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.
- package/README.md +297 -0
- package/dist/index.html +83 -0
- package/dist/static/image/arrow.6f27b59f.png +0 -0
- package/dist/static/image/blocksAtlasLatest.7850afa3.png +0 -0
- package/dist/static/image/blocksAtlasLegacy.5c76823d.png +0 -0
- package/dist/static/image/itemsAtlasLatest.36036f95.png +0 -0
- package/dist/static/image/itemsAtlasLegacy.dcb1b58d.png +0 -0
- package/dist/static/image/tipped_arrow.6f27b59f.png +0 -0
- package/dist/static/js/365.f05233ab.js +8462 -0
- package/dist/static/js/365.f05233ab.js.LICENSE.txt +52 -0
- package/dist/static/js/async/738.efa27644.js +1 -0
- package/dist/static/js/index.092ec5be.js +56 -0
- package/dist/static/js/lib-polyfill.98986ac5.js +1 -0
- package/dist/static/js/lib-react.5c9129e0.js +2 -0
- package/dist/static/js/lib-react.5c9129e0.js.LICENSE.txt +39 -0
- package/package.json +104 -0
- package/src/assets/destroy_stage_0.png +0 -0
- package/src/assets/destroy_stage_1.png +0 -0
- package/src/assets/destroy_stage_2.png +0 -0
- package/src/assets/destroy_stage_3.png +0 -0
- package/src/assets/destroy_stage_4.png +0 -0
- package/src/assets/destroy_stage_5.png +0 -0
- package/src/assets/destroy_stage_6.png +0 -0
- package/src/assets/destroy_stage_7.png +0 -0
- package/src/assets/destroy_stage_8.png +0 -0
- package/src/assets/destroy_stage_9.png +0 -0
- package/src/examples/README.md +146 -0
- package/src/examples/appViewerExample.ts +205 -0
- package/src/examples/initialMenuStart.ts +161 -0
- package/src/graphicsBackend/appViewer.ts +297 -0
- package/src/graphicsBackend/config.ts +119 -0
- package/src/graphicsBackend/index.ts +10 -0
- package/src/graphicsBackend/playerState.ts +61 -0
- package/src/graphicsBackend/types.ts +143 -0
- package/src/index.ts +97 -0
- package/src/lib/DebugGui.ts +190 -0
- package/src/lib/animationController.ts +85 -0
- package/src/lib/buildSharedConfig.mjs +1 -0
- package/src/lib/cameraBobbing.ts +94 -0
- package/src/lib/canvas2DOverlay.example.ts +361 -0
- package/src/lib/canvas2DOverlay.quickstart.ts +242 -0
- package/src/lib/canvas2DOverlay.ts +381 -0
- package/src/lib/cleanupDecorator.ts +29 -0
- package/src/lib/createPlayerObject.ts +55 -0
- package/src/lib/frameTimingCollector.ts +164 -0
- package/src/lib/guiRenderer.ts +283 -0
- package/src/lib/items.ts +140 -0
- package/src/lib/mesherlogReader.ts +131 -0
- package/src/lib/moreBlockDataGenerated.json +714 -0
- package/src/lib/preflatMap.json +1741 -0
- package/src/lib/simpleUtils.ts +40 -0
- package/src/lib/smoothSwitcher.ts +168 -0
- package/src/lib/spiral.ts +29 -0
- package/src/lib/ui/newStats.ts +120 -0
- package/src/lib/utils/proxy.ts +23 -0
- package/src/lib/utils/skins.ts +63 -0
- package/src/lib/utils.ts +76 -0
- package/src/lib/workerProxy.ts +342 -0
- package/src/lib/worldrendererCommon.ts +1088 -0
- package/src/mesher/mesher.ts +253 -0
- package/src/mesher/models.ts +769 -0
- package/src/mesher/modelsGeometryCommon.ts +142 -0
- package/src/mesher/shared.ts +80 -0
- package/src/mesher/standaloneRenderer.ts +270 -0
- package/src/mesher/test/a.ts +3 -0
- package/src/mesher/test/mesherTester.ts +76 -0
- package/src/mesher/test/playground.ts +19 -0
- package/src/mesher/test/test-perf.ts +74 -0
- package/src/mesher/test/tests.test.ts +56 -0
- package/src/mesher/world.ts +294 -0
- package/src/mesher/worldConstants.ts +1 -0
- package/src/modules/index.ts +11 -0
- package/src/modules/starfield.ts +313 -0
- package/src/modules/types.ts +110 -0
- package/src/playerState/playerState.ts +78 -0
- package/src/playerState/types.ts +36 -0
- package/src/playground/allEntitiesDebug.ts +170 -0
- package/src/playground/baseScene.ts +587 -0
- package/src/playground/mobileControls.tsx +268 -0
- package/src/playground/playground.html +83 -0
- package/src/playground/playground.ts +11 -0
- package/src/playground/playgroundUi.tsx +140 -0
- package/src/playground/reactUtils.ts +71 -0
- package/src/playground/scenes/allEntities.ts +13 -0
- package/src/playground/scenes/entities.ts +37 -0
- package/src/playground/scenes/floorRandom.ts +33 -0
- package/src/playground/scenes/frequentUpdates.ts +148 -0
- package/src/playground/scenes/geometryExport.ts +142 -0
- package/src/playground/scenes/index.ts +12 -0
- package/src/playground/scenes/lightingStarfield.ts +40 -0
- package/src/playground/scenes/main.ts +313 -0
- package/src/playground/scenes/railsCobweb.ts +14 -0
- package/src/playground/scenes/rotationIssue.ts +7 -0
- package/src/playground/scenes/slabsOptimization.ts +16 -0
- package/src/playground/scenes/transparencyIssue.ts +11 -0
- package/src/playground/shared.ts +79 -0
- package/src/resourcesManager/index.ts +5 -0
- package/src/resourcesManager/resourcesManager.ts +314 -0
- package/src/shims/minecraftData.ts +41 -0
- package/src/sign-renderer/index.html +21 -0
- package/src/sign-renderer/index.ts +216 -0
- package/src/sign-renderer/noop.js +1 -0
- package/src/sign-renderer/playground.ts +38 -0
- package/src/sign-renderer/tests.test.ts +69 -0
- package/src/sign-renderer/vite.config.ts +10 -0
- package/src/three/appShared.ts +75 -0
- package/src/three/bannerRenderer.ts +275 -0
- package/src/three/cameraShake.ts +120 -0
- package/src/three/cinimaticScript.ts +350 -0
- package/src/three/documentRenderer.ts +491 -0
- package/src/three/entities.ts +1580 -0
- package/src/three/entity/EntityMesh.ts +707 -0
- package/src/three/entity/animations.js +171 -0
- package/src/three/entity/armorModels.json +204 -0
- package/src/three/entity/armorModels.ts +36 -0
- package/src/three/entity/entities.json +6230 -0
- package/src/three/entity/exportedModels.js +38 -0
- package/src/three/entity/externalTextures.json +1 -0
- package/src/three/entity/models/allay.obj +325 -0
- package/src/three/entity/models/arrow.obj +60 -0
- package/src/three/entity/models/axolotl.obj +509 -0
- package/src/three/entity/models/blaze.obj +601 -0
- package/src/three/entity/models/boat.obj +417 -0
- package/src/three/entity/models/camel.obj +1061 -0
- package/src/three/entity/models/cat.obj +509 -0
- package/src/three/entity/models/chicken.obj +371 -0
- package/src/three/entity/models/cod.obj +371 -0
- package/src/three/entity/models/creeper.obj +279 -0
- package/src/three/entity/models/dolphin.obj +371 -0
- package/src/three/entity/models/ender_dragon.obj +2993 -0
- package/src/three/entity/models/enderman.obj +325 -0
- package/src/three/entity/models/endermite.obj +187 -0
- package/src/three/entity/models/fox.obj +463 -0
- package/src/three/entity/models/frog.obj +739 -0
- package/src/three/entity/models/ghast.obj +463 -0
- package/src/three/entity/models/goat.obj +601 -0
- package/src/three/entity/models/guardian.obj +1015 -0
- package/src/three/entity/models/horse.obj +1061 -0
- package/src/three/entity/models/llama.obj +509 -0
- package/src/three/entity/models/minecart.obj +233 -0
- package/src/three/entity/models/parrot.obj +509 -0
- package/src/three/entity/models/piglin.obj +739 -0
- package/src/three/entity/models/pillager.obj +371 -0
- package/src/three/entity/models/rabbit.obj +555 -0
- package/src/three/entity/models/sheep.obj +555 -0
- package/src/three/entity/models/shulker.obj +141 -0
- package/src/three/entity/models/sniffer.obj +693 -0
- package/src/three/entity/models/spider.obj +509 -0
- package/src/three/entity/models/tadpole.obj +95 -0
- package/src/three/entity/models/turtle.obj +371 -0
- package/src/three/entity/models/vex.obj +325 -0
- package/src/three/entity/models/villager.obj +509 -0
- package/src/three/entity/models/warden.obj +463 -0
- package/src/three/entity/models/witch.obj +647 -0
- package/src/three/entity/models/wolf.obj +509 -0
- package/src/three/entity/models/zombie_villager.obj +463 -0
- package/src/three/entity/objModels.js +1 -0
- package/src/three/fireworks.ts +661 -0
- package/src/three/fireworksRenderer.ts +434 -0
- package/src/three/globals.d.ts +7 -0
- package/src/three/graphicsBackend.ts +274 -0
- package/src/three/graphicsBackendOffThread.ts +107 -0
- package/src/three/hand.ts +89 -0
- package/src/three/holdingBlock.ts +926 -0
- package/src/three/index.ts +20 -0
- package/src/three/itemMesh.ts +427 -0
- package/src/three/modules.d.ts +14 -0
- package/src/three/panorama.ts +308 -0
- package/src/three/panoramaShared.ts +1 -0
- package/src/three/renderSlot.ts +82 -0
- package/src/three/skyboxRenderer.ts +406 -0
- package/src/three/starField.ts +13 -0
- package/src/three/threeJsMedia.ts +731 -0
- package/src/three/threeJsMethods.ts +15 -0
- package/src/three/threeJsParticles.ts +160 -0
- package/src/three/threeJsSound.ts +95 -0
- package/src/three/threeJsUtils.ts +90 -0
- package/src/three/waypointSprite.ts +435 -0
- package/src/three/waypoints.ts +163 -0
- package/src/three/world/cursorBlock.ts +172 -0
- package/src/three/world/vr.ts +257 -0
- package/src/three/worldGeometryExport.ts +259 -0
- package/src/three/worldGeometryHandler.ts +279 -0
- package/src/three/worldRendererThree.ts +1381 -0
- package/src/worldView/index.ts +6 -0
- package/src/worldView/types.ts +66 -0
- package/src/worldView/worldView.ts +424 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import { Vec3 } from 'vec3'
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
import MinecraftData, { IndexedData } from 'minecraft-data'
|
|
4
|
+
import BlockLoader from 'prismarine-block'
|
|
5
|
+
import ChunkLoader from 'prismarine-chunk'
|
|
6
|
+
import WorldLoader from 'prismarine-world'
|
|
7
|
+
import { proxy } from 'valtio'
|
|
8
|
+
import { BlockNames } from 'mc-bridge/dist/names.generated'
|
|
9
|
+
|
|
10
|
+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
|
11
|
+
// eslint-disable-next-line import/no-named-as-default
|
|
12
|
+
import GUI from 'lil-gui'
|
|
13
|
+
import _ from 'lodash'
|
|
14
|
+
import { defaultWorldRendererConfig, WorldRendererConfig } from '@/lib/worldrendererCommon'
|
|
15
|
+
import { getSyncWorld } from './shared'
|
|
16
|
+
import { AppViewer, getInitialPlayerState } from '@/graphicsBackend'
|
|
17
|
+
import { WorldView } from '@/worldView'
|
|
18
|
+
import { createGraphicsBackend } from '@/three'
|
|
19
|
+
|
|
20
|
+
window.THREE = THREE
|
|
21
|
+
|
|
22
|
+
// Scene configuration interface
|
|
23
|
+
export interface PlaygroundSceneConfig {
|
|
24
|
+
version?: string
|
|
25
|
+
viewDistance?: number
|
|
26
|
+
targetPos?: Vec3
|
|
27
|
+
enableCameraControls?: boolean
|
|
28
|
+
enableCameraOrbitControl?: boolean
|
|
29
|
+
worldConfig?: WorldRendererConfig
|
|
30
|
+
continuousRender?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const appGraphicBackends = [
|
|
34
|
+
createGraphicsBackend,
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
const includedVersions = globalThis.includedVersions
|
|
38
|
+
|
|
39
|
+
export class BasePlaygroundScene {
|
|
40
|
+
appViewer = new AppViewer({
|
|
41
|
+
config: {
|
|
42
|
+
statsVisible: 2,
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
mcData!: IndexedData
|
|
47
|
+
|
|
48
|
+
// Rendering state
|
|
49
|
+
continuousRender = false
|
|
50
|
+
stopRender = false
|
|
51
|
+
windowHidden = false
|
|
52
|
+
|
|
53
|
+
// Scene configuration
|
|
54
|
+
viewDistance = 0
|
|
55
|
+
targetPos = new Vec3(2, 90, 2)
|
|
56
|
+
version: string = new URLSearchParams(window.location.search).get('version') ?? includedVersions.at(-1)!
|
|
57
|
+
|
|
58
|
+
// World data
|
|
59
|
+
Chunk!: typeof import('prismarine-chunk/types/index').PCChunk
|
|
60
|
+
Block!: typeof import('prismarine-block').Block
|
|
61
|
+
world!: ReturnType<typeof getSyncWorld>
|
|
62
|
+
|
|
63
|
+
// GUI
|
|
64
|
+
gui = new GUI()
|
|
65
|
+
params = {} as Record<string, any>
|
|
66
|
+
paramOptions = {} as Partial<Record<keyof typeof this.params, {
|
|
67
|
+
hide?: boolean
|
|
68
|
+
options?: string[]
|
|
69
|
+
min?: number
|
|
70
|
+
max?: number
|
|
71
|
+
reloadOnChange?: boolean
|
|
72
|
+
}>>
|
|
73
|
+
onParamUpdate = {} as Record<string, () => void>
|
|
74
|
+
alwaysIgnoreQs = [] as string[]
|
|
75
|
+
skipUpdateQs = false
|
|
76
|
+
|
|
77
|
+
// Camera controls - own camera synced to backend
|
|
78
|
+
enableCameraControls = true
|
|
79
|
+
enableCameraOrbitControl = true
|
|
80
|
+
controls: OrbitControls | undefined
|
|
81
|
+
camera!: THREE.PerspectiveCamera
|
|
82
|
+
|
|
83
|
+
// World data emitter (from appViewer)
|
|
84
|
+
worldView: WorldView | undefined
|
|
85
|
+
|
|
86
|
+
// Debug FPS tracking
|
|
87
|
+
private debugFpsElement: HTMLElement | undefined
|
|
88
|
+
private frameCount = 0
|
|
89
|
+
private lastSecondTime = performance.now()
|
|
90
|
+
private frameTimes: number[] = []
|
|
91
|
+
private currentFps = 0
|
|
92
|
+
private maxFrameDelay = 0
|
|
93
|
+
|
|
94
|
+
// Getter for worldRenderer - accesses via window.world for advanced scene features
|
|
95
|
+
// This allows derived scenes to access worldRenderer when needed without storing it
|
|
96
|
+
get worldRenderer() {
|
|
97
|
+
//@ts-ignore
|
|
98
|
+
return window.world
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// World config - syncs with appViewer.inWorldRenderingConfig
|
|
102
|
+
get worldConfig() {
|
|
103
|
+
return this.appViewer.inWorldRenderingConfig
|
|
104
|
+
}
|
|
105
|
+
set worldConfig(value) {
|
|
106
|
+
// Merge the new values into appViewer's config to maintain reactivity
|
|
107
|
+
Object.assign(this.appViewer.inWorldRenderingConfig, value)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
constructor(config: PlaygroundSceneConfig = {}) {
|
|
111
|
+
// Apply config
|
|
112
|
+
if (config.version) this.version = config.version
|
|
113
|
+
|
|
114
|
+
// Ensure version is always set (fallback to latest supported version)
|
|
115
|
+
if (!this.version) {
|
|
116
|
+
throw new Error('Minecraft version is not set')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (config.viewDistance !== undefined) this.viewDistance = config.viewDistance
|
|
120
|
+
if (config.targetPos) this.targetPos = config.targetPos
|
|
121
|
+
if (config.enableCameraControls !== undefined) this.enableCameraControls = config.enableCameraControls
|
|
122
|
+
if (config.enableCameraOrbitControl !== undefined) this.enableCameraOrbitControl = config.enableCameraOrbitControl
|
|
123
|
+
if (config.worldConfig) {
|
|
124
|
+
// Merge config into appViewer's config to maintain reactivity
|
|
125
|
+
Object.assign(this.appViewer.inWorldRenderingConfig, config.worldConfig)
|
|
126
|
+
}
|
|
127
|
+
this.appViewer.inWorldRenderingConfig.showHand = false
|
|
128
|
+
this.appViewer.inWorldRenderingConfig.isPlayground = true
|
|
129
|
+
this.appViewer.inWorldRenderingConfig.instantCameraUpdate = this.enableCameraOrbitControl
|
|
130
|
+
this.appViewer.config.statsVisible = 2
|
|
131
|
+
if (config.continuousRender !== undefined) this.continuousRender = config.continuousRender
|
|
132
|
+
|
|
133
|
+
void this.initData().then(() => {
|
|
134
|
+
this.addKeyboardShortcuts()
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
onParamsUpdate(paramName: string, object: any) { }
|
|
139
|
+
|
|
140
|
+
updateQs(paramName: string, valueSet: any) {
|
|
141
|
+
if (this.skipUpdateQs) return
|
|
142
|
+
const newQs = new URLSearchParams(window.location.search)
|
|
143
|
+
for (const [key, value] of Object.entries({ [paramName]: valueSet })) {
|
|
144
|
+
if (typeof value === 'function' || this.params.skipQs?.includes(key) || this.alwaysIgnoreQs.includes(key)) continue
|
|
145
|
+
if (value) {
|
|
146
|
+
newQs.set(key, value)
|
|
147
|
+
} else {
|
|
148
|
+
newQs.delete(key)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
window.history.replaceState({}, '', `${window.location.pathname}?${newQs.toString()}`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
renderFinish() {
|
|
155
|
+
this.requestRender()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
initGui() {
|
|
159
|
+
const qs = new URLSearchParams(window.location.search)
|
|
160
|
+
for (const key of Object.keys(this.params)) {
|
|
161
|
+
const value = qs.get(key)
|
|
162
|
+
if (!value) continue
|
|
163
|
+
const parsed = /^-?\d+$/.test(value) ? Number(value) : value === 'true' ? true : value === 'false' ? false : value
|
|
164
|
+
this.params[key] = parsed
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const param of Object.keys(this.params)) {
|
|
168
|
+
const option = this.paramOptions[param]
|
|
169
|
+
if (option?.hide) continue
|
|
170
|
+
this.gui.add(this.params, param, option?.options ?? option?.min, option?.max)
|
|
171
|
+
}
|
|
172
|
+
if (window.innerHeight < 700) {
|
|
173
|
+
this.gui.open(false)
|
|
174
|
+
} else {
|
|
175
|
+
setTimeout(() => {
|
|
176
|
+
this.gui.domElement.classList.remove('transition')
|
|
177
|
+
}, 500)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this.gui.onChange(({ property, object }) => {
|
|
181
|
+
if (object === this.params) {
|
|
182
|
+
this.onParamUpdate[property]?.()
|
|
183
|
+
this.onParamsUpdate(property, object)
|
|
184
|
+
const value = this.params[property]
|
|
185
|
+
if (this.paramOptions[property]?.reloadOnChange && (typeof value === 'boolean' || this.paramOptions[property].options)) {
|
|
186
|
+
setTimeout(() => {
|
|
187
|
+
window.location.reload()
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
this.updateQs(property, value)
|
|
191
|
+
} else {
|
|
192
|
+
this.onParamsUpdate(property, object)
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Overridable methods
|
|
198
|
+
setupWorld() { }
|
|
199
|
+
sceneReset() { }
|
|
200
|
+
|
|
201
|
+
// eslint-disable-next-line max-params
|
|
202
|
+
addWorldBlock(xOffset: number, yOffset: number, zOffset: number, blockName: BlockNames, properties?: Record<string, any>) {
|
|
203
|
+
if (xOffset > 16 || yOffset > 16 || zOffset > 16) throw new Error('Offset too big')
|
|
204
|
+
const block =
|
|
205
|
+
properties ?
|
|
206
|
+
this.Block.fromProperties(this.mcData.blocksByName[blockName].id, properties ?? {}, 0) :
|
|
207
|
+
this.Block.fromStateId(this.mcData.blocksByName[blockName].defaultState, 0)
|
|
208
|
+
this.world.setBlock(this.targetPos.offset(xOffset, yOffset, zOffset), block)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Sync our camera state to the graphics backend
|
|
212
|
+
// Extract rotation from OrbitControls spherical coordinates to avoid flip issues
|
|
213
|
+
protected syncCameraToBackend(onlyRotation = false) {
|
|
214
|
+
if (!this.appViewer.backend || !this.camera) return
|
|
215
|
+
|
|
216
|
+
// Extract rotation from camera's quaternion to avoid gimbal lock issues
|
|
217
|
+
// Get forward direction vector to extract yaw/pitch properly
|
|
218
|
+
const forward = new THREE.Vector3(0, 0, -1)
|
|
219
|
+
forward.applyQuaternion(this.camera.quaternion)
|
|
220
|
+
|
|
221
|
+
// Calculate yaw and pitch from forward vector
|
|
222
|
+
// Yaw: rotation around Y axis (horizontal)
|
|
223
|
+
const yaw = Math.atan2(-forward.x, -forward.z)
|
|
224
|
+
// Pitch: angle from horizontal plane (vertical)
|
|
225
|
+
const pitch = Math.asin(forward.y)
|
|
226
|
+
|
|
227
|
+
if (onlyRotation) {
|
|
228
|
+
this.appViewer.backend.updateCamera(null, yaw, pitch)
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const pos = new Vec3(this.camera.position.x, this.camera.position.y, this.camera.position.z)
|
|
233
|
+
this.appViewer.backend.updateCamera(pos, yaw, pitch)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
resetCamera() {
|
|
237
|
+
if (!this.camera) return
|
|
238
|
+
const { targetPos } = this
|
|
239
|
+
this.controls?.target.set(targetPos.x + 0.5, targetPos.y + 0.5, targetPos.z + 0.5)
|
|
240
|
+
|
|
241
|
+
const cameraPos = targetPos.offset(2, 2, 2)
|
|
242
|
+
this.camera.position.set(cameraPos.x + 0.5, cameraPos.y + 0.5, cameraPos.z + 0.5)
|
|
243
|
+
this.camera.lookAt(targetPos.x + 0.5, targetPos.y + 0.5, targetPos.z + 0.5)
|
|
244
|
+
this.controls?.update()
|
|
245
|
+
// Sync after reset - this uses quaternion extraction which avoids flip issues
|
|
246
|
+
this.syncCameraToBackend()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async initData() {
|
|
250
|
+
const mcData: IndexedData = MinecraftData(this.version)
|
|
251
|
+
this.mcData = mcData
|
|
252
|
+
//@ts-ignore
|
|
253
|
+
window.loadedData = window.mcData = mcData
|
|
254
|
+
|
|
255
|
+
this.Chunk = (ChunkLoader as any)(this.version)
|
|
256
|
+
this.Block = (BlockLoader as any)(this.version)
|
|
257
|
+
|
|
258
|
+
const world = getSyncWorld(this.version)
|
|
259
|
+
world.setBlockStateId(this.targetPos, 0)
|
|
260
|
+
this.world = world
|
|
261
|
+
|
|
262
|
+
this.initGui()
|
|
263
|
+
|
|
264
|
+
// Use appViewer for resource management and world rendering
|
|
265
|
+
// worldConfig is already synced with appViewer.inWorldRenderingConfig via getter/setter
|
|
266
|
+
|
|
267
|
+
// Initialize resources manager via appViewer
|
|
268
|
+
this.appViewer.resourcesManager.currentConfig = { version: this.version, noInventoryGui: true }
|
|
269
|
+
await this.appViewer.resourcesManager.loadSourceData?.(this.version)
|
|
270
|
+
await this.appViewer.resourcesManager.updateAssetsData?.({})
|
|
271
|
+
|
|
272
|
+
// Load backend if not already loaded
|
|
273
|
+
if (!this.appViewer.backend) {
|
|
274
|
+
await this.appViewer.loadBackend(appGraphicBackends[0])
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Start world using appViewer
|
|
278
|
+
// This creates WorldDataEmitter, GraphicsBackend, and WorldRendererThree internally
|
|
279
|
+
await this.appViewer.startWorld(world, this.viewDistance, proxy(getInitialPlayerState()), this.targetPos)
|
|
280
|
+
|
|
281
|
+
// Get world view from appViewer
|
|
282
|
+
this.worldView = this.appViewer.worldView
|
|
283
|
+
|
|
284
|
+
// Create our own camera for OrbitControls - this is separate from the internal worldRenderer camera
|
|
285
|
+
// We sync our camera state to the backend via updateCamera()
|
|
286
|
+
this.camera = new THREE.PerspectiveCamera(
|
|
287
|
+
this.appViewer.inWorldRenderingConfig.fov || 75,
|
|
288
|
+
window.innerWidth / window.innerHeight,
|
|
289
|
+
0.1,
|
|
290
|
+
1000
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
// Setup world (adds blocks, etc.)
|
|
294
|
+
this.setupWorld()
|
|
295
|
+
|
|
296
|
+
// Initialize world view with target position (loads chunks after setup)
|
|
297
|
+
if (this.worldView) {
|
|
298
|
+
this.worldView.addWaitTime = 0
|
|
299
|
+
await this.worldView.init(this.targetPos)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Setup camera controls with our own camera
|
|
303
|
+
if (this.enableCameraControls) {
|
|
304
|
+
const canvas = document.querySelector('#viewer-canvas')
|
|
305
|
+
if (canvas) {
|
|
306
|
+
const controls = this.enableCameraOrbitControl
|
|
307
|
+
? new OrbitControls(this.camera, canvas as HTMLElement)
|
|
308
|
+
: undefined
|
|
309
|
+
this.controls = controls
|
|
310
|
+
|
|
311
|
+
this.resetCamera()
|
|
312
|
+
|
|
313
|
+
// Camera position from query string or localStorage
|
|
314
|
+
const cameraSet = this.params.camera || localStorage.camera
|
|
315
|
+
if (cameraSet) {
|
|
316
|
+
const [x, y, z, rx, ry] = cameraSet.split(',').map(Number)
|
|
317
|
+
this.camera.position.set(x, y, z)
|
|
318
|
+
this.camera.rotation.set(rx, ry, 0, 'ZYX')
|
|
319
|
+
this.controls?.update()
|
|
320
|
+
// this.syncCameraToBackend()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const throttledCamQsUpdate = _.throttle(() => {
|
|
324
|
+
if (!this.camera) return
|
|
325
|
+
localStorage.camera = [
|
|
326
|
+
this.camera.position.x.toFixed(2),
|
|
327
|
+
this.camera.position.y.toFixed(2),
|
|
328
|
+
this.camera.position.z.toFixed(2),
|
|
329
|
+
this.camera.rotation.x.toFixed(2),
|
|
330
|
+
this.camera.rotation.y.toFixed(2),
|
|
331
|
+
].join(',')
|
|
332
|
+
}, 200)
|
|
333
|
+
|
|
334
|
+
if (this.controls) {
|
|
335
|
+
const throttledCameraSync = _.throttle(() => {
|
|
336
|
+
// this.syncCameraToBackend(true) // Only sync rotation when OrbitControls changes
|
|
337
|
+
}, 16) // ~60fps sync rate
|
|
338
|
+
|
|
339
|
+
this.controls.addEventListener('change', () => {
|
|
340
|
+
throttledCameraSync()
|
|
341
|
+
throttledCamQsUpdate()
|
|
342
|
+
this.requestRender()
|
|
343
|
+
})
|
|
344
|
+
} else {
|
|
345
|
+
setInterval(() => {
|
|
346
|
+
throttledCamQsUpdate()
|
|
347
|
+
}, 200)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Manual camera controls (if orbit controls disabled)
|
|
353
|
+
if (!this.enableCameraOrbitControl && this.camera) {
|
|
354
|
+
let mouseMoveCounter = 0
|
|
355
|
+
const mouseMove = (e: PointerEvent) => {
|
|
356
|
+
if ((e.target as HTMLElement).closest('.lil-gui')) return
|
|
357
|
+
if (e.buttons === 1 || e.pointerType === 'touch') {
|
|
358
|
+
mouseMoveCounter++
|
|
359
|
+
this.camera.rotation.x -= e.movementY / 100
|
|
360
|
+
this.camera.rotation.y -= e.movementX / 100
|
|
361
|
+
if (this.camera.rotation.x < -Math.PI / 2) this.camera.rotation.x = -Math.PI / 2
|
|
362
|
+
if (this.camera.rotation.x > Math.PI / 2) this.camera.rotation.x = Math.PI / 2
|
|
363
|
+
this.syncCameraToBackend(true)
|
|
364
|
+
}
|
|
365
|
+
if (e.buttons === 2) {
|
|
366
|
+
this.camera.position.set(0, 0, 0)
|
|
367
|
+
this.syncCameraToBackend()
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
setInterval(() => {
|
|
371
|
+
mouseMoveCounter = 0
|
|
372
|
+
}, 1000)
|
|
373
|
+
window.addEventListener('pointermove', mouseMove)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Setup resize handler
|
|
377
|
+
this.onResize()
|
|
378
|
+
window.addEventListener('resize', () => this.onResize())
|
|
379
|
+
|
|
380
|
+
// Setup debug FPS GUI
|
|
381
|
+
this.setupDebugFpsGui()
|
|
382
|
+
|
|
383
|
+
// Wait for chunks and finish setup
|
|
384
|
+
// Access worldRenderer via window.world for this one-time operation
|
|
385
|
+
// const worldRenderer = window.world
|
|
386
|
+
// if (worldRenderer) {
|
|
387
|
+
// void worldRenderer.waitForChunksToRender().then(async () => {
|
|
388
|
+
// this.renderFinish()
|
|
389
|
+
// })
|
|
390
|
+
|
|
391
|
+
// // Listen for world updates to trigger on-demand renders
|
|
392
|
+
// worldRenderer.renderUpdateEmitter.addListener('update', () => {
|
|
393
|
+
// this.requestRender()
|
|
394
|
+
// })
|
|
395
|
+
// }
|
|
396
|
+
|
|
397
|
+
// // Start render loop if continuous, otherwise use on-demand rendering
|
|
398
|
+
// if (this.continuousRender) {
|
|
399
|
+
// this.loop()
|
|
400
|
+
// }
|
|
401
|
+
this.renderFinish()
|
|
402
|
+
this.mainDebugLoop()
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
mainDebugLoop() {
|
|
406
|
+
requestAnimationFrame(() => this.mainDebugLoop())
|
|
407
|
+
this.trackFrame()
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
loop() {
|
|
411
|
+
if (this.continuousRender && !this.windowHidden) {
|
|
412
|
+
this.requestRender()
|
|
413
|
+
requestAnimationFrame(() => this.loop())
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Request a render from the backend (on-demand rendering)
|
|
418
|
+
// The DocumentRenderer loop handles actual rendering continuously
|
|
419
|
+
// Camera sync happens via syncCameraToBackend() which updates the internal camera
|
|
420
|
+
requestRender() {
|
|
421
|
+
// No-op: rendering is handled by DocumentRenderer's continuous loop
|
|
422
|
+
// This method exists for API compatibility
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
private setupDebugFpsGui() {
|
|
426
|
+
// Create simple DOM element for debug FPS display in bottom left corner
|
|
427
|
+
this.debugFpsElement = document.createElement('div')
|
|
428
|
+
this.debugFpsElement.style.position = 'fixed'
|
|
429
|
+
this.debugFpsElement.style.bottom = '0'
|
|
430
|
+
this.debugFpsElement.style.left = '0'
|
|
431
|
+
this.debugFpsElement.style.zIndex = '1000'
|
|
432
|
+
this.debugFpsElement.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'
|
|
433
|
+
this.debugFpsElement.style.color = '#fff'
|
|
434
|
+
this.debugFpsElement.style.padding = '4px 6px'
|
|
435
|
+
this.debugFpsElement.style.fontFamily = 'monospace'
|
|
436
|
+
this.debugFpsElement.style.fontSize = '11px'
|
|
437
|
+
this.debugFpsElement.style.lineHeight = '1.2'
|
|
438
|
+
this.debugFpsElement.style.pointerEvents = 'none'
|
|
439
|
+
this.debugFpsElement.style.userSelect = 'none'
|
|
440
|
+
this.debugFpsElement.textContent = 'FPS: 0 | Max: 0 ms'
|
|
441
|
+
|
|
442
|
+
document.body.appendChild(this.debugFpsElement)
|
|
443
|
+
|
|
444
|
+
// Update debug info every second
|
|
445
|
+
setInterval(() => {
|
|
446
|
+
this.updateDebugInfo()
|
|
447
|
+
}, 1000)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private trackFrame() {
|
|
451
|
+
const now = performance.now()
|
|
452
|
+
this.frameTimes.push(now)
|
|
453
|
+
this.frameCount++
|
|
454
|
+
|
|
455
|
+
// Calculate frame delay (time since last frame)
|
|
456
|
+
if (this.frameTimes.length > 1) {
|
|
457
|
+
const delay = now - this.frameTimes.at(-2)!
|
|
458
|
+
if (delay > this.maxFrameDelay) {
|
|
459
|
+
this.maxFrameDelay = delay
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Keep only last second of frame times
|
|
464
|
+
const oneSecondAgo = now - 1000
|
|
465
|
+
this.frameTimes = this.frameTimes.filter(time => time > oneSecondAgo)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
private updateDebugInfo() {
|
|
469
|
+
if (!this.debugFpsElement) return
|
|
470
|
+
|
|
471
|
+
// Calculate FPS from number of frames in the last second
|
|
472
|
+
// frameTimes array contains timestamps from the last second after filtering
|
|
473
|
+
const fps = this.frameTimes.length
|
|
474
|
+
|
|
475
|
+
this.currentFps = fps
|
|
476
|
+
|
|
477
|
+
const isSeriousDelay = this.maxFrameDelay > 150
|
|
478
|
+
const delayText = isSeriousDelay
|
|
479
|
+
? `<span style="color: #ff4444;">${this.maxFrameDelay.toFixed(0)}ms</span>`
|
|
480
|
+
: `${this.maxFrameDelay.toFixed(0)}ms`
|
|
481
|
+
|
|
482
|
+
// Update the DOM element directly - single line format
|
|
483
|
+
this.debugFpsElement.innerHTML = `FPS: ${fps} | Max Delay: ${delayText}`
|
|
484
|
+
|
|
485
|
+
// Reset for next second
|
|
486
|
+
this.lastSecondTime = performance.now()
|
|
487
|
+
this.maxFrameDelay = 0
|
|
488
|
+
this.frameCount = 0
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Legacy render method for compatibility
|
|
492
|
+
render(fromLoop = false) {
|
|
493
|
+
this.requestRender()
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
addKeyboardShortcuts() {
|
|
497
|
+
document.addEventListener('keydown', (e) => {
|
|
498
|
+
if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
|
|
499
|
+
if (e.code === 'KeyR') {
|
|
500
|
+
this.controls?.reset()
|
|
501
|
+
this.resetCamera()
|
|
502
|
+
}
|
|
503
|
+
if (e.code === 'KeyE') { // refresh block (main)
|
|
504
|
+
this.worldView!.setBlockStateId(this.targetPos, this.world.getBlockStateId(this.targetPos))
|
|
505
|
+
}
|
|
506
|
+
if (e.code === 'KeyF') { // reload all chunks
|
|
507
|
+
this.sceneReset()
|
|
508
|
+
this.worldView!.unloadAllChunks()
|
|
509
|
+
void this.worldView!.init(this.targetPos)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
})
|
|
513
|
+
document.addEventListener('visibilitychange', () => {
|
|
514
|
+
this.windowHidden = document.visibilityState === 'hidden'
|
|
515
|
+
})
|
|
516
|
+
document.addEventListener('blur', () => {
|
|
517
|
+
this.windowHidden = true
|
|
518
|
+
})
|
|
519
|
+
document.addEventListener('focus', () => {
|
|
520
|
+
this.windowHidden = false
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
const pressedKeys = new Set<string>()
|
|
524
|
+
const updateKeys = () => {
|
|
525
|
+
if (pressedKeys.has('ControlLeft') || pressedKeys.has('MetaLeft')) {
|
|
526
|
+
return
|
|
527
|
+
}
|
|
528
|
+
if (!this.camera) return
|
|
529
|
+
|
|
530
|
+
const direction = new THREE.Vector3(0, 0, 0)
|
|
531
|
+
if (pressedKeys.has('KeyW')) {
|
|
532
|
+
direction.z = -0.5
|
|
533
|
+
}
|
|
534
|
+
if (pressedKeys.has('KeyS')) {
|
|
535
|
+
direction.z += 0.5
|
|
536
|
+
}
|
|
537
|
+
if (pressedKeys.has('KeyA')) {
|
|
538
|
+
direction.x -= 0.5
|
|
539
|
+
}
|
|
540
|
+
if (pressedKeys.has('KeyD')) {
|
|
541
|
+
direction.x += 0.5
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (pressedKeys.has('ShiftLeft')) {
|
|
545
|
+
this.camera.position.y -= 0.5
|
|
546
|
+
}
|
|
547
|
+
if (pressedKeys.has('Space')) {
|
|
548
|
+
this.camera.position.y += 0.5
|
|
549
|
+
}
|
|
550
|
+
direction.applyQuaternion(this.camera.quaternion)
|
|
551
|
+
direction.y = 0
|
|
552
|
+
|
|
553
|
+
if (pressedKeys.has('ShiftLeft')) {
|
|
554
|
+
direction.y *= 2
|
|
555
|
+
direction.x *= 2
|
|
556
|
+
direction.z *= 2
|
|
557
|
+
}
|
|
558
|
+
this.camera.position.add(direction.normalize())
|
|
559
|
+
this.controls?.update()
|
|
560
|
+
this.syncCameraToBackend()
|
|
561
|
+
this.requestRender()
|
|
562
|
+
}
|
|
563
|
+
setInterval(updateKeys, 1000 / 20)
|
|
564
|
+
|
|
565
|
+
const keys = (e: KeyboardEvent) => {
|
|
566
|
+
const { code } = e
|
|
567
|
+
const pressed = e.type === 'keydown'
|
|
568
|
+
if (pressed) {
|
|
569
|
+
pressedKeys.add(code)
|
|
570
|
+
} else {
|
|
571
|
+
pressedKeys.delete(code)
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
window.addEventListener('keydown', keys)
|
|
576
|
+
window.addEventListener('keyup', keys)
|
|
577
|
+
window.addEventListener('blur', () => {
|
|
578
|
+
for (const key of pressedKeys) {
|
|
579
|
+
keys(new KeyboardEvent('keyup', { code: key }))
|
|
580
|
+
}
|
|
581
|
+
})
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
onResize() {
|
|
585
|
+
this.requestRender()
|
|
586
|
+
}
|
|
587
|
+
}
|