minecraft-renderer 0.1.43 → 0.1.45
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 +35 -35
- package/dist/mesher.js.map +4 -4
- package/dist/mesherWasm.js +61 -61
- package/dist/minecraft-renderer.js +59 -59
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +458 -458
- package/package.json +1 -1
- package/src/graphicsBackend/appViewer.ts +19 -7
- package/src/graphicsBackend/types.ts +5 -1
- package/src/index.ts +33 -0
- package/src/lib/ui/newStats.ts +16 -2
- package/src/lib/worldrendererCommon.ts +2 -2
- package/src/mesher-shared/models.ts +28 -11
- package/src/mesher-shared/vertexShading.ts +35 -0
- package/src/three/entities.ts +22 -6
- package/src/three/graphicsBackendBase.ts +28 -20
- package/src/three/graphicsBackendOffThread.ts +1 -2
- package/src/three/menuBackground/activeView.ts +19 -0
- package/src/three/menuBackground/classic.ts +148 -0
- package/src/three/menuBackground/config.ts +23 -0
- package/src/three/menuBackground/defaultOptions.ts +141 -0
- package/src/three/menuBackground/futuristic.ts +859 -0
- package/src/three/menuBackground/index.ts +36 -0
- package/src/three/menuBackground/renderer.ts +97 -0
- package/src/three/menuBackground/shared.ts +3 -0
- package/src/three/menuBackground/types.ts +37 -0
- package/src/three/menuBackground/worldBlocks.ts +144 -0
- package/src/three/modules/rain.ts +21 -3
- package/src/three/waypointSprite.ts +108 -106
- package/src/three/worldRendererThree.ts +9 -12
- package/src/wasm-mesher/bridge/render-from-wasm.ts +57 -12
- package/src/three/panorama.ts +0 -312
- package/src/three/panoramaShared.ts +0 -2
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
import type { DocumentRenderer } from '../documentRenderer'
|
|
4
|
+
import { ResourcesManager } from '../../resourcesManager/resourcesManager'
|
|
5
|
+
import { MENU_BACKGROUND_MC_VERSION } from './shared'
|
|
6
|
+
import type { MenuBackgroundView } from './activeView'
|
|
7
|
+
import { resizeMenuBackgroundCamera } from './activeView'
|
|
8
|
+
import { loadThreeJsTextureFromBitmap } from '../threeJsUtils'
|
|
9
|
+
import { MENU_BACKGROUND_MOTION_DEFAULTS, MENU_BACKGROUND_OPTION_DEFAULTS } from './config'
|
|
10
|
+
|
|
11
|
+
export const FUTURISTIC_SCENE_IDS = ['galaxy', 'nether', 'end', 'cyber', 'light'] as const
|
|
12
|
+
export type FuturisticSceneId = typeof FUTURISTIC_SCENE_IDS[number]
|
|
13
|
+
|
|
14
|
+
export const FUTURISTIC_CAMERA_IDS = ['cruise', 'barrel', 'dive', 'orbit', 'snake'] as const
|
|
15
|
+
export type FuturisticCameraId = typeof FUTURISTIC_CAMERA_IDS[number]
|
|
16
|
+
|
|
17
|
+
export const FUTURISTIC_SCENE_LABELS: Record<FuturisticSceneId, string> = {
|
|
18
|
+
galaxy: 'Galaxy',
|
|
19
|
+
nether: 'Nether',
|
|
20
|
+
end: 'The End',
|
|
21
|
+
cyber: 'Cyber',
|
|
22
|
+
light: 'Light Space'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const FUTURISTIC_CAMERA_LABELS: Record<FuturisticCameraId, string> = {
|
|
26
|
+
cruise: 'Cruise',
|
|
27
|
+
barrel: 'Barrel',
|
|
28
|
+
dive: 'Dive',
|
|
29
|
+
orbit: 'Orbit',
|
|
30
|
+
snake: 'Snake'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Mouse parallax scale (HTML prototype uses 1). */
|
|
34
|
+
const MOUSE_INFLUENCE = 0.1
|
|
35
|
+
|
|
36
|
+
export interface FuturisticMenuBackgroundOptions {
|
|
37
|
+
useMinecraftTextures?: boolean
|
|
38
|
+
initialScene?: FuturisticSceneId
|
|
39
|
+
initialCamera?: FuturisticCameraId
|
|
40
|
+
initialBlockGroup?: MinecraftBlockGroupId
|
|
41
|
+
/** Camera path speed multiplier (0 = frozen path; mouse parallax unchanged). */
|
|
42
|
+
initialCameraSpeed?: number
|
|
43
|
+
/** Floating blocks + sky drift speed multiplier. */
|
|
44
|
+
initialBlockSpeed?: number
|
|
45
|
+
resourcesManager?: ResourcesManager
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Block pools for textured floating cubes (selected via {@link FuturisticMenuBackground.setBlockGroup}). */
|
|
49
|
+
export const MINECRAFT_BLOCK_GROUPS = {
|
|
50
|
+
mixed: [
|
|
51
|
+
'white_wool', 'cyan_wool', 'blue_wool', 'purple_wool',
|
|
52
|
+
'white_stained_glass', 'cyan_stained_glass', 'blue_stained_glass', 'purple_stained_glass',
|
|
53
|
+
'glowstone', 'sea_lantern', 'amethyst_block', 'copper_block', 'gold_block', 'diamond_block'
|
|
54
|
+
],
|
|
55
|
+
stainedGlass: [
|
|
56
|
+
'white_stained_glass', 'orange_stained_glass', 'magenta_stained_glass', 'light_blue_stained_glass',
|
|
57
|
+
'yellow_stained_glass', 'lime_stained_glass', 'pink_stained_glass', 'gray_stained_glass',
|
|
58
|
+
'light_gray_stained_glass', 'cyan_stained_glass', 'purple_stained_glass', 'blue_stained_glass',
|
|
59
|
+
'brown_stained_glass', 'green_stained_glass', 'red_stained_glass', 'black_stained_glass'
|
|
60
|
+
],
|
|
61
|
+
wool: [
|
|
62
|
+
'white_wool', 'orange_wool', 'magenta_wool', 'light_blue_wool', 'yellow_wool', 'lime_wool',
|
|
63
|
+
'pink_wool', 'gray_wool', 'light_gray_wool', 'cyan_wool', 'purple_wool', 'blue_wool',
|
|
64
|
+
'brown_wool', 'green_wool', 'red_wool', 'black_wool'
|
|
65
|
+
],
|
|
66
|
+
construction: [
|
|
67
|
+
'copper_block', 'exposed_copper', 'weathered_copper', 'oxidized_copper',
|
|
68
|
+
'cut_copper', 'exposed_cut_copper', 'weathered_cut_copper', 'oxidized_cut_copper',
|
|
69
|
+
'iron_block', 'gold_block', 'diamond_block', 'emerald_block', 'netherite_block',
|
|
70
|
+
'lapis_block', 'redstone_block', 'coal_block', 'quartz_block', 'amethyst_block',
|
|
71
|
+
'bricks', 'stone_bricks', 'deepslate_bricks', 'polished_blackstone'
|
|
72
|
+
],
|
|
73
|
+
glow: [
|
|
74
|
+
'glowstone', 'sea_lantern', 'shroomlight', 'ochre_froglight', 'verdant_froglight', 'pearlescent_froglight',
|
|
75
|
+
'redstone_lamp', 'beacon'
|
|
76
|
+
],
|
|
77
|
+
world: [
|
|
78
|
+
'grass_block', 'podzol', 'mycelium', 'dirt', 'coarse_dirt', 'rooted_dirt', 'mud', 'clay',
|
|
79
|
+
'stone', 'cobblestone', 'mossy_cobblestone', 'deepslate', 'cobbled_deepslate', 'tuff', 'calcite',
|
|
80
|
+
'sand', 'red_sand', 'gravel', 'snow_block',
|
|
81
|
+
'coal_ore', 'deepslate_coal_ore', 'iron_ore', 'deepslate_iron_ore', 'copper_ore', 'deepslate_copper_ore',
|
|
82
|
+
'gold_ore', 'deepslate_gold_ore', 'diamond_ore', 'deepslate_diamond_ore', 'emerald_ore', 'deepslate_emerald_ore',
|
|
83
|
+
'lapis_ore', 'deepslate_lapis_ore', 'redstone_ore', 'deepslate_redstone_ore', 'nether_gold_ore', 'ancient_debris',
|
|
84
|
+
'oak_log', 'birch_log', 'spruce_log', 'jungle_log', 'acacia_log', 'dark_oak_log', 'mangrove_log', 'cherry_log',
|
|
85
|
+
'netherrack', 'soul_sand', 'basalt', 'end_stone'
|
|
86
|
+
]
|
|
87
|
+
} as const
|
|
88
|
+
|
|
89
|
+
export const MINECRAFT_BLOCK_GROUP_IDS = ['mixed', 'stainedGlass', 'wool', 'construction', 'glow', 'world'] as const
|
|
90
|
+
export type MinecraftBlockGroupId = typeof MINECRAFT_BLOCK_GROUP_IDS[number]
|
|
91
|
+
|
|
92
|
+
export const MINECRAFT_BLOCK_GROUP_LABELS: Record<MinecraftBlockGroupId, string> = {
|
|
93
|
+
mixed: 'Mixed',
|
|
94
|
+
stainedGlass: 'Stained glass',
|
|
95
|
+
wool: 'Wool',
|
|
96
|
+
construction: 'Construction',
|
|
97
|
+
glow: 'Glow',
|
|
98
|
+
world: 'World (grass & ores)'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface ScenePalette {
|
|
102
|
+
bg: number
|
|
103
|
+
fog: number
|
|
104
|
+
fogD: number
|
|
105
|
+
blocks: number[]
|
|
106
|
+
emit: number[]
|
|
107
|
+
nebula: number[]
|
|
108
|
+
galFn: (b: number) => THREE.Color
|
|
109
|
+
ambient: number
|
|
110
|
+
dir: number
|
|
111
|
+
pt1: number
|
|
112
|
+
pt2: number
|
|
113
|
+
name: string
|
|
114
|
+
/** Vertical (or radial) sky gradient instead of a flat scene background. */
|
|
115
|
+
gradientBg?: { top: number, mid?: number, bottom: number, radial?: boolean }
|
|
116
|
+
starColor?: number
|
|
117
|
+
starOpacity?: number
|
|
118
|
+
galaxyOpacity?: number
|
|
119
|
+
nebulaOpacity?: number
|
|
120
|
+
edgeLineColor?: number
|
|
121
|
+
blockOpacity?: [number, number]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface CameraMode {
|
|
125
|
+
pos: (t: number, mx: number, my: number) => { x: number, y: number, z: number }
|
|
126
|
+
look: (t: number, mx: number, my: number) => { x: number, y: number, z: number }
|
|
127
|
+
roll: (t: number, mx?: number) => number
|
|
128
|
+
spd: number
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface FloatingBlock {
|
|
132
|
+
mesh: THREE.Mesh
|
|
133
|
+
spd: number
|
|
134
|
+
dx: number
|
|
135
|
+
dy: number
|
|
136
|
+
rx: number
|
|
137
|
+
ry: number
|
|
138
|
+
rz: number
|
|
139
|
+
minecraftBlockName?: string
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const PAL: Record<FuturisticSceneId, ScenePalette> = {
|
|
143
|
+
galaxy: {
|
|
144
|
+
bg: 0x02_04_12, fog: 0x02_04_12, fogD: 0.011,
|
|
145
|
+
blocks: [0x00_f0_ff, 0x00_d4_ff, 0x00_b8_ff, 0x00_e8_ff, 0x22_cc_ff, 0x00_a8_ff],
|
|
146
|
+
emit: [0x00_33_66, 0x00_22_55, 0x00_1a_44],
|
|
147
|
+
nebula: [0x00_11_33, 0x11_00_22, 0x00_11_22],
|
|
148
|
+
galFn: b => new THREE.Color(b * 0.05, b * 0.2, b),
|
|
149
|
+
ambient: 0x04_08_18, dir: 0x33_66_ff, pt1: 0x00_aa_ff, pt2: 0xff_44_ff, name: 'GALAXY'
|
|
150
|
+
},
|
|
151
|
+
nether: {
|
|
152
|
+
bg: 0x0e_01_00, fog: 0x0e_01_00, fogD: 0.016,
|
|
153
|
+
blocks: [0xff_22_00, 0xff_66_00, 0xff_99_00, 0xcc_11_00, 0xff_44_22, 0xff_aa_00],
|
|
154
|
+
emit: [0x22_08_00, 0x11_00_00, 0x33_11_00],
|
|
155
|
+
nebula: [0x1a_04_00, 0x0d_00_00, 0x1a_08_00],
|
|
156
|
+
galFn: b => new THREE.Color(b, b * 0.15, 0),
|
|
157
|
+
ambient: 0x18_02_00, dir: 0xff_33_00, pt1: 0xff_44_00, pt2: 0xff_aa_00, name: 'NETHER'
|
|
158
|
+
},
|
|
159
|
+
end: {
|
|
160
|
+
bg: 0x00_00_00, fog: 0x00_00_00, fogD: 0.009,
|
|
161
|
+
blocks: [0x77_22_aa, 0xaa_44_cc, 0x55_00_77, 0xdd_aa_ff, 0x33_00_55, 0xbb_aa_ff],
|
|
162
|
+
emit: [0x0a_00_15, 0x18_00_25, 0x05_00_10],
|
|
163
|
+
nebula: [0x08_00_18, 0x0d_00_15, 0x04_00_0e],
|
|
164
|
+
galFn: b => new THREE.Color(b * 0.4, 0, b),
|
|
165
|
+
ambient: 0x06_00_10, dir: 0x99_33_ff, pt1: 0xaa_44_ff, pt2: 0x44_00_aa, name: 'THE END'
|
|
166
|
+
},
|
|
167
|
+
cyber: {
|
|
168
|
+
bg: 0x00_0a_06, fog: 0x00_0a_06, fogD: 0.010,
|
|
169
|
+
blocks: [0x00_ff_ff, 0x00_ff_88, 0xaa_ff_00, 0x00_cc_ff, 0x66_ff_00, 0x00_ff_ee],
|
|
170
|
+
emit: [0x00_22_11, 0x00_1a_00, 0x00_1a_22],
|
|
171
|
+
nebula: [0x00_1a_12, 0x00_14_00, 0x00_12_1a],
|
|
172
|
+
galFn: b => new THREE.Color(0, b, b * 0.6),
|
|
173
|
+
ambient: 0x00_1a_0d, dir: 0x00_ff_aa, pt1: 0x00_ff_cc, pt2: 0x44_ff_00, name: 'CYBER'
|
|
174
|
+
},
|
|
175
|
+
light: {
|
|
176
|
+
bg: 0x88_98_b0,
|
|
177
|
+
fog: 0x78_88_a0,
|
|
178
|
+
fogD: 0.006,
|
|
179
|
+
gradientBg: { top: 0xd8_e4_f8, mid: 0xa0_b0_c8, bottom: 0x68_78_90, radial: true },
|
|
180
|
+
blocks: [
|
|
181
|
+
0xe8_f2_ff, 0xd0_e8_ff, 0xb8_d8_ff, 0xa0_c8_f8, 0x88_b8_f0, 0x70_a8_e8,
|
|
182
|
+
0x98_c8_ff, 0xc0_e0_ff, 0xf0_f8_ff, 0x78_b0_e8, 0xd8_ec_ff, 0xe0_e8_ff
|
|
183
|
+
],
|
|
184
|
+
emit: [0x68_98_d0, 0x88_b0_e0, 0xa8_c8_f0, 0xc0_d8_f8],
|
|
185
|
+
nebula: [0x90_a8_c8, 0xa8_c0_e0, 0xc0_d4_ec, 0xd8_e8_f8, 0x78_90_b0],
|
|
186
|
+
galFn: b => {
|
|
187
|
+
const c = new THREE.Color()
|
|
188
|
+
if (b < 0.4) {
|
|
189
|
+
c.lerpColors(new THREE.Color(0xf4_f8_ff), new THREE.Color(0xd8_e8_ff), b / 0.4)
|
|
190
|
+
} else if (b < 0.75) {
|
|
191
|
+
c.lerpColors(new THREE.Color(0xd8_e8_ff), new THREE.Color(0xa8_c8_f0), (b - 0.4) / 0.35)
|
|
192
|
+
} else {
|
|
193
|
+
c.lerpColors(new THREE.Color(0xa8_c8_f0), new THREE.Color(0x90_b0_e0), (b - 0.75) / 0.25)
|
|
194
|
+
}
|
|
195
|
+
return c
|
|
196
|
+
},
|
|
197
|
+
ambient: 0x78_88_a0,
|
|
198
|
+
dir: 0xd0_dce8,
|
|
199
|
+
pt1: 0xa8_c8_ff,
|
|
200
|
+
pt2: 0xc8_d8_ff,
|
|
201
|
+
name: 'LIGHT',
|
|
202
|
+
starColor: 0xe8_f0_ff,
|
|
203
|
+
starOpacity: 0.5,
|
|
204
|
+
galaxyOpacity: 0.7,
|
|
205
|
+
nebulaOpacity: 0.32,
|
|
206
|
+
edgeLineColor: 0x98_c0_e8,
|
|
207
|
+
blockOpacity: [0.42, 0.56]
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const CAMS: Record<FuturisticCameraId, CameraMode> = {
|
|
212
|
+
cruise: {
|
|
213
|
+
pos: (t, mx, my) => ({ x: Math.sin(t * 0.28) * 18 + Math.cos(t * 0.11) * 7 + mx * 10, y: Math.sin(t * 0.19) * 6 + Math.cos(t * 0.31) * 3 + my * 6, z: 0 }),
|
|
214
|
+
look: (t, mx, my) => ({ x: Math.sin((t + 0.18) * 0.28) * 18 + mx * 8, y: Math.sin((t + 0.18) * 0.19) * 6 + my * 4, z: -25 }),
|
|
215
|
+
roll: (t, mx = 0) => mx * 0.05 + Math.sin(t * 0.22) * 0.015,
|
|
216
|
+
spd: 0.18
|
|
217
|
+
},
|
|
218
|
+
barrel: {
|
|
219
|
+
pos: (t, mx, my) => {
|
|
220
|
+
const r = 10
|
|
221
|
+
const s = t * 2.4
|
|
222
|
+
return { x: Math.cos(s) * r + mx * 4, y: Math.sin(s) * r + my * 4, z: Math.sin(t * 0.4) * 8 }
|
|
223
|
+
},
|
|
224
|
+
look: t => ({ x: Math.sin(t * 0.5) * 5, y: Math.cos(t * 0.5) * 5, z: -30 }),
|
|
225
|
+
roll: t => t * 2.4 + Math.PI * 0.5,
|
|
226
|
+
spd: 0.24
|
|
227
|
+
},
|
|
228
|
+
dive: {
|
|
229
|
+
pos: (t, mx, my) => ({ x: Math.sin(t * 0.6) * 30 + mx * 8, y: Math.cos(t * 0.4) * 18 + my * 6, z: Math.sin(t * 0.3) * 12 }),
|
|
230
|
+
look: (t, mx, my) => ({ x: Math.sin(t * 0.6 + 0.2) * 30 + mx * 6, y: Math.cos(t * 0.4 + 0.2) * 18 - 8 + my * 4, z: -35 }),
|
|
231
|
+
roll: (t, mx = 0) => mx * 0.08 + Math.sin(t * 0.6) * 0.12,
|
|
232
|
+
spd: 0.3
|
|
233
|
+
},
|
|
234
|
+
orbit: {
|
|
235
|
+
pos: (t, mx, my) => ({ x: Math.cos(t * 0.5) * 20 + mx * 5, y: Math.sin(t * 0.25) * 10 + my * 5, z: Math.sin(t * 0.5) * 20 }),
|
|
236
|
+
look: () => ({ x: 0, y: 0, z: -60 }),
|
|
237
|
+
roll: t => Math.sin(t * 0.5) * 0.08,
|
|
238
|
+
spd: 0.15
|
|
239
|
+
},
|
|
240
|
+
snake: {
|
|
241
|
+
pos: (t, mx, my) => ({ x: Math.sin(t * 1.1) * 22 + Math.sin(t * 0.37) * 8 + mx * 10, y: Math.sin(t * 0.7) * 10 + mx * 4 + my * 8, z: 0 }),
|
|
242
|
+
look: (t, mx, my) => {
|
|
243
|
+
const la = t + 0.12
|
|
244
|
+
return { x: Math.sin(la * 1.1) * 22 + Math.sin(la * 0.37) * 8 + mx * 8, y: Math.sin(la * 0.7) * 10 + my * 6, z: -22 }
|
|
245
|
+
},
|
|
246
|
+
roll: (t, mx = 0) => mx * 0.1 + Math.sin(t * 1.1) * 0.06,
|
|
247
|
+
spd: 0.22
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const CAM_SPD: Record<FuturisticCameraId, number> = {
|
|
252
|
+
cruise: 1,
|
|
253
|
+
barrel: 1.6,
|
|
254
|
+
dive: 2.2,
|
|
255
|
+
orbit: 0.7,
|
|
256
|
+
snake: 1.4
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const BCOUNT = 250
|
|
260
|
+
const GCNT = 10_000
|
|
261
|
+
const NCNT = 3000
|
|
262
|
+
|
|
263
|
+
const rp = <T,>(arr: T[]) => arr[Math.floor(Math.random() * arr.length)]
|
|
264
|
+
|
|
265
|
+
const colorHex = (n: number) => '#' + n.toString(16).padStart(6, '0')
|
|
266
|
+
|
|
267
|
+
const makeSkyGradientTexture = (gradient: NonNullable<ScenePalette['gradientBg']>): THREE.CanvasTexture | null => {
|
|
268
|
+
if (typeof document === 'undefined') return null
|
|
269
|
+
const canvas = document.createElement('canvas')
|
|
270
|
+
canvas.width = 4
|
|
271
|
+
canvas.height = 512
|
|
272
|
+
const ctx = canvas.getContext('2d')
|
|
273
|
+
if (!ctx) return null
|
|
274
|
+
const w = canvas.width
|
|
275
|
+
const h = canvas.height
|
|
276
|
+
const grad = gradient.radial
|
|
277
|
+
? ctx.createRadialGradient(w / 2, h * 0.32, 0, w / 2, h * 0.32, h * 0.85)
|
|
278
|
+
: ctx.createLinearGradient(0, 0, 0, h)
|
|
279
|
+
grad.addColorStop(0, colorHex(gradient.top))
|
|
280
|
+
if (gradient.mid != null) grad.addColorStop(0.45, colorHex(gradient.mid))
|
|
281
|
+
grad.addColorStop(1, colorHex(gradient.bottom))
|
|
282
|
+
ctx.fillStyle = grad
|
|
283
|
+
ctx.fillRect(0, 0, w, h)
|
|
284
|
+
const tex = new THREE.CanvasTexture(canvas)
|
|
285
|
+
tex.needsUpdate = true
|
|
286
|
+
return tex
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export class FuturisticMenuBackground implements MenuBackgroundView {
|
|
290
|
+
readonly scene: THREE.Scene
|
|
291
|
+
readonly camera: THREE.PerspectiveCamera
|
|
292
|
+
|
|
293
|
+
private readonly ambient: THREE.AmbientLight
|
|
294
|
+
private readonly dir: THREE.DirectionalLight
|
|
295
|
+
private readonly pt1: THREE.PointLight
|
|
296
|
+
private readonly pt2: THREE.PointLight
|
|
297
|
+
private readonly blocks: FloatingBlock[] = []
|
|
298
|
+
private readonly bGroup = new THREE.Group()
|
|
299
|
+
private readonly galaxy: THREE.Points
|
|
300
|
+
private readonly nebula: THREE.Points
|
|
301
|
+
private readonly stars: THREE.Points
|
|
302
|
+
private readonly galGeo: THREE.BufferGeometry
|
|
303
|
+
private readonly nebGeo: THREE.BufferGeometry
|
|
304
|
+
private readonly bGeo = new THREE.BoxGeometry(1, 1, 1)
|
|
305
|
+
private readonly eGeo = new THREE.EdgesGeometry(this.bGeo)
|
|
306
|
+
|
|
307
|
+
private curScene: FuturisticSceneId
|
|
308
|
+
private curCam: FuturisticCameraId
|
|
309
|
+
private blockGroup: MinecraftBlockGroupId
|
|
310
|
+
private cameraSpeed: number
|
|
311
|
+
private blockSpeed: number
|
|
312
|
+
private camT = 0
|
|
313
|
+
private mx = 0
|
|
314
|
+
private my = 0
|
|
315
|
+
private tmx = 0
|
|
316
|
+
private tmy = 0
|
|
317
|
+
private transitioning = false
|
|
318
|
+
private useMinecraftTextures = false
|
|
319
|
+
private readonly resourcesManager?: ResourcesManager
|
|
320
|
+
private atlasTexture: THREE.Texture | null = null
|
|
321
|
+
private blockMaterialPool = new Map<string, THREE.MeshBasicMaterial>()
|
|
322
|
+
private gradientSky: THREE.Mesh | null = null
|
|
323
|
+
private gradientSkyTexture: THREE.CanvasTexture | null = null
|
|
324
|
+
private disposed = false
|
|
325
|
+
private animTime = 0
|
|
326
|
+
|
|
327
|
+
constructor(
|
|
328
|
+
private readonly documentRenderer: DocumentRenderer,
|
|
329
|
+
options: FuturisticMenuBackgroundOptions = {},
|
|
330
|
+
private readonly abortSignal?: AbortSignal
|
|
331
|
+
) {
|
|
332
|
+
const d = MENU_BACKGROUND_OPTION_DEFAULTS
|
|
333
|
+
this.curScene = options.initialScene ?? d.futuristicScene
|
|
334
|
+
this.curCam = options.initialCamera ?? d.futuristicCamera
|
|
335
|
+
this.blockGroup = options.initialBlockGroup ?? d.futuristicBlockGroup
|
|
336
|
+
this.cameraSpeed = options.initialCameraSpeed ?? MENU_BACKGROUND_MOTION_DEFAULTS.camera
|
|
337
|
+
this.blockSpeed = options.initialBlockSpeed ?? MENU_BACKGROUND_MOTION_DEFAULTS.block
|
|
338
|
+
this.useMinecraftTextures = options.useMinecraftTextures ?? d.minecraftTextures
|
|
339
|
+
this.resourcesManager = options.resourcesManager
|
|
340
|
+
|
|
341
|
+
const pal = PAL[this.curScene]
|
|
342
|
+
this.scene = new THREE.Scene()
|
|
343
|
+
this.scene.fog = new THREE.FogExp2(pal.fog, pal.fogD)
|
|
344
|
+
this.applyScenePalette(pal)
|
|
345
|
+
|
|
346
|
+
this.camera = new THREE.PerspectiveCamera(
|
|
347
|
+
80,
|
|
348
|
+
this.documentRenderer.canvas.width / this.documentRenderer.canvas.height,
|
|
349
|
+
0.1,
|
|
350
|
+
700
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
this.ambient = new THREE.AmbientLight(pal.ambient, 2.5)
|
|
354
|
+
this.scene.add(this.ambient)
|
|
355
|
+
this.dir = new THREE.DirectionalLight(pal.dir, 4)
|
|
356
|
+
this.dir.position.set(1, 1, 0)
|
|
357
|
+
this.scene.add(this.dir)
|
|
358
|
+
this.pt1 = new THREE.PointLight(pal.pt1, 5, 100)
|
|
359
|
+
this.scene.add(this.pt1)
|
|
360
|
+
this.pt2 = new THREE.PointLight(pal.pt2, 4, 80)
|
|
361
|
+
this.pt2.position.set(30, 20, -30)
|
|
362
|
+
this.scene.add(this.pt2)
|
|
363
|
+
|
|
364
|
+
for (let i = 0; i < BCOUNT; i++) this.spawnBlock(pal, true)
|
|
365
|
+
if (!this.useMinecraftTextures) {
|
|
366
|
+
const edgeColor = pal.edgeLineColor ?? 0x00_f5_ff
|
|
367
|
+
for (let i = 0; i < 40; i++) {
|
|
368
|
+
const lm = new THREE.LineBasicMaterial({ color: edgeColor, transparent: true, opacity: 0.25 })
|
|
369
|
+
rp(this.blocks).mesh.add(new THREE.LineSegments(this.eGeo, lm))
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
this.scene.add(this.bGroup)
|
|
373
|
+
|
|
374
|
+
const sGeo = new THREE.BufferGeometry()
|
|
375
|
+
const sp = new Float32Array(5000 * 3)
|
|
376
|
+
for (let i = 0; i < 5000 * 3; i++) sp[i] = (Math.random() - 0.5) * 500
|
|
377
|
+
sGeo.setAttribute('position', new THREE.BufferAttribute(sp, 3))
|
|
378
|
+
this.stars = new THREE.Points(sGeo, new THREE.PointsMaterial({
|
|
379
|
+
color: pal.starColor ?? 0xff_ff_ff,
|
|
380
|
+
size: 0.3,
|
|
381
|
+
transparent: true,
|
|
382
|
+
opacity: pal.starOpacity ?? 0.7,
|
|
383
|
+
sizeAttenuation: true
|
|
384
|
+
}))
|
|
385
|
+
this.scene.add(this.stars)
|
|
386
|
+
|
|
387
|
+
this.galGeo = new THREE.BufferGeometry()
|
|
388
|
+
const gp = new Float32Array(GCNT * 3)
|
|
389
|
+
const gc = new Float32Array(GCNT * 3)
|
|
390
|
+
for (let i = 0; i < GCNT; i++) {
|
|
391
|
+
const arm = i % 3
|
|
392
|
+
const t = Math.random()
|
|
393
|
+
const ang = (arm / 3) * Math.PI * 2 + t * Math.PI * 5
|
|
394
|
+
const r = t * 90 + Math.random() * 10
|
|
395
|
+
const sc = (1 - t) * 18
|
|
396
|
+
gp[i * 3] = Math.cos(ang) * r + (Math.random() - 0.5) * sc
|
|
397
|
+
gp[i * 3 + 1] = (Math.random() - 0.5) * 7
|
|
398
|
+
gp[i * 3 + 2] = Math.sin(ang) * r + (Math.random() - 0.5) * sc - 180
|
|
399
|
+
const b = 0.2 + t * 0.8
|
|
400
|
+
const c = pal.galFn(b)
|
|
401
|
+
gc[i * 3] = c.r
|
|
402
|
+
gc[i * 3 + 1] = c.g
|
|
403
|
+
gc[i * 3 + 2] = c.b
|
|
404
|
+
}
|
|
405
|
+
this.galGeo.setAttribute('position', new THREE.BufferAttribute(gp, 3))
|
|
406
|
+
this.galGeo.setAttribute('color', new THREE.BufferAttribute(gc, 3))
|
|
407
|
+
this.galaxy = new THREE.Points(this.galGeo, new THREE.PointsMaterial({
|
|
408
|
+
size: 0.9, vertexColors: true, transparent: true, opacity: pal.galaxyOpacity ?? 0.55, sizeAttenuation: true
|
|
409
|
+
}))
|
|
410
|
+
this.scene.add(this.galaxy)
|
|
411
|
+
|
|
412
|
+
this.nebGeo = new THREE.BufferGeometry()
|
|
413
|
+
const np = new Float32Array(NCNT * 3)
|
|
414
|
+
const nc = new Float32Array(NCNT * 3)
|
|
415
|
+
for (let i = 0; i < NCNT; i++) {
|
|
416
|
+
const r = 25 + Math.random() * 110
|
|
417
|
+
const th = Math.random() * Math.PI * 2
|
|
418
|
+
const ph = (Math.random() - 0.5) * Math.PI * 0.5
|
|
419
|
+
np[i * 3] = r * Math.cos(th) * Math.cos(ph)
|
|
420
|
+
np[i * 3 + 1] = r * Math.sin(ph) * 0.6
|
|
421
|
+
np[i * 3 + 2] = r * Math.sin(th) * Math.cos(ph) - 80
|
|
422
|
+
const c = new THREE.Color(rp(pal.nebula))
|
|
423
|
+
nc[i * 3] = c.r
|
|
424
|
+
nc[i * 3 + 1] = c.g
|
|
425
|
+
nc[i * 3 + 2] = c.b
|
|
426
|
+
}
|
|
427
|
+
this.nebGeo.setAttribute('position', new THREE.BufferAttribute(np, 3))
|
|
428
|
+
this.nebGeo.setAttribute('color', new THREE.BufferAttribute(nc, 3))
|
|
429
|
+
this.nebula = new THREE.Points(this.nebGeo, new THREE.PointsMaterial({
|
|
430
|
+
size: 3, vertexColors: true, transparent: true, opacity: pal.nebulaOpacity ?? 0.3, sizeAttenuation: true
|
|
431
|
+
}))
|
|
432
|
+
this.scene.add(this.nebula)
|
|
433
|
+
|
|
434
|
+
this.addBackgroundTextPlane()
|
|
435
|
+
this.setupMouseTracking()
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async init() {
|
|
439
|
+
if (this.useMinecraftTextures) {
|
|
440
|
+
try {
|
|
441
|
+
await this.loadMinecraftTextures()
|
|
442
|
+
} catch (err) {
|
|
443
|
+
console.warn('[FuturisticMenuBackground] Failed to load Minecraft textures, using solid colors:', err)
|
|
444
|
+
this.useMinecraftTextures = false
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private applyScenePalette(pal: ScenePalette) {
|
|
450
|
+
this.documentRenderer.renderer.setClearColor(pal.bg)
|
|
451
|
+
if (pal.gradientBg) {
|
|
452
|
+
this.scene.background = null
|
|
453
|
+
if (!this.gradientSky) {
|
|
454
|
+
const tex = makeSkyGradientTexture(pal.gradientBg)
|
|
455
|
+
if (tex) {
|
|
456
|
+
this.gradientSkyTexture = tex
|
|
457
|
+
this.gradientSky = new THREE.Mesh(
|
|
458
|
+
new THREE.PlaneGeometry(900, 700),
|
|
459
|
+
new THREE.MeshBasicMaterial({
|
|
460
|
+
map: tex,
|
|
461
|
+
depthWrite: false,
|
|
462
|
+
side: THREE.DoubleSide
|
|
463
|
+
})
|
|
464
|
+
)
|
|
465
|
+
this.gradientSky.position.set(0, 0, -280)
|
|
466
|
+
this.gradientSky.renderOrder = -1000
|
|
467
|
+
this.scene.add(this.gradientSky)
|
|
468
|
+
} else {
|
|
469
|
+
this.scene.background = new THREE.Color(pal.bg)
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
this.gradientSky.visible = true
|
|
473
|
+
if (this.gradientSkyTexture && pal.gradientBg) {
|
|
474
|
+
const next = makeSkyGradientTexture(pal.gradientBg)
|
|
475
|
+
if (next) {
|
|
476
|
+
this.gradientSkyTexture.dispose()
|
|
477
|
+
this.gradientSkyTexture = next
|
|
478
|
+
; (this.gradientSky.material as THREE.MeshBasicMaterial).map = next
|
|
479
|
+
; (this.gradientSky.material as THREE.MeshBasicMaterial).needsUpdate = true
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
this.scene.background = new THREE.Color(pal.bg)
|
|
485
|
+
if (this.gradientSky) this.gradientSky.visible = false
|
|
486
|
+
}
|
|
487
|
+
if (this.scene.fog instanceof THREE.FogExp2) {
|
|
488
|
+
this.scene.fog.color.set(pal.fog)
|
|
489
|
+
this.scene.fog.density = pal.fogD
|
|
490
|
+
}
|
|
491
|
+
const starMat = this.stars?.material as THREE.PointsMaterial | undefined
|
|
492
|
+
if (starMat) {
|
|
493
|
+
starMat.color.set(pal.starColor ?? 0xff_ff_ff)
|
|
494
|
+
starMat.opacity = pal.starOpacity ?? 0.7
|
|
495
|
+
}
|
|
496
|
+
const galMat = this.galaxy?.material as THREE.PointsMaterial | undefined
|
|
497
|
+
if (galMat) galMat.opacity = pal.galaxyOpacity ?? 0.55
|
|
498
|
+
const nebMat = this.nebula?.material as THREE.PointsMaterial | undefined
|
|
499
|
+
if (nebMat) nebMat.opacity = pal.nebulaOpacity ?? 0.3
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
private setupMouseTracking() {
|
|
503
|
+
const onMove = (e: MouseEvent) => {
|
|
504
|
+
const w = typeof window !== 'undefined' ? window.innerWidth : this.documentRenderer.canvas.width
|
|
505
|
+
const h = typeof window !== 'undefined' ? window.innerHeight : this.documentRenderer.canvas.height
|
|
506
|
+
this.tmx = (e.clientX / w - 0.5) * 2
|
|
507
|
+
this.tmy = -(e.clientY / h - 0.5) * 2
|
|
508
|
+
}
|
|
509
|
+
const target = typeof document !== 'undefined' ? document : undefined
|
|
510
|
+
target?.addEventListener('mousemove', onMove, { signal: this.abortSignal })
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private addBackgroundTextPlane() {
|
|
514
|
+
const tw = 2048
|
|
515
|
+
const th = 768
|
|
516
|
+
const tc = typeof document !== 'undefined' ? document.createElement('canvas') : null
|
|
517
|
+
if (!tc) return
|
|
518
|
+
tc.width = tw
|
|
519
|
+
tc.height = th
|
|
520
|
+
const ctx = tc.getContext('2d')
|
|
521
|
+
if (!ctx) return
|
|
522
|
+
ctx.clearRect(0, 0, tw, th)
|
|
523
|
+
ctx.save()
|
|
524
|
+
ctx.font = 'bold 560px Orbitron, sans-serif'
|
|
525
|
+
ctx.textAlign = 'center'
|
|
526
|
+
ctx.textBaseline = 'middle'
|
|
527
|
+
ctx.shadowColor = 'rgba(0,200,255,0.35)'
|
|
528
|
+
ctx.shadowBlur = 80
|
|
529
|
+
ctx.fillStyle = 'rgba(255,255,255,0.055)'
|
|
530
|
+
ctx.fillText('V2', tw * 0.28, th * 0.5)
|
|
531
|
+
ctx.restore()
|
|
532
|
+
ctx.save()
|
|
533
|
+
ctx.font = 'bold 148px Orbitron, sans-serif'
|
|
534
|
+
ctx.textAlign = 'center'
|
|
535
|
+
ctx.textBaseline = 'middle'
|
|
536
|
+
ctx.shadowColor = 'rgba(0,200,255,0.2)'
|
|
537
|
+
ctx.shadowBlur = 40
|
|
538
|
+
ctx.fillStyle = 'rgba(255,255,255,0.038)'
|
|
539
|
+
ctx.fillText('by ZARDOY', tw * 0.72, th * 0.52)
|
|
540
|
+
ctx.restore()
|
|
541
|
+
|
|
542
|
+
const tex = new THREE.CanvasTexture(tc)
|
|
543
|
+
const plane = new THREE.Mesh(
|
|
544
|
+
new THREE.PlaneGeometry(280, 105),
|
|
545
|
+
new THREE.MeshBasicMaterial({
|
|
546
|
+
map: tex,
|
|
547
|
+
transparent: true,
|
|
548
|
+
opacity: 1,
|
|
549
|
+
depthWrite: false,
|
|
550
|
+
side: THREE.DoubleSide,
|
|
551
|
+
blending: THREE.AdditiveBlending
|
|
552
|
+
})
|
|
553
|
+
)
|
|
554
|
+
plane.position.set(8, 4, -320)
|
|
555
|
+
this.scene.add(plane)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private spawnBlock(pal: ScenePalette, init: boolean, blockName?: string) {
|
|
559
|
+
const mat = this.createBlockMaterial(pal, blockName)
|
|
560
|
+
const mesh = new THREE.Mesh(this.bGeo, mat)
|
|
561
|
+
const s = 0.3 + Math.random() * 3
|
|
562
|
+
mesh.scale.setScalar(s)
|
|
563
|
+
mesh.position.set(
|
|
564
|
+
(Math.random() - 0.5) * 140,
|
|
565
|
+
(Math.random() - 0.5) * 70,
|
|
566
|
+
init ? -(Math.random() * 140) : -155 - Math.random() * 30
|
|
567
|
+
)
|
|
568
|
+
mesh.rotation.set(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2)
|
|
569
|
+
const d: FloatingBlock = {
|
|
570
|
+
mesh,
|
|
571
|
+
spd: 0.04 + Math.random() * 0.12,
|
|
572
|
+
dx: (Math.random() - 0.5) * 0.012,
|
|
573
|
+
dy: (Math.random() - 0.5) * 0.008,
|
|
574
|
+
rx: (Math.random() - 0.5) * 0.012,
|
|
575
|
+
ry: (Math.random() - 0.5) * 0.012,
|
|
576
|
+
rz: (Math.random() - 0.5) * 0.01,
|
|
577
|
+
minecraftBlockName: blockName
|
|
578
|
+
}
|
|
579
|
+
this.blocks.push(d)
|
|
580
|
+
this.bGroup.add(mesh)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
private createBlockMaterial(pal: ScenePalette, blockName?: string): THREE.MeshBasicMaterial {
|
|
584
|
+
if (this.useMinecraftTextures && blockName) {
|
|
585
|
+
const cached = this.blockMaterialPool.get(blockName)
|
|
586
|
+
if (cached) return cached.clone()
|
|
587
|
+
}
|
|
588
|
+
// Unlit neon cubes — no specular / scene-light response
|
|
589
|
+
const [opMin, opMax] = pal.blockOpacity ?? [0.32, 0.46]
|
|
590
|
+
return new THREE.MeshBasicMaterial({
|
|
591
|
+
color: rp(pal.blocks),
|
|
592
|
+
transparent: true,
|
|
593
|
+
opacity: opMin + Math.random() * (opMax - opMin),
|
|
594
|
+
depthWrite: false,
|
|
595
|
+
blending: THREE.AdditiveBlending
|
|
596
|
+
})
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
private removeBlockEdgeLines() {
|
|
600
|
+
for (const block of this.blocks) {
|
|
601
|
+
const edges = block.mesh.children.filter(c => c instanceof THREE.LineSegments)
|
|
602
|
+
for (const edge of edges) {
|
|
603
|
+
block.mesh.remove(edge)
|
|
604
|
+
edge.geometry?.dispose()
|
|
605
|
+
const mat = (edge as THREE.LineSegments).material
|
|
606
|
+
if (Array.isArray(mat)) mat.forEach(m => m.dispose())
|
|
607
|
+
else mat.dispose()
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
private async ensureMcDataLoaded() {
|
|
613
|
+
const loadMcData = (globalThis as { _LOAD_MC_DATA?: () => Promise<void> })._LOAD_MC_DATA
|
|
614
|
+
if (loadMcData) {
|
|
615
|
+
await loadMcData()
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
private resolveBlockAtlasUv(
|
|
620
|
+
blockName: string,
|
|
621
|
+
textures: Record<string, { u: number, v: number, su?: number, sv?: number }>,
|
|
622
|
+
atlasJson: { suSv: number }
|
|
623
|
+
): { u: number, v: number, su: number, sv: number } | null {
|
|
624
|
+
const pick = (key: string) => {
|
|
625
|
+
const tex = textures[key]
|
|
626
|
+
if (!tex) return null
|
|
627
|
+
return {
|
|
628
|
+
u: tex.u,
|
|
629
|
+
v: tex.v,
|
|
630
|
+
su: tex.su ?? atlasJson.suSv,
|
|
631
|
+
sv: tex.sv ?? atlasJson.suSv
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (textures[blockName]) return pick(blockName)
|
|
635
|
+
for (const suffix of ['_top', '_side', '_front', '_0']) {
|
|
636
|
+
const uv = pick(`${blockName}${suffix}`)
|
|
637
|
+
if (uv) return uv
|
|
638
|
+
}
|
|
639
|
+
for (const key of Object.keys(textures)) {
|
|
640
|
+
if (key.startsWith(blockName)) return pick(key)
|
|
641
|
+
}
|
|
642
|
+
return null
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private async loadMinecraftTextures() {
|
|
646
|
+
await this.ensureMcDataLoaded()
|
|
647
|
+
|
|
648
|
+
const resourcesManager = this.resourcesManager ?? new ResourcesManager()
|
|
649
|
+
const needsAssetUpdate = !resourcesManager.currentResources?.blocksAtlasImage
|
|
650
|
+
if (needsAssetUpdate) {
|
|
651
|
+
resourcesManager.currentConfig = {
|
|
652
|
+
...resourcesManager.currentConfig,
|
|
653
|
+
version: MENU_BACKGROUND_MC_VERSION,
|
|
654
|
+
noInventoryGui: true
|
|
655
|
+
}
|
|
656
|
+
await resourcesManager.updateAssetsData?.({})
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const resources = resourcesManager.currentResources
|
|
660
|
+
if (!resources?.blocksAtlasImage || !resources.blocksAtlasJson) {
|
|
661
|
+
throw new Error('Block atlas not available')
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const atlasJson = resources.blocksAtlasJson
|
|
665
|
+
const textures = atlasJson.textures
|
|
666
|
+
|
|
667
|
+
// Same path as WorldRendererThree — ImageBitmap → canvas Texture
|
|
668
|
+
this.atlasTexture = loadThreeJsTextureFromBitmap(resources.blocksAtlasImage)
|
|
669
|
+
this.atlasTexture.flipY = false
|
|
670
|
+
this.atlasTexture.needsUpdate = true
|
|
671
|
+
|
|
672
|
+
for (const blockName of MINECRAFT_BLOCK_GROUPS[this.blockGroup]) {
|
|
673
|
+
const uv = this.resolveBlockAtlasUv(blockName, textures, atlasJson)
|
|
674
|
+
if (!uv) continue
|
|
675
|
+
const map = this.atlasTexture.clone()
|
|
676
|
+
map.flipY = false
|
|
677
|
+
map.offset.set(uv.u, uv.v)
|
|
678
|
+
map.repeat.set(uv.su, uv.sv)
|
|
679
|
+
map.needsUpdate = true
|
|
680
|
+
const isGlass = blockName.includes('glass')
|
|
681
|
+
const mat = new THREE.MeshBasicMaterial({
|
|
682
|
+
map,
|
|
683
|
+
side: THREE.DoubleSide,
|
|
684
|
+
transparent: isGlass,
|
|
685
|
+
opacity: isGlass ? 0.85 : 1,
|
|
686
|
+
alphaTest: isGlass ? 0.08 : 0,
|
|
687
|
+
depthWrite: true,
|
|
688
|
+
toneMapped: false
|
|
689
|
+
})
|
|
690
|
+
this.blockMaterialPool.set(blockName, mat)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (this.blockMaterialPool.size === 0) {
|
|
694
|
+
throw new Error('No block textures resolved from atlas (check block names vs atlas keys)')
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
this.removeBlockEdgeLines()
|
|
698
|
+
|
|
699
|
+
for (const block of this.blocks) {
|
|
700
|
+
const name = rp([...this.blockMaterialPool.keys()])
|
|
701
|
+
block.minecraftBlockName = name
|
|
702
|
+
block.mesh.material = this.blockMaterialPool.get(name)!.clone()
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
setScene(name: FuturisticSceneId) {
|
|
707
|
+
if (!(FUTURISTIC_SCENE_IDS as readonly string[]).includes(name)) return
|
|
708
|
+
if (name === this.curScene || this.transitioning) return
|
|
709
|
+
this.transitioning = true
|
|
710
|
+
this.curScene = name
|
|
711
|
+
const pal = PAL[name]
|
|
712
|
+
setTimeout(() => {
|
|
713
|
+
if (this.disposed) return
|
|
714
|
+
this.applyScenePalette(pal)
|
|
715
|
+
this.ambient.color.set(pal.ambient)
|
|
716
|
+
this.dir.color.set(pal.dir)
|
|
717
|
+
this.pt1.color.set(pal.pt1)
|
|
718
|
+
this.pt2.color.set(pal.pt2)
|
|
719
|
+
for (const b of this.blocks) {
|
|
720
|
+
if (this.useMinecraftTextures && b.minecraftBlockName) continue
|
|
721
|
+
if (b.mesh.material instanceof THREE.MeshBasicMaterial) {
|
|
722
|
+
b.mesh.material.color.set(rp(pal.blocks))
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const ncA = this.nebGeo.attributes.color as THREE.BufferAttribute
|
|
726
|
+
for (let i = 0; i < NCNT; i++) {
|
|
727
|
+
const c = new THREE.Color(rp(pal.nebula))
|
|
728
|
+
ncA.setXYZ(i, c.r, c.g, c.b)
|
|
729
|
+
}
|
|
730
|
+
ncA.needsUpdate = true
|
|
731
|
+
const gcA = this.galGeo.attributes.color as THREE.BufferAttribute
|
|
732
|
+
for (let i = 0; i < GCNT; i++) {
|
|
733
|
+
const b = 0.2 + Math.random() * 0.8
|
|
734
|
+
const c = pal.galFn(b)
|
|
735
|
+
gcA.setXYZ(i, c.r, c.g, c.b)
|
|
736
|
+
}
|
|
737
|
+
gcA.needsUpdate = true
|
|
738
|
+
this.transitioning = false
|
|
739
|
+
}, 150)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
setCamera(name: FuturisticCameraId) {
|
|
743
|
+
if (!(FUTURISTIC_CAMERA_IDS as readonly string[]).includes(name)) return
|
|
744
|
+
this.curCam = name
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
setCameraSpeed(speed: number) {
|
|
748
|
+
this.cameraSpeed = Math.max(0, speed)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
setBlockSpeed(speed: number) {
|
|
752
|
+
this.blockSpeed = Math.max(0, speed)
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
async setBlockGroup(name: MinecraftBlockGroupId) {
|
|
756
|
+
if (!(MINECRAFT_BLOCK_GROUP_IDS as readonly string[]).includes(name)) return
|
|
757
|
+
if (name === this.blockGroup) return
|
|
758
|
+
this.blockGroup = name
|
|
759
|
+
if (!this.useMinecraftTextures || this.disposed) return
|
|
760
|
+
for (const mat of this.blockMaterialPool.values()) {
|
|
761
|
+
mat.map?.dispose()
|
|
762
|
+
mat.dispose()
|
|
763
|
+
}
|
|
764
|
+
this.blockMaterialPool.clear()
|
|
765
|
+
try {
|
|
766
|
+
await this.loadMinecraftTextures()
|
|
767
|
+
} catch (err) {
|
|
768
|
+
console.warn('[FuturisticMenuBackground] Failed to reload block group textures:', err)
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
getSceneId(): FuturisticSceneId {
|
|
773
|
+
return this.curScene
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
getCameraId(): FuturisticCameraId {
|
|
777
|
+
return this.curCam
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
getBlockGroupId(): MinecraftBlockGroupId {
|
|
781
|
+
return this.blockGroup
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
update(dt: number, sizeChanged: boolean) {
|
|
785
|
+
if (sizeChanged) {
|
|
786
|
+
resizeMenuBackgroundCamera(this.camera, this.documentRenderer.canvas)
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const mode = CAMS[this.curCam]
|
|
790
|
+
const cameraMotion = this.cameraSpeed
|
|
791
|
+
const blockMotion = this.blockSpeed
|
|
792
|
+
this.camT += dt * mode.spd * cameraMotion
|
|
793
|
+
this.mx += (this.tmx - this.mx) * 0.05
|
|
794
|
+
this.my += (this.tmy - this.my) * 0.05
|
|
795
|
+
|
|
796
|
+
const smx = this.mx * MOUSE_INFLUENCE
|
|
797
|
+
const smy = this.my * MOUSE_INFLUENCE
|
|
798
|
+
|
|
799
|
+
const p = mode.pos(this.camT, smx, smy)
|
|
800
|
+
const l = mode.look(this.camT, smx, smy)
|
|
801
|
+
this.camera.position.set(p.x, p.y, p.z)
|
|
802
|
+
this.camera.lookAt(l.x, l.y, l.z)
|
|
803
|
+
this.camera.rotation.z = mode.roll(this.camT, smx)
|
|
804
|
+
|
|
805
|
+
const mul = CAM_SPD[this.curCam] * blockMotion
|
|
806
|
+
this.animTime += dt * 1000 * blockMotion
|
|
807
|
+
for (const b of this.blocks) {
|
|
808
|
+
b.mesh.position.z += b.spd * mul * 60 * dt
|
|
809
|
+
b.mesh.position.x += b.dx * 60 * dt * blockMotion
|
|
810
|
+
b.mesh.position.y += b.dy * 60 * dt * blockMotion
|
|
811
|
+
b.mesh.rotation.x += b.rx * mul
|
|
812
|
+
b.mesh.rotation.y += b.ry * mul
|
|
813
|
+
b.mesh.rotation.z += b.rz
|
|
814
|
+
if (!this.useMinecraftTextures && b.mesh.material instanceof THREE.MeshBasicMaterial) {
|
|
815
|
+
const base = (b.mesh.userData.baseOpacity as number | undefined) ?? 0.38
|
|
816
|
+
if (b.mesh.userData.baseOpacity == null) b.mesh.userData.baseOpacity = base
|
|
817
|
+
const pulse = 0.88 + Math.abs(Math.sin(this.animTime * 0.0008 + b.mesh.position.x * 0.3)) * 0.12
|
|
818
|
+
b.mesh.material.opacity = base * pulse
|
|
819
|
+
}
|
|
820
|
+
if (b.mesh.position.z > this.camera.position.z + 15) {
|
|
821
|
+
b.mesh.position.set(
|
|
822
|
+
(Math.random() - 0.5) * 140,
|
|
823
|
+
(Math.random() - 0.5) * 70,
|
|
824
|
+
this.camera.position.z - 155 - Math.random() * 30
|
|
825
|
+
)
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
this.pt1.position.set(
|
|
830
|
+
Math.sin(this.camT * 0.8) * 25 + this.camera.position.x,
|
|
831
|
+
Math.cos(this.camT * 0.6) * 12 + this.camera.position.y,
|
|
832
|
+
this.camera.position.z - 18
|
|
833
|
+
)
|
|
834
|
+
this.pt2.position.set(
|
|
835
|
+
Math.cos(this.camT * 0.5) * 20 + this.camera.position.x,
|
|
836
|
+
Math.sin(this.camT * 0.9) * 15 + this.camera.position.y,
|
|
837
|
+
this.camera.position.z - 30
|
|
838
|
+
)
|
|
839
|
+
this.galaxy.rotation.y += dt * 0.006 * blockMotion
|
|
840
|
+
this.nebula.rotation.y -= dt * 0.003 * blockMotion
|
|
841
|
+
this.stars.rotation.y += dt * 0.0004 * blockMotion
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
dispose() {
|
|
845
|
+
this.disposed = true
|
|
846
|
+
this.scene.clear()
|
|
847
|
+
this.bGeo.dispose()
|
|
848
|
+
this.eGeo.dispose()
|
|
849
|
+
this.galGeo.dispose()
|
|
850
|
+
this.nebGeo.dispose()
|
|
851
|
+
this.gradientSkyTexture?.dispose()
|
|
852
|
+
this.atlasTexture?.dispose()
|
|
853
|
+
for (const mat of this.blockMaterialPool.values()) {
|
|
854
|
+
mat.map?.dispose()
|
|
855
|
+
mat.dispose()
|
|
856
|
+
}
|
|
857
|
+
this.blockMaterialPool.clear()
|
|
858
|
+
}
|
|
859
|
+
}
|