minecraft-renderer 0.1.21 → 0.1.23
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/dist/mesher.js +1 -1
- package/dist/mesher.js.map +2 -2
- package/dist/mesherWasm.js +5 -5
- package/dist/minecraft-renderer.js +53 -53
- package/dist/threeWorker.js +405 -405
- package/package.json +6 -1
- package/src/graphicsBackend/config.ts +8 -4
- package/src/graphicsBackend/types.ts +2 -2
- package/src/lib/worldrendererCommon.ts +45 -9
- package/src/mesher/mesher.ts +2 -2
- package/src/mesher/shared.ts +1 -1
- package/src/playground/allEntitiesDebug.ts +6 -4
- package/src/playground/scenes/allEntities.ts +1 -1
- package/src/playground/scenes/floorRandom.ts +1 -1
- package/src/three/appShared.ts +2 -2
- package/src/three/documentRenderer.ts +2 -2
- package/src/three/graphicsBackendBase.ts +2 -0
- package/src/three/holdingBlock.ts +1 -1
- package/src/three/modules/index.ts +2 -0
- package/src/three/modules/rain.ts +185 -0
- package/src/three/modules/sciFiWorldReveal.ts +9 -0
- package/src/three/modules/starfield.ts +9 -0
- package/src/three/renderSlot.ts +9 -9
- package/src/three/rendererModuleSystem.ts +4 -0
- package/src/three/worldRendererThree.ts +114 -23
- package/src/wasm-lib/render-from-wasm.ts +161 -161
- package/src/worldView/worldView.ts +1 -1
- package/wasm/wasm_mesher.d.ts +2 -2
- package/wasm/wasm_mesher.js +7 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minecraft-renderer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"description": "The most Modular Minecraft world renderer with Three.js WebGL backend",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"@types/three": "0.154.0",
|
|
75
75
|
"@zardoy/react-util": "^0.2.7",
|
|
76
76
|
"@zardoy/tsconfig": "^1.5.1",
|
|
77
|
+
"contro-max": "*",
|
|
77
78
|
"esbuild": "^0.19.3",
|
|
78
79
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
|
79
80
|
"fs-extra": "^11.0.0",
|
|
@@ -92,11 +93,15 @@
|
|
|
92
93
|
"vitest": "^4.0.14"
|
|
93
94
|
},
|
|
94
95
|
"peerDependencies": {
|
|
96
|
+
"contro-max": "*",
|
|
95
97
|
"mc-assets": ">=0.2.0",
|
|
96
98
|
"minecraft-data": ">=3.0.0",
|
|
97
99
|
"three": ">=0.150.0"
|
|
98
100
|
},
|
|
99
101
|
"peerDependenciesMeta": {
|
|
102
|
+
"contro-max": {
|
|
103
|
+
"optional": true
|
|
104
|
+
},
|
|
100
105
|
"mc-assets": {
|
|
101
106
|
"optional": true
|
|
102
107
|
},
|
|
@@ -20,7 +20,7 @@ export const defaultWorldRendererConfig = {
|
|
|
20
20
|
// Debug settings
|
|
21
21
|
showChunkBorders: false,
|
|
22
22
|
enableDebugOverlay: false,
|
|
23
|
-
debugModelVariant: undefined,
|
|
23
|
+
debugModelVariant: undefined as undefined | number[],
|
|
24
24
|
futuristicReveal: false,
|
|
25
25
|
|
|
26
26
|
// Performance settings
|
|
@@ -46,7 +46,7 @@ export const defaultWorldRendererConfig = {
|
|
|
46
46
|
showHand: false,
|
|
47
47
|
viewBobbing: false,
|
|
48
48
|
renderEars: true,
|
|
49
|
-
highlightBlockColor: 'blue' as 'blue' | 'classic' | undefined,
|
|
49
|
+
highlightBlockColor: 'blue' as 'blue' | 'classic' | 'auto' | undefined,
|
|
50
50
|
|
|
51
51
|
// Player models
|
|
52
52
|
fetchPlayerSkins: true,
|
|
@@ -59,7 +59,11 @@ export const defaultWorldRendererConfig = {
|
|
|
59
59
|
// World settings
|
|
60
60
|
clipWorldBelowY: undefined as undefined | number,
|
|
61
61
|
isPlayground: false,
|
|
62
|
-
instantCameraUpdate: false
|
|
62
|
+
instantCameraUpdate: false,
|
|
63
|
+
isRaining: false,
|
|
64
|
+
|
|
65
|
+
// Module states: 'enabled' = force on, 'disabled' = force off, 'auto' = use autoEnableCheck
|
|
66
|
+
moduleStates: {} as Record<string, 'enabled' | 'disabled' | 'auto'>
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
export type WorldRendererConfig = typeof defaultWorldRendererConfig
|
|
@@ -95,7 +99,7 @@ export const getDefaultRendererState = (): {
|
|
|
95
99
|
reactive: proxy({
|
|
96
100
|
world: {
|
|
97
101
|
chunksLoaded: new Set<string>(),
|
|
98
|
-
heightmaps: new Map<string,
|
|
102
|
+
heightmaps: new Map<string, Int16Array>(),
|
|
99
103
|
allChunksLoaded: false,
|
|
100
104
|
mesherWork: false,
|
|
101
105
|
intersectMedia: null
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* Core types for the graphics backend system.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { WorldRendererConfig } from '../lib/worldrendererCommon'
|
|
9
8
|
import { PlayerStateReactive } from '../playerState/playerState'
|
|
10
9
|
import { ResourcesManagerTransferred } from '../resourcesManager'
|
|
11
10
|
import { WorldViewWorker } from '../worldView'
|
|
12
11
|
import { Vec3 } from 'vec3'
|
|
12
|
+
import { WorldRendererConfig } from './config'
|
|
13
13
|
|
|
14
14
|
// ============================================================================
|
|
15
15
|
// Graphics Backend Configuration
|
|
@@ -69,7 +69,7 @@ export interface NonReactiveState {
|
|
|
69
69
|
export interface RendererReactiveState {
|
|
70
70
|
world: {
|
|
71
71
|
chunksLoaded: Set<string>
|
|
72
|
-
heightmaps: Map<string,
|
|
72
|
+
heightmaps: Map<string, Int16Array>
|
|
73
73
|
allChunksLoaded: boolean
|
|
74
74
|
mesherWork: boolean
|
|
75
75
|
intersectMedia: any | null
|
|
@@ -5,7 +5,7 @@ import { Vec3 } from 'vec3'
|
|
|
5
5
|
import TypedEmitter from 'typed-emitter'
|
|
6
6
|
import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
|
7
7
|
import { subscribeKey } from 'valtio/utils'
|
|
8
|
-
import { proxy } from 'valtio'
|
|
8
|
+
import { proxy, subscribe } from 'valtio'
|
|
9
9
|
import type { ResourcesManagerTransferred } from '../resourcesManager/resourcesManager'
|
|
10
10
|
import { dynamicMcDataFiles } from './buildSharedConfig.mjs'
|
|
11
11
|
import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState, SoundSystem } from '../graphicsBackend/types'
|
|
@@ -64,7 +64,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
64
64
|
dirty(pos: Vec3, value: boolean): void
|
|
65
65
|
update(/* pos: Vec3, value: boolean */): void
|
|
66
66
|
chunkFinished(key: string): void
|
|
67
|
-
heightmap(key: string, heightmap:
|
|
67
|
+
heightmap(key: string, heightmap: Int16Array): void
|
|
68
68
|
}>
|
|
69
69
|
customTexturesDataUrl = undefined as string | undefined
|
|
70
70
|
workers: any[] = []
|
|
@@ -106,6 +106,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
106
106
|
debugStopGeometryUpdate = false
|
|
107
107
|
|
|
108
108
|
protocolCustomBlocks = new Map<string, CustomBlockModels>()
|
|
109
|
+
private heightmapDebounceTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
109
110
|
|
|
110
111
|
blockStateModelInfo = new Map<string, BlockStateModelInfo>()
|
|
111
112
|
|
|
@@ -115,6 +116,11 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
115
116
|
|
|
116
117
|
abstract changeBackgroundColor(color: [number, number, number]): void
|
|
117
118
|
|
|
119
|
+
/** Override in subclass to check if any enabled module requires heightmap data */
|
|
120
|
+
protected anyModuleRequiresHeightmap(): boolean {
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
|
|
118
124
|
worldRendererConfig: WorldRendererConfig
|
|
119
125
|
playerStateReactive: PlayerStateReactive
|
|
120
126
|
playerStateUtils: PlayerStateUtils
|
|
@@ -279,7 +285,11 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
279
285
|
|
|
280
286
|
onReactiveConfigUpdated<T extends keyof typeof this.worldRendererConfig>(key: T, callback: (value: typeof this.worldRendererConfig[T]) => void) {
|
|
281
287
|
callback(this.worldRendererConfig[key])
|
|
282
|
-
|
|
288
|
+
if ((key as any) === '*') {
|
|
289
|
+
subscribe(this.worldRendererConfig, callback as any)
|
|
290
|
+
} else {
|
|
291
|
+
subscribeKey(this.worldRendererConfig, key, callback)
|
|
292
|
+
}
|
|
283
293
|
}
|
|
284
294
|
|
|
285
295
|
onReactiveDebugUpdated<T extends keyof typeof this.reactiveDebugParams>(key: T, callback: (value: typeof this.reactiveDebugParams[T]) => void) {
|
|
@@ -428,7 +438,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
428
438
|
}
|
|
429
439
|
|
|
430
440
|
if (data.type === 'heightmap') {
|
|
431
|
-
this.reactiveState.world.heightmaps.set(data.key, new
|
|
441
|
+
this.reactiveState.world.heightmaps.set(data.key, new Int16Array(data.heightmap))
|
|
432
442
|
}
|
|
433
443
|
}
|
|
434
444
|
|
|
@@ -622,11 +632,11 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
622
632
|
customBlockModels: customBlockModels || undefined
|
|
623
633
|
})
|
|
624
634
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
635
|
+
this.workers[0].postMessage({
|
|
636
|
+
type: 'getHeightmap',
|
|
637
|
+
x,
|
|
638
|
+
z,
|
|
639
|
+
})
|
|
630
640
|
this.logWorkerWork(() => `-> chunk ${JSON.stringify({ x, z, chunkLength: chunk.length, customBlockModelsLength: customBlockModels ? Object.keys(customBlockModels).length : 0 })}`)
|
|
631
641
|
this.mesherLogReader?.chunkReceived(x, z, chunk.length)
|
|
632
642
|
const sectionHeight = this.getSectionHeight()
|
|
@@ -665,6 +675,13 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
665
675
|
|
|
666
676
|
removeColumn(x, z) {
|
|
667
677
|
delete this.loadedChunks[`${x},${z}`]
|
|
678
|
+
// Cancel any pending heightmap debounce for this chunk
|
|
679
|
+
const debounceKey = `${x},${z}`
|
|
680
|
+
const pendingTimer = this.heightmapDebounceTimers.get(debounceKey)
|
|
681
|
+
if (pendingTimer) {
|
|
682
|
+
clearTimeout(pendingTimer)
|
|
683
|
+
this.heightmapDebounceTimers.delete(debounceKey)
|
|
684
|
+
}
|
|
668
685
|
for (const worker of this.workers) {
|
|
669
686
|
worker.postMessage({ type: 'unloadChunk', x, z })
|
|
670
687
|
}
|
|
@@ -687,6 +704,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
687
704
|
}
|
|
688
705
|
}
|
|
689
706
|
this.highestBlocksByChunks.delete(`${x},${z}`)
|
|
707
|
+
this.reactiveState.world.heightmaps.delete(`${Math.floor(x / 16)},${Math.floor(z / 16)}`)
|
|
690
708
|
|
|
691
709
|
this.updateChunksStats()
|
|
692
710
|
|
|
@@ -848,6 +866,18 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
848
866
|
customBlockModels
|
|
849
867
|
})
|
|
850
868
|
}
|
|
869
|
+
// Re-request heightmap for the affected chunk after block change (debounced)
|
|
870
|
+
if (this.anyModuleRequiresHeightmap()) {
|
|
871
|
+
const chunkCornerX = Math.floor(pos.x / CHUNK_SIZE) * CHUNK_SIZE
|
|
872
|
+
const chunkCornerZ = Math.floor(pos.z / CHUNK_SIZE) * CHUNK_SIZE
|
|
873
|
+
const chunkKey2 = `${chunkCornerX},${chunkCornerZ}`
|
|
874
|
+
const existing = this.heightmapDebounceTimers.get(chunkKey2)
|
|
875
|
+
if (existing) clearTimeout(existing)
|
|
876
|
+
this.heightmapDebounceTimers.set(chunkKey2, setTimeout(() => {
|
|
877
|
+
this.heightmapDebounceTimers.delete(chunkKey2)
|
|
878
|
+
this.workers[0]?.postMessage({ type: 'getHeightmap', x: chunkCornerX, z: chunkCornerZ })
|
|
879
|
+
}, 100))
|
|
880
|
+
}
|
|
851
881
|
this.logWorkerWork(`-> blockUpdate ${JSON.stringify({ pos, stateId, customBlockModels })}`)
|
|
852
882
|
this.setSectionDirty(pos, true, true)
|
|
853
883
|
if (this.neighborChunkUpdates) {
|
|
@@ -1018,6 +1048,12 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
1018
1048
|
}
|
|
1019
1049
|
|
|
1020
1050
|
destroy() {
|
|
1051
|
+
// Cancel all pending heightmap debounce timers
|
|
1052
|
+
for (const timer of this.heightmapDebounceTimers.values()) {
|
|
1053
|
+
clearTimeout(timer)
|
|
1054
|
+
}
|
|
1055
|
+
this.heightmapDebounceTimers.clear()
|
|
1056
|
+
|
|
1021
1057
|
// Stop all workers
|
|
1022
1058
|
for (const worker of this.workers) {
|
|
1023
1059
|
worker.terminate()
|
package/src/mesher/mesher.ts
CHANGED
|
@@ -153,7 +153,7 @@ const handleMessage = data => {
|
|
|
153
153
|
break
|
|
154
154
|
}
|
|
155
155
|
case 'getHeightmap': {
|
|
156
|
-
const heightmap = new
|
|
156
|
+
const heightmap = new Int16Array(256)
|
|
157
157
|
|
|
158
158
|
const blockPos = new Vec3(0, 0, 0)
|
|
159
159
|
for (let z = 0; z < 16; z++) {
|
|
@@ -169,7 +169,7 @@ const handleMessage = data => {
|
|
|
169
169
|
block = world.getBlock(blockPos)
|
|
170
170
|
}
|
|
171
171
|
const index = z * 16 + x
|
|
172
|
-
heightmap[index] = block ? blockPos.y :
|
|
172
|
+
heightmap[index] = block ? blockPos.y : -32768
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
postMessage({ type: 'heightmap', key: `${Math.floor(data.x / 16)},${Math.floor(data.z / 16)}`, heightmap })
|
package/src/mesher/shared.ts
CHANGED
|
@@ -65,7 +65,7 @@ export interface MesherMainEvents {
|
|
|
65
65
|
geometry: { type: 'geometry'; key: string; geometry: MesherGeometryOutput; workerIndex: number };
|
|
66
66
|
sectionFinished: { type: 'sectionFinished'; key: string; workerIndex: number; processTime?: number };
|
|
67
67
|
blockStateModelInfo: { type: 'blockStateModelInfo'; info: Record<string, BlockStateModelInfo> };
|
|
68
|
-
heightmap: { type: 'heightmap'; key: string; heightmap:
|
|
68
|
+
heightmap: { type: 'heightmap'; key: string; heightmap: Int16Array };
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
export type MesherMainEvent = MesherMainEvents[keyof MesherMainEvents]
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
|
+
import { IndexedData } from 'minecraft-data'
|
|
2
3
|
import { EntityMesh, rendererSpecialHandled, EntityDebugFlags } from '../three/entity/EntityMesh'
|
|
3
4
|
|
|
4
|
-
export const displayEntitiesDebugList = (
|
|
5
|
+
export const displayEntitiesDebugList = (mcData: IndexedData) => {
|
|
6
|
+
const version = mcData.version.minecraftVersion!
|
|
7
|
+
|
|
5
8
|
// Create results container
|
|
6
9
|
const container = document.createElement('div')
|
|
7
10
|
container.style.cssText = `
|
|
@@ -36,7 +39,6 @@ export const displayEntitiesDebugList = (version: string) => {
|
|
|
36
39
|
textureMap?: boolean;
|
|
37
40
|
errors?: string[];
|
|
38
41
|
}> = []
|
|
39
|
-
const { mcData } = window
|
|
40
42
|
const entityNames = Object.keys(mcData.entitiesArray.reduce((acc, entity) => {
|
|
41
43
|
acc[entity.name] = true
|
|
42
44
|
return acc
|
|
@@ -64,8 +66,8 @@ export const displayEntitiesDebugList = (version: string) => {
|
|
|
64
66
|
|
|
65
67
|
const { mesh: entityMesh } = new EntityMesh(version, entity, undefined, {}, debugFlags)
|
|
66
68
|
// find the most distant pos child
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
globalThis.objects ??= {}
|
|
70
|
+
globalThis.objects[entity] = entityMesh
|
|
69
71
|
|
|
70
72
|
results.push({
|
|
71
73
|
entity,
|
|
@@ -27,7 +27,7 @@ export default class RailsCobwebScene extends BasePlaygroundScene {
|
|
|
27
27
|
for (let x = -squareSize; x <= squareSize; x++) {
|
|
28
28
|
for (let z = -squareSize; z <= squareSize; z++) {
|
|
29
29
|
const i = Math.abs(x + z) * squareSize
|
|
30
|
-
this.worldView!.
|
|
30
|
+
this.worldView!.setBlockStateId(this.targetPos.offset(x, 0, z), this.mcData.blocksByName[fullBlocks[i % fullBlocks.length].name]!.defaultState)
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
}
|
package/src/three/appShared.ts
CHANGED
|
@@ -25,8 +25,8 @@ export const getItemUv = (item: Record<string, any>, specificProps: ItemSpecific
|
|
|
25
25
|
try {
|
|
26
26
|
const name =
|
|
27
27
|
blockState
|
|
28
|
-
? loadedData.blocksByStateId[blockState]?.name
|
|
29
|
-
: typeof idOrName === 'number' ? loadedData.items[idOrName]?.name : idOrName
|
|
28
|
+
? globalThis.loadedData.blocksByStateId[blockState]?.name
|
|
29
|
+
: typeof idOrName === 'number' ? globalThis.loadedData.items[idOrName]?.name : idOrName
|
|
30
30
|
if (!name) throw new Error(`Item not found: ${idOrName}`)
|
|
31
31
|
|
|
32
32
|
const model = getItemModelName({
|
|
@@ -13,8 +13,8 @@ import * as THREE from 'three'
|
|
|
13
13
|
import Stats from 'stats.js'
|
|
14
14
|
import StatsGl from 'stats-gl'
|
|
15
15
|
import * as tween from '@tweenjs/tween.js'
|
|
16
|
-
import { WorldRendererConfig } from '../lib/worldrendererCommon'
|
|
17
16
|
import type { GraphicsInitOptions } from '../graphicsBackend/types'
|
|
17
|
+
import { WorldRendererConfig } from '../graphicsBackend'
|
|
18
18
|
|
|
19
19
|
// ============================================================================
|
|
20
20
|
// Types (co-located with implementation)
|
|
@@ -208,7 +208,7 @@ export class DocumentRenderer {
|
|
|
208
208
|
|
|
209
209
|
try {
|
|
210
210
|
this.renderer = new THREE.WebGLRenderer({
|
|
211
|
-
canvas: this.canvas,
|
|
211
|
+
canvas: this.canvas as HTMLCanvasElement,
|
|
212
212
|
preserveDrawingBuffer: true,
|
|
213
213
|
logarithmicDepthBuffer: true,
|
|
214
214
|
powerPreference: this.config.powerPreference
|
|
@@ -66,6 +66,8 @@ export const getBackendMethods = (worldRenderer: WorldRendererThree): any => {
|
|
|
66
66
|
launchFirework: worldRenderer.fireworks.launchFirework.bind(worldRenderer.fireworks),
|
|
67
67
|
// New method for updating skybox
|
|
68
68
|
setSkyboxImage: worldRenderer.skyboxRenderer.setSkyboxImage.bind(worldRenderer.skyboxRenderer),
|
|
69
|
+
// Rain methods
|
|
70
|
+
setRain: (newState: boolean) => worldRenderer.toggleModule('rain', newState),
|
|
69
71
|
async loadGeometryExport(exportData: any) {
|
|
70
72
|
// Import dynamically to avoid circular dependencies
|
|
71
73
|
const { applyWorldGeometryExport } = await import('./worldGeometryExport')
|
|
@@ -7,7 +7,6 @@ import { BlockModel } from 'mc-assets'
|
|
|
7
7
|
import { DebugGui } from '../lib/DebugGui'
|
|
8
8
|
import { SmoothSwitcher } from '../lib/smoothSwitcher'
|
|
9
9
|
import { watchProperty } from '../lib/utils/proxy'
|
|
10
|
-
import { WorldRendererConfig } from '../lib/worldrendererCommon'
|
|
11
10
|
import { getMyHand } from './hand'
|
|
12
11
|
import { WorldRendererThree } from './worldRendererThree'
|
|
13
12
|
import { disposeObject } from './threeJsUtils'
|
|
@@ -16,6 +15,7 @@ import { PlayerStateRenderer } from '../playerState/playerState'
|
|
|
16
15
|
import { getThreeBlockModelGroup } from '../mesher/standaloneRenderer'
|
|
17
16
|
import { IndexedData } from 'minecraft-data'
|
|
18
17
|
import type { ResourcesManagerTransferred } from '../resourcesManager'
|
|
18
|
+
import { WorldRendererConfig } from '../graphicsBackend'
|
|
19
19
|
|
|
20
20
|
const rotationPositionData = {
|
|
21
21
|
itemRight: {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
|
+
import { rainManifest } from './rain'
|
|
2
3
|
import { sciFiWorldRevealManifest } from './sciFiWorldReveal'
|
|
3
4
|
import { starfieldManifest } from './starfield'
|
|
4
5
|
|
|
5
6
|
export const BUILTIN_MODULES = {
|
|
6
7
|
starfield: starfieldManifest,
|
|
7
8
|
futuristicReveal: sciFiWorldRevealManifest,
|
|
9
|
+
rain: rainManifest,
|
|
8
10
|
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
import type { WorldRendererThree } from '../worldRendererThree'
|
|
4
|
+
import type { RendererModuleController, RendererModuleManifest } from '../rendererModuleSystem'
|
|
5
|
+
|
|
6
|
+
interface RainParticleData {
|
|
7
|
+
velocity: THREE.Vector3
|
|
8
|
+
age: number
|
|
9
|
+
despawnOffset: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const PARTICLE_COUNT = 2000
|
|
13
|
+
const RANGE = 32
|
|
14
|
+
const HEIGHT = 32
|
|
15
|
+
const FALL_SPEED_MIN = 0.2
|
|
16
|
+
const FALL_SPEED_MAX = 0.4
|
|
17
|
+
const HORIZONTAL_DRIFT = 0.02
|
|
18
|
+
const RESPAWN_BELOW = -5
|
|
19
|
+
|
|
20
|
+
export class RainModule implements RendererModuleController {
|
|
21
|
+
private instancedMesh?: THREE.InstancedMesh
|
|
22
|
+
private geometry?: THREE.BoxGeometry
|
|
23
|
+
private material?: THREE.MeshBasicMaterial
|
|
24
|
+
private particles: RainParticleData[] = []
|
|
25
|
+
private enabled = false
|
|
26
|
+
private readonly dummy = new THREE.Matrix4()
|
|
27
|
+
private readonly tempPosition = new THREE.Vector3()
|
|
28
|
+
private readonly tempQuaternion = new THREE.Quaternion()
|
|
29
|
+
private readonly tempScale = new THREE.Vector3()
|
|
30
|
+
|
|
31
|
+
constructor(private readonly worldRenderer: WorldRendererThree) { }
|
|
32
|
+
|
|
33
|
+
enable(): void {
|
|
34
|
+
if (this.enabled) return
|
|
35
|
+
this.enabled = true
|
|
36
|
+
if (!this.instancedMesh) {
|
|
37
|
+
this.createRain()
|
|
38
|
+
} else {
|
|
39
|
+
this.instancedMesh.visible = true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
disable(): void {
|
|
44
|
+
if (!this.enabled) return
|
|
45
|
+
this.enabled = false
|
|
46
|
+
if (this.instancedMesh) {
|
|
47
|
+
this.instancedMesh.visible = false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
autoEnableCheck(): boolean {
|
|
52
|
+
return this.worldRenderer.worldRendererConfig.isRaining === true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
render?: () => void = () => {
|
|
56
|
+
if (!this.enabled || !this.instancedMesh) return
|
|
57
|
+
|
|
58
|
+
const cameraPos = this.worldRenderer.getCameraPosition()
|
|
59
|
+
this.instancedMesh.position.copy(cameraPos)
|
|
60
|
+
|
|
61
|
+
const heightmaps = this.worldRenderer.reactiveState.world.heightmaps
|
|
62
|
+
|
|
63
|
+
const { dummy, tempPosition: position, tempQuaternion: quaternion, tempScale: scale } = this
|
|
64
|
+
|
|
65
|
+
// Cache chunk key lookup to avoid redundant Map.get and string allocation
|
|
66
|
+
let prevChunkX = NaN
|
|
67
|
+
let prevChunkZ = NaN
|
|
68
|
+
let cachedHeightmap: Int16Array | undefined
|
|
69
|
+
|
|
70
|
+
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
|
71
|
+
const particle = this.particles[i]
|
|
72
|
+
this.instancedMesh.getMatrixAt(i, dummy)
|
|
73
|
+
dummy.decompose(position, quaternion, scale)
|
|
74
|
+
|
|
75
|
+
position.add(particle.velocity)
|
|
76
|
+
|
|
77
|
+
const relativeY = position.y
|
|
78
|
+
const horizontalDist = Math.sqrt(position.x * position.x + position.z * position.z)
|
|
79
|
+
|
|
80
|
+
// Convert camera-relative position to world coordinates
|
|
81
|
+
const worldX = cameraPos.x + position.x
|
|
82
|
+
const worldY = cameraPos.y + position.y
|
|
83
|
+
const worldZ = cameraPos.z + position.z
|
|
84
|
+
|
|
85
|
+
// Look up heightmap for this world position (cached per chunk)
|
|
86
|
+
const chunkX = Math.floor(worldX / 16)
|
|
87
|
+
const chunkZ = Math.floor(worldZ / 16)
|
|
88
|
+
if (chunkX !== prevChunkX || chunkZ !== prevChunkZ) {
|
|
89
|
+
cachedHeightmap = heightmaps.get(`${chunkX},${chunkZ}`)
|
|
90
|
+
prevChunkX = chunkX
|
|
91
|
+
prevChunkZ = chunkZ
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const localX = ((Math.floor(worldX) % 16) + 16) % 16
|
|
95
|
+
const localZ = ((Math.floor(worldZ) % 16) + 16) % 16
|
|
96
|
+
const heightY = cachedHeightmap?.[localZ * 16 + localX]
|
|
97
|
+
|
|
98
|
+
// Respawn when: out of range, hit heightmap surface (heightY + 1 = block top face), or fell too far
|
|
99
|
+
const shouldRespawn = horizontalDist > RANGE ||
|
|
100
|
+
(heightY !== undefined && heightY !== -32768 && worldY <= heightY + 1 + particle.despawnOffset) ||
|
|
101
|
+
relativeY < RESPAWN_BELOW
|
|
102
|
+
|
|
103
|
+
if (shouldRespawn) {
|
|
104
|
+
this.respawnParticle(position)
|
|
105
|
+
const speed = FALL_SPEED_MIN + Math.random() * (FALL_SPEED_MAX - FALL_SPEED_MIN)
|
|
106
|
+
particle.velocity.set(
|
|
107
|
+
(Math.random() - 0.5) * HORIZONTAL_DRIFT,
|
|
108
|
+
-speed,
|
|
109
|
+
(Math.random() - 0.5) * HORIZONTAL_DRIFT,
|
|
110
|
+
)
|
|
111
|
+
particle.despawnOffset = Math.random() * 0.5
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
dummy.compose(position, quaternion, scale)
|
|
115
|
+
this.instancedMesh.setMatrixAt(i, dummy)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.instancedMesh.instanceMatrix.needsUpdate = true
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
dispose(): void {
|
|
122
|
+
if (this.instancedMesh) {
|
|
123
|
+
this.worldRenderer.scene.remove(this.instancedMesh)
|
|
124
|
+
}
|
|
125
|
+
this.geometry?.dispose()
|
|
126
|
+
this.material?.dispose()
|
|
127
|
+
this.instancedMesh = undefined
|
|
128
|
+
this.geometry = undefined
|
|
129
|
+
this.material = undefined
|
|
130
|
+
this.particles = []
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private createRain(): void {
|
|
134
|
+
this.geometry = new THREE.BoxGeometry(0.03, 0.3, 0.03)
|
|
135
|
+
this.material = new THREE.MeshBasicMaterial({
|
|
136
|
+
color: 0x44_66_99,
|
|
137
|
+
transparent: true,
|
|
138
|
+
opacity: 0.6,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
this.instancedMesh = new THREE.InstancedMesh(this.geometry, this.material, PARTICLE_COUNT)
|
|
142
|
+
this.instancedMesh.name = 'rain-particles'
|
|
143
|
+
|
|
144
|
+
const dummy = new THREE.Matrix4()
|
|
145
|
+
const position = new THREE.Vector3()
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
|
148
|
+
this.respawnParticle(position)
|
|
149
|
+
position.y = Math.random() * HEIGHT
|
|
150
|
+
dummy.setPosition(position)
|
|
151
|
+
this.instancedMesh.setMatrixAt(i, dummy)
|
|
152
|
+
|
|
153
|
+
const speed = FALL_SPEED_MIN + Math.random() * (FALL_SPEED_MAX - FALL_SPEED_MIN)
|
|
154
|
+
this.particles.push({
|
|
155
|
+
velocity: new THREE.Vector3(
|
|
156
|
+
(Math.random() - 0.5) * HORIZONTAL_DRIFT,
|
|
157
|
+
-speed,
|
|
158
|
+
(Math.random() - 0.5) * HORIZONTAL_DRIFT,
|
|
159
|
+
),
|
|
160
|
+
age: 0,
|
|
161
|
+
despawnOffset: Math.random() * 0.5,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.instancedMesh.instanceMatrix.needsUpdate = true
|
|
166
|
+
this.worldRenderer.scene.add(this.instancedMesh)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private respawnParticle(position: THREE.Vector3): void {
|
|
170
|
+
const angle = Math.random() * Math.PI * 2
|
|
171
|
+
const distance = Math.random() * RANGE
|
|
172
|
+
position.set(
|
|
173
|
+
Math.cos(angle) * distance,
|
|
174
|
+
HEIGHT,
|
|
175
|
+
Math.sin(angle) * distance,
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const rainManifest: RendererModuleManifest = {
|
|
181
|
+
id: 'rain',
|
|
182
|
+
controller: RainModule,
|
|
183
|
+
enabledDefault: false,
|
|
184
|
+
requiresHeightmap: true,
|
|
185
|
+
}
|
|
@@ -99,6 +99,15 @@ export class SciFiWorldRevealModule implements RendererModuleController {
|
|
|
99
99
|
this.reset()
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
toggle(): boolean {
|
|
103
|
+
if (this.enabled) {
|
|
104
|
+
this.disable()
|
|
105
|
+
} else {
|
|
106
|
+
this.enable()
|
|
107
|
+
}
|
|
108
|
+
return this.enabled
|
|
109
|
+
}
|
|
110
|
+
|
|
102
111
|
render?: () => void = () => {
|
|
103
112
|
if (!this.enabled) return
|
|
104
113
|
this.update(16)
|
|
@@ -56,6 +56,15 @@ export class StarfieldModule implements RendererModuleController {
|
|
|
56
56
|
this.removeStars()
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
toggle(): boolean {
|
|
60
|
+
if (this.enabled) {
|
|
61
|
+
this.disable()
|
|
62
|
+
} else {
|
|
63
|
+
this.enable()
|
|
64
|
+
}
|
|
65
|
+
return this.enabled
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
enablementCheck?: () => boolean = () => {
|
|
60
69
|
if (!this.currentTime) return false
|
|
61
70
|
const nightTime = 13_500
|
package/src/three/renderSlot.ts
CHANGED
|
@@ -17,10 +17,10 @@ export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: Res
|
|
|
17
17
|
modelName: string | null,
|
|
18
18
|
} => {
|
|
19
19
|
let itemModelName = model.modelName
|
|
20
|
-
const isItem = loadedData.itemsByName[itemModelName]
|
|
20
|
+
const isItem = globalThis.loadedData.itemsByName[itemModelName]
|
|
21
21
|
|
|
22
22
|
// #region normalize item name
|
|
23
|
-
if (versionToNumber(
|
|
23
|
+
if (versionToNumber(resourcesManager.currentResources!.version) < versionToNumber('1.13')) itemModelName = getRenamedData(isItem ? 'items' : 'blocks', itemModelName, resourcesManager.currentResources!.version, '1.13.1') as string
|
|
24
24
|
// #endregion
|
|
25
25
|
|
|
26
26
|
|
|
@@ -47,18 +47,18 @@ export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: Res
|
|
|
47
47
|
const blockToTopTexture = (r) => r.top ?? r
|
|
48
48
|
|
|
49
49
|
try {
|
|
50
|
-
if (!
|
|
50
|
+
if (!resourcesManager.currentResources?.itemsRenderer) throw new Error('Items renderer is not available')
|
|
51
51
|
itemTexture =
|
|
52
|
-
|
|
53
|
-
?? (model.originalItemName ?
|
|
54
|
-
??
|
|
52
|
+
resourcesManager.currentResources.itemsRenderer.getItemTexture(itemModelName, {}, false, fullBlockModelSupport)
|
|
53
|
+
?? (model.originalItemName ? resourcesManager.currentResources.itemsRenderer.getItemTexture(model.originalItemName, {}, false, fullBlockModelSupport) : undefined)
|
|
54
|
+
?? resourcesManager.currentResources.itemsRenderer.getItemTexture('item/missing_texture')!
|
|
55
55
|
} catch (err) {
|
|
56
56
|
// get resourcepack from resource manager
|
|
57
|
-
reportError?.(`Failed to render item ${itemModelName} (original: ${model.originalItemName}) on ${
|
|
58
|
-
itemTexture = blockToTopTexture(
|
|
57
|
+
reportError?.(`Failed to render item ${itemModelName} (original: ${model.originalItemName}) on ${resourcesManager.currentResources!.version} (resourcepack: TODO!): ${err.stack}`)
|
|
58
|
+
itemTexture = blockToTopTexture(resourcesManager.currentResources!.itemsRenderer!.getItemTexture('errored')!)
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
itemTexture ??= blockToTopTexture(
|
|
61
|
+
itemTexture ??= blockToTopTexture(resourcesManager.currentResources!.itemsRenderer!.getItemTexture('unknown')!)
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
if ('type' in itemTexture) {
|
|
@@ -10,6 +10,7 @@ export interface RendererModuleController {
|
|
|
10
10
|
dispose(): void
|
|
11
11
|
|
|
12
12
|
enablementCheck?: () => boolean
|
|
13
|
+
autoEnableCheck?: () => boolean // Called when config updates, returns true to enable, false to disable
|
|
13
14
|
render?: () => void
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -29,10 +30,13 @@ export interface RendererModuleManifest {
|
|
|
29
30
|
cannotBeDisabled?: boolean
|
|
30
31
|
slowSystemAutoDisable?: boolean
|
|
31
32
|
userSettingsSchema?: Record<string, any>
|
|
33
|
+
|
|
34
|
+
requiresHeightmap?: boolean
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
export interface RegisteredModule {
|
|
35
38
|
manifest: RendererModuleManifest
|
|
36
39
|
controller: RendererModuleController
|
|
37
40
|
enabled: boolean
|
|
41
|
+
toggle: () => boolean
|
|
38
42
|
}
|