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,56 @@
1
+ import { test, expect } from 'vitest'
2
+ import supportedVersions from '../../../../../src/supportedVersions.mjs'
3
+ import { INVISIBLE_BLOCKS } from '../worldConstants'
4
+ import { setup } from './mesherTester'
5
+
6
+ const lastVersion = supportedVersions.at(-1)
7
+
8
+ const addPositions = [
9
+ // [[0, 0, 0], 'diamond_block'],
10
+ // [[1, 0, 0], 'stone'],
11
+ // [[-1, 0, 0], 'stone'],
12
+ // [[0, 1, 0], 'stone'],
13
+ // [[0, -1, 0], 'stone'],
14
+ // [[0, 0, 1], 'stone'],
15
+ // [[0, 0, -1], 'stone'],
16
+ ] as const
17
+
18
+ test('Known blocks are not rendered', () => {
19
+ const { mesherWorld, getGeometry, pos, mcData } = setup(lastVersion, addPositions as any)
20
+ const ignoreAsExpected = new Set([...INVISIBLE_BLOCKS, 'water', 'lava'])
21
+
22
+ let time = 0
23
+ let times = 0
24
+ const missingBlocks = {}/* as {[number, number]} */
25
+ const erroredBlocks = {}/* as {[number, number]} */
26
+ for (const block of mcData.blocksArray) {
27
+ if (ignoreAsExpected.has(block.name)) continue
28
+ // if (block.maxStateId! - block.minStateId! > 100) continue
29
+ // for (let i = block.minStateId!; i <= block.maxStateId!; i++) {
30
+ for (let i = block.defaultState; i <= block.defaultState; i++) {
31
+ // if (block.transparent) continue
32
+ mesherWorld.setBlockStateId(pos, i)
33
+ const start = performance.now()
34
+ const { centerFaces, totalTiles, centerTileNeighbors, attr } = getGeometry()
35
+ time += performance.now() - start
36
+ times++
37
+ if (centerFaces === 0) {
38
+ const objAdd = attr.hadErrors ? erroredBlocks : missingBlocks
39
+ if (objAdd[block.name]) continue
40
+ objAdd[block.name] = true
41
+ // invalidBlocks[block.name] = [i - block.defaultState!, centerTileNeighbors]
42
+ // console.log('INVALID', block.name, centerTileNeighbors, i - block.minStateId)
43
+ }
44
+ }
45
+ }
46
+ console.log('Checking blocks of version', lastVersion)
47
+ console.log('Average time', time / times)
48
+ // should be fixed, but to avoid regressions & for visibility
49
+ // TODO resolve creaking_heart issue (1.21.3)
50
+ expect(missingBlocks).toMatchInlineSnapshot(`
51
+ {
52
+ "structure_void": true,
53
+ }
54
+ `)
55
+ expect(erroredBlocks).toMatchInlineSnapshot('{}')
56
+ })
@@ -0,0 +1,294 @@
1
+ import Chunks from 'prismarine-chunk'
2
+ import mcData from 'minecraft-data'
3
+ import { Block } from 'prismarine-block'
4
+ import { Vec3 } from 'vec3'
5
+ import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
6
+ import moreBlockDataGeneratedJson from '../lib/moreBlockDataGenerated.json'
7
+ import legacyJson from '../lib/preflatMap.json'
8
+ import { getBlockPosKey } from '../lib/simpleUtils'
9
+ import { defaultMesherConfig, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherGeometryOutput } from './shared'
10
+ import { INVISIBLE_BLOCKS } from './worldConstants'
11
+ import { buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
12
+
13
+ const ignoreAoBlocks = Object.keys(moreBlockDataGeneratedJson.noOcclusions)
14
+
15
+ export function worldColumnKey(x, z) {
16
+ return `${x},${z}`
17
+ }
18
+
19
+ function isCube(shapes) {
20
+ if (!shapes || shapes.length !== 1) return false
21
+ const shape = shapes[0]
22
+ return shape[0] === 0 && shape[1] === 0 && shape[2] === 0 && shape[3] === 1 && shape[4] === 1 && shape[5] === 1
23
+ }
24
+
25
+ export type ElementPrecomputed = {
26
+ faces: {
27
+ [face: string]: {
28
+ dir: [number, number, number]
29
+ corners: Array<{
30
+ pos: number[]
31
+ vertex: number[]
32
+ uvs: number[]
33
+
34
+ side1Dir: [number, number, number]
35
+ side2Dir: [number, number, number]
36
+ cornerDir: [number, number, number]
37
+ }>
38
+ }
39
+ }
40
+ }
41
+
42
+ type BlockDataPrecomputed = {
43
+ initialMatrix?: any
44
+ initialShift?: any
45
+ elementsPrecomputed?: ElementPrecomputed[]
46
+ }
47
+
48
+ export type BlockModelPartsResolved = Array<Array<ReturnType<WorldBlockProvider['getAllResolvedModels0_1']>[number][number] & BlockDataPrecomputed>>
49
+
50
+ export type WorldBlock = Omit<Block, 'position'> & {
51
+ // todo
52
+ isCube: boolean
53
+ /** cache */
54
+ models?: BlockModelPartsResolved | null
55
+ _originalProperties?: Record<string, any>
56
+ _properties?: Record<string, any>
57
+ }
58
+
59
+ export class World {
60
+ config = defaultMesherConfig
61
+ Chunk: typeof import('prismarine-chunk/types/index').PCChunk
62
+ columns = {} as { [key: string]: import('prismarine-chunk/types/index').PCChunk }
63
+ blockCache = {}
64
+ biomeCache: { [id: number]: mcData.Biome }
65
+ preflat: boolean
66
+ erroredBlockModel?: BlockModelPartsResolved
67
+ customBlockModels = new Map<string, CustomBlockModels>() // chunkKey -> blockModels
68
+ sentBlockStateModels = new Set<string>()
69
+ blockStateModelInfo = new Map<string, BlockStateModelInfo>()
70
+
71
+ constructor(version) {
72
+ this.Chunk = Chunks(version) as any
73
+ this.biomeCache = mcData(version).biomes
74
+ this.preflat = !mcData(version).supportFeature('blockStateId')
75
+ this.config.version = version
76
+ }
77
+
78
+ getLight(pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') {
79
+ // for easier testing
80
+ if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
81
+ const { enableLighting, skyLight } = this.config
82
+ if (!enableLighting) return 15
83
+ // const key = `${pos.x},${pos.y},${pos.z}`
84
+ // if (lightsCache.has(key)) return lightsCache.get(key)
85
+ const column = this.getColumnByPos(pos)
86
+ if (!column || !hasChunkSection(column, pos)) return 15
87
+ let result = Math.min(
88
+ 15,
89
+ Math.max(
90
+ column.getBlockLight(posInChunk(pos)),
91
+ Math.min(skyLight, column.getSkyLight(posInChunk(pos)))
92
+ ) + 2
93
+ )
94
+ // lightsCache.set(key, result)
95
+ if (result === 2 && [this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong
96
+ const lights = [
97
+ this.getLight(pos.offset(0, 1, 0), undefined, true),
98
+ this.getLight(pos.offset(0, -1, 0), undefined, true),
99
+ this.getLight(pos.offset(0, 0, 1), undefined, true),
100
+ this.getLight(pos.offset(0, 0, -1), undefined, true),
101
+ this.getLight(pos.offset(1, 0, 0), undefined, true),
102
+ this.getLight(pos.offset(-1, 0, 0), undefined, true)
103
+ ].filter(x => x !== 2)
104
+ if (lights.length) {
105
+ const min = Math.min(...lights)
106
+ result = min
107
+ }
108
+ }
109
+ if (isNeighbor && result === 2) result = 15 // TODO
110
+ return result
111
+ }
112
+
113
+ addColumn(x, z, json) {
114
+ const chunk = this.Chunk.fromJson(json)
115
+ this.columns[worldColumnKey(x, z)] = chunk as any
116
+ return chunk
117
+ }
118
+
119
+ removeColumn(x, z) {
120
+ delete this.columns[worldColumnKey(x, z)]
121
+ }
122
+
123
+ getColumn(x, z) {
124
+ return this.columns[worldColumnKey(x, z)]
125
+ }
126
+
127
+ setBlockStateId(pos: Vec3, stateId) {
128
+ if (stateId === undefined) throw new Error('stateId is undefined')
129
+ const key = worldColumnKey(Math.floor(pos.x / 16) * 16, Math.floor(pos.z / 16) * 16)
130
+
131
+ const column = this.columns[key]
132
+ // null column means chunk not loaded
133
+ if (!column) return false
134
+
135
+ column.setBlockStateId(posInChunk(pos.floored()), stateId)
136
+
137
+ return true
138
+ }
139
+
140
+ getColumnByPos(pos: Vec3) {
141
+ return this.getColumn(Math.floor(pos.x / 16) * 16, Math.floor(pos.z / 16) * 16)
142
+ }
143
+
144
+ getBlock(pos: Vec3, blockProvider?: WorldBlockProvider, attr?: { hadErrors?: boolean }): WorldBlock | null {
145
+ // for easier testing
146
+ if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
147
+ const chunkKey = worldColumnKey(Math.floor(pos.x / 16) * 16, Math.floor(pos.z / 16) * 16)
148
+ const modelOverride = this.customBlockModels.get(chunkKey)?.[getBlockPosKey(pos)]
149
+
150
+ const column = this.columns[chunkKey]
151
+ // null column means chunk not loaded
152
+ if (!column) return null
153
+
154
+ const loc = pos.floored()
155
+ const locInChunk = posInChunk(loc)
156
+ const stateId = column.getBlockStateId(locInChunk)
157
+
158
+ const cacheKey = getBlockAssetsCacheKey(stateId, modelOverride)
159
+
160
+ if (!this.blockCache[cacheKey]) {
161
+ const b = column.getBlock(locInChunk) as unknown as WorldBlock
162
+ if (modelOverride) {
163
+ b.name = modelOverride
164
+ }
165
+ b.isCube = isCube(b.shapes)
166
+ this.blockCache[cacheKey] = b
167
+ Object.defineProperty(b, 'position', {
168
+ get() {
169
+ throw new Error('position is not reliable, use pos parameter instead of block.position')
170
+ }
171
+ })
172
+ if (this.preflat) {
173
+ b._properties = {}
174
+
175
+ const namePropsStr = legacyJson.blocks[b.type + ':' + b.metadata] || findClosestLegacyBlockFallback(b.type, b.metadata, pos)
176
+ if (namePropsStr) {
177
+ b.name = namePropsStr.split('[')[0]
178
+ const propsStr = namePropsStr.split('[')?.[1]?.split(']')
179
+ if (propsStr) {
180
+ const newProperties = Object.fromEntries(propsStr.join('').split(',').map(x => {
181
+ let [key, val] = x.split('=')
182
+ if (!isNaN(val)) val = parseInt(val, 10)
183
+ return [key, val]
184
+ }))
185
+ b._properties = newProperties
186
+ }
187
+ }
188
+ }
189
+ }
190
+
191
+ const block: WorldBlock = this.blockCache[cacheKey]
192
+
193
+ if (block.models === undefined && blockProvider) {
194
+ if (!attr) throw new Error('attr is required')
195
+ const props = block.getProperties()
196
+
197
+ try {
198
+ // fixme
199
+ if (this.preflat) {
200
+ if (block.name === 'cobblestone_wall') {
201
+ props.up = 'true'
202
+ for (const key of ['north', 'south', 'east', 'west']) {
203
+ const val = props[key]
204
+ if (val === 'false' || val === 'true') {
205
+ props[key] = val === 'true' ? 'low' : 'none'
206
+ }
207
+ }
208
+ }
209
+ }
210
+
211
+ const useFallbackModel = !!(this.preflat || modelOverride)
212
+ const issues = [] as string[]
213
+ const resolvedModelNames = [] as string[]
214
+ const resolvedConditions = [] as string[]
215
+ block.models = blockProvider.getAllResolvedModels0_1(
216
+ {
217
+ name: block.name,
218
+ properties: props,
219
+ },
220
+ useFallbackModel,
221
+ issues,
222
+ resolvedModelNames,
223
+ resolvedConditions
224
+ )!
225
+
226
+ // Track block state model info
227
+ if (!this.sentBlockStateModels.has(cacheKey)) {
228
+ this.blockStateModelInfo.set(cacheKey, {
229
+ cacheKey,
230
+ issues,
231
+ modelNames: resolvedModelNames,
232
+ conditions: resolvedConditions
233
+ })
234
+ }
235
+
236
+ if (!block.models.length) {
237
+ if (block.name !== 'water' && block.name !== 'lava' && !INVISIBLE_BLOCKS.has(block.name)) {
238
+ console.debug('[mesher] block to render not found', block.name, props)
239
+ }
240
+ block.models = null
241
+ }
242
+
243
+ if (block.models && modelOverride) {
244
+ const model = block.models[0]
245
+ block.transparent = model[0]?.['transparent'] ?? block.transparent
246
+ }
247
+ } catch (err) {
248
+ this.erroredBlockModel ??= blockProvider.getAllResolvedModels0_1({ name: 'errored', properties: {} })
249
+ block.models ??= this.erroredBlockModel
250
+ console.error(`Critical assets error. Unable to get block model for ${block.name}[${JSON.stringify(props)}]: ` + err.message, err.stack)
251
+ attr.hadErrors = true
252
+ }
253
+ }
254
+
255
+ if (block.name === 'flowing_water') block.name = 'water'
256
+ if (block.name === 'flowing_lava') block.name = 'lava'
257
+ if (block.name === 'bubble_column') block.name = 'water' // TODO need to distinguish between water and bubble column
258
+ // block.position = loc // it overrides position of all currently loaded blocks
259
+ //@ts-expect-error
260
+ block.biome = this.biomeCache[column.getBiome(locInChunk)] ?? this.biomeCache[1] ?? this.biomeCache[0]
261
+ if (block.name === 'redstone_ore') block.transparent = false
262
+ return block
263
+ }
264
+
265
+ shouldMakeAo(block: WorldBlock | null) {
266
+ return block?.isCube && !ignoreAoBlocks.includes(block.name) && block.boundingBox !== 'empty'
267
+ }
268
+ }
269
+
270
+ const findClosestLegacyBlockFallback = (id, metadata, pos) => {
271
+ console.warn(`[mesher] Unknown block with ${id}:${metadata} at ${pos}, falling back`) // todo has known issues
272
+ for (const [key, value] of Object.entries(legacyJson.blocks)) {
273
+ const [idKey, meta] = key.split(':')
274
+ if (idKey === id) return value
275
+ }
276
+ return null
277
+ }
278
+
279
+ // todo export in chunk instead
280
+ const hasChunkSection = (column, pos) => {
281
+ if (column._getSection) return column._getSection(pos)
282
+ if (column.skyLightSections) {
283
+ return column.skyLightSections[getLightSectionIndex(pos, column.minY)] || column.blockLightSections[getLightSectionIndex(pos, column.minY)]
284
+ }
285
+ if (column.sections) return column.sections[pos.y >> 4]
286
+ }
287
+
288
+ function posInChunk(pos) {
289
+ return new Vec3(Math.floor(pos.x) & 15, Math.floor(pos.y), Math.floor(pos.z) & 15)
290
+ }
291
+
292
+ function getLightSectionIndex(pos, minY = 0) {
293
+ return Math.floor((pos.y - minY) / 16) + 1
294
+ }
@@ -0,0 +1 @@
1
+ export const INVISIBLE_BLOCKS = new Set(['air', 'void_air', 'cave_air', 'barrier', 'light', 'moving_piston'])
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Renderer Modules System
3
+ *
4
+ * Provides a standardized way to create and manage renderer extensions.
5
+ */
6
+
7
+ // Types
8
+ export * from './types'
9
+
10
+ // Built-in modules
11
+ export * from './starfield'
@@ -0,0 +1,313 @@
1
+ /**
2
+ * StarField Module - Renders a twinkling star field effect for night sky.
3
+ *
4
+ * Uses Three.js Points with custom shader material for twinkling effect.
5
+ */
6
+
7
+ import * as THREE from 'three'
8
+ import type {
9
+ ModuleManifest,
10
+ ModuleContext,
11
+ RendererModule,
12
+ ModuleSettingsSchema,
13
+ InferSettings
14
+ } from './types'
15
+
16
+ // Get Three.js revision as integer
17
+ const threeVersion = parseInt(THREE.REVISION.replaceAll(/\D+/g, ''), 10)
18
+
19
+ // ============================================================================
20
+ // Settings Schema
21
+ // ============================================================================
22
+
23
+ const starfieldSettingsSchema = {
24
+ radius: {
25
+ default: 80,
26
+ label: 'Radius',
27
+ description: 'Inner radius of the star sphere',
28
+ min: 20,
29
+ max: 200,
30
+ },
31
+ depth: {
32
+ default: 50,
33
+ label: 'Depth',
34
+ description: 'Thickness of the star sphere',
35
+ min: 10,
36
+ max: 100,
37
+ },
38
+ count: {
39
+ default: 7000,
40
+ label: 'Star Count',
41
+ description: 'Number of stars to render',
42
+ min: 1000,
43
+ max: 20000,
44
+ step: 1000,
45
+ },
46
+ factor: {
47
+ default: 7,
48
+ label: 'Size Factor',
49
+ description: 'Star size multiplier',
50
+ min: 1,
51
+ max: 20,
52
+ },
53
+ saturation: {
54
+ default: 10,
55
+ label: 'Saturation',
56
+ description: 'Color saturation of stars',
57
+ min: 0,
58
+ max: 100,
59
+ },
60
+ speed: {
61
+ default: 0.2,
62
+ label: 'Twinkle Speed',
63
+ description: 'Speed of twinkling animation',
64
+ min: 0,
65
+ max: 1,
66
+ step: 0.1,
67
+ },
68
+ autoTimeOfDay: {
69
+ default: true,
70
+ label: 'Auto Time-of-Day',
71
+ description: 'Automatically show/hide stars based on in-game time',
72
+ },
73
+ } as const satisfies ModuleSettingsSchema
74
+
75
+ export type StarfieldSettings = typeof starfieldSettingsSchema
76
+
77
+ // ============================================================================
78
+ // Custom Shader Material
79
+ // ============================================================================
80
+
81
+ class StarfieldMaterial extends THREE.ShaderMaterial {
82
+ constructor() {
83
+ super({
84
+ uniforms: { time: { value: 0 }, fade: { value: 1 } },
85
+ vertexShader: /* glsl */ `
86
+ uniform float time;
87
+ attribute float size;
88
+ varying vec3 vColor;
89
+ attribute vec3 color;
90
+ void main() {
91
+ vColor = color;
92
+ vec4 mvPosition = modelViewMatrix * vec4(position, 0.5);
93
+ gl_PointSize = 0.7 * size * (30.0 / -mvPosition.z) * (3.0 + sin(time + 100.0));
94
+ gl_Position = projectionMatrix * mvPosition;
95
+ }`,
96
+ fragmentShader: /* glsl */ `
97
+ uniform sampler2D pointTexture;
98
+ uniform float fade;
99
+ varying vec3 vColor;
100
+ void main() {
101
+ float opacity = 1.0;
102
+ gl_FragColor = vec4(vColor, 1.0);
103
+
104
+ #include <tonemapping_fragment>
105
+ #include <${threeVersion >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
106
+ }`,
107
+ })
108
+ }
109
+ }
110
+
111
+ // ============================================================================
112
+ // Module Manifest
113
+ // ============================================================================
114
+
115
+ export const starfieldManifest: ModuleManifest<StarfieldSettings> = {
116
+ id: 'starfield',
117
+ name: 'Star Field',
118
+ description: 'Renders a twinkling star field effect for the night sky',
119
+ version: '1.0.0',
120
+ settings: starfieldSettingsSchema,
121
+ }
122
+
123
+ // ============================================================================
124
+ // Starfield Module Implementation
125
+ // ============================================================================
126
+
127
+ export class StarfieldModule implements RendererModule<StarfieldSettings> {
128
+ readonly manifest = starfieldManifest
129
+
130
+ // Current settings (mutable)
131
+ private _settings: InferSettings<StarfieldSettings>
132
+
133
+ // Module state
134
+ private _enabled = true
135
+ private context?: ModuleContext
136
+ private points?: THREE.Points
137
+ private readonly clock = new THREE.Clock()
138
+ private renderCallback?: () => void
139
+
140
+ constructor(initialSettings?: Partial<InferSettings<StarfieldSettings>>) {
141
+ // Initialize settings with defaults
142
+ this._settings = {
143
+ radius: starfieldSettingsSchema.radius.default,
144
+ depth: starfieldSettingsSchema.depth.default,
145
+ count: starfieldSettingsSchema.count.default,
146
+ factor: starfieldSettingsSchema.factor.default,
147
+ saturation: starfieldSettingsSchema.saturation.default,
148
+ speed: starfieldSettingsSchema.speed.default,
149
+ autoTimeOfDay: starfieldSettingsSchema.autoTimeOfDay.default,
150
+ ...initialSettings,
151
+ }
152
+ }
153
+
154
+ get settings(): InferSettings<StarfieldSettings> {
155
+ return this._settings
156
+ }
157
+
158
+ get enabled(): boolean {
159
+ return this._enabled
160
+ }
161
+
162
+ set enabled(value: boolean) {
163
+ if (this._enabled === value) return
164
+ this._enabled = value
165
+
166
+ if (value) {
167
+ this.onEnable?.()
168
+ } else {
169
+ this.onDisable?.()
170
+ }
171
+ }
172
+
173
+ init(context: ModuleContext): void {
174
+ this.context = context
175
+
176
+ // Create render callback
177
+ this.renderCallback = () => {
178
+ if (!this.points || !this.context) return
179
+ this.points.position.copy(this.context.getCameraPosition())
180
+ ; (this.points.material as StarfieldMaterial).uniforms.time.value =
181
+ this.clock.getElapsedTime() * this._settings.speed
182
+ }
183
+
184
+ if (this._enabled) {
185
+ this.createStars()
186
+ context.onRender(this.renderCallback)
187
+ }
188
+ }
189
+
190
+ setSetting<K extends keyof StarfieldSettings>(
191
+ key: K,
192
+ value: StarfieldSettings[K]['default']
193
+ ): void {
194
+ (this._settings as any)[key] = value
195
+
196
+ // Recreate stars if geometry-affecting settings change
197
+ if (['radius', 'depth', 'count', 'factor', 'saturation'].includes(key as string)) {
198
+ this.recreateStars()
199
+ }
200
+ }
201
+
202
+ onEnable(): void {
203
+ if (!this.context) return
204
+ this.createStars()
205
+ if (this.renderCallback) {
206
+ this.context.onRender(this.renderCallback)
207
+ }
208
+ }
209
+
210
+ onDisable(): void {
211
+ this.removeStars()
212
+ if (this.context && this.renderCallback) {
213
+ this.context.offRender(this.renderCallback)
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Update visibility based on time of day (0-24000 Minecraft ticks).
219
+ */
220
+ updateTimeOfDay(time: number): void {
221
+ if (!this._settings.autoTimeOfDay) return
222
+
223
+ const nightTime = 13_500
224
+ const morningStart = 23_000
225
+ const displayStars = time > nightTime && time < morningStart
226
+
227
+ if (displayStars && !this.points) {
228
+ this.createStars()
229
+ } else if (!displayStars && this.points) {
230
+ this.removeStars()
231
+ }
232
+ }
233
+
234
+ private createStars(): void {
235
+ if (!this.context || this.points) return
236
+
237
+ const { radius, depth, count, factor, saturation } = this._settings
238
+
239
+ const geometry = new THREE.BufferGeometry()
240
+
241
+ const genStar = (r: number): THREE.Vector3 =>
242
+ new THREE.Vector3().setFromSpherical(
243
+ new THREE.Spherical(
244
+ r,
245
+ Math.acos(1 - Math.random() * 2),
246
+ Math.random() * 2 * Math.PI
247
+ )
248
+ )
249
+
250
+ const positions: number[] = []
251
+ const colors: number[] = []
252
+ const sizes = Array.from({ length: count }, () => (0.5 + 0.5 * Math.random()) * factor)
253
+ const color = new THREE.Color()
254
+ let r = radius + depth
255
+ const increment = depth / count
256
+
257
+ for (let i = 0; i < count; i++) {
258
+ r -= increment * Math.random()
259
+ positions.push(...genStar(r).toArray())
260
+ color.setHSL(i / count, saturation, 0.9)
261
+ colors.push(color.r, color.g, color.b)
262
+ }
263
+
264
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))
265
+ geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3))
266
+ geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1))
267
+
268
+ const material = new StarfieldMaterial()
269
+ material.blending = THREE.AdditiveBlending
270
+ material.depthTest = false
271
+ material.transparent = true
272
+
273
+ this.points = new THREE.Points(geometry, material)
274
+ this.points.renderOrder = -1
275
+ this.context.scene.add(this.points)
276
+ }
277
+
278
+ private removeStars(): void {
279
+ if (!this.context || !this.points) return
280
+
281
+ this.points.geometry.dispose()
282
+ ; (this.points.material as THREE.Material).dispose()
283
+ this.context.scene.remove(this.points)
284
+ this.points = undefined
285
+ }
286
+
287
+ private recreateStars(): void {
288
+ if (!this._enabled) return
289
+ this.removeStars()
290
+ this.createStars()
291
+ }
292
+
293
+ dispose(): void {
294
+ this.onDisable()
295
+ this.context = undefined
296
+ }
297
+ }
298
+
299
+ // ============================================================================
300
+ // Module Factory
301
+ // ============================================================================
302
+
303
+ /**
304
+ * Create a new StarfieldModule instance.
305
+ */
306
+ export const createStarfieldModule = (
307
+ initialSettings?: Partial<InferSettings<StarfieldSettings>>
308
+ ): StarfieldModule => {
309
+ return new StarfieldModule(initialSettings)
310
+ }
311
+
312
+ // Re-export the old class for backwards compatibility
313
+ export { StarfieldModule as StarField }