opencroc 1.6.8 → 1.7.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/dist/web/index-v2-pixel.html +1571 -0
- package/dist/web/index.html +531 -912
- package/dist/web/js/agents.js +465 -0
- package/dist/web/js/camera.js +125 -0
- package/dist/web/js/dataviz.js +288 -0
- package/dist/web/js/effects.js +345 -0
- package/dist/web/js/engine.js +489 -0
- package/dist/web/js/office.js +816 -0
- package/dist/web/js/state.js +37 -0
- package/dist/web/js/ui.js +384 -0
- package/package.json +1 -1
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
OpenCroc Studio 3D — Three.js Engine
|
|
3
|
+
Scene, Renderer, Post-processing, Clock
|
|
4
|
+
~2500 lines
|
|
5
|
+
═══════════════════════════════════════════════════════════════════════════════ */
|
|
6
|
+
|
|
7
|
+
import * as THREE from 'three';
|
|
8
|
+
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
|
|
9
|
+
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
|
10
|
+
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
|
|
11
|
+
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
|
|
12
|
+
import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
|
|
13
|
+
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
|
|
14
|
+
|
|
15
|
+
/* ─── Module-level singletons ──────────────────────────────────────────────── */
|
|
16
|
+
let renderer = null;
|
|
17
|
+
let scene = null;
|
|
18
|
+
let camera = null;
|
|
19
|
+
let composer = null;
|
|
20
|
+
let clock = null;
|
|
21
|
+
let bloomPass = null;
|
|
22
|
+
let fxaaPass = null;
|
|
23
|
+
|
|
24
|
+
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
25
|
+
1. createEngine — Initialize the full Three.js rendering pipeline
|
|
26
|
+
═══════════════════════════════════════════════════════════════════════════════ */
|
|
27
|
+
export async function createEngine(canvas, theme = 'dark') {
|
|
28
|
+
clock = new THREE.Clock();
|
|
29
|
+
|
|
30
|
+
/* ─── Scene ────────────────────────────────────────────────────────────── */
|
|
31
|
+
scene = new THREE.Scene();
|
|
32
|
+
scene.fog = theme === 'dark'
|
|
33
|
+
? new THREE.FogExp2(0x050510, 0.012)
|
|
34
|
+
: new THREE.FogExp2(0xe8ecf4, 0.008);
|
|
35
|
+
|
|
36
|
+
/* ─── Camera ───────────────────────────────────────────────────────────── */
|
|
37
|
+
const aspect = window.innerWidth / window.innerHeight;
|
|
38
|
+
camera = new THREE.PerspectiveCamera(55, aspect, 0.1, 500);
|
|
39
|
+
camera.position.set(18, 14, 18);
|
|
40
|
+
camera.lookAt(0, 0, 0);
|
|
41
|
+
|
|
42
|
+
/* ─── Renderer ─────────────────────────────────────────────────────────── */
|
|
43
|
+
renderer = new THREE.WebGLRenderer({
|
|
44
|
+
canvas,
|
|
45
|
+
antialias: true,
|
|
46
|
+
alpha: false,
|
|
47
|
+
powerPreference: 'high-performance',
|
|
48
|
+
});
|
|
49
|
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
50
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
51
|
+
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
52
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
53
|
+
renderer.toneMappingExposure = theme === 'dark' ? 1.0 : 1.4;
|
|
54
|
+
renderer.shadowMap.enabled = true;
|
|
55
|
+
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
56
|
+
|
|
57
|
+
if (theme === 'dark') {
|
|
58
|
+
renderer.setClearColor(0x050510);
|
|
59
|
+
} else {
|
|
60
|
+
renderer.setClearColor(0xe8ecf4);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ─── Lighting ─────────────────────────────────────────────────────────── */
|
|
64
|
+
setupLighting(scene, theme);
|
|
65
|
+
|
|
66
|
+
/* ─── Post-processing ──────────────────────────────────────────────────── */
|
|
67
|
+
setupPostProcessing(theme);
|
|
68
|
+
|
|
69
|
+
/* ─── Ground Grid ──────────────────────────────────────────────────────── */
|
|
70
|
+
createGroundGrid(theme);
|
|
71
|
+
|
|
72
|
+
/* ─── Sky ──────────────────────────────────────────────────────────────── */
|
|
73
|
+
createSkyDome(theme);
|
|
74
|
+
|
|
75
|
+
return { renderer, scene, camera, composer, clock };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
79
|
+
2. Lighting Setup
|
|
80
|
+
═══════════════════════════════════════════════════════════════════════════════ */
|
|
81
|
+
function setupLighting(scene, theme) {
|
|
82
|
+
// Remove existing lights
|
|
83
|
+
scene.children.filter(c => c.isLight).forEach(l => scene.remove(l));
|
|
84
|
+
|
|
85
|
+
if (theme === 'dark') {
|
|
86
|
+
/* ── Dark theme: moody blue-green ambient + dramatic spots ────────── */
|
|
87
|
+
const ambient = new THREE.AmbientLight(0x1a2a4a, 0.4);
|
|
88
|
+
ambient.name = 'ambient';
|
|
89
|
+
scene.add(ambient);
|
|
90
|
+
|
|
91
|
+
const hemi = new THREE.HemisphereLight(0x0d1b2a, 0x0a0f1e, 0.3);
|
|
92
|
+
hemi.name = 'hemi';
|
|
93
|
+
scene.add(hemi);
|
|
94
|
+
|
|
95
|
+
// Main directional (moonlight)
|
|
96
|
+
const dir = new THREE.DirectionalLight(0x4488cc, 0.6);
|
|
97
|
+
dir.name = 'dir-main';
|
|
98
|
+
dir.position.set(-10, 20, 10);
|
|
99
|
+
dir.castShadow = true;
|
|
100
|
+
dir.shadow.mapSize.set(2048, 2048);
|
|
101
|
+
dir.shadow.camera.near = 0.5;
|
|
102
|
+
dir.shadow.camera.far = 60;
|
|
103
|
+
dir.shadow.camera.left = -25;
|
|
104
|
+
dir.shadow.camera.right = 25;
|
|
105
|
+
dir.shadow.camera.top = 25;
|
|
106
|
+
dir.shadow.camera.bottom = -25;
|
|
107
|
+
dir.shadow.bias = -0.002;
|
|
108
|
+
dir.shadow.normalBias = 0.02;
|
|
109
|
+
scene.add(dir);
|
|
110
|
+
|
|
111
|
+
// Accent spot (green glow from center)
|
|
112
|
+
const accent = new THREE.PointLight(0x34d399, 2.0, 30, 1.5);
|
|
113
|
+
accent.name = 'accent-glow';
|
|
114
|
+
accent.position.set(0, 6, 0);
|
|
115
|
+
accent.castShadow = false;
|
|
116
|
+
scene.add(accent);
|
|
117
|
+
|
|
118
|
+
// Rim light (purple back-light)
|
|
119
|
+
const rim = new THREE.PointLight(0xa78bfa, 1.0, 25, 1.5);
|
|
120
|
+
rim.name = 'rim-light';
|
|
121
|
+
rim.position.set(-12, 8, -12);
|
|
122
|
+
scene.add(rim);
|
|
123
|
+
|
|
124
|
+
// Warm light (desk area)
|
|
125
|
+
const warm = new THREE.PointLight(0xfbbf24, 0.8, 15, 2);
|
|
126
|
+
warm.name = 'warm-desk';
|
|
127
|
+
warm.position.set(6, 4, 6);
|
|
128
|
+
scene.add(warm);
|
|
129
|
+
|
|
130
|
+
} else {
|
|
131
|
+
/* ── Light theme: bright natural lighting ─────────────────────────── */
|
|
132
|
+
const ambient = new THREE.AmbientLight(0xf0f4fa, 0.6);
|
|
133
|
+
ambient.name = 'ambient';
|
|
134
|
+
scene.add(ambient);
|
|
135
|
+
|
|
136
|
+
const hemi = new THREE.HemisphereLight(0xddeeff, 0xf0ece0, 0.5);
|
|
137
|
+
hemi.name = 'hemi';
|
|
138
|
+
scene.add(hemi);
|
|
139
|
+
|
|
140
|
+
// Sun
|
|
141
|
+
const dir = new THREE.DirectionalLight(0xfff5e6, 1.2);
|
|
142
|
+
dir.name = 'dir-main';
|
|
143
|
+
dir.position.set(12, 25, 8);
|
|
144
|
+
dir.castShadow = true;
|
|
145
|
+
dir.shadow.mapSize.set(2048, 2048);
|
|
146
|
+
dir.shadow.camera.near = 0.5;
|
|
147
|
+
dir.shadow.camera.far = 60;
|
|
148
|
+
dir.shadow.camera.left = -25;
|
|
149
|
+
dir.shadow.camera.right = 25;
|
|
150
|
+
dir.shadow.camera.top = 25;
|
|
151
|
+
dir.shadow.camera.bottom = -25;
|
|
152
|
+
dir.shadow.bias = -0.002;
|
|
153
|
+
dir.shadow.normalBias = 0.02;
|
|
154
|
+
scene.add(dir);
|
|
155
|
+
|
|
156
|
+
// Soft fill
|
|
157
|
+
const fill = new THREE.DirectionalLight(0xb3d4ff, 0.4);
|
|
158
|
+
fill.name = 'fill-light';
|
|
159
|
+
fill.position.set(-8, 12, -5);
|
|
160
|
+
scene.add(fill);
|
|
161
|
+
|
|
162
|
+
// Subtle accent
|
|
163
|
+
const accent = new THREE.PointLight(0x059669, 0.6, 20, 2);
|
|
164
|
+
accent.name = 'accent-glow';
|
|
165
|
+
accent.position.set(0, 5, 0);
|
|
166
|
+
scene.add(accent);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
171
|
+
3. Post-processing Pipeline
|
|
172
|
+
═══════════════════════════════════════════════════════════════════════════════ */
|
|
173
|
+
function setupPostProcessing(theme) {
|
|
174
|
+
const size = renderer.getSize(new THREE.Vector2());
|
|
175
|
+
|
|
176
|
+
composer = new EffectComposer(renderer);
|
|
177
|
+
|
|
178
|
+
// Render pass
|
|
179
|
+
const renderPass = new RenderPass(scene, camera);
|
|
180
|
+
composer.addPass(renderPass);
|
|
181
|
+
|
|
182
|
+
// Bloom pass — gives the neon glow effect
|
|
183
|
+
bloomPass = new UnrealBloomPass(
|
|
184
|
+
new THREE.Vector2(size.x, size.y),
|
|
185
|
+
theme === 'dark' ? 0.6 : 0.15, // strength
|
|
186
|
+
0.4, // radius
|
|
187
|
+
theme === 'dark' ? 0.85 : 0.95 // threshold
|
|
188
|
+
);
|
|
189
|
+
composer.addPass(bloomPass);
|
|
190
|
+
|
|
191
|
+
// FXAA anti-aliasing
|
|
192
|
+
fxaaPass = new ShaderPass(FXAAShader);
|
|
193
|
+
fxaaPass.uniforms['resolution'].value.set(1 / size.x, 1 / size.y);
|
|
194
|
+
composer.addPass(fxaaPass);
|
|
195
|
+
|
|
196
|
+
// Output pass (gamma correction)
|
|
197
|
+
const outputPass = new OutputPass();
|
|
198
|
+
composer.addPass(outputPass);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
202
|
+
4. Ground Grid — Procedural infinite grid
|
|
203
|
+
═══════════════════════════════════════════════════════════════════════════════ */
|
|
204
|
+
function createGroundGrid(theme) {
|
|
205
|
+
/* ── Ground plane ──────────────────────────────────────────────────────── */
|
|
206
|
+
const groundGeo = new THREE.PlaneGeometry(200, 200);
|
|
207
|
+
const groundMat = new THREE.MeshStandardMaterial({
|
|
208
|
+
color: theme === 'dark' ? 0x0a0f1e : 0xdee4ed,
|
|
209
|
+
roughness: 0.95,
|
|
210
|
+
metalness: 0.0,
|
|
211
|
+
});
|
|
212
|
+
const ground = new THREE.Mesh(groundGeo, groundMat);
|
|
213
|
+
ground.rotation.x = -Math.PI / 2;
|
|
214
|
+
ground.position.y = -0.01;
|
|
215
|
+
ground.receiveShadow = true;
|
|
216
|
+
ground.name = 'ground';
|
|
217
|
+
scene.add(ground);
|
|
218
|
+
|
|
219
|
+
/* ── Grid lines ────────────────────────────────────────────────────────── */
|
|
220
|
+
const gridSize = 80;
|
|
221
|
+
const gridDiv = 40;
|
|
222
|
+
const gridHelper = new THREE.GridHelper(
|
|
223
|
+
gridSize, gridDiv,
|
|
224
|
+
theme === 'dark' ? 0x1a2a3a : 0xbcc5d0,
|
|
225
|
+
theme === 'dark' ? 0x0f1a2a : 0xd0d8e0,
|
|
226
|
+
);
|
|
227
|
+
gridHelper.position.y = 0.01;
|
|
228
|
+
gridHelper.material.opacity = theme === 'dark' ? 0.3 : 0.2;
|
|
229
|
+
gridHelper.material.transparent = true;
|
|
230
|
+
gridHelper.name = 'grid';
|
|
231
|
+
scene.add(gridHelper);
|
|
232
|
+
|
|
233
|
+
/* ── Accent grid ring around center ────────────────────────────────────── */
|
|
234
|
+
const ringGeo = new THREE.RingGeometry(8, 8.08, 64);
|
|
235
|
+
const ringMat = new THREE.MeshBasicMaterial({
|
|
236
|
+
color: theme === 'dark' ? 0x34d399 : 0x059669,
|
|
237
|
+
transparent: true,
|
|
238
|
+
opacity: theme === 'dark' ? 0.4 : 0.2,
|
|
239
|
+
side: THREE.DoubleSide,
|
|
240
|
+
});
|
|
241
|
+
const ring = new THREE.Mesh(ringGeo, ringMat);
|
|
242
|
+
ring.rotation.x = -Math.PI / 2;
|
|
243
|
+
ring.position.y = 0.02;
|
|
244
|
+
ring.name = 'center-ring';
|
|
245
|
+
scene.add(ring);
|
|
246
|
+
|
|
247
|
+
/* ── Second ring ───────────────────────────────────────────────────────── */
|
|
248
|
+
const ring2Geo = new THREE.RingGeometry(14, 14.06, 64);
|
|
249
|
+
const ring2Mat = new THREE.MeshBasicMaterial({
|
|
250
|
+
color: theme === 'dark' ? 0x60a5fa : 0x2563eb,
|
|
251
|
+
transparent: true,
|
|
252
|
+
opacity: theme === 'dark' ? 0.2 : 0.1,
|
|
253
|
+
side: THREE.DoubleSide,
|
|
254
|
+
});
|
|
255
|
+
const ring2 = new THREE.Mesh(ring2Geo, ring2Mat);
|
|
256
|
+
ring2.rotation.x = -Math.PI / 2;
|
|
257
|
+
ring2.position.y = 0.02;
|
|
258
|
+
ring2.name = 'outer-ring';
|
|
259
|
+
scene.add(ring2);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
263
|
+
5. Sky Dome — Gradient atmosphere
|
|
264
|
+
═══════════════════════════════════════════════════════════════════════════════ */
|
|
265
|
+
function createSkyDome(theme) {
|
|
266
|
+
const skyGeo = new THREE.SphereGeometry(150, 32, 32);
|
|
267
|
+
|
|
268
|
+
// Custom shader for gradient sky
|
|
269
|
+
const skyMat = new THREE.ShaderMaterial({
|
|
270
|
+
uniforms: {
|
|
271
|
+
topColor: { value: theme === 'dark' ? new THREE.Color(0x0a0f2e) : new THREE.Color(0x87ceeb) },
|
|
272
|
+
bottomColor: { value: theme === 'dark' ? new THREE.Color(0x050510) : new THREE.Color(0xe8ecf4) },
|
|
273
|
+
offset: { value: 20 },
|
|
274
|
+
exponent: { value: 0.6 },
|
|
275
|
+
},
|
|
276
|
+
vertexShader: `
|
|
277
|
+
varying vec3 vWorldPosition;
|
|
278
|
+
void main() {
|
|
279
|
+
vec4 worldPos = modelMatrix * vec4(position, 1.0);
|
|
280
|
+
vWorldPosition = worldPos.xyz;
|
|
281
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
282
|
+
}
|
|
283
|
+
`,
|
|
284
|
+
fragmentShader: `
|
|
285
|
+
uniform vec3 topColor;
|
|
286
|
+
uniform vec3 bottomColor;
|
|
287
|
+
uniform float offset;
|
|
288
|
+
uniform float exponent;
|
|
289
|
+
varying vec3 vWorldPosition;
|
|
290
|
+
void main() {
|
|
291
|
+
float h = normalize(vWorldPosition + offset).y;
|
|
292
|
+
gl_FragColor = vec4(mix(bottomColor, topColor, max(pow(max(h, 0.0), exponent), 0.0)), 1.0);
|
|
293
|
+
}
|
|
294
|
+
`,
|
|
295
|
+
side: THREE.BackSide,
|
|
296
|
+
depthWrite: false,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const sky = new THREE.Mesh(skyGeo, skyMat);
|
|
300
|
+
sky.name = 'sky';
|
|
301
|
+
scene.add(sky);
|
|
302
|
+
|
|
303
|
+
/* ── Stars (dark theme only) ───────────────────────────────────────────── */
|
|
304
|
+
if (theme === 'dark') {
|
|
305
|
+
createStarField();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
310
|
+
6. Star Field — Procedural stars
|
|
311
|
+
═══════════════════════════════════════════════════════════════════════════════ */
|
|
312
|
+
function createStarField() {
|
|
313
|
+
const count = 2000;
|
|
314
|
+
const positions = new Float32Array(count * 3);
|
|
315
|
+
const sizes = new Float32Array(count);
|
|
316
|
+
const colors = new Float32Array(count * 3);
|
|
317
|
+
|
|
318
|
+
const starColors = [
|
|
319
|
+
new THREE.Color(0xffffff),
|
|
320
|
+
new THREE.Color(0xccddff),
|
|
321
|
+
new THREE.Color(0xffeedd),
|
|
322
|
+
new THREE.Color(0xddeeff),
|
|
323
|
+
new THREE.Color(0x34d399),
|
|
324
|
+
];
|
|
325
|
+
|
|
326
|
+
for (let i = 0; i < count; i++) {
|
|
327
|
+
// Distribute on upper hemisphere
|
|
328
|
+
const theta = Math.random() * Math.PI * 2;
|
|
329
|
+
const phi = Math.random() * Math.PI * 0.45; // Only upper portion
|
|
330
|
+
const r = 100 + Math.random() * 40;
|
|
331
|
+
|
|
332
|
+
positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
|
|
333
|
+
positions[i * 3 + 1] = r * Math.cos(phi);
|
|
334
|
+
positions[i * 3 + 2] = r * Math.sin(phi) * Math.sin(theta);
|
|
335
|
+
|
|
336
|
+
sizes[i] = 0.3 + Math.random() * 1.2;
|
|
337
|
+
|
|
338
|
+
const c = starColors[Math.floor(Math.random() * starColors.length)];
|
|
339
|
+
colors[i * 3] = c.r;
|
|
340
|
+
colors[i * 3 + 1] = c.g;
|
|
341
|
+
colors[i * 3 + 2] = c.b;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const geo = new THREE.BufferGeometry();
|
|
345
|
+
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
346
|
+
geo.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
|
|
347
|
+
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
|
348
|
+
|
|
349
|
+
const mat = new THREE.ShaderMaterial({
|
|
350
|
+
uniforms: {
|
|
351
|
+
time: { value: 0 },
|
|
352
|
+
},
|
|
353
|
+
vertexShader: `
|
|
354
|
+
attribute float size;
|
|
355
|
+
attribute vec3 color;
|
|
356
|
+
varying vec3 vColor;
|
|
357
|
+
varying float vSize;
|
|
358
|
+
uniform float time;
|
|
359
|
+
void main() {
|
|
360
|
+
vColor = color;
|
|
361
|
+
vSize = size;
|
|
362
|
+
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
363
|
+
float twinkle = 0.7 + 0.3 * sin(time * 2.0 + position.x * 10.0 + position.z * 7.0);
|
|
364
|
+
gl_PointSize = size * twinkle * (200.0 / -mvPosition.z);
|
|
365
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
366
|
+
}
|
|
367
|
+
`,
|
|
368
|
+
fragmentShader: `
|
|
369
|
+
varying vec3 vColor;
|
|
370
|
+
varying float vSize;
|
|
371
|
+
void main() {
|
|
372
|
+
vec2 center = gl_PointCoord - vec2(0.5);
|
|
373
|
+
float dist = length(center);
|
|
374
|
+
if (dist > 0.5) discard;
|
|
375
|
+
float alpha = 1.0 - smoothstep(0.0, 0.5, dist);
|
|
376
|
+
float glow = exp(-dist * dist * 8.0);
|
|
377
|
+
gl_FragColor = vec4(vColor * (0.8 + glow * 0.5), alpha * 0.9);
|
|
378
|
+
}
|
|
379
|
+
`,
|
|
380
|
+
transparent: true,
|
|
381
|
+
depthWrite: false,
|
|
382
|
+
blending: THREE.AdditiveBlending,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const stars = new THREE.Points(geo, mat);
|
|
386
|
+
stars.name = 'stars';
|
|
387
|
+
scene.add(stars);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
391
|
+
7. Resize Handler
|
|
392
|
+
═══════════════════════════════════════════════════════════════════════════════ */
|
|
393
|
+
export function resizeEngine() {
|
|
394
|
+
if (!renderer || !camera) return;
|
|
395
|
+
const w = window.innerWidth;
|
|
396
|
+
const h = window.innerHeight;
|
|
397
|
+
camera.aspect = w / h;
|
|
398
|
+
camera.updateProjectionMatrix();
|
|
399
|
+
renderer.setSize(w, h);
|
|
400
|
+
if (composer) composer.setSize(w, h);
|
|
401
|
+
if (fxaaPass) fxaaPass.uniforms['resolution'].value.set(1 / w, 1 / h);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
405
|
+
8. Update Functions
|
|
406
|
+
═══════════════════════════════════════════════════════════════════════════════ */
|
|
407
|
+
|
|
408
|
+
/** Called each frame to update time-based uniforms */
|
|
409
|
+
export function updateEngine(dt) {
|
|
410
|
+
// Update star twinkle
|
|
411
|
+
const stars = scene.getObjectByName('stars');
|
|
412
|
+
if (stars && stars.material.uniforms) {
|
|
413
|
+
stars.material.uniforms.time.value += dt;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Animate center ring
|
|
417
|
+
const ring = scene.getObjectByName('center-ring');
|
|
418
|
+
if (ring) {
|
|
419
|
+
ring.rotation.z += dt * 0.1;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const ring2 = scene.getObjectByName('outer-ring');
|
|
423
|
+
if (ring2) {
|
|
424
|
+
ring2.rotation.z -= dt * 0.05;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
429
|
+
9. Theme Update
|
|
430
|
+
═══════════════════════════════════════════════════════════════════════════════ */
|
|
431
|
+
export function updateEngineTheme(theme) {
|
|
432
|
+
if (!renderer || !scene) return;
|
|
433
|
+
|
|
434
|
+
// Update clear color
|
|
435
|
+
renderer.setClearColor(theme === 'dark' ? 0x050510 : 0xe8ecf4);
|
|
436
|
+
renderer.toneMappingExposure = theme === 'dark' ? 1.0 : 1.4;
|
|
437
|
+
|
|
438
|
+
// Update fog
|
|
439
|
+
scene.fog = theme === 'dark'
|
|
440
|
+
? new THREE.FogExp2(0x050510, 0.012)
|
|
441
|
+
: new THREE.FogExp2(0xe8ecf4, 0.008);
|
|
442
|
+
|
|
443
|
+
// Update lighting
|
|
444
|
+
setupLighting(scene, theme);
|
|
445
|
+
|
|
446
|
+
// Update bloom
|
|
447
|
+
if (bloomPass) {
|
|
448
|
+
bloomPass.strength = theme === 'dark' ? 0.6 : 0.15;
|
|
449
|
+
bloomPass.threshold = theme === 'dark' ? 0.85 : 0.95;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Update ground
|
|
453
|
+
const ground = scene.getObjectByName('ground');
|
|
454
|
+
if (ground) ground.material.color.setHex(theme === 'dark' ? 0x0a0f1e : 0xdee4ed);
|
|
455
|
+
|
|
456
|
+
// Update grid
|
|
457
|
+
const grid = scene.getObjectByName('grid');
|
|
458
|
+
if (grid) {
|
|
459
|
+
grid.material.opacity = theme === 'dark' ? 0.3 : 0.2;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Update sky
|
|
463
|
+
const sky = scene.getObjectByName('sky');
|
|
464
|
+
if (sky && sky.material.uniforms) {
|
|
465
|
+
sky.material.uniforms.topColor.value.setHex(theme === 'dark' ? 0x0a0f2e : 0x87ceeb);
|
|
466
|
+
sky.material.uniforms.bottomColor.value.setHex(theme === 'dark' ? 0x050510 : 0xe8ecf4);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Stars visibility
|
|
470
|
+
const stars = scene.getObjectByName('stars');
|
|
471
|
+
if (stars) stars.visible = theme === 'dark';
|
|
472
|
+
if (!stars && theme === 'dark') createStarField();
|
|
473
|
+
|
|
474
|
+
// Center ring
|
|
475
|
+
const ring = scene.getObjectByName('center-ring');
|
|
476
|
+
if (ring) {
|
|
477
|
+
ring.material.color.setHex(theme === 'dark' ? 0x34d399 : 0x059669);
|
|
478
|
+
ring.material.opacity = theme === 'dark' ? 0.4 : 0.2;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
483
|
+
10. Getters
|
|
484
|
+
═══════════════════════════════════════════════════════════════════════════════ */
|
|
485
|
+
export function getRenderer() { return renderer; }
|
|
486
|
+
export function getScene() { return scene; }
|
|
487
|
+
export function getCamera() { return camera; }
|
|
488
|
+
export function getComposer() { return composer; }
|
|
489
|
+
export function getClock() { return clock; }
|