minecraft-renderer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +297 -0
  2. package/dist/index.html +83 -0
  3. package/dist/static/image/arrow.6f27b59f.png +0 -0
  4. package/dist/static/image/blocksAtlasLatest.7850afa3.png +0 -0
  5. package/dist/static/image/blocksAtlasLegacy.5c76823d.png +0 -0
  6. package/dist/static/image/itemsAtlasLatest.36036f95.png +0 -0
  7. package/dist/static/image/itemsAtlasLegacy.dcb1b58d.png +0 -0
  8. package/dist/static/image/tipped_arrow.6f27b59f.png +0 -0
  9. package/dist/static/js/365.f05233ab.js +8462 -0
  10. package/dist/static/js/365.f05233ab.js.LICENSE.txt +52 -0
  11. package/dist/static/js/async/738.efa27644.js +1 -0
  12. package/dist/static/js/index.092ec5be.js +56 -0
  13. package/dist/static/js/lib-polyfill.98986ac5.js +1 -0
  14. package/dist/static/js/lib-react.5c9129e0.js +2 -0
  15. package/dist/static/js/lib-react.5c9129e0.js.LICENSE.txt +39 -0
  16. package/package.json +104 -0
  17. package/src/assets/destroy_stage_0.png +0 -0
  18. package/src/assets/destroy_stage_1.png +0 -0
  19. package/src/assets/destroy_stage_2.png +0 -0
  20. package/src/assets/destroy_stage_3.png +0 -0
  21. package/src/assets/destroy_stage_4.png +0 -0
  22. package/src/assets/destroy_stage_5.png +0 -0
  23. package/src/assets/destroy_stage_6.png +0 -0
  24. package/src/assets/destroy_stage_7.png +0 -0
  25. package/src/assets/destroy_stage_8.png +0 -0
  26. package/src/assets/destroy_stage_9.png +0 -0
  27. package/src/examples/README.md +146 -0
  28. package/src/examples/appViewerExample.ts +205 -0
  29. package/src/examples/initialMenuStart.ts +161 -0
  30. package/src/graphicsBackend/appViewer.ts +297 -0
  31. package/src/graphicsBackend/config.ts +119 -0
  32. package/src/graphicsBackend/index.ts +10 -0
  33. package/src/graphicsBackend/playerState.ts +61 -0
  34. package/src/graphicsBackend/types.ts +143 -0
  35. package/src/index.ts +97 -0
  36. package/src/lib/DebugGui.ts +190 -0
  37. package/src/lib/animationController.ts +85 -0
  38. package/src/lib/buildSharedConfig.mjs +1 -0
  39. package/src/lib/cameraBobbing.ts +94 -0
  40. package/src/lib/canvas2DOverlay.example.ts +361 -0
  41. package/src/lib/canvas2DOverlay.quickstart.ts +242 -0
  42. package/src/lib/canvas2DOverlay.ts +381 -0
  43. package/src/lib/cleanupDecorator.ts +29 -0
  44. package/src/lib/createPlayerObject.ts +55 -0
  45. package/src/lib/frameTimingCollector.ts +164 -0
  46. package/src/lib/guiRenderer.ts +283 -0
  47. package/src/lib/items.ts +140 -0
  48. package/src/lib/mesherlogReader.ts +131 -0
  49. package/src/lib/moreBlockDataGenerated.json +714 -0
  50. package/src/lib/preflatMap.json +1741 -0
  51. package/src/lib/simpleUtils.ts +40 -0
  52. package/src/lib/smoothSwitcher.ts +168 -0
  53. package/src/lib/spiral.ts +29 -0
  54. package/src/lib/ui/newStats.ts +120 -0
  55. package/src/lib/utils/proxy.ts +23 -0
  56. package/src/lib/utils/skins.ts +63 -0
  57. package/src/lib/utils.ts +76 -0
  58. package/src/lib/workerProxy.ts +342 -0
  59. package/src/lib/worldrendererCommon.ts +1088 -0
  60. package/src/mesher/mesher.ts +253 -0
  61. package/src/mesher/models.ts +769 -0
  62. package/src/mesher/modelsGeometryCommon.ts +142 -0
  63. package/src/mesher/shared.ts +80 -0
  64. package/src/mesher/standaloneRenderer.ts +270 -0
  65. package/src/mesher/test/a.ts +3 -0
  66. package/src/mesher/test/mesherTester.ts +76 -0
  67. package/src/mesher/test/playground.ts +19 -0
  68. package/src/mesher/test/test-perf.ts +74 -0
  69. package/src/mesher/test/tests.test.ts +56 -0
  70. package/src/mesher/world.ts +294 -0
  71. package/src/mesher/worldConstants.ts +1 -0
  72. package/src/modules/index.ts +11 -0
  73. package/src/modules/starfield.ts +313 -0
  74. package/src/modules/types.ts +110 -0
  75. package/src/playerState/playerState.ts +78 -0
  76. package/src/playerState/types.ts +36 -0
  77. package/src/playground/allEntitiesDebug.ts +170 -0
  78. package/src/playground/baseScene.ts +587 -0
  79. package/src/playground/mobileControls.tsx +268 -0
  80. package/src/playground/playground.html +83 -0
  81. package/src/playground/playground.ts +11 -0
  82. package/src/playground/playgroundUi.tsx +140 -0
  83. package/src/playground/reactUtils.ts +71 -0
  84. package/src/playground/scenes/allEntities.ts +13 -0
  85. package/src/playground/scenes/entities.ts +37 -0
  86. package/src/playground/scenes/floorRandom.ts +33 -0
  87. package/src/playground/scenes/frequentUpdates.ts +148 -0
  88. package/src/playground/scenes/geometryExport.ts +142 -0
  89. package/src/playground/scenes/index.ts +12 -0
  90. package/src/playground/scenes/lightingStarfield.ts +40 -0
  91. package/src/playground/scenes/main.ts +313 -0
  92. package/src/playground/scenes/railsCobweb.ts +14 -0
  93. package/src/playground/scenes/rotationIssue.ts +7 -0
  94. package/src/playground/scenes/slabsOptimization.ts +16 -0
  95. package/src/playground/scenes/transparencyIssue.ts +11 -0
  96. package/src/playground/shared.ts +79 -0
  97. package/src/resourcesManager/index.ts +5 -0
  98. package/src/resourcesManager/resourcesManager.ts +314 -0
  99. package/src/shims/minecraftData.ts +41 -0
  100. package/src/sign-renderer/index.html +21 -0
  101. package/src/sign-renderer/index.ts +216 -0
  102. package/src/sign-renderer/noop.js +1 -0
  103. package/src/sign-renderer/playground.ts +38 -0
  104. package/src/sign-renderer/tests.test.ts +69 -0
  105. package/src/sign-renderer/vite.config.ts +10 -0
  106. package/src/three/appShared.ts +75 -0
  107. package/src/three/bannerRenderer.ts +275 -0
  108. package/src/three/cameraShake.ts +120 -0
  109. package/src/three/cinimaticScript.ts +350 -0
  110. package/src/three/documentRenderer.ts +491 -0
  111. package/src/three/entities.ts +1580 -0
  112. package/src/three/entity/EntityMesh.ts +707 -0
  113. package/src/three/entity/animations.js +171 -0
  114. package/src/three/entity/armorModels.json +204 -0
  115. package/src/three/entity/armorModels.ts +36 -0
  116. package/src/three/entity/entities.json +6230 -0
  117. package/src/three/entity/exportedModels.js +38 -0
  118. package/src/three/entity/externalTextures.json +1 -0
  119. package/src/three/entity/models/allay.obj +325 -0
  120. package/src/three/entity/models/arrow.obj +60 -0
  121. package/src/three/entity/models/axolotl.obj +509 -0
  122. package/src/three/entity/models/blaze.obj +601 -0
  123. package/src/three/entity/models/boat.obj +417 -0
  124. package/src/three/entity/models/camel.obj +1061 -0
  125. package/src/three/entity/models/cat.obj +509 -0
  126. package/src/three/entity/models/chicken.obj +371 -0
  127. package/src/three/entity/models/cod.obj +371 -0
  128. package/src/three/entity/models/creeper.obj +279 -0
  129. package/src/three/entity/models/dolphin.obj +371 -0
  130. package/src/three/entity/models/ender_dragon.obj +2993 -0
  131. package/src/three/entity/models/enderman.obj +325 -0
  132. package/src/three/entity/models/endermite.obj +187 -0
  133. package/src/three/entity/models/fox.obj +463 -0
  134. package/src/three/entity/models/frog.obj +739 -0
  135. package/src/three/entity/models/ghast.obj +463 -0
  136. package/src/three/entity/models/goat.obj +601 -0
  137. package/src/three/entity/models/guardian.obj +1015 -0
  138. package/src/three/entity/models/horse.obj +1061 -0
  139. package/src/three/entity/models/llama.obj +509 -0
  140. package/src/three/entity/models/minecart.obj +233 -0
  141. package/src/three/entity/models/parrot.obj +509 -0
  142. package/src/three/entity/models/piglin.obj +739 -0
  143. package/src/three/entity/models/pillager.obj +371 -0
  144. package/src/three/entity/models/rabbit.obj +555 -0
  145. package/src/three/entity/models/sheep.obj +555 -0
  146. package/src/three/entity/models/shulker.obj +141 -0
  147. package/src/three/entity/models/sniffer.obj +693 -0
  148. package/src/three/entity/models/spider.obj +509 -0
  149. package/src/three/entity/models/tadpole.obj +95 -0
  150. package/src/three/entity/models/turtle.obj +371 -0
  151. package/src/three/entity/models/vex.obj +325 -0
  152. package/src/three/entity/models/villager.obj +509 -0
  153. package/src/three/entity/models/warden.obj +463 -0
  154. package/src/three/entity/models/witch.obj +647 -0
  155. package/src/three/entity/models/wolf.obj +509 -0
  156. package/src/three/entity/models/zombie_villager.obj +463 -0
  157. package/src/three/entity/objModels.js +1 -0
  158. package/src/three/fireworks.ts +661 -0
  159. package/src/three/fireworksRenderer.ts +434 -0
  160. package/src/three/globals.d.ts +7 -0
  161. package/src/three/graphicsBackend.ts +274 -0
  162. package/src/three/graphicsBackendOffThread.ts +107 -0
  163. package/src/three/hand.ts +89 -0
  164. package/src/three/holdingBlock.ts +926 -0
  165. package/src/three/index.ts +20 -0
  166. package/src/three/itemMesh.ts +427 -0
  167. package/src/three/modules.d.ts +14 -0
  168. package/src/three/panorama.ts +308 -0
  169. package/src/three/panoramaShared.ts +1 -0
  170. package/src/three/renderSlot.ts +82 -0
  171. package/src/three/skyboxRenderer.ts +406 -0
  172. package/src/three/starField.ts +13 -0
  173. package/src/three/threeJsMedia.ts +731 -0
  174. package/src/three/threeJsMethods.ts +15 -0
  175. package/src/three/threeJsParticles.ts +160 -0
  176. package/src/three/threeJsSound.ts +95 -0
  177. package/src/three/threeJsUtils.ts +90 -0
  178. package/src/three/waypointSprite.ts +435 -0
  179. package/src/three/waypoints.ts +163 -0
  180. package/src/three/world/cursorBlock.ts +172 -0
  181. package/src/three/world/vr.ts +257 -0
  182. package/src/three/worldGeometryExport.ts +259 -0
  183. package/src/three/worldGeometryHandler.ts +279 -0
  184. package/src/three/worldRendererThree.ts +1381 -0
  185. package/src/worldView/index.ts +6 -0
  186. package/src/worldView/types.ts +66 -0
  187. package/src/worldView/worldView.ts +424 -0
