nova64 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -8
- package/bin/nova64.js +165 -0
- package/dist/assets/console-CY_kygm3.js +14 -0
- package/dist/assets/console-CY_kygm3.js.map +1 -0
- package/dist/assets/main-l0sNRNKZ.js.map +1 -0
- package/dist/assets/sky/studio/nx.png +0 -0
- package/dist/assets/sky/studio/ny.png +0 -0
- package/dist/assets/sky/studio/nz.png +0 -0
- package/dist/assets/sky/studio/px.png +0 -0
- package/dist/assets/sky/studio/py.png +0 -0
- package/dist/assets/sky/studio/pz.png +0 -0
- package/dist/assets/vanilla-Dcuy32gi.js +2 -0
- package/dist/assets/vanilla-Dcuy32gi.js.map +1 -0
- package/dist/console.html +899 -0
- package/dist/docs/BENCHMARK.md +77 -0
- package/dist/docs/CHEATSHEET.md +255 -0
- package/dist/docs/EFFECTS_API_GUIDE.md +577 -0
- package/dist/docs/EFFECTS_QUICK_REFERENCE.md +331 -0
- package/dist/docs/FONT_CHARACTER_REFERENCE.md +219 -0
- package/dist/docs/FREE_GLB_ASSETS.md +330 -0
- package/dist/docs/FULLSCREEN_BUTTON_FEATURE.md +296 -0
- package/dist/docs/GAMEPAD_SUPPORT.md +348 -0
- package/dist/docs/GAME_IMPROVEMENTS.md +278 -0
- package/dist/docs/GAME_QUALITY_STATUS.md +300 -0
- package/dist/docs/MIGRATION_GUIDE.md +553 -0
- package/dist/docs/NOVA64_3D_API.md +356 -0
- package/dist/docs/NOVA64_API_REFERENCE.md +1406 -0
- package/dist/docs/NOVA64_UI_API.md +503 -0
- package/dist/docs/UI_SYSTEM_SUMMARY.md +445 -0
- package/dist/docs/VOXEL_ENGINE_GUIDE.md +662 -0
- package/dist/docs/VOXEL_QUICK_REFERENCE.md +386 -0
- package/dist/docs/api-3d.html +750 -0
- package/dist/docs/api-effects.html +385 -0
- package/dist/docs/api-improvements.md +121 -0
- package/dist/docs/api-skybox.html +407 -0
- package/dist/docs/api-sprites.html +321 -0
- package/dist/docs/api-voxel.html +337 -0
- package/dist/docs/api.html +543 -0
- package/dist/docs/assets.html +306 -0
- package/dist/docs/audio.html +340 -0
- package/dist/docs/blogs.html +286 -0
- package/dist/docs/collision.html +316 -0
- package/dist/docs/console.html +247 -0
- package/dist/docs/editor.html +297 -0
- package/dist/docs/font.html +247 -0
- package/dist/docs/framebuffer.html +247 -0
- package/dist/docs/fullscreen-button.html +297 -0
- package/dist/docs/gpu-systems.html +247 -0
- package/dist/docs/index.html +580 -0
- package/dist/docs/input.html +491 -0
- package/dist/docs/physics.html +311 -0
- package/dist/docs/screens.html +311 -0
- package/dist/docs/storage.html +311 -0
- package/dist/docs/textinput.html +332 -0
- package/dist/docs/ui.html +488 -0
- package/dist/examples/3d-advanced/code.js +695 -0
- package/dist/examples/adventure-comic-3d/code.js +342 -0
- package/dist/examples/audio-lab/code.js +150 -0
- package/dist/examples/boids-flocking/code.js +270 -0
- package/dist/examples/crystal-cathedral-3d/code.js +706 -0
- package/dist/examples/cyberpunk-city-3d/code.js +1383 -0
- package/dist/examples/demoscene/README.md +192 -0
- package/dist/examples/demoscene/code.js +1081 -0
- package/dist/examples/demoscene/meta.json +21 -0
- package/dist/examples/dungeon-crawler-3d/code.js +1117 -0
- package/dist/examples/f-zero-nova-3d/code.js +865 -0
- package/dist/examples/f-zero-nova-3d/code_old.js +1555 -0
- package/dist/examples/fps-demo-3d/code.js +744 -0
- package/dist/examples/game-of-life-3d/code.js +338 -0
- package/dist/examples/generative-art/code.js +632 -0
- package/dist/examples/hello-3d/code.js +325 -0
- package/dist/examples/hello-skybox/code.js +183 -0
- package/dist/examples/hello-world/code.js +19 -0
- package/dist/examples/input-showcase/code.js +109 -0
- package/dist/examples/instancing-demo/code.js +315 -0
- package/dist/examples/minecraft-demo/code.js +387 -0
- package/dist/examples/model-viewer-3d/code.js +114 -0
- package/dist/examples/mystical-realm-3d/code.js +1203 -0
- package/dist/examples/nature-explorer-3d/code.js +1318 -0
- package/dist/examples/particles-demo/code.js +522 -0
- package/dist/examples/pbr-showcase/code.js +140 -0
- package/dist/examples/physics-demo-3d/code.js +948 -0
- package/dist/examples/screen-demo/code.js +267 -0
- package/dist/examples/shooter-demo-3d/code.js +1286 -0
- package/dist/examples/space-combat-3d/IMPLEMENTATION_SUMMARY.md +109 -0
- package/dist/examples/space-combat-3d/README.md +135 -0
- package/dist/examples/space-combat-3d/code.js +1332 -0
- package/dist/examples/space-harrier-3d/code.js +923 -0
- package/dist/examples/star-fox-nova-3d/code.js +1116 -0
- package/dist/examples/star-fox-nova-3d/code_backup.js +410 -0
- package/dist/examples/star-fox-nova-3d/code_broken.js +1821 -0
- package/dist/examples/storage-quest/code.js +209 -0
- package/dist/examples/strider-demo-3d/IMPROVEMENT_OPTIONS.md +285 -0
- package/dist/examples/strider-demo-3d/cache-test.html +132 -0
- package/dist/examples/strider-demo-3d/code-fixed.js +582 -0
- package/dist/examples/strider-demo-3d/code-old.js +1537 -0
- package/dist/examples/strider-demo-3d/code.js +1462 -0
- package/dist/examples/strider-demo-3d/code.js.bak2 +1169 -0
- package/dist/examples/strider-demo-3d/fix-game.sh +53 -0
- package/dist/examples/super-plumber-64/README.md +128 -0
- package/dist/examples/super-plumber-64/code.js +1185 -0
- package/dist/examples/super-plumber-64/index.html +88 -0
- package/dist/examples/test-2d-overlay/code.js +32 -0
- package/dist/examples/test-font/code.js +51 -0
- package/dist/examples/test-minimal/code.js +21 -0
- package/dist/examples/ui-demo/code.js +306 -0
- package/dist/examples/wing-commander-space/README.md +180 -0
- package/dist/examples/wing-commander-space/code.js +1285 -0
- package/dist/examples/wizardry-3d/CHANGELOG.md +366 -0
- package/dist/examples/wizardry-3d/code.js +3928 -0
- package/dist/index.html +666 -0
- package/dist/os9-shell/assets/index-DIHfrTaW.css +1 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/dist/os9-shell/index.html +23 -0
- package/dist/os9-shell/nova-icon.svg +12 -0
- package/dist/runtime/api-2d.js +1158 -0
- package/dist/runtime/api-3d/camera.js +73 -0
- package/dist/runtime/api-3d/instancing.js +180 -0
- package/dist/runtime/api-3d/lights.js +51 -0
- package/dist/runtime/api-3d/materials.js +47 -0
- package/dist/runtime/api-3d/models.js +84 -0
- package/dist/runtime/api-3d/particles.js +296 -0
- package/dist/runtime/api-3d/pbr.js +113 -0
- package/dist/runtime/api-3d/primitives.js +304 -0
- package/dist/runtime/api-3d/scene.js +169 -0
- package/dist/runtime/api-3d/transforms.js +161 -0
- package/dist/runtime/api-3d.js +166 -0
- package/dist/runtime/api-effects.js +840 -0
- package/dist/runtime/api-gameutils.js +476 -0
- package/dist/runtime/api-generative.js +610 -0
- package/dist/runtime/api-presets.js +85 -0
- package/dist/runtime/api-skybox.js +232 -0
- package/dist/runtime/api-sprites.js +100 -0
- package/dist/runtime/api-voxel.js +712 -0
- package/dist/runtime/api.js +201 -0
- package/dist/runtime/assets.js +27 -0
- package/dist/runtime/audio.js +114 -0
- package/dist/runtime/collision.js +47 -0
- package/dist/runtime/console.js +101 -0
- package/dist/runtime/editor.js +233 -0
- package/dist/runtime/font.js +233 -0
- package/dist/runtime/framebuffer.js +28 -0
- package/dist/runtime/fullscreen-button.js +185 -0
- package/dist/runtime/gpu-canvas2d.js +47 -0
- package/dist/runtime/gpu-threejs.js +643 -0
- package/dist/runtime/gpu-webgl2.js +310 -0
- package/dist/runtime/index.d.ts +682 -0
- package/dist/runtime/index.js +22 -0
- package/dist/runtime/input.js +225 -0
- package/dist/runtime/logger.js +60 -0
- package/dist/runtime/physics.js +101 -0
- package/dist/runtime/screens.js +213 -0
- package/dist/runtime/storage.js +38 -0
- package/dist/runtime/store.js +151 -0
- package/dist/runtime/textinput.js +68 -0
- package/dist/runtime/ui/buttons.js +124 -0
- package/dist/runtime/ui/panels.js +105 -0
- package/dist/runtime/ui/text.js +86 -0
- package/dist/runtime/ui/widgets.js +141 -0
- package/dist/runtime/ui.js +111 -0
- package/index.html +6 -1
- package/package.json +9 -2
- package/public/assets/sky/studio/nx.png +0 -0
- package/public/assets/sky/studio/ny.png +0 -0
- package/public/assets/sky/studio/nz.png +0 -0
- package/public/assets/sky/studio/px.png +0 -0
- package/public/assets/sky/studio/py.png +0 -0
- package/public/assets/sky/studio/pz.png +0 -0
- package/public/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/public/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/public/os9-shell/index.html +10 -1
- package/runtime/api-2d.js +301 -21
- package/runtime/api-3d/pbr.js +45 -1
- package/runtime/api-3d.js +1 -0
- package/runtime/api-effects.js +90 -3
- package/runtime/api-gameutils.js +476 -0
- package/runtime/api-generative.js +610 -0
- package/runtime/api-skybox.js +54 -0
- package/runtime/api-voxel.js +139 -28
- package/runtime/gpu-threejs.js +13 -9
- package/runtime/ui.js +2 -2
- package/src/main.js +20 -0
- package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
- package/public/os9-shell/assets/index-B1Uvacma.js.map +0 -1
|
@@ -0,0 +1,1081 @@
|
|
|
1
|
+
// 🎬 NOVA64 DEMOSCENE - TRON ODYSSEY
|
|
2
|
+
// An epic visual journey through a neon digital realm
|
|
3
|
+
// Showcasing: Bloom, Post-Processing, Particles, Shaders, and Dynamic Effects
|
|
4
|
+
|
|
5
|
+
/* eslint-disable no-undef */
|
|
6
|
+
// Nova64 runtime provides these globals: enableBloom, enableFXAA, setBloomStrength, etc.
|
|
7
|
+
|
|
8
|
+
let gameTime = 0;
|
|
9
|
+
let sceneTime = 0;
|
|
10
|
+
let currentScene = 0;
|
|
11
|
+
let transitioning = false;
|
|
12
|
+
let transitionProgress = 0;
|
|
13
|
+
|
|
14
|
+
// Scene objects
|
|
15
|
+
let gridFloor = null;
|
|
16
|
+
let tunnelSegments = [];
|
|
17
|
+
let dataStreams = [];
|
|
18
|
+
let pulseRings = [];
|
|
19
|
+
let lightCycles = [];
|
|
20
|
+
let digitalTowers = [];
|
|
21
|
+
let particleSystems = [];
|
|
22
|
+
let energyFields = [];
|
|
23
|
+
|
|
24
|
+
// Camera control
|
|
25
|
+
let camera = {
|
|
26
|
+
x: 0,
|
|
27
|
+
y: 15,
|
|
28
|
+
z: 35,
|
|
29
|
+
targetX: 0,
|
|
30
|
+
targetY: 0,
|
|
31
|
+
targetZ: 0,
|
|
32
|
+
fov: 75,
|
|
33
|
+
roll: 0,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// State management
|
|
37
|
+
let gameState = 'start';
|
|
38
|
+
let startScreenTime = 0;
|
|
39
|
+
|
|
40
|
+
// Scene definitions
|
|
41
|
+
const SCENES = [
|
|
42
|
+
{ name: 'GRID AWAKENING', duration: 8, color: 0x00ffff },
|
|
43
|
+
{ name: 'DATA TUNNEL', duration: 10, color: 0xff00ff },
|
|
44
|
+
{ name: 'DIGITAL CITY', duration: 12, color: 0xffff00 },
|
|
45
|
+
{ name: 'ENERGY CORE', duration: 10, color: 0xff0099 },
|
|
46
|
+
{ name: 'THE VOID', duration: 8, color: 0x0099ff },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Color palettes for each scene
|
|
50
|
+
const COLORS = {
|
|
51
|
+
neonCyan: 0x00ffff,
|
|
52
|
+
neonMagenta: 0xff00ff,
|
|
53
|
+
neonYellow: 0xffff00,
|
|
54
|
+
neonPink: 0xff0099,
|
|
55
|
+
neonGreen: 0x00ff99,
|
|
56
|
+
neonOrange: 0xff6600,
|
|
57
|
+
electric: 0x0099ff,
|
|
58
|
+
pulse: 0xffffff,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export async function init() {
|
|
62
|
+
console.log('========================================');
|
|
63
|
+
console.log('🎬 NOVA64 DEMOSCENE - TRON ODYSSEY INIT');
|
|
64
|
+
console.log('========================================');
|
|
65
|
+
console.log('Initial gameState:', gameState);
|
|
66
|
+
|
|
67
|
+
// Initial camera setup
|
|
68
|
+
setCameraPosition(camera.x, camera.y, camera.z);
|
|
69
|
+
setCameraTarget(0, 0, 0);
|
|
70
|
+
setCameraFOV(camera.fov);
|
|
71
|
+
|
|
72
|
+
// Enable post-processing effects with BALANCED settings
|
|
73
|
+
console.log('🎨 Enabling post-processing effects...');
|
|
74
|
+
const bloomEnabled = enableBloom(1.2, 0.6, 0.3); // Balanced bloom: visible glow without washing out
|
|
75
|
+
console.log('✨ Bloom enabled:', bloomEnabled);
|
|
76
|
+
enableFXAA(); // Smooth edges
|
|
77
|
+
console.log('✨ FXAA enabled');
|
|
78
|
+
|
|
79
|
+
// Verify effects are enabled
|
|
80
|
+
if (typeof isEffectsEnabled === 'function') {
|
|
81
|
+
console.log('✅ Effects system active:', isEffectsEnabled());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Additional effects
|
|
85
|
+
enableChromaticAberration(0.003);
|
|
86
|
+
enableVignette(1.5, 0.85);
|
|
87
|
+
|
|
88
|
+
// Scene lighting - Balanced for visibility with neon contrast
|
|
89
|
+
setLightDirection(-0.5, -0.8, -0.3);
|
|
90
|
+
setAmbientLight(0x1a1a2a); // Darker but not pitch black - you can see objects
|
|
91
|
+
|
|
92
|
+
// Dark fog for TRON aesthetic
|
|
93
|
+
setFog(0x000020, 30, 150);
|
|
94
|
+
|
|
95
|
+
// Build initial scene
|
|
96
|
+
await buildStartScene();
|
|
97
|
+
|
|
98
|
+
// Initialize start screen
|
|
99
|
+
initStartScreen();
|
|
100
|
+
|
|
101
|
+
console.log('✨ Demoscene initialized - Ready to journey!');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function initStartScreen() {
|
|
105
|
+
// Clear any existing buttons first
|
|
106
|
+
clearButtons();
|
|
107
|
+
|
|
108
|
+
console.log('🎬 Initializing start screen buttons...');
|
|
109
|
+
|
|
110
|
+
// Main start button - extra large and flashy
|
|
111
|
+
const startBtn = createButton(
|
|
112
|
+
centerX(280),
|
|
113
|
+
180,
|
|
114
|
+
280,
|
|
115
|
+
70,
|
|
116
|
+
'▶ BEGIN ODYSSEY ▶',
|
|
117
|
+
() => {
|
|
118
|
+
console.log('🚀🚀🚀 START BUTTON CLICKED! 🚀🚀🚀');
|
|
119
|
+
console.log('Setting gameState from', gameState, 'to playing');
|
|
120
|
+
gameState = 'playing';
|
|
121
|
+
currentScene = 0;
|
|
122
|
+
sceneTime = 0;
|
|
123
|
+
console.log('gameState is now:', gameState);
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
normalColor: rgba8(0, 255, 255, 255),
|
|
127
|
+
hoverColor: rgba8(100, 255, 255, 255),
|
|
128
|
+
pressedColor: rgba8(0, 200, 200, 255),
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
console.log('✅ Start button created:', startBtn);
|
|
133
|
+
|
|
134
|
+
// Info button
|
|
135
|
+
const infoBtn = createButton(
|
|
136
|
+
centerX(240),
|
|
137
|
+
270,
|
|
138
|
+
240,
|
|
139
|
+
50,
|
|
140
|
+
'💡 ABOUT DEMO',
|
|
141
|
+
() => {
|
|
142
|
+
console.log('ℹ️ ABOUT BUTTON CLICKED!');
|
|
143
|
+
console.log('TRON ODYSSEY - A visual showcase of Nova64 capabilities');
|
|
144
|
+
console.log('Features: Bloom, Particles, Shaders, Dynamic Scenes');
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
normalColor: rgba8(255, 0, 255, 255),
|
|
148
|
+
hoverColor: rgba8(255, 100, 255, 255),
|
|
149
|
+
pressedColor: rgba8(200, 0, 200, 255),
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
console.log('✅ Info button created:', infoBtn);
|
|
154
|
+
console.log('🎬 Start screen initialization complete');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function buildStartScene() {
|
|
158
|
+
// Create a VIBRANT neon intro scene
|
|
159
|
+
// Glowing grid floor - subtle base glow
|
|
160
|
+
const gridSize = 80;
|
|
161
|
+
gridFloor = createAdvancedCube(
|
|
162
|
+
gridSize,
|
|
163
|
+
{
|
|
164
|
+
color: COLORS.neonCyan,
|
|
165
|
+
emissive: COLORS.neonCyan,
|
|
166
|
+
emissiveIntensity: 0.3, // Reduced from 0.8 - subtle foundation
|
|
167
|
+
flatShading: true,
|
|
168
|
+
},
|
|
169
|
+
[0, -0.5, 0]
|
|
170
|
+
);
|
|
171
|
+
setScale(gridFloor, 1, 0.01, 1); // Make it flat
|
|
172
|
+
|
|
173
|
+
// Grid lines with moderate emissive glow
|
|
174
|
+
for (let i = -40; i <= 40; i += 5) {
|
|
175
|
+
// Horizontal - alternating cyan and magenta
|
|
176
|
+
const hColor = i % 10 === 0 ? COLORS.neonCyan : COLORS.electric;
|
|
177
|
+
const hLine = createAdvancedCube(
|
|
178
|
+
1,
|
|
179
|
+
{
|
|
180
|
+
color: hColor,
|
|
181
|
+
emissive: hColor,
|
|
182
|
+
emissiveIntensity: 0.6, // Reduced from 1.2
|
|
183
|
+
flatShading: true,
|
|
184
|
+
},
|
|
185
|
+
[0, 0.1, i]
|
|
186
|
+
);
|
|
187
|
+
setScale(hLine, gridSize, 0.15, 0.3);
|
|
188
|
+
tunnelSegments.push(hLine);
|
|
189
|
+
|
|
190
|
+
// Vertical - alternating colors
|
|
191
|
+
const vColor = i % 10 === 0 ? COLORS.neonMagenta : COLORS.neonPink;
|
|
192
|
+
const vLine = createAdvancedCube(
|
|
193
|
+
1,
|
|
194
|
+
{
|
|
195
|
+
color: vColor,
|
|
196
|
+
emissive: vColor,
|
|
197
|
+
emissiveIntensity: 0.6, // Reduced from 1.2
|
|
198
|
+
flatShading: true,
|
|
199
|
+
},
|
|
200
|
+
[i, 0.1, 0]
|
|
201
|
+
);
|
|
202
|
+
setScale(vLine, 0.3, 0.15, gridSize);
|
|
203
|
+
tunnelSegments.push(vLine);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Floating GLOWING crystalline structures - much brighter
|
|
207
|
+
for (let i = 0; i < 8; i++) {
|
|
208
|
+
const angle = (i / 8) * Math.PI * 2;
|
|
209
|
+
const radius = 25;
|
|
210
|
+
const x = Math.cos(angle) * radius;
|
|
211
|
+
const z = Math.sin(angle) * radius;
|
|
212
|
+
const size = 2 + Math.random() * 3;
|
|
213
|
+
|
|
214
|
+
// Rainbow of colors for crystals
|
|
215
|
+
const crystalColors = [
|
|
216
|
+
COLORS.neonCyan,
|
|
217
|
+
COLORS.neonMagenta,
|
|
218
|
+
COLORS.neonYellow,
|
|
219
|
+
COLORS.neonPink,
|
|
220
|
+
COLORS.neonGreen,
|
|
221
|
+
];
|
|
222
|
+
const crystalColor = crystalColors[i % crystalColors.length];
|
|
223
|
+
|
|
224
|
+
const crystal = createAdvancedCube(
|
|
225
|
+
size,
|
|
226
|
+
{
|
|
227
|
+
color: crystalColor,
|
|
228
|
+
emissive: crystalColor,
|
|
229
|
+
emissiveIntensity: 0.8, // Reduced from 1.5 - bright but not blinding
|
|
230
|
+
flatShading: true,
|
|
231
|
+
},
|
|
232
|
+
[x, size, z]
|
|
233
|
+
);
|
|
234
|
+
setRotation(crystal, Math.PI / 4, angle, Math.PI / 6);
|
|
235
|
+
digitalTowers.push({ mesh: crystal, x, z, angle, rotSpeed: 0.5 + Math.random() });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Particle system
|
|
239
|
+
await createParticleField();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function createParticleField() {
|
|
243
|
+
// Ambient floating particles - BRIGHT and GLOWING
|
|
244
|
+
for (let i = 0; i < 150; i++) {
|
|
245
|
+
// More particles!
|
|
246
|
+
const x = (Math.random() - 0.5) * 100;
|
|
247
|
+
const y = Math.random() * 30;
|
|
248
|
+
const z = (Math.random() - 0.5) * 100;
|
|
249
|
+
|
|
250
|
+
const colors = [
|
|
251
|
+
COLORS.neonCyan,
|
|
252
|
+
COLORS.neonMagenta,
|
|
253
|
+
COLORS.neonYellow,
|
|
254
|
+
COLORS.neonPink,
|
|
255
|
+
COLORS.neonGreen,
|
|
256
|
+
COLORS.neonOrange,
|
|
257
|
+
];
|
|
258
|
+
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
259
|
+
|
|
260
|
+
const particle = createSphere(0.3, color, [x, y, z], 6, {
|
|
261
|
+
emissive: color,
|
|
262
|
+
emissiveIntensity: 0.7, // Reduced from 2.0 - visible sparkle without blinding
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
particleSystems.push({
|
|
266
|
+
mesh: particle,
|
|
267
|
+
x,
|
|
268
|
+
y,
|
|
269
|
+
z,
|
|
270
|
+
vx: (Math.random() - 0.5) * 2,
|
|
271
|
+
vy: Math.random() * 0.5,
|
|
272
|
+
vz: (Math.random() - 0.5) * 2,
|
|
273
|
+
life: 100,
|
|
274
|
+
color,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function update(dt) {
|
|
280
|
+
gameTime += dt;
|
|
281
|
+
|
|
282
|
+
// Start screen state
|
|
283
|
+
if (gameState === 'start') {
|
|
284
|
+
startScreenTime += dt;
|
|
285
|
+
|
|
286
|
+
// Update buttons - this handles mouse clicks
|
|
287
|
+
const clicked = updateAllButtons();
|
|
288
|
+
if (clicked) {
|
|
289
|
+
console.log('🖱️ A button was clicked!');
|
|
290
|
+
// Extra safety: force state change if button was clicked but callback didn't fire
|
|
291
|
+
if (gameState === 'start') {
|
|
292
|
+
console.log('💡 Button clicked but state not changed, forcing...');
|
|
293
|
+
gameState = 'playing';
|
|
294
|
+
currentScene = 0;
|
|
295
|
+
sceneTime = 0;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// KEYBOARD SUPPORT: Press SPACE or ENTER to start (use isKeyDown for continuous detection)
|
|
300
|
+
if (isKeyDown('Space') || isKeyDown('Enter')) {
|
|
301
|
+
console.log('⌨️ Keyboard pressed! Starting demoscene journey...');
|
|
302
|
+
gameState = 'playing';
|
|
303
|
+
currentScene = 0;
|
|
304
|
+
sceneTime = 0;
|
|
305
|
+
clearButtons(); // Clear buttons when starting
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Animated camera orbit on start screen
|
|
309
|
+
const radius = 40;
|
|
310
|
+
camera.x = Math.cos(startScreenTime * 0.3) * radius;
|
|
311
|
+
camera.z = Math.sin(startScreenTime * 0.3) * radius;
|
|
312
|
+
camera.y = 20 + Math.sin(startScreenTime * 0.5) * 5;
|
|
313
|
+
|
|
314
|
+
setCameraPosition(camera.x, camera.y, camera.z);
|
|
315
|
+
setCameraTarget(0, 5, 0);
|
|
316
|
+
|
|
317
|
+
// Animate start scene objects
|
|
318
|
+
updateStartSceneAnimation(dt);
|
|
319
|
+
updateParticles(dt);
|
|
320
|
+
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Main demo progression
|
|
325
|
+
sceneTime += dt;
|
|
326
|
+
|
|
327
|
+
// Check for scene transition
|
|
328
|
+
if (sceneTime >= SCENES[currentScene].duration && !transitioning) {
|
|
329
|
+
if (currentScene < SCENES.length - 1) {
|
|
330
|
+
startSceneTransition();
|
|
331
|
+
} else {
|
|
332
|
+
// End of demo - loop back
|
|
333
|
+
currentScene = 0;
|
|
334
|
+
sceneTime = 0;
|
|
335
|
+
transitionToNextScene();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Handle transitions
|
|
340
|
+
if (transitioning) {
|
|
341
|
+
transitionProgress += dt * 0.5; // 2 second transitions
|
|
342
|
+
if (transitionProgress >= 1.0) {
|
|
343
|
+
transitioning = false;
|
|
344
|
+
transitionProgress = 0;
|
|
345
|
+
transitionToNextScene();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Update current scene
|
|
350
|
+
updateCurrentScene(dt);
|
|
351
|
+
updateCamera(dt);
|
|
352
|
+
updateParticles(dt);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function updateStartSceneAnimation(dt) {
|
|
356
|
+
// Rotate crystals
|
|
357
|
+
digitalTowers.forEach(tower => {
|
|
358
|
+
tower.angle += tower.rotSpeed * dt;
|
|
359
|
+
setRotation(tower.mesh, Math.PI / 4 + Math.sin(gameTime * 2) * 0.2, tower.angle, Math.PI / 6);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function updateCurrentScene(dt) {
|
|
364
|
+
const progress = sceneTime / SCENES[currentScene].duration;
|
|
365
|
+
|
|
366
|
+
switch (currentScene) {
|
|
367
|
+
case 0: // GRID AWAKENING
|
|
368
|
+
updateGridAwakening(dt, progress);
|
|
369
|
+
break;
|
|
370
|
+
case 1: // DATA TUNNEL
|
|
371
|
+
updateDataTunnel(dt, progress);
|
|
372
|
+
break;
|
|
373
|
+
case 2: // DIGITAL CITY
|
|
374
|
+
updateDigitalCity(dt, progress);
|
|
375
|
+
break;
|
|
376
|
+
case 3: // ENERGY CORE
|
|
377
|
+
updateEnergyCore(dt, progress);
|
|
378
|
+
break;
|
|
379
|
+
case 4: // THE VOID
|
|
380
|
+
updateTheVoid(dt, progress);
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Scene 0: GRID AWAKENING
|
|
386
|
+
function updateGridAwakening(dt, progress) {
|
|
387
|
+
// Rotate floating crystals
|
|
388
|
+
digitalTowers.forEach((tower, i) => {
|
|
389
|
+
const heightOffset = Math.sin(gameTime * 2 + i) * 3;
|
|
390
|
+
const rotSpeed = 1 + Math.sin(gameTime + i) * 0.5;
|
|
391
|
+
tower.angle += rotSpeed * dt;
|
|
392
|
+
|
|
393
|
+
setPosition(tower.mesh, tower.x, 4 + heightOffset, tower.z);
|
|
394
|
+
setRotation(tower.mesh, gameTime * 0.5, tower.angle, gameTime * 0.3);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// Spawn pulse rings periodically
|
|
398
|
+
if (Math.floor(gameTime * 2) !== Math.floor((gameTime - dt) * 2)) {
|
|
399
|
+
createPulseRing();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Update pulse rings
|
|
403
|
+
updatePulseRings(dt);
|
|
404
|
+
|
|
405
|
+
// Camera movement - rising up
|
|
406
|
+
camera.y = 15 + progress * 10;
|
|
407
|
+
camera.z = 35 - progress * 15;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Scene 1: DATA TUNNEL
|
|
411
|
+
function updateDataTunnel(dt, progress) {
|
|
412
|
+
// Create tunnel segments on the fly
|
|
413
|
+
if (tunnelSegments.length < 50 && Math.random() < 0.3) {
|
|
414
|
+
createTunnelSegment();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Move tunnel segments
|
|
418
|
+
for (let i = tunnelSegments.length - 1; i >= 0; i--) {
|
|
419
|
+
const seg = tunnelSegments[i];
|
|
420
|
+
if (seg.z) {
|
|
421
|
+
seg.z += 20 * dt;
|
|
422
|
+
setPosition(seg.mesh, seg.x || 0, seg.y || 0, seg.z);
|
|
423
|
+
|
|
424
|
+
// Remove if behind camera
|
|
425
|
+
if (seg.z > 50) {
|
|
426
|
+
destroyMesh(seg.mesh);
|
|
427
|
+
tunnelSegments.splice(i, 1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Create data streams
|
|
433
|
+
if (dataStreams.length < 30 && Math.random() < 0.2) {
|
|
434
|
+
createDataStream();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Update data streams
|
|
438
|
+
updateDataStreams(dt);
|
|
439
|
+
|
|
440
|
+
// Camera - flying forward through tunnel
|
|
441
|
+
camera.z = 35 - progress * 50;
|
|
442
|
+
camera.y = 10 + Math.sin(progress * Math.PI * 4) * 3;
|
|
443
|
+
camera.roll = Math.sin(progress * Math.PI * 2) * 0.2;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Scene 2: DIGITAL CITY
|
|
447
|
+
function updateDigitalCity(dt, progress) {
|
|
448
|
+
// Build city towers as we go
|
|
449
|
+
if (digitalTowers.length < 40 && Math.random() < 0.1) {
|
|
450
|
+
createDigitalTower();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Animate towers with pulsing effect
|
|
454
|
+
digitalTowers.forEach(tower => {
|
|
455
|
+
if (tower.pulsePhase !== undefined) {
|
|
456
|
+
tower.pulsePhase += dt * 3;
|
|
457
|
+
const scale = 1 + Math.sin(tower.pulsePhase) * 0.15;
|
|
458
|
+
const width = tower.width || 3;
|
|
459
|
+
const height = tower.height || 15;
|
|
460
|
+
setScale(tower.mesh, width * scale, height * scale, width * scale);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Spawn light cycles
|
|
465
|
+
if (lightCycles.length < 6 && Math.random() < 0.1) {
|
|
466
|
+
createLightCycle();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Update light cycles
|
|
470
|
+
updateLightCycles(dt);
|
|
471
|
+
|
|
472
|
+
// Camera - orbiting the city
|
|
473
|
+
const orbitRadius = 40 + Math.sin(progress * Math.PI) * 15;
|
|
474
|
+
const orbitAngle = progress * Math.PI * 2;
|
|
475
|
+
camera.x = Math.cos(orbitAngle) * orbitRadius;
|
|
476
|
+
camera.z = Math.sin(orbitAngle) * orbitRadius;
|
|
477
|
+
camera.y = 20 + Math.sin(progress * Math.PI * 4) * 5;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Scene 3: ENERGY CORE
|
|
481
|
+
function updateEnergyCore(dt, progress) {
|
|
482
|
+
// Create energy fields
|
|
483
|
+
if (energyFields.length < 20 && Math.random() < 0.15) {
|
|
484
|
+
createEnergyField();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Rotate and pulse energy fields
|
|
488
|
+
energyFields.forEach(field => {
|
|
489
|
+
field.rotation += field.rotSpeed * dt;
|
|
490
|
+
field.pulsePhase += dt * 4;
|
|
491
|
+
|
|
492
|
+
const scale = 1 + Math.sin(field.pulsePhase) * 0.3;
|
|
493
|
+
setScale(field.mesh, scale, scale, scale);
|
|
494
|
+
setRotation(field.mesh, field.rotation, field.rotation * 1.5, field.rotation * 0.5);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// Camera - spiraling into the core
|
|
498
|
+
const spiralRadius = 30 - progress * 20;
|
|
499
|
+
const spiralHeight = 20 - progress * 15;
|
|
500
|
+
const spiralAngle = progress * Math.PI * 6;
|
|
501
|
+
|
|
502
|
+
camera.x = Math.cos(spiralAngle) * spiralRadius;
|
|
503
|
+
camera.z = Math.sin(spiralAngle) * spiralRadius;
|
|
504
|
+
camera.y = spiralHeight;
|
|
505
|
+
|
|
506
|
+
// Increase bloom intensity for energy core climax
|
|
507
|
+
setBloomStrength(1.2 + progress * 1.0); // Goes from 1.2 to 2.2 - dramatic but visible
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Scene 4: THE VOID
|
|
511
|
+
function updateTheVoid(dt, progress) {
|
|
512
|
+
// Fade to darkness
|
|
513
|
+
const fogFar = 120 - progress * 100;
|
|
514
|
+
setFog(0x000000, 10, Math.max(20, fogFar));
|
|
515
|
+
|
|
516
|
+
// Create final particle explosion
|
|
517
|
+
if (progress > 0.5 && Math.random() < 0.5) {
|
|
518
|
+
createExplosionParticle();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Camera - pulling back dramatically
|
|
522
|
+
camera.z = -20 + progress * 60;
|
|
523
|
+
camera.y = 5 + progress * 30;
|
|
524
|
+
|
|
525
|
+
// Gradually reduce bloom as we fade to void
|
|
526
|
+
setBloomStrength(1.2 - progress * 1.0); // Fades from 1.2 to 0.2
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Helper functions for creating scene elements
|
|
530
|
+
function createPulseRing() {
|
|
531
|
+
const ringColors = [COLORS.neonCyan, COLORS.neonMagenta, COLORS.neonYellow];
|
|
532
|
+
const color = ringColors[Math.floor(Math.random() * ringColors.length)];
|
|
533
|
+
|
|
534
|
+
const ring = createSphere(1, color, [0, 0.2, 0], 8, {
|
|
535
|
+
emissive: color,
|
|
536
|
+
emissiveIntensity: 1.0, // Reduced from 2.5 - noticeable pulse without washing out
|
|
537
|
+
});
|
|
538
|
+
pulseRings.push({
|
|
539
|
+
mesh: ring,
|
|
540
|
+
scale: 1,
|
|
541
|
+
life: 2,
|
|
542
|
+
maxLife: 2,
|
|
543
|
+
color,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function updatePulseRings(dt) {
|
|
548
|
+
for (let i = pulseRings.length - 1; i >= 0; i--) {
|
|
549
|
+
const ring = pulseRings[i];
|
|
550
|
+
ring.life -= dt;
|
|
551
|
+
ring.scale += dt * 15;
|
|
552
|
+
|
|
553
|
+
setScale(ring.mesh, ring.scale, 0.1, ring.scale);
|
|
554
|
+
|
|
555
|
+
if (ring.life <= 0) {
|
|
556
|
+
destroyMesh(ring.mesh);
|
|
557
|
+
pulseRings.splice(i, 1);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function createTunnelSegment() {
|
|
563
|
+
const z = -50 - Math.random() * 20;
|
|
564
|
+
const segments = 8;
|
|
565
|
+
const tunnelColors = [COLORS.neonCyan, COLORS.neonMagenta, COLORS.neonYellow, COLORS.neonPink];
|
|
566
|
+
|
|
567
|
+
for (let i = 0; i < segments; i++) {
|
|
568
|
+
const angle = (i / segments) * Math.PI * 2;
|
|
569
|
+
const radius = 15;
|
|
570
|
+
const x = Math.cos(angle) * radius;
|
|
571
|
+
const y = Math.sin(angle) * radius;
|
|
572
|
+
|
|
573
|
+
const color = tunnelColors[i % tunnelColors.length];
|
|
574
|
+
const seg = createAdvancedCube(
|
|
575
|
+
1,
|
|
576
|
+
{
|
|
577
|
+
color: color,
|
|
578
|
+
emissive: color,
|
|
579
|
+
emissiveIntensity: 0.8, // Reduced from 1.5
|
|
580
|
+
flatShading: true,
|
|
581
|
+
},
|
|
582
|
+
[x, y, z]
|
|
583
|
+
);
|
|
584
|
+
setScale(seg, 1, 1, 2);
|
|
585
|
+
|
|
586
|
+
tunnelSegments.push({
|
|
587
|
+
mesh: seg,
|
|
588
|
+
x,
|
|
589
|
+
y,
|
|
590
|
+
z,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function createDataStream() {
|
|
596
|
+
const angle = Math.random() * Math.PI * 2;
|
|
597
|
+
const radius = 10 + Math.random() * 5;
|
|
598
|
+
const x = Math.cos(angle) * radius;
|
|
599
|
+
const y = Math.sin(angle) * radius;
|
|
600
|
+
|
|
601
|
+
const colors = [
|
|
602
|
+
COLORS.neonCyan,
|
|
603
|
+
COLORS.neonMagenta,
|
|
604
|
+
COLORS.neonYellow,
|
|
605
|
+
COLORS.neonGreen,
|
|
606
|
+
COLORS.neonOrange,
|
|
607
|
+
];
|
|
608
|
+
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
609
|
+
|
|
610
|
+
const stream = createAdvancedCube(
|
|
611
|
+
1,
|
|
612
|
+
{
|
|
613
|
+
color: color,
|
|
614
|
+
emissive: color,
|
|
615
|
+
emissiveIntensity: 0.9, // Reduced from 1.8
|
|
616
|
+
flatShading: true,
|
|
617
|
+
},
|
|
618
|
+
[x, y, -60]
|
|
619
|
+
);
|
|
620
|
+
setScale(stream, 0.4, 0.4, 4);
|
|
621
|
+
|
|
622
|
+
dataStreams.push({
|
|
623
|
+
mesh: stream,
|
|
624
|
+
x,
|
|
625
|
+
y,
|
|
626
|
+
z: -60,
|
|
627
|
+
speed: 30 + Math.random() * 20,
|
|
628
|
+
color,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function updateDataStreams(dt) {
|
|
633
|
+
for (let i = dataStreams.length - 1; i >= 0; i--) {
|
|
634
|
+
const stream = dataStreams[i];
|
|
635
|
+
stream.z += stream.speed * dt;
|
|
636
|
+
setPosition(stream.mesh, stream.x, stream.y, stream.z);
|
|
637
|
+
|
|
638
|
+
if (stream.z > 50) {
|
|
639
|
+
destroyMesh(stream.mesh);
|
|
640
|
+
dataStreams.splice(i, 1);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function createDigitalTower() {
|
|
646
|
+
const x = (Math.random() - 0.5) * 80;
|
|
647
|
+
const z = (Math.random() - 0.5) * 80;
|
|
648
|
+
|
|
649
|
+
// Avoid center
|
|
650
|
+
if (Math.abs(x) < 15 && Math.abs(z) < 15) return;
|
|
651
|
+
|
|
652
|
+
const width = 2 + Math.random() * 3;
|
|
653
|
+
const height = 10 + Math.random() * 20;
|
|
654
|
+
|
|
655
|
+
const colors = [
|
|
656
|
+
COLORS.neonCyan,
|
|
657
|
+
COLORS.neonMagenta,
|
|
658
|
+
COLORS.neonYellow,
|
|
659
|
+
COLORS.neonPink,
|
|
660
|
+
COLORS.neonGreen,
|
|
661
|
+
COLORS.neonOrange,
|
|
662
|
+
];
|
|
663
|
+
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
664
|
+
|
|
665
|
+
const tower = createAdvancedCube(
|
|
666
|
+
1,
|
|
667
|
+
{
|
|
668
|
+
color: color,
|
|
669
|
+
emissive: color,
|
|
670
|
+
emissiveIntensity: 0.7, // Reduced from 1.3
|
|
671
|
+
flatShading: true,
|
|
672
|
+
},
|
|
673
|
+
[x, height / 2, z]
|
|
674
|
+
);
|
|
675
|
+
setScale(tower, width, height, width);
|
|
676
|
+
|
|
677
|
+
digitalTowers.push({
|
|
678
|
+
mesh: tower,
|
|
679
|
+
x,
|
|
680
|
+
z,
|
|
681
|
+
height,
|
|
682
|
+
width,
|
|
683
|
+
baseScale: 1,
|
|
684
|
+
pulsePhase: Math.random() * Math.PI * 2,
|
|
685
|
+
color,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function createLightCycle() {
|
|
690
|
+
const angle = Math.random() * Math.PI * 2;
|
|
691
|
+
const radius = 30;
|
|
692
|
+
const x = Math.cos(angle) * radius;
|
|
693
|
+
const z = Math.sin(angle) * radius;
|
|
694
|
+
|
|
695
|
+
const cycleColors = [COLORS.neonCyan, COLORS.neonMagenta, COLORS.neonYellow, COLORS.neonOrange];
|
|
696
|
+
const bodyColor = cycleColors[Math.floor(Math.random() * cycleColors.length)];
|
|
697
|
+
const trailColor = bodyColor; // Matching trail
|
|
698
|
+
|
|
699
|
+
const body = createAdvancedCube(
|
|
700
|
+
1,
|
|
701
|
+
{
|
|
702
|
+
color: bodyColor,
|
|
703
|
+
emissive: bodyColor,
|
|
704
|
+
emissiveIntensity: 0.8, // Reduced from 1.5
|
|
705
|
+
flatShading: true,
|
|
706
|
+
},
|
|
707
|
+
[x, 1, z]
|
|
708
|
+
);
|
|
709
|
+
setScale(body, 2, 0.5, 1);
|
|
710
|
+
|
|
711
|
+
const trail = createAdvancedCube(
|
|
712
|
+
1,
|
|
713
|
+
{
|
|
714
|
+
color: trailColor,
|
|
715
|
+
emissive: trailColor,
|
|
716
|
+
emissiveIntensity: 0.6, // Reduced from 1.2
|
|
717
|
+
flatShading: true,
|
|
718
|
+
},
|
|
719
|
+
[x, 1, z]
|
|
720
|
+
);
|
|
721
|
+
setScale(trail, 0.5, 0.5, 8);
|
|
722
|
+
|
|
723
|
+
lightCycles.push({
|
|
724
|
+
body,
|
|
725
|
+
trail,
|
|
726
|
+
x,
|
|
727
|
+
z,
|
|
728
|
+
angle,
|
|
729
|
+
speed: 2 + Math.random(),
|
|
730
|
+
color: bodyColor,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function updateLightCycles(dt) {
|
|
735
|
+
lightCycles.forEach(cycle => {
|
|
736
|
+
cycle.angle += cycle.speed * dt;
|
|
737
|
+
|
|
738
|
+
const radius = 30;
|
|
739
|
+
cycle.x = Math.cos(cycle.angle) * radius;
|
|
740
|
+
cycle.z = Math.sin(cycle.angle) * radius;
|
|
741
|
+
|
|
742
|
+
setPosition(cycle.body, cycle.x, 1, cycle.z);
|
|
743
|
+
setRotation(cycle.body, 0, cycle.angle + Math.PI / 2, 0);
|
|
744
|
+
|
|
745
|
+
const trailX = cycle.x - Math.cos(cycle.angle + Math.PI / 2) * 4;
|
|
746
|
+
const trailZ = cycle.z - Math.sin(cycle.angle + Math.PI / 2) * 4;
|
|
747
|
+
setPosition(cycle.trail, trailX, 1, trailZ);
|
|
748
|
+
setRotation(cycle.trail, 0, cycle.angle + Math.PI / 2, 0);
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function createEnergyField() {
|
|
753
|
+
const x = (Math.random() - 0.5) * 20;
|
|
754
|
+
const y = (Math.random() - 0.5) * 20;
|
|
755
|
+
const z = (Math.random() - 0.5) * 20;
|
|
756
|
+
|
|
757
|
+
const size = 1 + Math.random() * 2;
|
|
758
|
+
const colors = [
|
|
759
|
+
COLORS.neonMagenta,
|
|
760
|
+
COLORS.neonYellow,
|
|
761
|
+
COLORS.neonPink,
|
|
762
|
+
COLORS.neonCyan,
|
|
763
|
+
COLORS.neonGreen,
|
|
764
|
+
];
|
|
765
|
+
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
766
|
+
|
|
767
|
+
const field = createSphere(size, color, [x, y, z], 10, {
|
|
768
|
+
emissive: color,
|
|
769
|
+
emissiveIntensity: 1.0, // Reduced from 2.0 - bright but not blinding
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
energyFields.push({
|
|
773
|
+
mesh: field,
|
|
774
|
+
rotation: 0,
|
|
775
|
+
rotSpeed: 0.5 + Math.random(),
|
|
776
|
+
pulsePhase: Math.random() * Math.PI * 2,
|
|
777
|
+
color,
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function createExplosionParticle() {
|
|
782
|
+
const x = (Math.random() - 0.5) * 40;
|
|
783
|
+
const y = (Math.random() - 0.5) * 40;
|
|
784
|
+
const z = (Math.random() - 0.5) * 40;
|
|
785
|
+
|
|
786
|
+
const colors = Object.values(COLORS);
|
|
787
|
+
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
788
|
+
|
|
789
|
+
const particle = createSphere(0.5, color, [x, y, z], 6, {
|
|
790
|
+
emissive: color,
|
|
791
|
+
emissiveIntensity: 1.2, // Reduced from 2.5 - bright explosion without washing out
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
particleSystems.push({
|
|
795
|
+
mesh: particle,
|
|
796
|
+
x,
|
|
797
|
+
y,
|
|
798
|
+
z,
|
|
799
|
+
vx: (Math.random() - 0.5) * 20,
|
|
800
|
+
vy: (Math.random() - 0.5) * 20,
|
|
801
|
+
vz: (Math.random() - 0.5) * 20,
|
|
802
|
+
life: 3,
|
|
803
|
+
color,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function updateParticles(dt) {
|
|
808
|
+
for (let i = particleSystems.length - 1; i >= 0; i--) {
|
|
809
|
+
const particle = particleSystems[i];
|
|
810
|
+
|
|
811
|
+
particle.x += particle.vx * dt;
|
|
812
|
+
particle.y += particle.vy * dt;
|
|
813
|
+
particle.z += particle.vz * dt;
|
|
814
|
+
|
|
815
|
+
// Slight gravity
|
|
816
|
+
particle.vy -= dt * 2;
|
|
817
|
+
|
|
818
|
+
particle.life -= dt;
|
|
819
|
+
|
|
820
|
+
setPosition(particle.mesh, particle.x, particle.y, particle.z);
|
|
821
|
+
|
|
822
|
+
// Fade out
|
|
823
|
+
const scale = Math.max(0, particle.life / 3);
|
|
824
|
+
setScale(particle.mesh, scale, scale, scale);
|
|
825
|
+
|
|
826
|
+
if (particle.life <= 0 || scale <= 0) {
|
|
827
|
+
destroyMesh(particle.mesh);
|
|
828
|
+
particleSystems.splice(i, 1);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function updateCamera(_dt) {
|
|
834
|
+
setCameraPosition(camera.x, camera.y, camera.z);
|
|
835
|
+
setCameraTarget(camera.targetX, camera.targetY, camera.targetZ);
|
|
836
|
+
|
|
837
|
+
// Apply camera roll if needed
|
|
838
|
+
if (Math.abs(camera.roll) > 0.01) {
|
|
839
|
+
// Roll effect would be applied here if supported
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function startSceneTransition() {
|
|
844
|
+
transitioning = true;
|
|
845
|
+
transitionProgress = 0;
|
|
846
|
+
console.log(`🎬 Transitioning to: ${SCENES[currentScene + 1].name}`);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function transitionToNextScene() {
|
|
850
|
+
// Clean up previous scene objects
|
|
851
|
+
cleanupScene();
|
|
852
|
+
|
|
853
|
+
// Move to next scene
|
|
854
|
+
currentScene++;
|
|
855
|
+
if (currentScene >= SCENES.length) {
|
|
856
|
+
currentScene = 0;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
sceneTime = 0;
|
|
860
|
+
|
|
861
|
+
// Setup new scene
|
|
862
|
+
setupScene(currentScene);
|
|
863
|
+
|
|
864
|
+
console.log(`✨ Now showing: ${SCENES[currentScene].name}`);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function cleanupScene() {
|
|
868
|
+
// Remove all dynamic objects (keep start scene for now)
|
|
869
|
+
dataStreams.forEach(s => destroyMesh(s.mesh));
|
|
870
|
+
dataStreams = [];
|
|
871
|
+
|
|
872
|
+
pulseRings.forEach(r => destroyMesh(r.mesh));
|
|
873
|
+
pulseRings = [];
|
|
874
|
+
|
|
875
|
+
lightCycles.forEach(c => {
|
|
876
|
+
destroyMesh(c.body);
|
|
877
|
+
destroyMesh(c.trail);
|
|
878
|
+
});
|
|
879
|
+
lightCycles = [];
|
|
880
|
+
|
|
881
|
+
energyFields.forEach(f => destroyMesh(f.mesh));
|
|
882
|
+
energyFields = [];
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function setupScene(_sceneIndex) {
|
|
886
|
+
// Set theme lighting based on scene - balanced darkness
|
|
887
|
+
setFog(0x000020, 30, 150);
|
|
888
|
+
setBloomStrength(1.2); // Balanced bloom setting
|
|
889
|
+
|
|
890
|
+
// Reset camera for new scene
|
|
891
|
+
camera.roll = 0;
|
|
892
|
+
camera.fov = 75;
|
|
893
|
+
setCameraFOV(camera.fov);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
let drawCallCount = 0;
|
|
897
|
+
|
|
898
|
+
export function draw() {
|
|
899
|
+
// Log first few draw calls to verify it's working
|
|
900
|
+
if (drawCallCount < 3) {
|
|
901
|
+
console.log(`✏️ draw() called, gameState: ${gameState}, drawCallCount: ${drawCallCount}`);
|
|
902
|
+
drawCallCount++;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Start screen
|
|
906
|
+
if (gameState === 'start') {
|
|
907
|
+
drawStartScreen();
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Demo HUD
|
|
912
|
+
drawDemoHUD();
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function drawStartScreen() {
|
|
916
|
+
// Dark gradient overlay
|
|
917
|
+
drawGradientRect(0, 0, 640, 360, rgba8(0, 10, 20, 220), rgba8(20, 0, 40, 240), true);
|
|
918
|
+
|
|
919
|
+
// Animated title
|
|
920
|
+
const pulse = Math.sin(startScreenTime * 3) * 0.3 + 0.7;
|
|
921
|
+
const bounce = Math.sin(startScreenTime * 2) * 8;
|
|
922
|
+
|
|
923
|
+
setFont('huge');
|
|
924
|
+
setTextAlign('center');
|
|
925
|
+
|
|
926
|
+
// Title with color shift
|
|
927
|
+
const r = Math.floor(128 + Math.sin(startScreenTime * 2) * 127);
|
|
928
|
+
const g = Math.floor(128 + Math.sin(startScreenTime * 2 + 2) * 127);
|
|
929
|
+
const b = Math.floor(128 + Math.sin(startScreenTime * 2 + 4) * 127);
|
|
930
|
+
|
|
931
|
+
drawTextShadow('NOVA64', 320, 50 + bounce, rgba8(r, g, b, 255), rgba8(0, 0, 0, 255), 6, 1);
|
|
932
|
+
|
|
933
|
+
setFont('large');
|
|
934
|
+
const cyan = rgba8(0, 255, 255, Math.floor(pulse * 255));
|
|
935
|
+
drawTextOutline('DEMOSCENE', 320, 110, cyan, rgba8(0, 0, 0, 255), 2);
|
|
936
|
+
|
|
937
|
+
// Subtitle
|
|
938
|
+
setFont('normal');
|
|
939
|
+
const magenta = rgba8(255, 0, 255, 255);
|
|
940
|
+
drawText('▶ TRON ODYSSEY ◀', 320, 145, magenta, 1);
|
|
941
|
+
|
|
942
|
+
// Info panel
|
|
943
|
+
const panel = createPanel(centerX(500), 210, 500, 200, {
|
|
944
|
+
bgColor: rgba8(10, 0, 20, 200),
|
|
945
|
+
borderColor: rgba8(0, 255, 255, 255),
|
|
946
|
+
borderWidth: 3,
|
|
947
|
+
shadow: true,
|
|
948
|
+
gradient: true,
|
|
949
|
+
gradientColor: rgba8(20, 0, 40, 200),
|
|
950
|
+
});
|
|
951
|
+
drawPanel(panel);
|
|
952
|
+
|
|
953
|
+
setFont('small');
|
|
954
|
+
setTextAlign('center');
|
|
955
|
+
drawText('A VISUAL SHOWCASE OF NOVA64 CAPABILITIES', 320, 225, uiColors.warning, 1);
|
|
956
|
+
drawText('', 320, 240, uiColors.light, 1);
|
|
957
|
+
drawText('✨ BLOOM & POST-PROCESSING EFFECTS', 320, 255, uiColors.light, 1);
|
|
958
|
+
drawText('🎨 DYNAMIC SHADER MATERIALS', 320, 270, uiColors.light, 1);
|
|
959
|
+
drawText('💫 GPU-ACCELERATED PARTICLES', 320, 285, uiColors.light, 1);
|
|
960
|
+
drawText('🎬 CINEMATIC CAMERA CHOREOGRAPHY', 320, 300, uiColors.light, 1);
|
|
961
|
+
drawText('🌈 PROCEDURAL NEON GEOMETRY', 320, 315, uiColors.light, 1);
|
|
962
|
+
|
|
963
|
+
setFont('tiny');
|
|
964
|
+
drawText(
|
|
965
|
+
'Journey through 5 unique scenes showcasing the engine',
|
|
966
|
+
320,
|
|
967
|
+
335,
|
|
968
|
+
uiColors.secondary,
|
|
969
|
+
1
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
// Draw buttons
|
|
973
|
+
drawAllButtons();
|
|
974
|
+
|
|
975
|
+
// Pulsing prompt
|
|
976
|
+
const alpha = Math.floor((Math.sin(startScreenTime * 5) * 0.5 + 0.5) * 255);
|
|
977
|
+
setFont('normal');
|
|
978
|
+
drawText('▶ PRESS BEGIN OR SPACEBAR TO START ◀', 320, 375, rgba8(0, 255, 255, alpha), 1);
|
|
979
|
+
|
|
980
|
+
// Credits
|
|
981
|
+
setFont('tiny');
|
|
982
|
+
drawText('CONTROLS: CLICK BUTTON OR PRESS SPACE/ENTER', 320, 395, rgba8(150, 150, 200, 200), 1);
|
|
983
|
+
drawText('NOVA64 - THE ULTIMATE FANTASY CONSOLE', 320, 410, rgba8(100, 100, 150, 180), 1);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function drawDemoHUD() {
|
|
987
|
+
// Minimal HUD during demo
|
|
988
|
+
const scene = SCENES[currentScene];
|
|
989
|
+
const progress = (sceneTime / scene.duration) * 100;
|
|
990
|
+
|
|
991
|
+
// Scene info panel - top left
|
|
992
|
+
const panelWidth = 280;
|
|
993
|
+
rect(16, 16, panelWidth, 90, rgba8(0, 0, 20, 200), true);
|
|
994
|
+
rect(16, 16, panelWidth, 90, scene.color, false);
|
|
995
|
+
|
|
996
|
+
setFont('normal');
|
|
997
|
+
setTextAlign('left');
|
|
998
|
+
print('🎬 DEMOSCENE', 24, 24, rgba8(255, 255, 255, 255));
|
|
999
|
+
|
|
1000
|
+
setFont('small');
|
|
1001
|
+
print(
|
|
1002
|
+
`Scene ${currentScene + 1}/${SCENES.length}: ${scene.name}`,
|
|
1003
|
+
24,
|
|
1004
|
+
45,
|
|
1005
|
+
rgba8(200, 200, 255, 255)
|
|
1006
|
+
);
|
|
1007
|
+
|
|
1008
|
+
// Progress bar
|
|
1009
|
+
const barWidth = panelWidth - 16;
|
|
1010
|
+
const barFill = (progress / 100) * barWidth;
|
|
1011
|
+
|
|
1012
|
+
rect(24, 62, barWidth, 8, rgba8(40, 40, 60, 200), true);
|
|
1013
|
+
rect(24, 62, barFill, 8, scene.color, true);
|
|
1014
|
+
rect(24, 62, barWidth, 8, rgba8(255, 255, 255, 100), false);
|
|
1015
|
+
|
|
1016
|
+
setFont('tiny');
|
|
1017
|
+
print(`${progress.toFixed(1)}%`, 24, 78, rgba8(150, 150, 200, 255));
|
|
1018
|
+
print(`Time: ${sceneTime.toFixed(1)}s / ${scene.duration}s`, 24, 90, rgba8(150, 150, 200, 255));
|
|
1019
|
+
|
|
1020
|
+
// Effect status - top right
|
|
1021
|
+
const statsX = 640 - 200;
|
|
1022
|
+
rect(statsX - 16, 16, 200, 65, rgba8(0, 0, 20, 200), true);
|
|
1023
|
+
rect(statsX - 16, 16, 200, 65, rgba8(255, 0, 255, 255), false);
|
|
1024
|
+
|
|
1025
|
+
setFont('tiny');
|
|
1026
|
+
setTextAlign('left');
|
|
1027
|
+
print('EFFECTS ACTIVE:', statsX - 8, 24, rgba8(255, 255, 255, 255));
|
|
1028
|
+
print('✓ BLOOM', statsX - 8, 37, rgba8(0, 255, 0, 255));
|
|
1029
|
+
print('✓ FXAA', statsX - 8, 48, rgba8(0, 255, 0, 255));
|
|
1030
|
+
print('✓ PARTICLES', statsX - 8, 59, rgba8(0, 255, 0, 255));
|
|
1031
|
+
print('✓ FOG', statsX - 8, 70, rgba8(0, 255, 0, 255));
|
|
1032
|
+
|
|
1033
|
+
// Scene description - bottom
|
|
1034
|
+
rect(16, 360 - 45, 640 - 32, 30, rgba8(0, 0, 20, 220), true);
|
|
1035
|
+
|
|
1036
|
+
setFont('small');
|
|
1037
|
+
setTextAlign('center');
|
|
1038
|
+
const desc = getSceneDescription(currentScene);
|
|
1039
|
+
print(desc, 320, 360 - 35, rgba8(255, 255, 100, 255));
|
|
1040
|
+
|
|
1041
|
+
// Nova64 watermark
|
|
1042
|
+
setFont('tiny');
|
|
1043
|
+
print('NOVA64 - POWERED BY THREE.JS', 320, 360 - 20, rgba8(100, 100, 150, 200));
|
|
1044
|
+
|
|
1045
|
+
// Transition overlay
|
|
1046
|
+
if (transitioning) {
|
|
1047
|
+
const alpha = Math.floor(Math.sin(transitionProgress * Math.PI) * 200);
|
|
1048
|
+
rect(0, 0, 640, 360, rgba8(0, 0, 0, alpha), true);
|
|
1049
|
+
|
|
1050
|
+
if (transitionProgress > 0.4 && transitionProgress < 0.6) {
|
|
1051
|
+
setFont('large');
|
|
1052
|
+
setTextAlign('center');
|
|
1053
|
+
const nextScene = SCENES[currentScene + 1] || SCENES[0];
|
|
1054
|
+
drawTextShadow(
|
|
1055
|
+
nextScene.name,
|
|
1056
|
+
320,
|
|
1057
|
+
180,
|
|
1058
|
+
rgba8(255, 255, 255, 255),
|
|
1059
|
+
rgba8(0, 0, 0, 255),
|
|
1060
|
+
4,
|
|
1061
|
+
1
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function getSceneDescription(sceneIndex) {
|
|
1068
|
+
const descriptions = [
|
|
1069
|
+
'Grid awakening - The digital realm comes to life with pulsing energy',
|
|
1070
|
+
'Data tunnel - Racing through streams of information at lightspeed',
|
|
1071
|
+
'Digital city - Towering structures of pure light and geometry',
|
|
1072
|
+
'Energy core - Spiraling into the heart of the system',
|
|
1073
|
+
"The void - Journey's end, returning to infinite darkness",
|
|
1074
|
+
];
|
|
1075
|
+
return descriptions[sceneIndex] || '';
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Utility functions
|
|
1079
|
+
function centerX(width) {
|
|
1080
|
+
return (640 - width) / 2;
|
|
1081
|
+
}
|