minecraft-renderer 0.1.18 → 0.1.20

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.
@@ -0,0 +1,93 @@
1
+ //@ts-nocheck
2
+ import { existsSync, readFileSync, writeFileSync } from 'fs'
3
+ import { join } from 'path'
4
+
5
+ /**
6
+ * Serialize any output object for snapshot comparison
7
+ * Converts TypedArrays to regular arrays for JSON serialization
8
+ */
9
+ export function serializeOutput(output: any): any {
10
+ const serialized: any = {}
11
+ for (const [key, value] of Object.entries(output)) {
12
+ if (value instanceof Float32Array || value instanceof Uint32Array || value instanceof Uint16Array || value instanceof Uint8Array) {
13
+ serialized[key] = Array.from(value as any)
14
+ } else if (value instanceof ArrayBuffer) {
15
+ serialized[key] = Array.from(new Uint8Array(value))
16
+ } else if (typeof value === 'object' && value !== null) {
17
+ serialized[key] = serializeOutput(value)
18
+ } else {
19
+ serialized[key] = value
20
+ }
21
+ }
22
+ return serialized
23
+ }
24
+
25
+ /**
26
+ * Deep equality comparison for snapshot testing
27
+ */
28
+ export function deepEqual(obj1: any, obj2: any): boolean {
29
+ if (obj1 === obj2) return true
30
+ if (obj1 == null || obj2 == null) return false
31
+ if (typeof obj1 !== typeof obj2) return false
32
+
33
+ if (Array.isArray(obj1) && Array.isArray(obj2)) {
34
+ if (obj1.length !== obj2.length) return false
35
+ for (let i = 0; i < obj1.length; i++) {
36
+ if (!deepEqual(obj1[i], obj2[i])) return false
37
+ }
38
+ return true
39
+ }
40
+
41
+ if (typeof obj1 === 'object') {
42
+ const keys1 = Object.keys(obj1)
43
+ const keys2 = Object.keys(obj2)
44
+ if (keys1.length !== keys2.length) return false
45
+ for (const key of keys1) {
46
+ if (!keys2.includes(key)) return false
47
+ if (!deepEqual(obj1[key], obj2[key])) return false
48
+ }
49
+ return true
50
+ }
51
+
52
+ return false
53
+ }
54
+
55
+ /**
56
+ * Compare output with snapshot file, or write snapshot if it doesn't exist
57
+ * @param output - The output object to compare/write
58
+ * @param snapshotPath - Path to snapshot file (absolute or relative to __dirname)
59
+ * @param baseDir - Base directory for relative paths (defaults to __dirname, ignored if snapshotPath is absolute)
60
+ * @returns true if snapshot matches or was created, throws error if mismatch
61
+ */
62
+ export function compareOrWriteSnapshot(
63
+ output: any,
64
+ snapshotPath: string,
65
+ baseDir?: string
66
+ ): boolean {
67
+ // If path is absolute, use it directly; otherwise join with baseDir or __dirname
68
+ const fullPath = snapshotPath.startsWith('/') || snapshotPath.includes(':')
69
+ ? snapshotPath
70
+ : baseDir ? join(baseDir, snapshotPath) : join(__dirname, snapshotPath)
71
+ const serialized = serializeOutput(output)
72
+
73
+ if (!existsSync(fullPath)) {
74
+ // Write snapshot if file doesn't exist
75
+ writeFileSync(fullPath, JSON.stringify(serialized, null, 2), 'utf-8')
76
+ console.log('Snapshot file created:', fullPath)
77
+ return true
78
+ } else {
79
+ // Compare with existing snapshot
80
+ const snapshotContent = readFileSync(fullPath, 'utf-8')
81
+ const snapshotData = JSON.parse(snapshotContent)
82
+
83
+ if (!deepEqual(serialized, snapshotData)) {
84
+ console.error('Snapshot mismatch! Current output differs from snapshot.')
85
+ // console.error('Expected (snapshot):', JSON.stringify(snapshotData, null, 2))
86
+ // console.error('Actual (current):', JSON.stringify(serialized, null, 2))
87
+ throw new Error('Snapshot comparison failed - output has changed')
88
+ }
89
+
90
+ console.log('Snapshot comparison passed - no changes detected')
91
+ return true
92
+ }
93
+ }
@@ -13,7 +13,7 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
13
13
  // eslint-disable-next-line import/no-named-as-default
