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,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas 2D Overlay for Three.js WebGL Canvas
|
|
3
|
+
*
|
|
4
|
+
* Provides methods to draw 2D graphics on top of Three.js rendering
|
|
5
|
+
* Works in both main thread and Web Worker contexts (OffscreenCanvas)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as THREE from 'three'
|
|
9
|
+
|
|
10
|
+
export class Canvas2DOverlay {
|
|
11
|
+
private gl: WebGLRenderingContext | WebGL2RenderingContext
|
|
12
|
+
private overlayScene: THREE.Scene
|
|
13
|
+
private overlayCamera: THREE.OrthographicCamera
|
|
14
|
+
private overlayObjects: THREE.Mesh[] = []
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private renderer: THREE.WebGLRenderer,
|
|
18
|
+
private canvas: HTMLCanvasElement | OffscreenCanvas
|
|
19
|
+
) {
|
|
20
|
+
this.gl = this.renderer.getContext()
|
|
21
|
+
|
|
22
|
+
// Setup orthographic camera for 2D overlay
|
|
23
|
+
this.overlayCamera = new THREE.OrthographicCamera(
|
|
24
|
+
0, // left
|
|
25
|
+
this.canvas.width, // right
|
|
26
|
+
this.canvas.height, // top
|
|
27
|
+
0, // bottom
|
|
28
|
+
0.1, // near
|
|
29
|
+
1000 // far
|
|
30
|
+
)
|
|
31
|
+
this.overlayCamera.position.z = 10
|
|
32
|
+
|
|
33
|
+
this.overlayScene = new THREE.Scene()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Update camera when canvas size changes
|
|
38
|
+
*/
|
|
39
|
+
updateSize(width: number, height: number) {
|
|
40
|
+
this.overlayCamera.left = 0
|
|
41
|
+
this.overlayCamera.right = width
|
|
42
|
+
this.overlayCamera.top = height
|
|
43
|
+
this.overlayCamera.bottom = 0
|
|
44
|
+
this.overlayCamera.updateProjectionMatrix()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Clear all overlay objects
|
|
49
|
+
*/
|
|
50
|
+
clear() {
|
|
51
|
+
for (const obj of this.overlayObjects) {
|
|
52
|
+
obj.geometry.dispose()
|
|
53
|
+
if (obj.material instanceof THREE.Material) {
|
|
54
|
+
obj.material.dispose()
|
|
55
|
+
}
|
|
56
|
+
this.overlayScene.remove(obj)
|
|
57
|
+
}
|
|
58
|
+
this.overlayObjects = []
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Draw a filled rectangle (2D box)
|
|
63
|
+
* @param x X position (pixels from left)
|
|
64
|
+
* @param y Y position (pixels from top)
|
|
65
|
+
* @param width Width in pixels
|
|
66
|
+
* @param height Height in pixels
|
|
67
|
+
* @param color Color (hex or CSS color)
|
|
68
|
+
* @param opacity Opacity (0-1)
|
|
69
|
+
*/
|
|
70
|
+
drawRect(x: number, y: number, width: number, height: number, color: number | string = 0x000000, opacity = 1) {
|
|
71
|
+
const geometry = new THREE.PlaneGeometry(width, height)
|
|
72
|
+
const material = new THREE.MeshBasicMaterial({
|
|
73
|
+
color: typeof color === 'string' ? new THREE.Color(color) : color,
|
|
74
|
+
transparent: opacity < 1,
|
|
75
|
+
opacity,
|
|
76
|
+
depthTest: false, // Always render on top
|
|
77
|
+
depthWrite: false
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const mesh = new THREE.Mesh(geometry, material)
|
|
81
|
+
|
|
82
|
+
// Position: origin is top-left for pixel coordinates
|
|
83
|
+
mesh.position.set(
|
|
84
|
+
x + width / 2, // Center X
|
|
85
|
+
y + height / 2, // Center Y (from top)
|
|
86
|
+
0
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
this.overlayScene.add(mesh)
|
|
90
|
+
this.overlayObjects.push(mesh)
|
|
91
|
+
|
|
92
|
+
return mesh
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Draw a rectangle outline (border only)
|
|
97
|
+
*/
|
|
98
|
+
drawRectOutline(x: number, y: number, width: number, height: number, color: number | string = 0x000000, lineWidth = 1, opacity = 1) {
|
|
99
|
+
const points = [
|
|
100
|
+
new THREE.Vector3(x, y, 0),
|
|
101
|
+
new THREE.Vector3(x + width, y, 0),
|
|
102
|
+
new THREE.Vector3(x + width, y + height, 0),
|
|
103
|
+
new THREE.Vector3(x, y + height, 0),
|
|
104
|
+
new THREE.Vector3(x, y, 0)
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
const geometry = new THREE.BufferGeometry().setFromPoints(points)
|
|
108
|
+
const material = new THREE.LineBasicMaterial({
|
|
109
|
+
color: typeof color === 'string' ? new THREE.Color(color) : color,
|
|
110
|
+
transparent: opacity < 1,
|
|
111
|
+
opacity,
|
|
112
|
+
linewidth: lineWidth // Note: linewidth may not work on all platforms
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const line = new THREE.Line(geometry, material)
|
|
116
|
+
this.overlayScene.add(line)
|
|
117
|
+
this.overlayObjects.push(line as any)
|
|
118
|
+
|
|
119
|
+
return line
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Draw text using canvas texture (works in worker with OffscreenCanvas)
|
|
124
|
+
*/
|
|
125
|
+
drawText(text: string, x: number, y: number, options: {
|
|
126
|
+
fontSize?: number
|
|
127
|
+
fontFamily?: string
|
|
128
|
+
color?: string
|
|
129
|
+
backgroundColor?: string
|
|
130
|
+
padding?: number
|
|
131
|
+
opacity?: number
|
|
132
|
+
} = {}) {
|
|
133
|
+
const {
|
|
134
|
+
fontSize = 16,
|
|
135
|
+
fontFamily = 'Arial',
|
|
136
|
+
color = '#ffffff',
|
|
137
|
+
backgroundColor = '#000000',
|
|
138
|
+
padding = 4,
|
|
139
|
+
opacity = 1
|
|
140
|
+
} = options
|
|
141
|
+
|
|
142
|
+
// Create a temporary canvas for text rendering
|
|
143
|
+
// OffscreenCanvas works in workers!
|
|
144
|
+
const textCanvas = new OffscreenCanvas(512, 128)
|
|
145
|
+
const ctx = textCanvas.getContext('2d')!
|
|
146
|
+
|
|
147
|
+
ctx.font = `${fontSize}px ${fontFamily}`
|
|
148
|
+
const metrics = ctx.measureText(text)
|
|
149
|
+
const textWidth = metrics.width
|
|
150
|
+
const textHeight = fontSize
|
|
151
|
+
|
|
152
|
+
// Resize canvas to fit text
|
|
153
|
+
textCanvas.width = Math.ceil(textWidth + padding * 2)
|
|
154
|
+
textCanvas.height = Math.ceil(textHeight + padding * 2)
|
|
155
|
+
|
|
156
|
+
// Redraw with proper size
|
|
157
|
+
ctx.font = `${fontSize}px ${fontFamily}`
|
|
158
|
+
ctx.fillStyle = backgroundColor
|
|
159
|
+
ctx.fillRect(0, 0, textCanvas.width, textCanvas.height)
|
|
160
|
+
|
|
161
|
+
ctx.fillStyle = color
|
|
162
|
+
ctx.textBaseline = 'top'
|
|
163
|
+
ctx.fillText(text, padding, padding)
|
|
164
|
+
|
|
165
|
+
// Create texture from canvas
|
|
166
|
+
const texture = new THREE.CanvasTexture(textCanvas as any)
|
|
167
|
+
texture.needsUpdate = true
|
|
168
|
+
|
|
169
|
+
const material = new THREE.MeshBasicMaterial({
|
|
170
|
+
map: texture,
|
|
171
|
+
transparent: true,
|
|
172
|
+
opacity,
|
|
173
|
+
depthTest: false,
|
|
174
|
+
depthWrite: false
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const geometry = new THREE.PlaneGeometry(textCanvas.width, textCanvas.height)
|
|
178
|
+
const mesh = new THREE.Mesh(geometry, material)
|
|
179
|
+
|
|
180
|
+
mesh.position.set(
|
|
181
|
+
x + textCanvas.width / 2,
|
|
182
|
+
y + textCanvas.height / 2,
|
|
183
|
+
0
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
this.overlayScene.add(mesh)
|
|
187
|
+
this.overlayObjects.push(mesh)
|
|
188
|
+
|
|
189
|
+
return mesh
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Draw a circle
|
|
194
|
+
*/
|
|
195
|
+
drawCircle(x: number, y: number, radius: number, color: number | string = 0x000000, opacity = 1) {
|
|
196
|
+
const geometry = new THREE.CircleGeometry(radius, 32)
|
|
197
|
+
const material = new THREE.MeshBasicMaterial({
|
|
198
|
+
color: typeof color === 'string' ? new THREE.Color(color) : color,
|
|
199
|
+
transparent: opacity < 1,
|
|
200
|
+
opacity,
|
|
201
|
+
depthTest: false,
|
|
202
|
+
depthWrite: false
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const mesh = new THREE.Mesh(geometry, material)
|
|
206
|
+
mesh.position.set(x, y, 0)
|
|
207
|
+
|
|
208
|
+
this.overlayScene.add(mesh)
|
|
209
|
+
this.overlayObjects.push(mesh)
|
|
210
|
+
|
|
211
|
+
return mesh
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Render the overlay on top of Three.js scene
|
|
216
|
+
* Call this after rendering your main 3D scene
|
|
217
|
+
*/
|
|
218
|
+
render() {
|
|
219
|
+
// Disable depth test so overlay renders on top
|
|
220
|
+
const oldAutoClear = this.renderer.autoClear
|
|
221
|
+
this.renderer.autoClear = false
|
|
222
|
+
|
|
223
|
+
this.renderer.render(this.overlayScene, this.overlayCamera)
|
|
224
|
+
|
|
225
|
+
this.renderer.autoClear = oldAutoClear
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Dispose of all resources
|
|
230
|
+
*/
|
|
231
|
+
dispose() {
|
|
232
|
+
this.clear()
|
|
233
|
+
this.overlayScene.clear()
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Alternative: Direct WebGL 2D Drawing
|
|
239
|
+
* For more performance or if you need more control
|
|
240
|
+
*/
|
|
241
|
+
export class WebGLDirect2DOverlay {
|
|
242
|
+
private gl: WebGLRenderingContext | WebGL2RenderingContext
|
|
243
|
+
private program: WebGLProgram
|
|
244
|
+
private positionBuffer: WebGLBuffer
|
|
245
|
+
private colorBuffer: WebGLBuffer
|
|
246
|
+
|
|
247
|
+
constructor(
|
|
248
|
+
private renderer: THREE.WebGLRenderer,
|
|
249
|
+
private canvas: HTMLCanvasElement | OffscreenCanvas
|
|
250
|
+
) {
|
|
251
|
+
this.gl = this.renderer.getContext()
|
|
252
|
+
|
|
253
|
+
// Create shader program for 2D rendering
|
|
254
|
+
const vertexShader = this.createShader(this.gl.VERTEX_SHADER, `
|
|
255
|
+
attribute vec2 position;
|
|
256
|
+
attribute vec4 color;
|
|
257
|
+
varying vec4 vColor;
|
|
258
|
+
uniform vec2 resolution;
|
|
259
|
+
|
|
260
|
+
void main() {
|
|
261
|
+
// Convert pixel coordinates to clip space (-1 to 1)
|
|
262
|
+
vec2 clipSpace = (position / resolution) * 2.0 - 1.0;
|
|
263
|
+
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
|
|
264
|
+
vColor = color;
|
|
265
|
+
}
|
|
266
|
+
`)
|
|
267
|
+
|
|
268
|
+
const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, `
|
|
269
|
+
precision mediump float;
|
|
270
|
+
varying vec4 vColor;
|
|
271
|
+
|
|
272
|
+
void main() {
|
|
273
|
+
gl_FragColor = vColor;
|
|
274
|
+
}
|
|
275
|
+
`)
|
|
276
|
+
|
|
277
|
+
this.program = this.createProgram(vertexShader, fragmentShader)
|
|
278
|
+
|
|
279
|
+
this.positionBuffer = this.gl.createBuffer()!
|
|
280
|
+
this.colorBuffer = this.gl.createBuffer()!
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private createShader(type: number, source: string): WebGLShader {
|
|
284
|
+
const shader = this.gl.createShader(type)!
|
|
285
|
+
this.gl.shaderSource(shader, source)
|
|
286
|
+
this.gl.compileShader(shader)
|
|
287
|
+
|
|
288
|
+
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
|
|
289
|
+
console.error('Shader compilation error:', this.gl.getShaderInfoLog(shader))
|
|
290
|
+
this.gl.deleteShader(shader)
|
|
291
|
+
throw new Error('Shader compilation failed')
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return shader
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private createProgram(vertexShader: WebGLShader, fragmentShader: WebGLShader): WebGLProgram {
|
|
298
|
+
const program = this.gl.createProgram()!
|
|
299
|
+
this.gl.attachShader(program, vertexShader)
|
|
300
|
+
this.gl.attachShader(program, fragmentShader)
|
|
301
|
+
this.gl.linkProgram(program)
|
|
302
|
+
|
|
303
|
+
if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
|
|
304
|
+
console.error('Program linking error:', this.gl.getProgramInfoLog(program))
|
|
305
|
+
throw new Error('Program linking failed')
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return program
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Draw a rectangle using raw WebGL
|
|
313
|
+
*/
|
|
314
|
+
drawRect(x: number, y: number, width: number, height: number, r: number, g: number, b: number, a = 1) {
|
|
315
|
+
const x1 = x
|
|
316
|
+
const y1 = y
|
|
317
|
+
const x2 = x + width
|
|
318
|
+
const y2 = y + height
|
|
319
|
+
|
|
320
|
+
const positions = new Float32Array([
|
|
321
|
+
x1, y1,
|
|
322
|
+
x2, y1,
|
|
323
|
+
x1, y2,
|
|
324
|
+
x1, y2,
|
|
325
|
+
x2, y1,
|
|
326
|
+
x2, y2
|
|
327
|
+
])
|
|
328
|
+
|
|
329
|
+
const colors = new Float32Array([
|
|
330
|
+
r, g, b, a,
|
|
331
|
+
r, g, b, a,
|
|
332
|
+
r, g, b, a,
|
|
333
|
+
r, g, b, a,
|
|
334
|
+
r, g, b, a,
|
|
335
|
+
r, g, b, a
|
|
336
|
+
])
|
|
337
|
+
|
|
338
|
+
this.gl.useProgram(this.program)
|
|
339
|
+
|
|
340
|
+
// Set resolution uniform
|
|
341
|
+
const resolutionLocation = this.gl.getUniformLocation(this.program, 'resolution')
|
|
342
|
+
this.gl.uniform2f(resolutionLocation, this.canvas.width, this.canvas.height)
|
|
343
|
+
|
|
344
|
+
// Setup position attribute
|
|
345
|
+
const positionLocation = this.gl.getAttribLocation(this.program, 'position')
|
|
346
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer)
|
|
347
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW)
|
|
348
|
+
this.gl.enableVertexAttribArray(positionLocation)
|
|
349
|
+
this.gl.vertexAttribPointer(positionLocation, 2, this.gl.FLOAT, false, 0, 0)
|
|
350
|
+
|
|
351
|
+
// Setup color attribute
|
|
352
|
+
const colorLocation = this.gl.getAttribLocation(this.program, 'color')
|
|
353
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.colorBuffer)
|
|
354
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, colors, this.gl.STATIC_DRAW)
|
|
355
|
+
this.gl.enableVertexAttribArray(colorLocation)
|
|
356
|
+
this.gl.vertexAttribPointer(colorLocation, 4, this.gl.FLOAT, false, 0, 0)
|
|
357
|
+
|
|
358
|
+
// Enable blending for transparency
|
|
359
|
+
this.gl.enable(this.gl.BLEND)
|
|
360
|
+
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA)
|
|
361
|
+
|
|
362
|
+
// Disable depth test to render on top
|
|
363
|
+
this.gl.disable(this.gl.DEPTH_TEST)
|
|
364
|
+
|
|
365
|
+
this.gl.drawArrays(this.gl.TRIANGLES, 0, 6)
|
|
366
|
+
|
|
367
|
+
// Restore state
|
|
368
|
+
this.gl.enable(this.gl.DEPTH_TEST)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
dispose() {
|
|
372
|
+
this.gl.deleteBuffer(this.positionBuffer)
|
|
373
|
+
this.gl.deleteBuffer(this.colorBuffer)
|
|
374
|
+
this.gl.deleteProgram(this.program)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function buildCleanupDecorator (cleanupMethod: string) {
|
|
2
|
+
return function () {
|
|
3
|
+
return function (_target: { snapshotInitialValues }, propertyKey: string) {
|
|
4
|
+
const target = _target as any
|
|
5
|
+
// Store the initial value of the property
|
|
6
|
+
if (!target._snapshotMethodPatched) {
|
|
7
|
+
target.snapshotInitialValues = function () {
|
|
8
|
+
this._initialValues = {}
|
|
9
|
+
for (const key of target._toCleanup) {
|
|
10
|
+
this._initialValues[key] = this[key]
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
target._snapshotMethodPatched = true
|
|
14
|
+
}
|
|
15
|
+
(target._toCleanup ??= []).push(propertyKey)
|
|
16
|
+
if (!target._cleanupPatched) {
|
|
17
|
+
const originalMethod = target[cleanupMethod]
|
|
18
|
+
target[cleanupMethod] = function () {
|
|
19
|
+
for (const key of target._toCleanup) {
|
|
20
|
+
this[key] = this._initialValues[key]
|
|
21
|
+
}
|
|
22
|
+
// eslint-disable-next-line prefer-rest-params
|
|
23
|
+
Reflect.apply(originalMethod, this, arguments)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
target._cleanupPatched = true
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { PlayerObject, PlayerAnimation } from 'skinview3d'
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
import { WalkingGeneralSwing } from '../three/entity/animations'
|
|
4
|
+
import { loadSkinImage, stevePngUrl } from './utils/skins'
|
|
5
|
+
|
|
6
|
+
export type PlayerObjectType = PlayerObject & {
|
|
7
|
+
animation?: PlayerAnimation
|
|
8
|
+
realPlayerUuid: string
|
|
9
|
+
realUsername: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createPlayerObject (options: {
|
|
13
|
+
username?: string
|
|
14
|
+
uuid?: string
|
|
15
|
+
scale?: number
|
|
16
|
+
}): {
|
|
17
|
+
playerObject: PlayerObjectType
|
|
18
|
+
wrapper: THREE.Group
|
|
19
|
+
} {
|
|
20
|
+
const wrapper = new THREE.Group()
|
|
21
|
+
const playerObject = new PlayerObject() as PlayerObjectType
|
|
22
|
+
|
|
23
|
+
playerObject.realPlayerUuid = options.uuid ?? ''
|
|
24
|
+
playerObject.realUsername = options.username ?? ''
|
|
25
|
+
playerObject.position.set(0, 16, 0)
|
|
26
|
+
|
|
27
|
+
// fix issues with starfield
|
|
28
|
+
playerObject.traverse((obj) => {
|
|
29
|
+
if (obj instanceof THREE.Mesh && obj.material instanceof THREE.MeshStandardMaterial) {
|
|
30
|
+
obj.material.transparent = true
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
wrapper.add(playerObject as any)
|
|
35
|
+
const scale = options.scale ?? (1 / 16)
|
|
36
|
+
wrapper.scale.set(scale, scale, scale)
|
|
37
|
+
wrapper.rotation.set(0, Math.PI, 0)
|
|
38
|
+
|
|
39
|
+
// Set up animation
|
|
40
|
+
playerObject.animation = new WalkingGeneralSwing()
|
|
41
|
+
;(playerObject.animation as WalkingGeneralSwing).isMoving = false
|
|
42
|
+
playerObject.animation.update(playerObject, 0)
|
|
43
|
+
|
|
44
|
+
return { playerObject, wrapper }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const applySkinToPlayerObject = async (playerObject: PlayerObjectType, skinUrl: string) => {
|
|
48
|
+
return loadSkinImage(skinUrl || stevePngUrl).then(({ canvas }) => {
|
|
49
|
+
const skinTexture = new THREE.CanvasTexture(canvas)
|
|
50
|
+
skinTexture.magFilter = THREE.NearestFilter
|
|
51
|
+
skinTexture.minFilter = THREE.NearestFilter
|
|
52
|
+
skinTexture.needsUpdate = true
|
|
53
|
+
playerObject.skin.map = skinTexture as any
|
|
54
|
+
}).catch(console.error)
|
|
55
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame Timing Collector (Worker-side)
|
|
3
|
+
*
|
|
4
|
+
* Collects frame timing events and sends them to main thread via nonReactiveState
|
|
5
|
+
* This runs in the worker context and updates the shared state
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { NonReactiveState } from '@/graphicsBackend'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export interface FrameTimingEvent {
|
|
12
|
+
type: 'frameStart' | 'frameEnd' | 'cameraUpdate' | 'frameDisplay'
|
|
13
|
+
timestamp: number
|
|
14
|
+
duration?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class FrameTimingCollector {
|
|
18
|
+
private events: FrameTimingEvent[] = []
|
|
19
|
+
private frozenEvents: FrameTimingEvent[] = []
|
|
20
|
+
private lastSecondEvents: FrameTimingEvent[] = []
|
|
21
|
+
|
|
22
|
+
private currentFrameStartTime: number | null = null
|
|
23
|
+
private lastInteractionTime = 0
|
|
24
|
+
private lastSecondUpdateTime = 0
|
|
25
|
+
|
|
26
|
+
private readonly timeWindowMs = 5000 // Show last 5 seconds
|
|
27
|
+
private readonly lastSecondWindowMs = 1000 // Show last 1 second
|
|
28
|
+
private readonly maxEvents = 500 // Limit events to prevent memory issues
|
|
29
|
+
private readonly lastSecondUpdateInterval = 1000 // Update once per second
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
private nonReactiveState: NonReactiveState
|
|
33
|
+
) {
|
|
34
|
+
// Initialize timeline if not exists
|
|
35
|
+
if (!this.nonReactiveState.renderer) {
|
|
36
|
+
(this.nonReactiveState as any).renderer = { timeline: { live: [], frozen: [], lastSecond: [] } }
|
|
37
|
+
}
|
|
38
|
+
if (!this.nonReactiveState.renderer.timeline) {
|
|
39
|
+
this.nonReactiveState.renderer.timeline = { live: [], frozen: [], lastSecond: [] }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Start interval to update last second timeline
|
|
43
|
+
setInterval(() => {
|
|
44
|
+
this.updateLastSecondTimeline()
|
|
45
|
+
}, this.lastSecondUpdateInterval)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
markFrameStart() {
|
|
49
|
+
this.currentFrameStartTime = performance.now()
|
|
50
|
+
this.addEvent({
|
|
51
|
+
type: 'frameStart',
|
|
52
|
+
timestamp: this.currentFrameStartTime
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
markFrameEnd() {
|
|
57
|
+
if (!this.currentFrameStartTime) return
|
|
58
|
+
|
|
59
|
+
const now = performance.now()
|
|
60
|
+
const duration = now - this.currentFrameStartTime
|
|
61
|
+
|
|
62
|
+
this.addEvent({
|
|
63
|
+
type: 'frameEnd',
|
|
64
|
+
timestamp: now,
|
|
65
|
+
duration
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
this.currentFrameStartTime = null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
markCameraUpdate(posIsFalsey: boolean) {
|
|
72
|
+
if (!posIsFalsey) return
|
|
73
|
+
|
|
74
|
+
const now = performance.now()
|
|
75
|
+
this.lastInteractionTime = now
|
|
76
|
+
|
|
77
|
+
this.addEvent({
|
|
78
|
+
type: 'cameraUpdate',
|
|
79
|
+
timestamp: now
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Update frozen timeline when interaction happens
|
|
83
|
+
this.frozenEvents = [...this.events]
|
|
84
|
+
this.trimEvents(this.frozenEvents)
|
|
85
|
+
this.syncFrozenEvents()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
markFrameDisplay() {
|
|
89
|
+
this.addEvent({
|
|
90
|
+
type: 'frameDisplay',
|
|
91
|
+
timestamp: performance.now()
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private addEvent(event: FrameTimingEvent) {
|
|
96
|
+
this.events.push(event)
|
|
97
|
+
this.lastSecondEvents.push(event)
|
|
98
|
+
this.trimEvents(this.events)
|
|
99
|
+
this.trimLastSecondEvents()
|
|
100
|
+
this.syncLiveEvents()
|
|
101
|
+
// Note: lastSecond syncs separately via interval
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private trimEvents(events: FrameTimingEvent[]) {
|
|
105
|
+
const now = performance.now()
|
|
106
|
+
const cutoff = now - this.timeWindowMs
|
|
107
|
+
|
|
108
|
+
// Remove old events
|
|
109
|
+
while (events.length > 0 && events[0].timestamp < cutoff) {
|
|
110
|
+
events.shift()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Limit total events to prevent memory issues
|
|
114
|
+
if (events.length > this.maxEvents) {
|
|
115
|
+
events.splice(0, events.length - this.maxEvents)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private trimLastSecondEvents() {
|
|
120
|
+
const now = performance.now()
|
|
121
|
+
const cutoff = now - this.lastSecondWindowMs
|
|
122
|
+
|
|
123
|
+
// Remove old events
|
|
124
|
+
while (this.lastSecondEvents.length > 0 && this.lastSecondEvents[0].timestamp < cutoff) {
|
|
125
|
+
this.lastSecondEvents.shift()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private updateLastSecondTimeline() {
|
|
130
|
+
const now = performance.now()
|
|
131
|
+
|
|
132
|
+
// Only update if at least 1 second has passed
|
|
133
|
+
if (now - this.lastSecondUpdateTime < this.lastSecondUpdateInterval) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.lastSecondUpdateTime = now
|
|
138
|
+
this.trimLastSecondEvents()
|
|
139
|
+
this.syncLastSecondEvents()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private syncLiveEvents() {
|
|
143
|
+
// Update nonReactiveState with current events
|
|
144
|
+
// This will sync to main thread via workerProxy
|
|
145
|
+
this.nonReactiveState.renderer.timeline.live = [...this.events]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private syncFrozenEvents() {
|
|
149
|
+
this.nonReactiveState.renderer.timeline.frozen = [...this.frozenEvents]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private syncLastSecondEvents() {
|
|
153
|
+
this.nonReactiveState.renderer.timeline.lastSecond = [...this.lastSecondEvents]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
clear() {
|
|
157
|
+
this.events = []
|
|
158
|
+
this.frozenEvents = []
|
|
159
|
+
this.lastSecondEvents = []
|
|
160
|
+
this.syncLiveEvents()
|
|
161
|
+
this.syncFrozenEvents()
|
|
162
|
+
this.syncLastSecondEvents()
|
|
163
|
+
}
|
|
164
|
+
}
|