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,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,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 }
|