14
14
  import GUI from 'lil-gui'
15
15
  import _ from 'lodash'
16
- import { defaultWorldRendererConfig, WorldRendererConfig } from '../lib/worldrendererCommon'
16
+ import { WorldRendererConfig } from '../graphicsBackend/config'
17
17
  import { getSyncWorld } from './shared'
18
18
  import { AppViewer, getInitialPlayerState } from '../graphicsBackend'
19
19
  import { WorldView } from '../worldView'
@@ -35,8 +35,8 @@ export interface PlaygroundSceneConfig {
35
35
  }
36
36
 
37
37
  const appGraphicBackends = [
38
- // createGraphicsBackendSingleThread,
39
- createGraphicsBackendOffThread
38
+ createGraphicsBackendSingleThread,
39
+ // createGraphicsBackendOffThread
40
40
  ]
41
41
 
42
42
  const includedVersions = globalThis.includedVersions
@@ -130,6 +130,7 @@ export class BasePlaygroundScene {
130
130
  Object.assign(this.appViewer.inWorldRenderingConfig, config.worldConfig)
131
131
  }
132
132
  this.appViewer.inWorldRenderingConfig.showHand = false
133
+ this.appViewer.inWorldRenderingConfig.showChunkBorders = true
133
134
  this.appViewer.inWorldRenderingConfig.isPlayground = true
134
135
  this.appViewer.inWorldRenderingConfig.instantCameraUpdate = this.enableCameraOrbitControl
135
136
  this.appViewer.config.statsVisible = 2
@@ -308,8 +308,7 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<Re
308
308
  }
309
309
 
310
310
  async generateGuiTextures() {
311
- // TODO!
312
- // await generateGuiAtlas()
311
+ // todo-low handled now in the client
313
312
  }
314
313
 