@@ -0,0 +1,707 @@
1
+ import * as THREE from 'three'
2
+ import { OBJLoader } from 'three-stdlib'
3
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
4
+ import huskPng from 'mc-assets/dist/other-textures/latest/entity/zombie/husk.png'
5
+ import { Vec3 } from 'vec3'
6
+ import ocelotPng from 'mc-assets/dist/other-textures/latest/entity/cat/ocelot.png'
7
+ import arrowTexture from 'mc-assets/dist/other-textures/1.21.2/entity/projectiles/arrow.png'
8
+ import spectralArrowTexture from 'mc-assets/dist/other-textures/1.21.2/entity/projectiles/spectral_arrow.png'
9
+ import tippedArrowTexture from 'mc-assets/dist/other-textures/1.21.2/entity/projectiles/tipped_arrow.png'
10
+ import { loadTexture } from '../threeJsUtils'
11
+ import { WorldRendererThree } from '../worldRendererThree'
12
+ import entities from './entities.json'
13
+ import { externalModels } from './objModels'
14
+ import externalTexturesJson from './externalTextures.json'
15
+
16
+ interface ElemFace {
17
+ dir: [number, number, number]
18
+ u0: [number, number, number]
19
+ v0: [number, number, number]
20
+ u1: [number, number, number]
21
+ v1: [number, number, number]
22
+ corners: Array<[number, number, number, number, number]>
23
+ }
24
+
25
+ interface GeoData {
26
+ positions: number[]
27
+ normals: number[]
28
+ uvs: number[]
29
+ indices: number[]
30
+ skinIndices: number[]
31
+ skinWeights: number[]
32
+ }
33
+
34
+ interface JsonBone {
35
+ name: string
36
+ pivot?: [number, number, number]
37
+ bind_pose_rotation?: [number, number, number]
38
+ rotation?: [number, number, number]
39
+ parent?: string
40
+ cubes?: JsonCube[]
41
+ mirror?: boolean
42
+ }
43
+
44
+ interface JsonCube {
45
+ origin: [number, number, number]
46
+ size: [number, number, number]
47
+ uv: [number, number]
48
+ inflate?: number
49
+ rotation?: [number, number, number]
50
+ }
51
+
52
+ interface JsonModel {
53
+ texturewidth?: number
54
+ textureheight?: number
55
+ bones: JsonBone[]
56
+ }
57
+
58
+ interface EntityOverrides {
59
+ textures?: Record<string, string>
60
+ rotation?: Record<string, { x?: number; y?: number; z?: number }>
61
+ customModel?: {
62
+ modelPath: string | ArrayBuffer
63
+ modelType: 'obj' | 'bedrock' | 'gltf'
64
+ metadata?: {
65
+ scale?: number
66
+ offset?: { x?: number, y?: number, z?: number }
67
+ texture?: string
68
+ textures?: Record<string, string>
69
+ }
70
+ }
71
+ }
72
+
73
+ const elemFaces: Record<string, ElemFace> = {
74
+ up: {
75
+ dir: [0, 1, 0],
76
+ u0: [0, 0, 1],
77
+ v0: [0, 0, 0],
78
+ u1: [1, 0, 1],
79
+ v1: [0, 0, 1],
80
+ corners: [
81
+ [0, 1, 1, 0, 0],
82
+ [1, 1, 1, 1, 0],
83
+ [0, 1, 0, 0, 1],
84
+ [1, 1, 0, 1, 1]
85
+ ]
86
+ },
87
+ down: {
88
+ dir: [0, -1, 0],
89
+ u0: [1, 0, 1],
90
+ v0: [0, 0, 0],
91
+ u1: [2, 0, 1],
92
+ v1: [0, 0, 1],
93
+ corners: [
94
+ [1, 0, 1, 0, 0],
95
+ [0, 0, 1, 1, 0],
96
+ [1, 0, 0, 0, 1],
97
+ [0, 0, 0, 1, 1]
98
+ ]
99
+ },
100
+ east: {
101
+ dir: [1, 0, 0],
102
+ u0: [0, 0, 0],
103
+ v0: [0, 0, 1],
104
+ u1: [0, 0, 1],
105
+ v1: [0, 1, 1],
106
+ corners: [
107
+ [1, 1, 1, 0, 0],
108
+ [1, 0, 1, 0, 1],
109
+ [1, 1, 0, 1, 0],
110
+ [1, 0, 0, 1, 1]
111
+ ]
112
+ },
113
+ west: {
114
+ dir: [-1, 0, 0],
115
+ u0: [1, 0, 1],
116
+ v0: [0, 0, 1],
117
+ u1: [1, 0, 2],
118
+ v1: [0, 1, 1],
119
+ corners: [
120
+ [0, 1, 0, 0, 0],
121
+ [0, 0, 0, 0, 1],
122
+ [0, 1, 1, 1, 0],
123
+ [0, 0, 1, 1, 1]
124
+ ]
125
+ },
126
+ north: {
127
+ dir: [0, 0, -1],
128
+ u0: [0, 0, 1],
129
+ v0: [0, 0, 1],
130
+ u1: [1, 0, 1],
131
+ v1: [0, 1, 1],
132
+ corners: [
133
+ [1, 0, 0, 0, 1],
134
+ [0, 0, 0, 1, 1],
135
+ [1, 1, 0, 0, 0],
136
+ [0, 1, 0, 1, 0]
137
+ ]
138
+ },
139
+ south: {
140
+ dir: [0, 0, 1],
141
+ u0: [1, 0, 2],
142
+ v0: [0, 0, 1],
143
+ u1: [2, 0, 2],
144
+ v1: [0, 1, 1],
145
+ corners: [
146
+ [0, 0, 1, 0, 1],
147
+ [1, 0, 1, 1, 1],
148
+ [0, 1, 1, 0, 0],
149
+ [1, 1, 1, 1, 0]
150
+ ]
151
+ }
152
+ }
153
+
154
+ function dot(a: number[], b: number[]): number {
155
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
156
+ }
157
+
158
+ function addCube(
159
+ attr: GeoData,
160
+ boneId: number,
161
+ bone: THREE.Bone,
162
+ cube: JsonCube,
163
+ sameTextureForAllFaces = false,
164
+ texWidth = 64,
165
+ texHeight = 64,
166
+ mirror = false,
167
+ errors: string[] = []
168
+ ): void {
169
+ const cubeRotation = new THREE.Euler(0, 0, 0)
170
+ if (cube.rotation) {
171
+ cubeRotation.x = -cube.rotation[0] * Math.PI / 180
172
+ cubeRotation.y = -cube.rotation[1] * Math.PI / 180
173
+ cubeRotation.z = -cube.rotation[2] * Math.PI / 180
174
+ }
175
+ for (const { dir, corners, u0, v0, u1, v1 } of Object.values(elemFaces)) {
176
+ const ndx = Math.floor(attr.positions.length / 3)
177
+
178
+ const eastOrWest = dir[0] !== 0
179
+ const faceUvs: number[] = []
180
+ for (const pos of corners) {
181
+ let u: number
182
+ let v: number
183
+ if (sameTextureForAllFaces) {
184
+ u = (cube.uv[0] + pos[3] * cube.size[0]) / texWidth
185
+ v = (cube.uv[1] + pos[4] * cube.size[1]) / texHeight
186
+ } else {
187
+ u = (cube.uv[0] + dot(pos[3] ? u1 : u0, cube.size)) / texWidth
188
+ v = (cube.uv[1] + dot(pos[4] ? v1 : v0, cube.size)) / texHeight
189
+ }
190
+ // if (isNaN(u) || isNaN(v)) {
191
+ // errors.push(`NaN u: ${u}, v: ${v}`)
192
+ // continue
193
+ // }
194
+ // if (u < 0 || u > 1 || v < 0 || v > 1) {
195
+ // errors.push(`u: ${u}, v: ${v} out of range`)
196
+ // continue
197
+ // }
198
+
199
+ const posX = eastOrWest && mirror ? pos[0] ^ 1 : pos[0]
200
+ const posY = pos[1]
201
+ const posZ = eastOrWest && mirror ? pos[2] ^ 1 : pos[2]
202
+ const inflate = cube.inflate ?? 0
203
+ let vecPos = new THREE.Vector3(
204
+ cube.origin[0] + posX * cube.size[0] + (posX ? inflate : -inflate),
205
+ cube.origin[1] + posY * cube.size[1] + (posY ? inflate : -inflate),
206
+ cube.origin[2] + posZ * cube.size[2] + (posZ ? inflate : -inflate)
207
+ )
208
+
209
+ vecPos = vecPos.applyEuler(cubeRotation)
210
+ vecPos = vecPos.sub(bone.position)
211
+ vecPos = vecPos.applyEuler(bone.rotation)
212
+ vecPos = vecPos.add(bone.position)
213
+
214
+ attr.positions.push(vecPos.x, vecPos.y, vecPos.z)
215
+ attr.normals.push(dir[0], dir[1], dir[2])
216
+ faceUvs.push(u, v)
217
+ attr.skinIndices.push(boneId, 0, 0, 0)
218
+ attr.skinWeights.push(1, 0, 0, 0)
219
+ }
220
+
221
+ if (mirror) {
222
+ for (let i = 0; i + 1 < corners.length; i += 2) {
223
+ const faceIndex = i * 2
224
+ const tempFaceUvs = faceUvs.slice(faceIndex, faceIndex + 4)
225
+ faceUvs[faceIndex] = tempFaceUvs[2]
226
+ faceUvs[faceIndex + 1] = tempFaceUvs[eastOrWest ? 1 : 3]
227
+ faceUvs[faceIndex + 2] = tempFaceUvs[0]
228
+ faceUvs[faceIndex + 3] = tempFaceUvs[eastOrWest ? 3 : 1]
229
+ }
230
+ }
231
+ attr.uvs.push(...faceUvs)
232
+
233
+ attr.indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3)
234
+ }
235
+ }
236
+
237
+ export function getMesh(
238
+ worldRenderer: WorldRendererThree | undefined,
239
+ texture: string,
240
+ jsonModel: JsonModel,
241
+ overrides: EntityOverrides = {},
242
+ debugFlags: EntityDebugFlags = {}
243
+ ): THREE.SkinnedMesh {
244
+ const textureWidth = jsonModel.texturewidth ?? 64
245
+ const textureHeight = jsonModel.textureheight ?? 64
246
+ let textureOffset: number[] | undefined
247
+ const useBlockTexture = texture.startsWith('block:')
248
+ const blocksTexture = worldRenderer?.material.map
249
+ if (useBlockTexture) {
250
+ // if (!worldRenderer) throw new Error('worldRenderer is required for block textures')
251
+ // const blockName = texture.slice(6)
252
+ // const textureInfo = worldRenderer.resourcesManager.currentResources.blocksAtlasJson.textures[blockName]
253
+ // if (textureInfo) {
254
+ // textureWidth = blocksTexture?.image.width ?? textureWidth
255
+ // textureHeight = blocksTexture?.image.height ?? textureHeight
256
+ // // todo support su/sv
257
+ // textureOffset = [textureInfo.u, textureInfo.v]
258
+ // } else {
259
+ // console.error(`Unknown block ${blockName}`)
260
+ // }
261
+ }
262
+
263
+ const bones: Record<string, THREE.Bone> = {}
264
+
265
+ const geoData: GeoData = {
266
+ positions: [],
267
+ normals: [],
268
+ uvs: [],
269
+ indices: [],
270
+ skinIndices: [],
271
+ skinWeights: []
272
+ }
273
+ let i = 0
274
+ for (const jsonBone of jsonModel.bones) {
275
+ const bone = new THREE.Bone()
276
+ if (jsonBone.pivot) {
277
+ bone.position.x = jsonBone.pivot[0]
278
+ bone.position.y = jsonBone.pivot[1]
279
+ bone.position.z = jsonBone.pivot[2]
280
+ }
281
+ if (jsonBone.bind_pose_rotation) {
282
+ bone.rotation.x = -jsonBone.bind_pose_rotation[0] * Math.PI / 180
283
+ bone.rotation.y = -jsonBone.bind_pose_rotation[1] * Math.PI / 180
284
+ bone.rotation.z = -jsonBone.bind_pose_rotation[2] * Math.PI / 180
285
+ } else if (jsonBone.rotation) {
286
+ bone.rotation.x = -jsonBone.rotation[0] * Math.PI / 180
287
+ bone.rotation.y = -jsonBone.rotation[1] * Math.PI / 180
288
+ bone.rotation.z = -jsonBone.rotation[2] * Math.PI / 180
289
+ }
290
+ if (overrides.rotation?.[jsonBone.name]) {
291
+ bone.rotation.x -= (overrides.rotation[jsonBone.name].x ?? 0) * Math.PI / 180
292
+ bone.rotation.y -= (overrides.rotation[jsonBone.name].y ?? 0) * Math.PI / 180
293
+ bone.rotation.z -= (overrides.rotation[jsonBone.name].z ?? 0) * Math.PI / 180
294
+ }
295
+ bone.name = `bone_${jsonBone.name}`
296
+ bones[jsonBone.name] = bone
297
+
298
+ if (jsonBone.cubes) {
299
+ for (const cube of jsonBone.cubes) {
300
+ const errors: string[] = []
301
+ addCube(geoData, i, bone, cube, useBlockTexture, textureWidth, textureHeight, jsonBone.mirror, errors)
302
+ if (errors.length) {
303
+ debugFlags.errors ??= []
304
+ debugFlags.errors.push(...errors.map(error => `Bone ${jsonBone.name}: ${error}`))
305
+ }
306
+ }
307
+ }
308
+ i++
309
+ }
310
+
311
+ const rootBones: THREE.Object3D[] = []
312
+ for (const jsonBone of jsonModel.bones) {
313
+ if (jsonBone.parent && bones[jsonBone.parent]) {
314
+ bones[jsonBone.parent].add(bones[jsonBone.name])
315
+ } else {
316
+ rootBones.push(bones[jsonBone.name])
317
+ }
318
+ }
319
+
320
+ const skeleton = new THREE.Skeleton(Object.values(bones))
321
+
322
+ const geometry = new THREE.BufferGeometry()
323
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(geoData.positions, 3))
324
+ geometry.setAttribute('normal', new THREE.Float32BufferAttribute(geoData.normals, 3))
325
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(geoData.uvs, 2))
326
+ geometry.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(geoData.skinIndices, 4))
327
+ geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(geoData.skinWeights, 4))
328
+ geometry.setIndex(geoData.indices)
329
+
330
+ const material = new THREE.MeshLambertMaterial({ transparent: true, alphaTest: 0.1 })
331
+ const mesh = new THREE.SkinnedMesh(geometry, material)
332
+ mesh.add(...rootBones)
333
+ mesh.bind(skeleton)
334
+ mesh.scale.set(1 / 16, 1 / 16, 1 / 16)
335
+
336
+ if (textureOffset) {
337
+ // todo(memory) dont clone
338
+ const loadedTexture = blocksTexture!.clone()
339
+ loadedTexture.offset.set(textureOffset[0], textureOffset[1])
340
+ loadedTexture.needsUpdate = true
341
+ material.map = loadedTexture
342
+ } else {
343
+ void loadTexture(texture, loadedTexture => {
344
+ if (material.map) {
345
+ // texture is already loaded
346
+ return
347
+ }
348
+ loadedTexture.magFilter = THREE.NearestFilter
349
+ loadedTexture.minFilter = THREE.NearestFilter
350
+ loadedTexture.flipY = false
351
+ loadedTexture.wrapS = THREE.RepeatWrapping
352
+ loadedTexture.wrapT = THREE.RepeatWrapping
353
+ material.map = loadedTexture
354
+ }, () => {
355
+ // This callback runs after the texture is fully loaded
356
+ const actualWidth = material.map!.image.width
357
+ if (actualWidth && textureWidth !== actualWidth) {
358
+ material.map!.repeat.x = textureWidth / actualWidth
359
+ }
360
+ const actualHeight = material.map!.image.height
361
+ if (actualHeight && textureHeight !== actualHeight) {
362
+ material.map!.repeat.y = textureHeight / actualHeight
363
+ }
364
+ material.needsUpdate = true
365
+ })
366
+ }
367
+
368
+ return mesh
369
+ }
370
+
371
+ export const rendererSpecialHandled = ['item_frame', 'item', 'player']
372
+
373
+ type EntityMapping = {
374
+ pattern: string | RegExp
375
+ target: string
376
+ }
377
+
378
+ const temporaryMappings: EntityMapping[] = [
379
+ // Exact matches
380
+ { pattern: 'furnace_minecart', target: 'minecart' },
381
+ { pattern: 'spawner_minecart', target: 'minecart' },
382
+ { pattern: 'chest_minecart', target: 'minecart' },
383
+ { pattern: 'hopper_minecart', target: 'minecart' },
384
+ { pattern: 'command_block_minecart', target: 'minecart' },
385
+ { pattern: 'tnt_minecart', target: 'minecart' },
386
+ { pattern: 'glow_item_frame', target: 'item_frame' },
387
+ { pattern: 'glow_squid', target: 'squid' },
388
+ { pattern: 'trader_llama', target: 'llama' },
389
+ { pattern: 'chest_boat', target: 'boat' },
390
+ { pattern: 'spectral_arrow', target: 'arrow' },
391
+ { pattern: 'husk', target: 'zombie' },
392
+ { pattern: 'zombie_horse', target: 'horse' },
393
+ { pattern: 'donkey', target: 'horse' },
394
+ { pattern: 'skeleton_horse', target: 'horse' },
395
+ { pattern: 'mule', target: 'horse' },
396
+ { pattern: 'ocelot', target: 'cat' },
397
+ // Regex patterns
398
+ { pattern: /_minecraft$/, target: 'minecraft' },
399
+ { pattern: /_boat$/, target: 'boat' },
400
+ { pattern: /_raft$/, target: 'boat' },
401
+ { pattern: /_horse$/, target: 'horse' },
402
+ { pattern: /_zombie$/, target: 'zombie' },
403
+ { pattern: /_arrow$/, target: 'zombie' },
404
+ ]
405
+
406
+ function getEntityMapping(type: string): string | undefined {
407
+ for (const mapping of temporaryMappings) {
408
+ if (typeof mapping.pattern === 'string') {
409
+ if (mapping.pattern === type) return mapping.target
410
+ } else if (mapping.pattern.test(type)) { return mapping.target }
411
+ }
412
+ return undefined
413
+ }
414
+
415
+ const getEntity = (name: string) => {
416
+ return entities[name]
417
+ }
418
+
419
+ const scaleEntity: Record<string, number> = {
420
+ zombie: 1.85,
421
+ husk: 1.85,
422
+ arrow: 0.0025
423
+ }
424
+
425
+ const offsetEntity: Record<string, Vec3> = {
426
+ zombie: new Vec3(0, 1, 0),
427
+ husk: new Vec3(0, 1, 0),
428
+ boat: new Vec3(0, -1, 0),
429
+ arrow: new Vec3(0, -0.9, 0)
430
+ }
431
+
432
+ interface EntityGeometry {
433
+ geometry: Array<{
434
+ name: string;
435
+ [key: string]: any;
436
+ }>;
437
+ }
438
+
439
+ export type EntityModelType = 'obj' | 'bedrock' | 'gltf'
440
+
441
+ export type EntityDebugFlags = {
442
+ type?: 'obj' | 'bedrock' | 'special'
443
+ tempMap?: string
444
+ textureMap?: boolean
445
+ errors?: string[]
446
+ isHardcodedTexture?: boolean
447
+ }
448
+
449
+ export class EntityMesh {
450
+ mesh!: THREE.Object3D
451
+ animations?: THREE.AnimationClip[]
452
+ mixer?: THREE.AnimationMixer
453
+
454
+ constructor(
455
+ version: string,
456
+ type: string,
457
+ worldRenderer?: WorldRendererThree,
458
+ overrides: EntityOverrides = {},
459
+ debugFlags: EntityDebugFlags = {}
460
+ ) {
461
+ const originalType = type
462
+ const mappedValue = getEntityMapping(type)
463
+ if (mappedValue) {
464
+ type = mappedValue
465
+ debugFlags.tempMap = mappedValue
466
+ }
467
+
468
+ // Handle custom model override
469
+ if (overrides.customModel) {
470
+ const { modelPath, modelType, metadata } = overrides.customModel
471
+
472
+ switch (modelType) {
473
+ case 'gltf': {
474
+ const loader = new GLTFLoader()
475
+ const gltfData = loader.parseAsync(modelPath as ArrayBuffer, '')
476
+
477
+ gltfData.then(gltf => {
478
+ this.mesh = gltf.scene
479
+ this.animations = gltf.animations
480
+ this.mixer = new THREE.AnimationMixer(this.mesh)
481
+
482
+ // Apply metadata overrides if available
483
+ if (metadata?.scale) {
484
+ const { scale } = metadata
485
+ this.mesh.scale.set(scale, scale, scale)
486
+ }
487
+ if (metadata?.offset) {
488
+ const { x = 0, y = 0, z = 0 } = metadata.offset
489
+ this.mesh.position.set(x, y, z)
490
+ }
491
+
492
+ // Apply texture if provided
493
+ if (metadata?.texture) {
494
+ const texture = new THREE.TextureLoader().load(metadata.texture)
495
+ texture.minFilter = THREE.NearestFilter
496
+ texture.magFilter = THREE.NearestFilter
497
+ this.mesh.traverse((child) => {
498
+ if (child instanceof THREE.Mesh) {
499
+ child.material = new THREE.MeshBasicMaterial({
500
+ map: texture,
501
+ transparent: true,
502
+ alphaTest: 0.1
503
+ })
504
+ }
505
+ })
506
+ }
507
+ }).catch(err => {
508
+ console.error('Failed to load GLTF model:', err)
509
+ })
510
+
511
+ // debugFlags.type = 'gltf'
512
+ return
513
+ }
514
+ case 'obj': {
515
+ const objLoader = new OBJLoader()
516
+ const obj = objLoader.parse(modelPath as string)
517
+
518
+ // Apply metadata overrides if available
519
+ if (metadata?.scale) {
520
+ const { scale } = metadata
521
+ obj.scale.set(scale, scale, scale)
522
+ }
523
+ if (metadata?.offset) {
524
+ const { x = 0, y = 0, z = 0 } = metadata.offset
525
+ obj.position.set(x, y, z)
526
+ }
527
+
528
+ // Apply texture if provided
529
+ if (metadata?.texture) {
530
+ const texture = new THREE.TextureLoader().load(metadata.texture)
531
+ texture.minFilter = THREE.NearestFilter
532
+ texture.magFilter = THREE.NearestFilter
533
+ const material = new THREE.MeshBasicMaterial({
534
+ map: texture,
535
+ transparent: true,
536
+ alphaTest: 0.1
537
+ })
538
+ obj.traverse((child) => {
539
+ if (child instanceof THREE.Mesh) {
540
+ child.material = material
541
+ }
542
+ })
543
+ }
544
+
545
+ this.mesh = obj
546
+ debugFlags.type = 'obj'
547
+ return
548
+ }
549
+ case 'bedrock': {
550
+ // Parse bedrock model JSON
551
+ const modelData = JSON.parse(modelPath as string)
552
+ this.mesh = new THREE.Object3D()
553
+
554
+ // Apply metadata overrides
555
+ if (metadata?.scale) {
556
+ this.mesh.scale.set(metadata.scale, metadata.scale, metadata.scale)
557
+ }
558
+ if (metadata?.offset) {
559
+ const { x = 0, y = 0, z = 0 } = metadata.offset
560
+ this.mesh.position.set(x, y, z)
561
+ }
562
+
563
+ // Create mesh from bedrock model
564
+ for (const [name, jsonModel] of Object.entries(modelData.geometry)) {
565
+ const texture = metadata?.textures?.[name] ?? modelData.textures?.[name]
566
+ if (!texture) continue
567
+
568
+ const mesh = getMesh(worldRenderer,
569
+ texture.endsWith('.png') || texture.startsWith('data:image/') || texture.startsWith('block:')
570
+ ? texture : texture + '.png',
571
+ jsonModel,
572
+ overrides,
573
+ debugFlags)
574
+ mesh.name = `geometry_${name}`
575
+ this.mesh.add(mesh)
576
+ }
577
+ debugFlags.type = 'bedrock'
578
+ return
579
+ }
580
+ // No default
581
+ }
582
+ }
583
+
584
+ if (externalModels[type]) {
585
+ const objLoader = new OBJLoader()
586
+ const texturePathMap = {
587
+ 'zombie_horse': `textures/${version}/entity/horse/horse_zombie.png`,
588
+ 'husk': huskPng,
589
+ 'skeleton_horse': `textures/${version}/entity/horse/horse_skeleton.png`,
590
+ 'donkey': `textures/${version}/entity/horse/donkey.png`,
591
+ 'mule': `textures/${version}/entity/horse/mule.png`,
592
+ 'ocelot': ocelotPng,
593
+ 'arrow': arrowTexture,
594
+ 'spectral_arrow': spectralArrowTexture,
595
+ 'tipped_arrow': tippedArrowTexture
596
+ }
597
+ const tempTextureMap = texturePathMap[originalType] || texturePathMap[type]
598
+ if (tempTextureMap) {
599
+ debugFlags.textureMap = true
600
+ }
601
+ const texturePath = tempTextureMap || externalTexturesJson[type]
602
+ if (externalTexturesJson[type]) {
603
+ debugFlags.isHardcodedTexture = true
604
+ }
605
+ if (!texturePath) throw new Error(`No texture for ${type}`)
606
+ const texture = new THREE.TextureLoader().load(texturePath)
607
+ texture.minFilter = THREE.NearestFilter
608
+ texture.magFilter = THREE.NearestFilter
609
+ const material = new THREE.MeshBasicMaterial({
610
+ map: texture,
611
+ transparent: true,
612
+ alphaTest: 0.1
613
+ })
614
+ const obj = objLoader.parse(externalModels[type])
615
+ const scale = scaleEntity[originalType] || scaleEntity[type]
616
+ if (scale) obj.scale.set(scale, scale, scale)
617
+ const offset = offsetEntity[originalType]
618
+ if (offset) obj.position.set(offset.x, offset.y, offset.z)
619
+ obj.traverse((child) => {
620
+ if (child instanceof THREE.Mesh) {
621
+ child.material = material
622
+ // todo
623
+ if (child.name === 'Head layer') child.visible = false
624
+ if (child.name === 'Head' && overrides.rotation?.head) { // todo
625
+ child.rotation.x -= (overrides.rotation.head.x ?? 0) * Math.PI / 180
626
+ child.rotation.y -= (overrides.rotation.head.y ?? 0) * Math.PI / 180
627
+ child.rotation.z -= (overrides.rotation.head.z ?? 0) * Math.PI / 180
628
+ }
629
+ }
630
+ })
631
+ this.mesh = obj
632
+ debugFlags.type = 'obj'
633
+ return
634
+ }
635
+
636
+ if (originalType === 'arrow') {
637
+ // overrides.textures = {
638
+ // 'default': testArrow,
639
+ // ...overrides.textures,
640
+ // }
641
+ }
642
+
643
+ const e = getEntity(type)
644
+ if (!e) {
645
+ // if (knownNotHandled.includes(type)) return
646
+ // throw new Error(`Unknown entity ${type}`)
647
+ return
648
+ }
649
+
650
+ this.mesh = new THREE.Object3D()
651
+ for (const [name, jsonModel] of Object.entries(e.geometry)) {
652
+ const texture = overrides.textures?.[name] ?? e.textures[name]
653
+ if (!texture) continue
654
+ // console.log(JSON.stringify(jsonModel, null, 2))
655
+ const mesh = getMesh(worldRenderer,
656
+ texture.endsWith('.png') || texture.startsWith('data:image/') || texture.startsWith('block:')
657
+ ? texture : texture + '.png',
658
+ jsonModel,
659
+ overrides,
660
+ debugFlags)
661
+ mesh.name = `geometry_${name}`
662
+ this.mesh.add(mesh)
663
+ }
664
+ debugFlags.type = 'bedrock'
665
+ }
666
+
667
+ playAnimation(name: string, loop = false) {
668
+ if (!this.mixer || !this.animations) return
669
+
670
+ // Find animation by name
671
+ const clip = this.animations.find(a => a.name === name)
672
+ if (!clip) {
673
+ console.warn(`Animation "${name}" not found`)
674
+ return
675
+ }
676
+
677
+ // Stop any existing animations
678
+ this.mixer.stopAllAction()
679
+
680
+ // Play new animation
681
+ const action = this.mixer.clipAction(clip)
682
+ action.setLoop(loop ? THREE.LoopRepeat : THREE.LoopOnce, Infinity)
683
+ action.clampWhenFinished = !loop
684
+ action.reset().play()
685
+ }
686
+
687
+ update(deltaTime: number) {
688
+ if (this.mixer) {
689
+ this.mixer.update(deltaTime)
690
+ }
691
+ }
692
+
693
+ static getStaticData(name: string): { boneNames: string[] } {
694
+ name = getEntityMapping(name) || name
695
+ if (externalModels[name]) {
696
+ return {
697
+ boneNames: [] // todo
698
+ }
699
+ }
700
+ const e = getEntity(name) as EntityGeometry
701
+ if (!e) throw new Error(`Unknown entity ${name}`)
702
+ return {
703
+ boneNames: Object.values(e.geometry).flatMap(x => x.name)
704
+ }
705
+ }
706
+ }
707
+ globalThis.EntityMesh = EntityMesh