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,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentRenderer - Manages Three.js WebGLRenderer and render loop.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Automatic canvas sizing with resize event optimization
|
|
6
|
+
* - FPS limiting support
|
|
7
|
+
* - Stats overlay (optional)
|
|
8
|
+
* - Timeout-based rendering option for background tabs
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as THREE from 'three'
|
|
12
|
+
import Stats from 'stats.js'
|
|
13
|
+
import StatsGl from 'stats-gl'
|
|
14
|
+
import * as tween from '@tweenjs/tween.js'
|
|
15
|
+
import { WorldRendererConfig } from '../lib/worldrendererCommon'
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Types (co-located with implementation)
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
export interface GraphicsBackendConfig {
|
|
22
|
+
fpsLimit?: number
|
|
23
|
+
powerPreference?: 'high-performance' | 'low-power'
|
|
24
|
+
statsVisible?: number
|
|
25
|
+
sceneBackground: string
|
|
26
|
+
timeoutRendering?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface FrameTimingEvent {
|
|
30
|
+
type: 'frameStart' | 'frameEnd' | 'cameraUpdate' | 'frameDisplay'
|
|
31
|
+
timestamp: number
|
|
32
|
+
duration?: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface NonReactiveState {
|
|
36
|
+
fps: number
|
|
37
|
+
worstRenderTime: number
|
|
38
|
+
avgRenderTime: number
|
|
39
|
+
world: {
|
|
40
|
+
chunksLoaded: Set<string>
|
|
41
|
+
chunksTotalNumber: number
|
|
42
|
+
}
|
|
43
|
+
renderer: {
|
|
44
|
+
timeline: {
|
|
45
|
+
live: FrameTimingEvent[]
|
|
46
|
+
frozen: FrameTimingEvent[]
|
|
47
|
+
lastSecond: FrameTimingEvent[]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface GraphicsInitOptions<S = any> {
|
|
53
|
+
resourcesManager: any
|
|
54
|
+
config: GraphicsBackendConfig
|
|
55
|
+
rendererSpecificSettings: S
|
|
56
|
+
callbacks: {
|
|
57
|
+
displayCriticalError: (error: Error) => void
|
|
58
|
+
setRendererSpecificSettings: (key: string, value: any) => void
|
|
59
|
+
fireCustomEvent: (eventName: string, ...args: any[]) => void
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ThreeRendererMainData {
|
|
64
|
+
canvas: OffscreenCanvas
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const isWebWorker = typeof (globalThis as any).WorkerGlobalScope !== 'undefined' &&
|
|
68
|
+
globalThis instanceof (globalThis as any).WorkerGlobalScope
|
|
69
|
+
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// TopRightStats - Performance stats display
|
|
72
|
+
// ============================================================================
|
|
73
|
+
|
|
74
|
+
class TopRightStats {
|
|
75
|
+
private readonly stats: Stats
|
|
76
|
+
private readonly stats2: Stats
|
|
77
|
+
private readonly statsGl: StatsGl
|
|
78
|
+
private total = 0
|
|
79
|
+
private readonly denseMode: boolean
|
|
80
|
+
|
|
81
|
+
constructor(private readonly canvas: HTMLCanvasElement, initialStatsVisible = 0) {
|
|
82
|
+
this.stats = new Stats()
|
|
83
|
+
this.stats2 = new Stats()
|
|
84
|
+
this.statsGl = new StatsGl({ minimal: true })
|
|
85
|
+
this.stats2.showPanel(2)
|
|
86
|
+
this.denseMode = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production' ||
|
|
87
|
+
(typeof window !== 'undefined' && window.innerHeight < 500)
|
|
88
|
+
|
|
89
|
+
this.initStats()
|
|
90
|
+
this.setVisibility(initialStatsVisible)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private addStat(dom: HTMLElement, size = 80) {
|
|
94
|
+
dom.style.position = 'absolute'
|
|
95
|
+
if (this.denseMode) dom.style.height = '12px'
|
|
96
|
+
dom.style.overflow = 'hidden'
|
|
97
|
+
dom.style.left = ''
|
|
98
|
+
dom.style.top = '0'
|
|
99
|
+
dom.style.right = `${this.total}px`
|
|
100
|
+
dom.style.width = '80px'
|
|
101
|
+
dom.style.zIndex = '1'
|
|
102
|
+
dom.style.opacity = '0.8'
|
|
103
|
+
const hasAppContainer = document.getElementById('corner-indicator-stats')
|
|
104
|
+
const container = hasAppContainer ?? document.body
|
|
105
|
+
if (hasAppContainer) {
|
|
106
|
+
dom.style.position = 'relative'
|
|
107
|
+
}
|
|
108
|
+
container.appendChild(dom)
|
|
109
|
+
this.total += size
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private initStats() {
|
|
113
|
+
const hasRamPanel = this.stats2.dom.children.length === 3
|
|
114
|
+
|
|
115
|
+
this.addStat(this.stats.dom)
|
|
116
|
+
if (hasRamPanel) {
|
|
117
|
+
this.addStat(this.stats2.dom)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.statsGl.init(this.canvas)
|
|
121
|
+
this.statsGl.container.style.display = 'flex'
|
|
122
|
+
this.statsGl.container.style.justifyContent = 'flex-end'
|
|
123
|
+
|
|
124
|
+
let i = 0
|
|
125
|
+
for (const _child of this.statsGl.container.children) {
|
|
126
|
+
const child = _child as HTMLElement
|
|
127
|
+
if (i++ === 0) {
|
|
128
|
+
child.style.display = 'none'
|
|
129
|
+
}
|
|
130
|
+
child.style.position = ''
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setVisibility(level: number) {
|
|
135
|
+
const visible = level > 0
|
|
136
|
+
if (visible) {
|
|
137
|
+
this.stats.dom.style.display = 'block'
|
|
138
|
+
this.stats2.dom.style.display = level >= 2 ? 'block' : 'none'
|
|
139
|
+
this.statsGl.container.style.display = level >= 2 ? 'block' : 'none'
|
|
140
|
+
} else {
|
|
141
|
+
this.stats.dom.style.display = 'none'
|
|
142
|
+
this.stats2.dom.style.display = 'none'
|
|
143
|
+
this.statsGl.container.style.display = 'none'
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
markStart() {
|
|
148
|
+
this.stats.begin()
|
|
149
|
+
this.stats2.begin()
|
|
150
|
+
this.statsGl.begin()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
markEnd() {
|
|
154
|
+
this.stats.end()
|
|
155
|
+
this.stats2.end()
|
|
156
|
+
this.statsGl.end()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
dispose() {
|
|
160
|
+
this.stats.dom.remove()
|
|
161
|
+
this.stats2.dom.remove()
|
|
162
|
+
this.statsGl.container.remove()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// DocumentRenderer - Core rendering loop manager
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
export class DocumentRenderer {
|
|
171
|
+
canvas!: HTMLCanvasElement | OffscreenCanvas
|
|
172
|
+
readonly renderer: THREE.WebGLRenderer
|
|
173
|
+
private animationFrameId?: number
|
|
174
|
+
readonly timeoutId?: number
|
|
175
|
+
private lastRenderTime = 0
|
|
176
|
+
|
|
177
|
+
// Size tracking - optimized to only update on resize
|
|
178
|
+
private previousCanvasWidth = 0
|
|
179
|
+
private previousCanvasHeight = 0
|
|
180
|
+
private currentWidth = 0
|
|
181
|
+
private currentHeight = 0
|
|
182
|
+
private pendingResize = false
|
|
183
|
+
|
|
184
|
+
private renderedFps = 0
|
|
185
|
+
private fpsInterval: any
|
|
186
|
+
private readonly stats: TopRightStats | undefined
|
|
187
|
+
private paused = false
|
|
188
|
+
disconnected = false
|
|
189
|
+
|
|
190
|
+
// Render hooks
|
|
191
|
+
preRender = () => { }
|
|
192
|
+
render = (sizeChanged: boolean) => { }
|
|
193
|
+
postRender = () => { }
|
|
194
|
+
sizeChanged = () => { }
|
|
195
|
+
|
|
196
|
+
droppedFpsPercentage = 0
|
|
197
|
+
config: GraphicsBackendConfig
|
|
198
|
+
onRender: Array<(sizeChanged: boolean) => void> = []
|
|
199
|
+
inWorldRenderingConfig: WorldRendererConfig | undefined
|
|
200
|
+
public nonReactiveState: NonReactiveState | undefined
|
|
201
|
+
|
|
202
|
+
constructor(
|
|
203
|
+
public initOptions: GraphicsInitOptions,
|
|
204
|
+
public externalCanvas?: OffscreenCanvas,
|
|
205
|
+
mainData?: ThreeRendererMainData
|
|
206
|
+
) {
|
|
207
|
+
this.config = initOptions.config
|
|
208
|
+
|
|
209
|
+
// Handle canvas creation/transfer based on context
|
|
210
|
+
if (externalCanvas) {
|
|
211
|
+
this.canvas = externalCanvas
|
|
212
|
+
} else {
|
|
213
|
+
this.addToPage()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
this.renderer = new THREE.WebGLRenderer({
|
|
218
|
+
canvas: this.canvas,
|
|
219
|
+
preserveDrawingBuffer: true,
|
|
220
|
+
logarithmicDepthBuffer: true,
|
|
221
|
+
powerPreference: this.config.powerPreference
|
|
222
|
+
})
|
|
223
|
+
} catch (err: any) {
|
|
224
|
+
initOptions.callbacks.displayCriticalError(
|
|
225
|
+
new Error(`Failed to create WebGL context, not possible to render (restart browser): ${err.message}`)
|
|
226
|
+
)
|
|
227
|
+
throw err
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// @ts-ignore - legacy lights API
|
|
231
|
+
this.renderer.useLegacyLights = true
|
|
232
|
+
this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace
|
|
233
|
+
|
|
234
|
+
if (!externalCanvas) {
|
|
235
|
+
this.updatePixelRatio()
|
|
236
|
+
// Setup resize listener - only update size on actual resize events
|
|
237
|
+
this.setupResizeListener()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.sizeUpdated()
|
|
241
|
+
// Initialize previous dimensions
|
|
242
|
+
this.previousCanvasWidth = this.canvas.width
|
|
243
|
+
this.previousCanvasHeight = this.canvas.height
|
|
244
|
+
|
|
245
|
+
const supportsWebGL2 = 'WebGL2RenderingContext' in globalThis
|
|
246
|
+
// Only initialize stats and DOM-related features in main thread (not worker)
|
|
247
|
+
if (!externalCanvas && supportsWebGL2 && !isWebWorker) {
|
|
248
|
+
this.stats = new TopRightStats(this.canvas as HTMLCanvasElement, this.config.statsVisible)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
this.setupFpsTracking()
|
|
252
|
+
this.startRenderLoop()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Setup resize event listener for optimized size updates.
|
|
257
|
+
* Only reads document.body dimensions when resize event fires.
|
|
258
|
+
*/
|
|
259
|
+
private setupResizeListener(): void {
|
|
260
|
+
if (typeof window === 'undefined') return
|
|
261
|
+
|
|
262
|
+
let resizeTimeout: ReturnType<typeof setTimeout> | undefined
|
|
263
|
+
|
|
264
|
+
const handleResize = () => {
|
|
265
|
+
// Debounce resize to avoid excessive updates
|
|
266
|
+
if (resizeTimeout) {
|
|
267
|
+
clearTimeout(resizeTimeout)
|
|
268
|
+
}
|
|
269
|
+
resizeTimeout = setTimeout(() => {
|
|
270
|
+
this.pendingResize = true
|
|
271
|
+
}, 16) // ~60fps debounce
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
window.addEventListener('resize', handleResize, { passive: true })
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
updatePixelRatio(): void {
|
|
278
|
+
if (typeof window === 'undefined') return
|
|
279
|
+
|
|
280
|
+
let pixelRatio = window.devicePixelRatio || 1
|
|
281
|
+
if (!this.renderer.capabilities.isWebGL2) {
|
|
282
|
+
pixelRatio = 1 // WebGL1 has issues with high pixel ratio
|
|
283
|
+
}
|
|
284
|
+
this.renderer.setPixelRatio(pixelRatio)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
sizeUpdated(): void {
|
|
288
|
+
this.renderer.setSize(this.currentWidth, this.currentHeight, false)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private addToPage(): void {
|
|
292
|
+
this.canvas = addCanvasToPage()
|
|
293
|
+
// Initial size read
|
|
294
|
+
this.updateCanvasSize()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Update size from external source (for worker/offscreen scenarios).
|
|
299
|
+
*/
|
|
300
|
+
updateSizeExternal(newWidth: number, newHeight: number, pixelRatio: number): void {
|
|
301
|
+
this.currentWidth = newWidth
|
|
302
|
+
this.currentHeight = newHeight
|
|
303
|
+
this.renderer.setPixelRatio(pixelRatio)
|
|
304
|
+
this.sizeUpdated()
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Update canvas size from document body.
|
|
309
|
+
* Only called when pendingResize is true (after resize event).
|
|
310
|
+
*/
|
|
311
|
+
private updateCanvasSize(): void {
|
|
312
|
+
if (this.externalCanvas) return
|
|
313
|
+
if (typeof document === 'undefined') return
|
|
314
|
+
|
|
315
|
+
// Only read body dimensions when we know a resize occurred
|
|
316
|
+
const innerWidth = document.body.offsetWidth
|
|
317
|
+
const innerHeight = document.body.offsetHeight
|
|
318
|
+
|
|
319
|
+
if (this.currentWidth !== innerWidth) {
|
|
320
|
+
this.currentWidth = innerWidth
|
|
321
|
+
}
|
|
322
|
+
if (this.currentHeight !== innerHeight) {
|
|
323
|
+
this.currentHeight = innerHeight
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private setupFpsTracking(): void {
|
|
328
|
+
let max = 0
|
|
329
|
+
this.fpsInterval = setInterval(() => {
|
|
330
|
+
if (max > 0) {
|
|
331
|
+
this.droppedFpsPercentage = this.renderedFps / max
|
|
332
|
+
}
|
|
333
|
+
max = Math.max(this.renderedFps, max)
|
|
334
|
+
if (this.nonReactiveState) {
|
|
335
|
+
this.nonReactiveState.fps = this.renderedFps
|
|
336
|
+
}
|
|
337
|
+
this.renderedFps = 0
|
|
338
|
+
}, 1000)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private startRenderLoop(): void {
|
|
342
|
+
const animate = () => {
|
|
343
|
+
if (this.disconnected) return
|
|
344
|
+
|
|
345
|
+
// Schedule next frame based on rendering mode
|
|
346
|
+
if (this.config.timeoutRendering) {
|
|
347
|
+
const targetFps = this.config.fpsLimit ? Math.min(this.config.fpsLimit, 60) : 60
|
|
348
|
+
const timeoutMs = 1000 / targetFps
|
|
349
|
+
; (this as any).timeoutId = setTimeout(animate, timeoutMs)
|
|
350
|
+
} else {
|
|
351
|
+
this.animationFrameId = requestAnimationFrame(animate)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (this.paused ||
|
|
355
|
+
(this.renderer.xr.isPresenting && !this.inWorldRenderingConfig?.vrPageGameRendering)) {
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Handle FPS limiting (for requestAnimationFrame mode)
|
|
360
|
+
if (!this.config.timeoutRendering && this.config.fpsLimit) {
|
|
361
|
+
const now = performance.now()
|
|
362
|
+
const elapsed = now - this.lastRenderTime
|
|
363
|
+
const fpsInterval = 1000 / this.config.fpsLimit
|
|
364
|
+
|
|
365
|
+
if (elapsed < fpsInterval) {
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
this.lastRenderTime = now - (elapsed % fpsInterval)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let sizeChanged = false
|
|
373
|
+
|
|
374
|
+
// Only update canvas size if a resize event occurred
|
|
375
|
+
if (this.pendingResize) {
|
|
376
|
+
this.updateCanvasSize()
|
|
377
|
+
this.pendingResize = false
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (this.previousCanvasWidth !== this.currentWidth ||
|
|
381
|
+
this.previousCanvasHeight !== this.currentHeight) {
|
|
382
|
+
this.previousCanvasWidth = this.currentWidth
|
|
383
|
+
this.previousCanvasHeight = this.currentHeight
|
|
384
|
+
this.sizeUpdated()
|
|
385
|
+
sizeChanged = true
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
this.frameRender(sizeChanged)
|
|
389
|
+
|
|
390
|
+
// Update stats visibility each frame (main thread only)
|
|
391
|
+
if (this.config.statsVisible !== undefined) {
|
|
392
|
+
this.stats?.setVisibility(this.config.statsVisible)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
animate()
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
frameRender(sizeChanged: boolean): void {
|
|
400
|
+
this.preRender()
|
|
401
|
+
this.stats?.markStart()
|
|
402
|
+
tween.update()
|
|
403
|
+
|
|
404
|
+
if (!(globalThis as any).freezeRender) {
|
|
405
|
+
this.render(sizeChanged)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
for (const fn of this.onRender) {
|
|
409
|
+
fn(sizeChanged)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
this.renderedFps++
|
|
413
|
+
this.stats?.markEnd()
|
|
414
|
+
this.postRender()
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
setPaused(paused: boolean): void {
|
|
418
|
+
this.paused = paused
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
dispose(): void {
|
|
422
|
+
this.disconnected = true
|
|
423
|
+
|
|
424
|
+
if (this.animationFrameId) {
|
|
425
|
+
cancelAnimationFrame(this.animationFrameId)
|
|
426
|
+
}
|
|
427
|
+
if (this.timeoutId) {
|
|
428
|
+
clearTimeout(this.timeoutId)
|
|
429
|
+
}
|
|
430
|
+
if (this.canvas instanceof HTMLCanvasElement) {
|
|
431
|
+
this.canvas.remove()
|
|
432
|
+
}
|
|
433
|
+
clearInterval(this.fpsInterval)
|
|
434
|
+
this.stats?.dispose()
|
|
435
|
+
this.renderer.dispose()
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ============================================================================
|
|
440
|
+
// Helper functions
|
|
441
|
+
// ============================================================================
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Adds a canvas element to the page.
|
|
445
|
+
*/
|
|
446
|
+
function addCanvasToPage(): HTMLCanvasElement {
|
|
447
|
+
const canvas = document.createElement('canvas')
|
|
448
|
+
canvas.id = 'viewer-canvas'
|
|
449
|
+
document.body.appendChild(canvas)
|
|
450
|
+
return canvas
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Creates a canvas for worker thread rendering.
|
|
455
|
+
*/
|
|
456
|
+
export const addCanvasForWorker = (): {
|
|
457
|
+
canvas: OffscreenCanvas
|
|
458
|
+
destroy: () => void
|
|
459
|
+
onSizeChanged: (cb: (width: number, height: number) => void) => void
|
|
460
|
+
size: { width: number; height: number }
|
|
461
|
+
} => {
|
|
462
|
+
const canvas = addCanvasToPage()
|
|
463
|
+
const transferred = canvas.transferControlToOffscreen()
|
|
464
|
+
let removed = false
|
|
465
|
+
let onSizeChanged = (w: number, h: number) => { }
|
|
466
|
+
let oldSize = { width: 0, height: 0 }
|
|
467
|
+
|
|
468
|
+
const checkSize = () => {
|
|
469
|
+
if (removed) return
|
|
470
|
+
if (oldSize.width !== window.innerWidth || oldSize.height !== window.innerHeight) {
|
|
471
|
+
onSizeChanged(window.innerWidth, window.innerHeight)
|
|
472
|
+
oldSize = { width: window.innerWidth, height: window.innerHeight }
|
|
473
|
+
}
|
|
474
|
+
requestAnimationFrame(checkSize)
|
|
475
|
+
}
|
|
476
|
+
requestAnimationFrame(checkSize)
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
canvas: transferred,
|
|
480
|
+
destroy() {
|
|
481
|
+
removed = true
|
|
482
|
+
canvas.remove()
|
|
483
|
+
},
|
|
484
|
+
onSizeChanged(cb: (width: number, height: number) => void) {
|
|
485
|
+
onSizeChanged = cb
|
|
486
|
+
},
|
|
487
|
+
get size() {
|
|
488
|
+
return { width: window.innerWidth, height: window.innerHeight }
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|