315
314
  async downloadDebugAtlas(isItems = false) {
@@ -250,9 +250,11 @@ export class SciFiWorldRevealModule implements RendererModuleController {
250
250
  // Section keys are in format "x,y,z" where x, y, z are section coordinates
251
251
  // Mesh position is at geometry.sx, geometry.sy, geometry.sz
252
252
  const pos = mesh.position
253
- const sectionX = Math.floor(pos.x / 16) * 16
254
- const sectionY = Math.floor(pos.y / 16) * 16
255
- const sectionZ = Math.floor(pos.z / 16) * 16
253
+ const CHUNK_SIZE = 16
254
+ const sectionHeight = this.worldRenderer.getSectionHeight()
255
+ const sectionX = Math.floor(pos.x / CHUNK_SIZE) * CHUNK_SIZE
256
+ const sectionY = Math.floor(pos.y / sectionHeight) * sectionHeight
257
+ const sectionZ = Math.floor(pos.z / CHUNK_SIZE) * CHUNK_SIZE
256
258
  const derivedKey = `${sectionX},${sectionY},${sectionZ}`
257
259
 
258
260
  // Verify this key exists in sectionObjects
@@ -2,7 +2,7 @@
2
2
  import * as THREE from 'three'
3
3
  import { Vec3 } from 'vec3'
4
4
  import nbt from 'prismarine-nbt'
5
- import { MesherGeometryOutput } from '../mesher/shared'
5
+ import { MesherGeometryOutput, IS_FULL_WORLD_SECTION } from '../mesher/shared'
6
6
  import { getBannerTexture, createBannerMesh, releaseBannerTexture } from './bannerRenderer'
7
7
  import { disposeObject } from './threeJsUtils'
8
8
  import type { WorldRendererThree } from './worldRendererThree'
@@ -70,9 +70,11 @@ export class WorldBlockGeometry {
70
70
  mesh.name = 'mesh'
71
71
  object = new THREE.Group()
72
72
  object.add(mesh)
73
- // mesh with static dimensions: 16x16x16
73
+ // mesh with static dimensions: 16x16xsectionHeight
74
+ const sectionHeight = data.geometry.sectionEndY - data.geometry.sectionStartY
75
+ const CHUNK_SIZE = 16
74
76
  const staticChunkMesh = new THREE.Mesh(
75
- new THREE.BoxGeometry(16, 16, 16),
77
+ new THREE.BoxGeometry(CHUNK_SIZE, sectionHeight, CHUNK_SIZE),
76
78
  new THREE.MeshBasicMaterial({ color: 0x00_00_00, transparent: true, opacity: 0 })
77
79
  )
78
80
  staticChunkMesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz)
@@ -230,7 +232,11 @@ export class WorldBlockGeometry {
230
232
  }
231
233
 
232
234
  removeColumn(x: number, z: number) {
233
- for (let y = this.worldRenderer.worldSizeParams.minY; y < this.worldRenderer.worldSizeParams.worldHeight; y += 16) {
235
+ const sectionHeight = this.worldRenderer.getSectionHeight()
236
+ const worldMinY = this.worldRenderer.worldMinYRender
237
+ if (IS_FULL_WORLD_SECTION) {
238
+ // Only one section per chunk when full world section
239
+ const y = worldMinY
234
240
  this.worldRenderer.setSectionDirty(new Vec3(x, y, z), false)
235
241
  const key = `${x},${y},${z}`
236
242
  const mesh = this.sectionObjects[key]
@@ -247,6 +253,25 @@ export class WorldBlockGeometry {
247
253
  disposeObject(mesh)
248
254
  }
249
255
  delete this.sectionObjects[key]
256
+ } else {
257
+ for (let y = worldMinY; y < this.worldRenderer.worldSizeParams.worldHeight; y += sectionHeight) {
258
+ this.worldRenderer.setSectionDirty(new Vec3(x, y, z), false)
259
+ const key = `${x},${y},${z}`
260
+ const mesh = this.sectionObjects[key]
261
+ if (mesh) {
262
+ // Track memory usage removal
263
+ this.removeSectionMemoryUsage(mesh)
264
+ // Cleanup banner textures before disposing
265
+ mesh.traverse((child) => {
266
+ if ((child as any).bannerTexture) {
267
+ releaseBannerTexture((child as any).bannerTexture)
268
+ }
269
+ })
270
+ this.scene.remove(mesh)
271
+ disposeObject(mesh)
272
+ }
273
+ delete this.sectionObjects[key]
274
+ }
250
275
  }
251
276
  }
252
277
  }
@@ -796,7 +796,8 @@ export class WorldRendererThree extends WorldRendererCommon {
796
796
  debugChunksVisibilityOverride() {
797
797
  const { chunksRenderAboveOverride, chunksRenderBelowOverride, chunksRenderDistanceOverride, chunksRenderAboveEnabled, chunksRenderBelowEnabled, chunksRenderDistanceEnabled } = this.reactiveDebugParams
798
798
 
799
- const baseY = this.cameraSectionPos.y * 16
799
+ const sectionHeight = this.getSectionHeight()
800
+ const baseY = this.cameraSectionPos.y * sectionHeight
800
801
 
801
802
  if (
802
803
  this.displayOptions.inWorldRenderingConfig.enableDebugOverlay &&
@@ -1077,9 +1078,11 @@ export class WorldRendererThree extends WorldRendererCommon {
1077
1078
 
1078
1079
  shouldObjectVisible(object: THREE.Object3D) {
1079
1080
  // Get chunk coordinates
1080
- const chunkX = Math.floor(object.position.x / 16) * 16
1081
- const chunkZ = Math.floor(object.position.z / 16) * 16
1082
- const sectionY = Math.floor(object.position.y / 16) * 16
1081
+ const CHUNK_SIZE = 16
1082
+ const sectionHeight = this.getSectionHeight()
1083
+ const chunkX = Math.floor(object.position.x / CHUNK_SIZE) * CHUNK_SIZE
1084
+ const chunkZ = Math.floor(object.position.z / CHUNK_SIZE) * CHUNK_SIZE
1085
+ const sectionY = Math.floor(object.position.y / sectionHeight) * sectionHeight
1083
1086
 
1084
1087
  const chunkKey = `${chunkX},${chunkZ}`
1085
1088
  const sectionKey = `${chunkX},${sectionY},${chunkZ}`
@@ -0,0 +1,192 @@
1
+ //@ts-nocheck
2
+ import { Vec3 } from 'vec3'
3
+ import MinecraftData from 'minecraft-data'
4
+ import PrismarineBlockLoader from 'prismarine-block'
5
+ import moreBlockDataGeneratedJson from '../lib/moreBlockDataGenerated.json'
6
+
7
+ type BlockMeta = {
8
+ invisibleBlocks: Uint16Array
9
+ transparentBlocks: Uint16Array
10
+ noAoBlocks: Uint16Array
11
+ cullIdenticalBlocks: Uint16Array
12
+ occludingBlocks: Uint16Array
13
+ }
14
+
15
+ const metaCache = new Map<string, BlockMeta>()
16
+
17
+ const blockToIds = (block: { minStateId: number, maxStateId: number }) => {
18
+ const ids: number[] = []
19
+ for (let i = block.minStateId; i <= block.maxStateId; i++) {
20
+ ids.push(i)
21
+ }
22
+ return ids
23
+ }
24
+
25
+ const isCube = (shapes: any) => {
26
+ if (!shapes || shapes.length !== 1) return false
27
+ const s = shapes[0]
28
+ return s[0] === 0 && s[1] === 0 && s[2] === 0 && s[3] === 1 && s[4] === 1 && s[5] === 1
29
+ }
30
+
31
+ const isLikelyFullCubeBlockName = (name: string) => {
32
+ if (!name) return false
33
+ if (name.includes('stairs')) return false
34
+ if (name.includes('slab')) return false
35
+ if (name.includes('fence')) return false
36
+ if (name.includes('gate')) return false
37
+ if (name.includes('pane')) return false
38
+ if (name.includes('wall')) return false
39
+ if (name.includes('door')) return false
40
+ if (name.includes('trapdoor')) return false
41
+ if (name.includes('sign')) return false
42
+ if (name.includes('banner')) return false
43
+ if (name.includes('rail')) return false
44
+ if (name.includes('torch')) return false
45
+ if (name.includes('lantern')) return false
46
+ if (name.includes('button')) return false
47
+ if (name.includes('lever')) return false
48
+ if (name.includes('pressure_plate')) return false
49
+ if (name.includes('carpet')) return false
50
+ if (name.includes('flower')) return false
51
+ if (name.includes('sapling')) return false
52
+ if (name.includes('tall_grass')) return false
53
+ if (name === 'grass' || name === 'short_grass' || name === 'tall_grass') return false
54
+ return true
55
+ }
56
+
57
+ const getBlockMeta = (version: string): BlockMeta => {
58
+ const cached = metaCache.get(version)
59
+ if (cached) return cached
60
+
61
+ const mcData = MinecraftData(version)
62
+
63
+ const invisibleBlocks = new Uint16Array(mcData.blocksArray.filter(x => moreBlockDataGeneratedJson.invisibleBlocks[x.name]).flatMap(blockToIds))
64
+ const transparentBlocks = new Uint16Array(mcData.blocksArray.filter(x => x.transparent).flatMap(blockToIds))
65
+ const noAoBlocks = new Uint16Array(mcData.blocksArray.filter(x => moreBlockDataGeneratedJson.noOcclusions[x.name]).flatMap(blockToIds))
66
+ const cullIdenticalBlocks = new Uint16Array(mcData.blocksArray.filter(x => x.name.includes('glass') || x.name.includes('ice')).flatMap(blockToIds))
67
+ const Block = PrismarineBlockLoader(version)
68
+ const noOcclusionsSet = new Set(Object.keys(moreBlockDataGeneratedJson.noOcclusions))
69
+
70
+ const occludingBlockIds: number[] = []
71
+ for (const idStr of Object.keys((mcData as any).blocksByStateId)) {
72
+ const id = Number(idStr)
73
+ if (!id) continue
74
+ const b = (Block as any).fromStateId(id, 0)
75
+ if (!b) continue
76
+ if (b.transparent) continue
77
+ if (b.boundingBox !== 'block') continue
78
+ if (noOcclusionsSet.has(b.name)) continue
79
+ if (!isCube(b.shapes)) continue
80
+ occludingBlockIds.push(id)
81
+ }
82
+
83
+ const occludingBlocks = new Uint16Array(occludingBlockIds)
84
+
85
+ const meta = {
86
+ invisibleBlocks,
87
+ transparentBlocks,
88
+ noAoBlocks,
89
+ cullIdenticalBlocks,
90
+ occludingBlocks
91
+ }
92
+
93
+ metaCache.set(version, meta)
94
+ return meta
95
+ }
96
+
97
+ export interface ChunkConversionResult {
98
+ blockStates: Uint16Array
99
+ blockLight: Uint8Array
100
+ skyLight: Uint8Array
101
+ biomesArray: Uint8Array
102
+ invisibleBlocks: Uint16Array
103
+ transparentBlocks: Uint16Array
104
+ noAoBlocks: Uint16Array
105
+ cullIdenticalBlocks: Uint16Array
106
+ occludingBlocks: Uint16Array
107
+ blockCount: number
108
+ }
109
+
110
+ /**
111
+ * Convert a prismarine chunk to WASM format
112
+ */
113
+ export function convertChunkToWasm(
114
+ chunk: any,
115
+ version: string,
116
+ chunkX: number = 0,
117
+ chunkZ: number = 0,
118
+ worldMinY: number = 0,
119
+ worldMaxY: number = 256,
120
+ sectionY?: number,
121
+ sectionHeight?: number
122
+ ): ChunkConversionResult {
123
+ const CHUNK_SIZE = 16
124
+
125
+ // If sectionY and sectionHeight are provided, only convert that section
126
+ // Otherwise convert the full chunk
127
+ const startY = sectionY !== undefined ? sectionY : worldMinY
128
+ const endY = sectionHeight !== undefined ? startY + sectionHeight : worldMaxY
129
+ const totalBlocks = CHUNK_SIZE * CHUNK_SIZE * (endY - startY)
130
+
131
+ const blockStates = new Uint16Array(totalBlocks)
132
+ const blockLight = new Uint8Array(totalBlocks)
133
+ const skyLight = new Uint8Array(totalBlocks)
134
+ const biomesArray = new Uint8Array(totalBlocks)
135
+
136
+ // Traverse chunk and extract data
137
+ let blockCount = 0
138
+
139
+ for (let y = startY; y < endY; y++) {
140
+ for (let z = 0; z < CHUNK_SIZE; z++) {
141
+ for (let x = 0; x < CHUNK_SIZE; x++) {
142
+ const pos = new Vec3(x, y, z)
143
+ const idx = x + z * CHUNK_SIZE + (y - startY) * CHUNK_SIZE * CHUNK_SIZE
144
+
145
+ try {
146
+ // Get block state ID
147
+ const stateId = chunk.getBlockStateId(pos)
148
+ blockStates[idx] = stateId || 0
149
+
150
+ // Get light values
151
+ const bl = chunk.getBlockLight(pos)
152
+ const sl = chunk.getSkyLight(pos)
153
+ blockLight[idx] = bl !== undefined ? bl : 0
154
+ skyLight[idx] = sl !== undefined ? sl : 15
155
+
156
+ // Get biome
157
+ const biome = chunk.getBiome ? chunk.getBiome(pos) : 1
158
+ biomesArray[idx] = biome || 1
159
+
160
+ if (stateId && stateId !== 0) blockCount++
161
+ } catch (err) {
162
+ // If position is out of bounds, set to air
163
+ blockStates[idx] = 0
164
+ blockLight[idx] = 0
165
+ skyLight[idx] = 15
166
+ biomesArray[idx] = 1
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ const {
173
+ invisibleBlocks,
174
+ transparentBlocks,
175
+ noAoBlocks,
176
+ cullIdenticalBlocks,
177
+ occludingBlocks
178
+ } = getBlockMeta(version)
179
+
180
+ return {
181
+ blockStates,
182
+ blockLight,
183
+ skyLight,
184
+ biomesArray,
185
+ invisibleBlocks,
186
+ transparentBlocks,
187
+ noAoBlocks,
188
+ cullIdenticalBlocks,
189
+ occludingBlocks,
190
+ blockCount,
191
+ }
192
+ }