nova64 0.2.4 → 0.2.6
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/index.html +6 -1
- package/package.json +37 -32
- 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 +24 -1
- package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
- package/public/os9-shell/assets/index-B1Uvacma.js.map +0 -1
|
@@ -0,0 +1,1555 @@
|
|
|
1
|
+
// F-ZERO NOVA - Ultimate 3D Racing Experience
|
|
2
|
+
// Nintendo 64 F-Zero style racing with full GPU acceleration and retro effects
|
|
3
|
+
|
|
4
|
+
// Ensure API functions are available
|
|
5
|
+
if (typeof get3DStats === 'undefined') {
|
|
6
|
+
globalThis.get3DStats = () => ({ meshes: 0, renderer: 'Unknown' });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Screen management
|
|
10
|
+
let gameState = 'start'; // 'start', 'racing'
|
|
11
|
+
let startScreenTime = 0;
|
|
12
|
+
let uiButtons = [];
|
|
13
|
+
|
|
14
|
+
let gameTime = 0;
|
|
15
|
+
let raceTrack = [];
|
|
16
|
+
let player = null;
|
|
17
|
+
let opponents = [];
|
|
18
|
+
let trackPieces = [];
|
|
19
|
+
let particles = [];
|
|
20
|
+
let powerUps = [];
|
|
21
|
+
let lapTime = 0;
|
|
22
|
+
let currentLap = 1;
|
|
23
|
+
let maxLaps = 3;
|
|
24
|
+
let racePosition = 1;
|
|
25
|
+
let speed = 0;
|
|
26
|
+
let maxSpeed = 120;
|
|
27
|
+
let boost = 0;
|
|
28
|
+
let health = 100;
|
|
29
|
+
|
|
30
|
+
// Track configuration
|
|
31
|
+
const TRACK_WIDTH = 20;
|
|
32
|
+
const TRACK_SEGMENTS = 200;
|
|
33
|
+
const TRACK_RADIUS = 150;
|
|
34
|
+
const OPPONENT_COUNT = 7;
|
|
35
|
+
|
|
36
|
+
// Colors (F-Zero inspired)
|
|
37
|
+
const COLORS = {
|
|
38
|
+
player: 0x0088ff,
|
|
39
|
+
opponents: [0xff4444, 0x44ff44, 0xffff44, 0xff44ff, 0x44ffff, 0xff8844, 0x8844ff],
|
|
40
|
+
track: [0x333366, 0x444477, 0x555588],
|
|
41
|
+
powerup: [0xff0080, 0x80ff00, 0x0080ff, 0xff8000],
|
|
42
|
+
particle: [0xff6600, 0x0066ff, 0x66ff00, 0xff0066],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export async function init() {
|
|
46
|
+
console.log('🏁 F-ZERO NOVA - Initializing Ultimate 3D Racing...');
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Professional 3D setup
|
|
50
|
+
setCameraPosition(-20, 15, 20);
|
|
51
|
+
setCameraTarget(0, 2, 0);
|
|
52
|
+
setCameraFOV(90); // Wide FOV for racing
|
|
53
|
+
|
|
54
|
+
// Advanced multi-layered lighting system
|
|
55
|
+
setupAdvancedLighting();
|
|
56
|
+
|
|
57
|
+
// Dynamic atmospheric fog with color shifting
|
|
58
|
+
setupDynamicFog();
|
|
59
|
+
|
|
60
|
+
// Professional post-processing pipeline
|
|
61
|
+
setupPostProcessingEffects();
|
|
62
|
+
|
|
63
|
+
await buildRaceTrack();
|
|
64
|
+
createPlayer();
|
|
65
|
+
spawnOpponents();
|
|
66
|
+
spawnPowerUps();
|
|
67
|
+
initRaceParticles();
|
|
68
|
+
|
|
69
|
+
// Initialize start screen
|
|
70
|
+
initStartScreen();
|
|
71
|
+
|
|
72
|
+
console.log('✅ F-ZERO NOVA - Initialization complete!');
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('❌ F-ZERO NOVA - Initialization failed:', error);
|
|
75
|
+
// Fallback initialization
|
|
76
|
+
setCameraPosition(-20, 15, 20);
|
|
77
|
+
setCameraTarget(0, 2, 0);
|
|
78
|
+
setFog(0x220044, 50, 300);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function initStartScreen() {
|
|
83
|
+
uiButtons = [];
|
|
84
|
+
|
|
85
|
+
uiButtons.push(
|
|
86
|
+
createButton(
|
|
87
|
+
centerX(240),
|
|
88
|
+
150,
|
|
89
|
+
240,
|
|
90
|
+
60,
|
|
91
|
+
'🏁 START RACE',
|
|
92
|
+
() => {
|
|
93
|
+
console.log('🎯 START RACE CLICKED! Changing gameState to racing...');
|
|
94
|
+
gameState = 'racing';
|
|
95
|
+
// Reset race state
|
|
96
|
+
currentLap = 1;
|
|
97
|
+
lapTime = 0;
|
|
98
|
+
speed = 0;
|
|
99
|
+
health = 100;
|
|
100
|
+
console.log('✅ gameState is now:', gameState);
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
normalColor: rgba8(255, 150, 0, 255),
|
|
104
|
+
hoverColor: rgba8(255, 180, 30, 255),
|
|
105
|
+
pressedColor: rgba8(220, 120, 0, 255),
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
uiButtons.push(
|
|
111
|
+
createButton(
|
|
112
|
+
centerX(200),
|
|
113
|
+
355,
|
|
114
|
+
200,
|
|
115
|
+
45,
|
|
116
|
+
'🎮 CONTROLS',
|
|
117
|
+
() => {
|
|
118
|
+
console.log('Controls: ARROWS=Steer/Accelerate, SPACE=Boost, Z=Brake');
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
normalColor: rgba8(100, 200, 255, 255),
|
|
122
|
+
hoverColor: rgba8(130, 220, 255, 255),
|
|
123
|
+
pressedColor: rgba8(70, 170, 230, 255),
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function update(dt) {
|
|
130
|
+
gameTime += dt;
|
|
131
|
+
|
|
132
|
+
if (gameState === 'start') {
|
|
133
|
+
startScreenTime += dt;
|
|
134
|
+
updateAllButtons();
|
|
135
|
+
|
|
136
|
+
// Animate scene in background
|
|
137
|
+
updateOpponents(dt);
|
|
138
|
+
updateTrack(dt);
|
|
139
|
+
updateParticles(dt);
|
|
140
|
+
updateAdvancedLighting(dt);
|
|
141
|
+
|
|
142
|
+
// Cinematic camera orbit
|
|
143
|
+
const angle = gameTime * 0.3;
|
|
144
|
+
setCameraPosition(Math.cos(angle) * 30, 18, Math.sin(angle) * 30);
|
|
145
|
+
setCameraTarget(0, 2, 0);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
lapTime += dt;
|
|
150
|
+
|
|
151
|
+
handleInput(dt);
|
|
152
|
+
updatePlayer(dt);
|
|
153
|
+
updateOpponents(dt);
|
|
154
|
+
updateTrack(dt);
|
|
155
|
+
updateParticles(dt);
|
|
156
|
+
updatePowerUps(dt);
|
|
157
|
+
updateCamera(dt);
|
|
158
|
+
updateRaceLogic(dt);
|
|
159
|
+
|
|
160
|
+
// Advanced visual effects
|
|
161
|
+
updateAdvancedLighting(dt);
|
|
162
|
+
updatePostProcessing(dt);
|
|
163
|
+
updateTrackLighting(dt);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function draw() {
|
|
167
|
+
if (gameState === 'start') {
|
|
168
|
+
drawStartScreen();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 3D scene automatically rendered
|
|
173
|
+
try {
|
|
174
|
+
drawRacingHUD();
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error('Error in F-Zero draw function:', error);
|
|
177
|
+
// Fallback minimal HUD
|
|
178
|
+
cls();
|
|
179
|
+
print('F-ZERO NOVA', 10, 10, rgba8(255, 255, 255, 255));
|
|
180
|
+
print('Error: Reloading...', 10, 30, rgba8(255, 0, 0, 255));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function drawStartScreen() {
|
|
185
|
+
// Racing gradient background (orange to red)
|
|
186
|
+
drawGradientRect(0, 0, 640, 360, rgba8(80, 30, 10, 235), rgba8(40, 15, 5, 250), true);
|
|
187
|
+
|
|
188
|
+
// Animated title with speed effect
|
|
189
|
+
setFont('huge');
|
|
190
|
+
setTextAlign('center');
|
|
191
|
+
const speed = Math.sin(startScreenTime * 5) * 0.4 + 0.6;
|
|
192
|
+
const speedColor = rgba8(255, Math.floor(speed * 180), 0, 255);
|
|
193
|
+
|
|
194
|
+
const offset = Math.sin(startScreenTime * 8) * 5;
|
|
195
|
+
drawTextShadow('F-ZERO', 320 + offset, 50, speedColor, rgba8(0, 0, 0, 255), 8, 1);
|
|
196
|
+
drawTextShadow('NOVA', 320, 110, rgba8(100, 200, 255, 255), rgba8(0, 0, 0, 255), 8, 1);
|
|
197
|
+
|
|
198
|
+
// Subtitle
|
|
199
|
+
setFont('large');
|
|
200
|
+
const pulse = Math.sin(startScreenTime * 6) * 0.2 + 0.8;
|
|
201
|
+
drawTextOutline(
|
|
202
|
+
'🏁 Extreme 3D Racing 🏁',
|
|
203
|
+
320,
|
|
204
|
+
170,
|
|
205
|
+
rgba8(255, 255, 0, Math.floor(pulse * 255)),
|
|
206
|
+
rgba8(0, 0, 0, 255),
|
|
207
|
+
1
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// Info panel
|
|
211
|
+
const panel = createPanel(centerX(480), 215, 480, 185, {
|
|
212
|
+
bgColor: rgba8(30, 15, 10, 220),
|
|
213
|
+
borderColor: rgba8(255, 150, 0, 255),
|
|
214
|
+
borderWidth: 3,
|
|
215
|
+
shadow: true,
|
|
216
|
+
gradient: true,
|
|
217
|
+
gradientColor: rgba8(50, 25, 15, 220),
|
|
218
|
+
});
|
|
219
|
+
drawPanel(panel);
|
|
220
|
+
|
|
221
|
+
setFont('normal');
|
|
222
|
+
setTextAlign('center');
|
|
223
|
+
drawText('RACE SPECIFICATIONS', 320, 235, rgba8(255, 150, 0, 255), 1);
|
|
224
|
+
|
|
225
|
+
setFont('small');
|
|
226
|
+
drawText('🏁 High-speed futuristic racing', 320, 260, uiColors.light, 1);
|
|
227
|
+
drawText('🏁 Maximum speed: 120 km/h with boost', 320, 275, uiColors.light, 1);
|
|
228
|
+
drawText('🏁 3 laps against 7 AI opponents', 320, 290, uiColors.light, 1);
|
|
229
|
+
drawText('🏁 Nintendo 64 F-Zero inspired graphics', 320, 305, uiColors.light, 1);
|
|
230
|
+
|
|
231
|
+
setFont('tiny');
|
|
232
|
+
drawText('ARROWS: Steer/Accelerate | SPACE: Boost | Z: Brake', 320, 325, uiColors.secondary, 1);
|
|
233
|
+
|
|
234
|
+
// Draw buttons
|
|
235
|
+
drawAllButtons();
|
|
236
|
+
|
|
237
|
+
// Pulsing prompt
|
|
238
|
+
const alpha = Math.floor((Math.sin(startScreenTime * 7) * 0.5 + 0.5) * 255);
|
|
239
|
+
setFont('normal');
|
|
240
|
+
drawText('🏁 PREPARE FOR MAXIMUM VELOCITY 🏁', 320, 435, rgba8(255, 200, 0, alpha), 1);
|
|
241
|
+
|
|
242
|
+
// Info
|
|
243
|
+
setFont('tiny');
|
|
244
|
+
drawText('Advanced 3D Racing Engine', 320, 350, rgba8(200, 150, 100, 150), 1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function buildRaceTrack() {
|
|
248
|
+
// Create main track loop with elevation changes and banking
|
|
249
|
+
for (let i = 0; i < TRACK_SEGMENTS; i++) {
|
|
250
|
+
const angle = (i / TRACK_SEGMENTS) * Math.PI * 2;
|
|
251
|
+
const nextAngle = ((i + 1) / TRACK_SEGMENTS) * Math.PI * 2;
|
|
252
|
+
|
|
253
|
+
// Track position with curves and elevation
|
|
254
|
+
const x = Math.cos(angle) * TRACK_RADIUS;
|
|
255
|
+
const z = Math.sin(angle) * TRACK_RADIUS;
|
|
256
|
+
const y = Math.sin(angle * 3) * 8 + Math.cos(angle * 1.5) * 4; // Elevation changes
|
|
257
|
+
|
|
258
|
+
const nextX = Math.cos(nextAngle) * TRACK_RADIUS;
|
|
259
|
+
const nextZ = Math.sin(nextAngle) * TRACK_RADIUS;
|
|
260
|
+
const nextY = Math.sin(nextAngle * 3) * 8 + Math.cos(nextAngle * 1.5) * 4;
|
|
261
|
+
|
|
262
|
+
// Calculate banking angle
|
|
263
|
+
const bankingAngle = Math.sin(angle * 2) * 0.3;
|
|
264
|
+
|
|
265
|
+
// Create track segment
|
|
266
|
+
const segment = createTrackSegment(x, y, z, nextX, nextY, nextZ, bankingAngle, i);
|
|
267
|
+
trackPieces.push(segment);
|
|
268
|
+
|
|
269
|
+
// Add track decorations with lighting
|
|
270
|
+
if (i % 10 === 0) {
|
|
271
|
+
await createTrackDecorations(x, y, z, angle);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Add lighting elements along the track
|
|
275
|
+
if (i % 5 === 0) {
|
|
276
|
+
createTrackLighting(x, y, z, angle, i);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Start/finish line with special lighting
|
|
280
|
+
if (i === 0) {
|
|
281
|
+
createStartFinishLine(x, y, z, angle);
|
|
282
|
+
createStartLineLighting(x, y, z, angle);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Boost pads
|
|
286
|
+
if (i % 25 === 0) {
|
|
287
|
+
createBoostPad(x, y + 0.5, z, angle);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Create outer barriers
|
|
292
|
+
for (let i = 0; i < TRACK_SEGMENTS; i++) {
|
|
293
|
+
const angle = (i / TRACK_SEGMENTS) * Math.PI * 2;
|
|
294
|
+
const outerRadius = TRACK_RADIUS + TRACK_WIDTH;
|
|
295
|
+
const innerRadius = TRACK_RADIUS - TRACK_WIDTH;
|
|
296
|
+
|
|
297
|
+
const x = Math.cos(angle) * TRACK_RADIUS;
|
|
298
|
+
const z = Math.sin(angle) * TRACK_RADIUS;
|
|
299
|
+
const y = Math.sin(angle * 3) * 8 + Math.cos(angle * 1.5) * 4;
|
|
300
|
+
|
|
301
|
+
// Outer barrier
|
|
302
|
+
const outerX = Math.cos(angle) * outerRadius;
|
|
303
|
+
const outerZ = Math.sin(angle) * outerRadius;
|
|
304
|
+
const outerBarrier = createCube(2, 6, 2, 0x666688, [outerX, y + 3, outerZ]);
|
|
305
|
+
|
|
306
|
+
// Inner barrier
|
|
307
|
+
const innerX = Math.cos(angle) * innerRadius;
|
|
308
|
+
const innerZ = Math.sin(angle) * innerRadius;
|
|
309
|
+
const innerBarrier = createCube(2, 6, 2, 0x666688, [innerX, y + 3, innerZ]);
|
|
310
|
+
|
|
311
|
+
// Add neon lighting to barriers
|
|
312
|
+
if (i % 5 === 0) {
|
|
313
|
+
const neonColor = COLORS.powerup[i % COLORS.powerup.length];
|
|
314
|
+
const neonOuter = createCube(2.2, 0.5, 2.2, neonColor, [outerX, y + 5.5, outerZ]);
|
|
315
|
+
const neonInner = createCube(2.2, 0.5, 2.2, neonColor, [innerX, y + 5.5, innerZ]);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function createTrackSegment(x, y, z, nextX, nextY, nextZ, banking, index) {
|
|
321
|
+
// Main track surface
|
|
322
|
+
const centerX = (x + nextX) / 2;
|
|
323
|
+
const centerY = (y + nextY) / 2;
|
|
324
|
+
const centerZ = (z + nextZ) / 2;
|
|
325
|
+
|
|
326
|
+
const length = Math.sqrt((nextX - x) ** 2 + (nextY - y) ** 2 + (nextZ - z) ** 2);
|
|
327
|
+
const angle = Math.atan2(nextZ - z, nextX - x);
|
|
328
|
+
|
|
329
|
+
const trackColor = COLORS.track[index % COLORS.track.length];
|
|
330
|
+
const segment = createCube(length, 0.5, TRACK_WIDTH, trackColor, [centerX, centerY, centerZ]);
|
|
331
|
+
|
|
332
|
+
setRotation(segment, 0, angle, banking);
|
|
333
|
+
|
|
334
|
+
// Racing line markers
|
|
335
|
+
if (index % 8 === 0) {
|
|
336
|
+
const marker = createCube(length, 0.1, 1, 0xffffff, [centerX, centerY + 0.3, centerZ]);
|
|
337
|
+
setRotation(marker, 0, angle, banking);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
mesh: segment,
|
|
342
|
+
x: centerX,
|
|
343
|
+
y: centerY,
|
|
344
|
+
z: centerZ,
|
|
345
|
+
angle: angle,
|
|
346
|
+
banking: banking,
|
|
347
|
+
index: index,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function createTrackDecorations(x, y, z, angle) {
|
|
352
|
+
// Grandstands
|
|
353
|
+
if (Math.random() < 0.3) {
|
|
354
|
+
const standX = x + Math.cos(angle + Math.PI / 2) * 40;
|
|
355
|
+
const standZ = z + Math.sin(angle + Math.PI / 2) * 40;
|
|
356
|
+
const grandstand = createCube(20, 12, 8, 0x555577, [standX, y + 6, standZ]);
|
|
357
|
+
|
|
358
|
+
// Stadium lights
|
|
359
|
+
for (let i = 0; i < 4; i++) {
|
|
360
|
+
const lightX = standX + (i - 1.5) * 6;
|
|
361
|
+
const light = createSphere(1, 0xffffaa, [lightX, y + 18, standZ]);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Sponsor banners
|
|
366
|
+
if (Math.random() < 0.4) {
|
|
367
|
+
const bannerX = x + Math.cos(angle + Math.PI / 2) * 25;
|
|
368
|
+
const bannerZ = z + Math.sin(angle + Math.PI / 2) * 25;
|
|
369
|
+
const banner = createCube(
|
|
370
|
+
12,
|
|
371
|
+
4,
|
|
372
|
+
0.5,
|
|
373
|
+
COLORS.powerup[Math.floor(Math.random() * COLORS.powerup.length)],
|
|
374
|
+
[bannerX, y + 8, bannerZ]
|
|
375
|
+
);
|
|
376
|
+
setRotation(banner, 0, angle, 0);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function createStartFinishLine(x, y, z, angle) {
|
|
381
|
+
// Checkered pattern start/finish
|
|
382
|
+
for (let i = 0; i < 8; i++) {
|
|
383
|
+
const stripX = x + Math.cos(angle + Math.PI / 2) * (i - 4) * 2.5;
|
|
384
|
+
const stripZ = z + Math.sin(angle + Math.PI / 2) * (i - 4) * 2.5;
|
|
385
|
+
const color = i % 2 === 0 ? 0xffffff : 0x000000;
|
|
386
|
+
const strip = createCube(2, 0.1, 2, color, [stripX, y + 0.3, stripZ]);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Start gantry
|
|
390
|
+
const gantry = createCube(TRACK_WIDTH * 2, 8, 2, 0x888899, [x, y + 8, z]);
|
|
391
|
+
setRotation(gantry, 0, angle + Math.PI / 2, 0);
|
|
392
|
+
|
|
393
|
+
// Digital display
|
|
394
|
+
const display = createCube(16, 4, 0.5, 0x002200, [x, y + 10, z + 1]);
|
|
395
|
+
setRotation(display, 0, angle + Math.PI / 2, 0);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function createBoostPad(x, y, z, angle) {
|
|
399
|
+
// Glowing boost pad
|
|
400
|
+
const pad = createCube(8, 0.3, 4, 0x00ff88, [x, y, z]);
|
|
401
|
+
setRotation(pad, 0, angle + Math.PI / 2, 0);
|
|
402
|
+
|
|
403
|
+
// Energy field effect
|
|
404
|
+
const field = createCube(8.5, 1, 4.5, 0x0088ff, [x, y + 0.5, z]);
|
|
405
|
+
setRotation(field, 0, angle + Math.PI / 2, 0);
|
|
406
|
+
|
|
407
|
+
trackPieces.push({
|
|
408
|
+
type: 'boost',
|
|
409
|
+
mesh: pad,
|
|
410
|
+
field: field,
|
|
411
|
+
x: x,
|
|
412
|
+
y: y,
|
|
413
|
+
z: z,
|
|
414
|
+
angle: angle,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function createPlayer() {
|
|
419
|
+
// Sleek F-Zero style racer
|
|
420
|
+
const body = createCube(4, 0.8, 2, COLORS.player, [0, 2, 0]);
|
|
421
|
+
const cockpit = createSphere(1.2, 0x004488, [0, 2.5, 0.3]);
|
|
422
|
+
const wing = createCube(6, 0.2, 0.8, 0x0066aa, [0, 2.8, -1.5]);
|
|
423
|
+
|
|
424
|
+
// Hover engines
|
|
425
|
+
const engine1 = createCube(0.8, 0.6, 1.5, 0xff4400, [-1.5, 1.5, -0.8]);
|
|
426
|
+
const engine2 = createCube(0.8, 0.6, 1.5, 0xff4400, [1.5, 1.5, -0.8]);
|
|
427
|
+
|
|
428
|
+
// Underglow
|
|
429
|
+
const glow = createCube(4.5, 0.2, 2.5, 0x00aaff, [0, 1, 0]);
|
|
430
|
+
|
|
431
|
+
player = {
|
|
432
|
+
body: body,
|
|
433
|
+
cockpit: cockpit,
|
|
434
|
+
wing: wing,
|
|
435
|
+
engines: [engine1, engine2],
|
|
436
|
+
glow: glow,
|
|
437
|
+
x: 0,
|
|
438
|
+
y: 2,
|
|
439
|
+
z: -TRACK_RADIUS,
|
|
440
|
+
vx: 0,
|
|
441
|
+
vy: 0,
|
|
442
|
+
vz: 0,
|
|
443
|
+
rotation: 0,
|
|
444
|
+
tilt: 0,
|
|
445
|
+
trackPosition: 0,
|
|
446
|
+
lanePosition: 0,
|
|
447
|
+
speed: 0,
|
|
448
|
+
health: 100,
|
|
449
|
+
boost: 0,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function spawnOpponents() {
|
|
454
|
+
for (let i = 0; i < OPPONENT_COUNT; i++) {
|
|
455
|
+
const opponent = createOpponent(i);
|
|
456
|
+
opponents.push(opponent);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function createOpponent(index) {
|
|
461
|
+
const color = COLORS.opponents[index % COLORS.opponents.length];
|
|
462
|
+
const startPos = (index + 1) * -15; // Stagger starting positions
|
|
463
|
+
|
|
464
|
+
const body = createCube(4, 0.8, 2, color, [0, 2, startPos]);
|
|
465
|
+
const cockpit = createSphere(1, color - 0x222222, [0, 2.4, startPos + 0.2]);
|
|
466
|
+
const glow = createCube(4.2, 0.2, 2.2, color + 0x333333, [0, 1, startPos]);
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
body: body,
|
|
470
|
+
cockpit: cockpit,
|
|
471
|
+
glow: glow,
|
|
472
|
+
x: 0,
|
|
473
|
+
y: 2,
|
|
474
|
+
z: startPos,
|
|
475
|
+
vx: 0,
|
|
476
|
+
vy: 0,
|
|
477
|
+
vz: 0,
|
|
478
|
+
rotation: 0,
|
|
479
|
+
trackPosition: startPos,
|
|
480
|
+
lanePosition: (Math.random() - 0.5) * 8,
|
|
481
|
+
speed: 40 + Math.random() * 30,
|
|
482
|
+
maxSpeed: 80 + Math.random() * 40,
|
|
483
|
+
aiType: Math.random() < 0.5 ? 'aggressive' : 'defensive',
|
|
484
|
+
nextDecision: Math.random() * 2,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function spawnPowerUps() {
|
|
489
|
+
for (let i = 0; i < 20; i++) {
|
|
490
|
+
const angle = Math.random() * Math.PI * 2;
|
|
491
|
+
const x = Math.cos(angle) * TRACK_RADIUS;
|
|
492
|
+
const z = Math.sin(angle) * TRACK_RADIUS;
|
|
493
|
+
const y = Math.sin(angle * 3) * 8 + Math.cos(angle * 1.5) * 4 + 3;
|
|
494
|
+
|
|
495
|
+
const type = Math.floor(Math.random() * 4);
|
|
496
|
+
const color = COLORS.powerup[type];
|
|
497
|
+
const powerup = createSphere(1, color, [x, y, z]);
|
|
498
|
+
|
|
499
|
+
powerUps.push({
|
|
500
|
+
mesh: powerup,
|
|
501
|
+
type: ['boost', 'health', 'shield', 'speed'][type],
|
|
502
|
+
x: x,
|
|
503
|
+
y: y,
|
|
504
|
+
z: z,
|
|
505
|
+
rotation: 0,
|
|
506
|
+
active: true,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function initRaceParticles() {
|
|
512
|
+
// Create exhaust particles for all vehicles
|
|
513
|
+
for (let i = 0; i < 50; i++) {
|
|
514
|
+
createExhaustParticle(0, 0, 0);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function handleInput(dt) {
|
|
519
|
+
const acceleration = 80;
|
|
520
|
+
const steering = 40;
|
|
521
|
+
const braking = 60;
|
|
522
|
+
|
|
523
|
+
// Acceleration
|
|
524
|
+
if (btn(2)) {
|
|
525
|
+
// Up
|
|
526
|
+
speed = Math.min(speed + acceleration * dt, maxSpeed);
|
|
527
|
+
} else {
|
|
528
|
+
speed = Math.max(speed - 20 * dt, 0);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Braking
|
|
532
|
+
if (btn(3)) {
|
|
533
|
+
// Down
|
|
534
|
+
speed = Math.max(speed - braking * dt, 0);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Steering
|
|
538
|
+
if (btn(0)) {
|
|
539
|
+
// Left
|
|
540
|
+
player.lanePosition = Math.max(player.lanePosition - steering * dt, -TRACK_WIDTH / 2);
|
|
541
|
+
player.tilt = Math.max(player.tilt - dt * 3, -0.5);
|
|
542
|
+
} else if (btn(1)) {
|
|
543
|
+
// Right
|
|
544
|
+
player.lanePosition = Math.min(player.lanePosition + steering * dt, TRACK_WIDTH / 2);
|
|
545
|
+
player.tilt = Math.min(player.tilt + dt * 3, 0.5);
|
|
546
|
+
} else {
|
|
547
|
+
player.tilt *= 0.9;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Boost
|
|
551
|
+
if (btnp(5) && boost > 20) {
|
|
552
|
+
// X
|
|
553
|
+
speed += 40;
|
|
554
|
+
boost -= 20;
|
|
555
|
+
createBoostEffect();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function updatePlayer(dt) {
|
|
560
|
+
// Update track position
|
|
561
|
+
player.trackPosition += speed * dt;
|
|
562
|
+
|
|
563
|
+
// Wrap around track
|
|
564
|
+
const trackLength = TRACK_SEGMENTS * 10; // Approximate
|
|
565
|
+
if (player.trackPosition >= trackLength) {
|
|
566
|
+
player.trackPosition -= trackLength;
|
|
567
|
+
currentLap++;
|
|
568
|
+
lapTime = 0;
|
|
569
|
+
|
|
570
|
+
// Spectacular lap completion celebration
|
|
571
|
+
createLapCompletionEffect();
|
|
572
|
+
console.log(`🏁 LAP ${currentLap - 1} COMPLETED! Spectacular celebration!`);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Calculate world position from track position
|
|
576
|
+
const normalizedPos = (player.trackPosition / trackLength) * Math.PI * 2;
|
|
577
|
+
const trackX = Math.cos(normalizedPos) * TRACK_RADIUS;
|
|
578
|
+
const trackZ = Math.sin(normalizedPos) * TRACK_RADIUS;
|
|
579
|
+
const trackY = Math.sin(normalizedPos * 3) * 8 + Math.cos(normalizedPos * 1.5) * 4;
|
|
580
|
+
|
|
581
|
+
// Apply lane offset
|
|
582
|
+
const laneX = trackX + Math.cos(normalizedPos + Math.PI / 2) * player.lanePosition;
|
|
583
|
+
const laneZ = trackZ + Math.sin(normalizedPos + Math.PI / 2) * player.lanePosition;
|
|
584
|
+
|
|
585
|
+
player.x = laneX;
|
|
586
|
+
player.y = trackY + 2;
|
|
587
|
+
player.z = laneZ;
|
|
588
|
+
player.rotation = normalizedPos + Math.PI / 2;
|
|
589
|
+
|
|
590
|
+
// Update mesh positions
|
|
591
|
+
setPosition(player.body, player.x, player.y, player.z);
|
|
592
|
+
setPosition(player.cockpit, player.x, player.y + 0.5, player.z + 0.3);
|
|
593
|
+
setPosition(player.wing, player.x, player.y + 0.8, player.z - 1.5);
|
|
594
|
+
setPosition(player.glow, player.x, player.y - 1, player.z);
|
|
595
|
+
|
|
596
|
+
setPosition(player.engines[0], player.x - 1.5, player.y - 0.5, player.z - 0.8);
|
|
597
|
+
setPosition(player.engines[1], player.x + 1.5, player.y - 0.5, player.z - 0.8);
|
|
598
|
+
|
|
599
|
+
// Apply rotations
|
|
600
|
+
setRotation(player.body, player.tilt * 0.2, player.rotation, player.tilt);
|
|
601
|
+
setRotation(player.cockpit, 0, player.rotation, 0);
|
|
602
|
+
setRotation(player.wing, 0, player.rotation, player.tilt * 0.5);
|
|
603
|
+
|
|
604
|
+
// Create exhaust particles
|
|
605
|
+
if (speed > 10) {
|
|
606
|
+
createExhaustParticle(player.x, player.y - 0.5, player.z - 2);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Boost decay
|
|
610
|
+
boost = Math.max(0, boost - 10 * dt);
|
|
611
|
+
|
|
612
|
+
// Speed effects
|
|
613
|
+
if (speed > 80) {
|
|
614
|
+
createSpeedParticles();
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function updateOpponents(dt) {
|
|
619
|
+
opponents.forEach((opponent, index) => {
|
|
620
|
+
// AI decision making
|
|
621
|
+
opponent.nextDecision -= dt;
|
|
622
|
+
if (opponent.nextDecision <= 0) {
|
|
623
|
+
// Change lane or adjust speed based on AI type
|
|
624
|
+
if (opponent.aiType === 'aggressive') {
|
|
625
|
+
opponent.speed = Math.min(opponent.speed + 10, opponent.maxSpeed);
|
|
626
|
+
opponent.lanePosition += (Math.random() - 0.5) * 8;
|
|
627
|
+
} else {
|
|
628
|
+
opponent.speed = Math.max(opponent.speed - 5, 20);
|
|
629
|
+
// Stay in lane more
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
opponent.lanePosition = Math.max(
|
|
633
|
+
-TRACK_WIDTH / 2,
|
|
634
|
+
Math.min(TRACK_WIDTH / 2, opponent.lanePosition)
|
|
635
|
+
);
|
|
636
|
+
opponent.nextDecision = 1 + Math.random() * 3;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Update position similar to player
|
|
640
|
+
opponent.trackPosition += opponent.speed * dt;
|
|
641
|
+
|
|
642
|
+
const trackLength = TRACK_SEGMENTS * 10;
|
|
643
|
+
if (opponent.trackPosition >= trackLength) {
|
|
644
|
+
opponent.trackPosition -= trackLength;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const normalizedPos = (opponent.trackPosition / trackLength) * Math.PI * 2;
|
|
648
|
+
const trackX = Math.cos(normalizedPos) * TRACK_RADIUS;
|
|
649
|
+
const trackZ = Math.sin(normalizedPos) * TRACK_RADIUS;
|
|
650
|
+
const trackY = Math.sin(normalizedPos * 3) * 8 + Math.cos(normalizedPos * 1.5) * 4;
|
|
651
|
+
|
|
652
|
+
const laneX = trackX + Math.cos(normalizedPos + Math.PI / 2) * opponent.lanePosition;
|
|
653
|
+
const laneZ = trackZ + Math.sin(normalizedPos + Math.PI / 2) * opponent.lanePosition;
|
|
654
|
+
|
|
655
|
+
opponent.x = laneX;
|
|
656
|
+
opponent.y = trackY + 2;
|
|
657
|
+
opponent.z = laneZ;
|
|
658
|
+
opponent.rotation = normalizedPos + Math.PI / 2;
|
|
659
|
+
|
|
660
|
+
// Update mesh positions
|
|
661
|
+
setPosition(opponent.body, opponent.x, opponent.y, opponent.z);
|
|
662
|
+
setPosition(opponent.cockpit, opponent.x, opponent.y + 0.4, opponent.z + 0.2);
|
|
663
|
+
setPosition(opponent.glow, opponent.x, opponent.y - 1, opponent.z);
|
|
664
|
+
|
|
665
|
+
setRotation(opponent.body, 0, opponent.rotation, 0);
|
|
666
|
+
|
|
667
|
+
// Occasional exhaust
|
|
668
|
+
if (Math.random() < 0.3) {
|
|
669
|
+
createExhaustParticle(opponent.x, opponent.y - 0.5, opponent.z - 1.5);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function updateTrack(dt) {
|
|
675
|
+
// Animate boost pads
|
|
676
|
+
trackPieces.forEach(piece => {
|
|
677
|
+
if (piece.type === 'boost') {
|
|
678
|
+
piece.pulsePhase = (piece.pulsePhase || 0) + dt * 4;
|
|
679
|
+
const intensity = 0.7 + 0.3 * Math.sin(piece.pulsePhase);
|
|
680
|
+
// In real implementation, would modify material emission
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function updateParticles(dt) {
|
|
686
|
+
for (let i = particles.length - 1; i >= 0; i--) {
|
|
687
|
+
const particle = particles[i];
|
|
688
|
+
|
|
689
|
+
// Physics
|
|
690
|
+
particle.x += particle.vx * dt;
|
|
691
|
+
particle.y += particle.vy * dt;
|
|
692
|
+
particle.z += particle.vz * dt;
|
|
693
|
+
|
|
694
|
+
particle.vy -= 20 * dt; // Gravity
|
|
695
|
+
particle.life -= dt;
|
|
696
|
+
|
|
697
|
+
// Update position
|
|
698
|
+
setPosition(particle.mesh, particle.x, particle.y, particle.z);
|
|
699
|
+
|
|
700
|
+
// Fade out
|
|
701
|
+
const alpha = particle.life / particle.maxLife;
|
|
702
|
+
setScale(particle.mesh, alpha);
|
|
703
|
+
|
|
704
|
+
// Remove dead particles
|
|
705
|
+
if (particle.life <= 0) {
|
|
706
|
+
destroyMesh(particle.mesh);
|
|
707
|
+
particles.splice(i, 1);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function updatePowerUps(dt) {
|
|
713
|
+
powerUps.forEach(powerup => {
|
|
714
|
+
if (!powerup.active) return;
|
|
715
|
+
|
|
716
|
+
// Rotation animation
|
|
717
|
+
powerup.rotation += dt * 3;
|
|
718
|
+
setRotation(powerup.mesh, 0, powerup.rotation, 0);
|
|
719
|
+
|
|
720
|
+
// Bob up and down
|
|
721
|
+
const bobY = powerup.y + Math.sin(gameTime * 2 + powerup.x) * 0.5;
|
|
722
|
+
setPosition(powerup.mesh, powerup.x, bobY, powerup.z);
|
|
723
|
+
|
|
724
|
+
// Check collision with player
|
|
725
|
+
const dx = powerup.x - player.x;
|
|
726
|
+
const dy = powerup.y - player.y;
|
|
727
|
+
const dz = powerup.z - player.z;
|
|
728
|
+
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
729
|
+
|
|
730
|
+
if (distance < 3) {
|
|
731
|
+
collectPowerUp(powerup);
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function updateCamera(dt) {
|
|
737
|
+
// Dynamic racing camera
|
|
738
|
+
const followDistance = 8 + speed * 0.1;
|
|
739
|
+
const followHeight = 4 + speed * 0.02;
|
|
740
|
+
const lookAhead = speed * 0.05;
|
|
741
|
+
|
|
742
|
+
const cameraX = player.x - Math.sin(player.rotation) * followDistance;
|
|
743
|
+
const cameraY = player.y + followHeight;
|
|
744
|
+
const cameraZ = player.z - Math.cos(player.rotation) * followDistance;
|
|
745
|
+
|
|
746
|
+
const targetX = player.x + Math.sin(player.rotation) * lookAhead;
|
|
747
|
+
const targetY = player.y + 1;
|
|
748
|
+
const targetZ = player.z + Math.cos(player.rotation) * lookAhead;
|
|
749
|
+
|
|
750
|
+
setCameraPosition(cameraX, cameraY, cameraZ);
|
|
751
|
+
setCameraTarget(targetX, targetY, targetZ);
|
|
752
|
+
|
|
753
|
+
// Field of view based on speed
|
|
754
|
+
const fov = 90 + speed * 0.2;
|
|
755
|
+
setCameraFOV(Math.min(fov, 120));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function updateRaceLogic(dt) {
|
|
759
|
+
// Calculate race position
|
|
760
|
+
let position = 1;
|
|
761
|
+
opponents.forEach(opponent => {
|
|
762
|
+
if (opponent.trackPosition > player.trackPosition) {
|
|
763
|
+
position++;
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
racePosition = position;
|
|
767
|
+
|
|
768
|
+
// Check for race completion
|
|
769
|
+
if (currentLap > maxLaps) {
|
|
770
|
+
// Race finished!
|
|
771
|
+
console.log(`Race completed! Final position: ${racePosition}`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function collectPowerUp(powerup) {
|
|
776
|
+
switch (powerup.type) {
|
|
777
|
+
case 'boost':
|
|
778
|
+
boost = Math.min(100, boost + 30);
|
|
779
|
+
break;
|
|
780
|
+
case 'health':
|
|
781
|
+
health = Math.min(100, health + 25);
|
|
782
|
+
break;
|
|
783
|
+
case 'shield':
|
|
784
|
+
// Temporary invincibility
|
|
785
|
+
break;
|
|
786
|
+
case 'speed':
|
|
787
|
+
maxSpeed += 10;
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Hide powerup
|
|
792
|
+
powerup.active = false;
|
|
793
|
+
setScale(powerup.mesh, 0);
|
|
794
|
+
|
|
795
|
+
// Create spectacular collection effect
|
|
796
|
+
const pos = getPosition(powerup.mesh);
|
|
797
|
+
if (pos) {
|
|
798
|
+
createPowerUpCollectionEffect(pos[0], pos[1], pos[2], powerup.type);
|
|
799
|
+
}
|
|
800
|
+
createCollectionEffect(powerup.x, powerup.y, powerup.z);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function createExhaustParticle(x, y, z) {
|
|
804
|
+
// Dynamic particle color based on speed and boost
|
|
805
|
+
let particleColor;
|
|
806
|
+
const speedFactor = Math.min(speed / maxSpeed, 1.0);
|
|
807
|
+
const boostFactor = boost / 100;
|
|
808
|
+
|
|
809
|
+
if (boostFactor > 0.5) {
|
|
810
|
+
// Boost particles - hot orange/yellow
|
|
811
|
+
particleColor = 0xff6600 + Math.floor(boostFactor * 0x009900);
|
|
812
|
+
} else if (speedFactor > 0.7) {
|
|
813
|
+
// High speed particles - blue to white
|
|
814
|
+
const intensity = Math.floor(speedFactor * 255);
|
|
815
|
+
particleColor = (intensity << 16) | (intensity << 8) | 0xff;
|
|
816
|
+
} else {
|
|
817
|
+
// Normal particles - use original random colors
|
|
818
|
+
particleColor = COLORS.particle[Math.floor(Math.random() * COLORS.particle.length)];
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const particle = createSphere(0.3, particleColor, [x, y, z]);
|
|
822
|
+
|
|
823
|
+
particles.push({
|
|
824
|
+
mesh: particle,
|
|
825
|
+
x: x,
|
|
826
|
+
y: y,
|
|
827
|
+
z: z,
|
|
828
|
+
vx: (Math.random() - 0.5) * 8,
|
|
829
|
+
vy: Math.random() * 3,
|
|
830
|
+
vz: -Math.random() * 12 - 5,
|
|
831
|
+
life: 1.5,
|
|
832
|
+
maxLife: 1.5,
|
|
833
|
+
type: 'exhaust',
|
|
834
|
+
color: particleColor,
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function createSpeedParticles() {
|
|
839
|
+
const speedFactor = Math.min(speed / maxSpeed, 1.0);
|
|
840
|
+
const particleCount = Math.floor(3 + speedFactor * 3); // More particles at higher speeds
|
|
841
|
+
|
|
842
|
+
for (let i = 0; i < particleCount; i++) {
|
|
843
|
+
// Dynamic speed particle colors
|
|
844
|
+
const intensity = Math.floor(128 + speedFactor * 127);
|
|
845
|
+
const speedColor = (intensity << 8) | 0xff | (Math.floor(speedFactor * 0xaa) << 16);
|
|
846
|
+
|
|
847
|
+
const particle = createSphere(0.15 + speedFactor * 0.1, speedColor, [
|
|
848
|
+
player.x + (Math.random() - 0.5) * 8,
|
|
849
|
+
player.y + (Math.random() - 0.5) * 3,
|
|
850
|
+
player.z + (Math.random() - 0.5) * 6,
|
|
851
|
+
]);
|
|
852
|
+
|
|
853
|
+
particles.push({
|
|
854
|
+
mesh: particle,
|
|
855
|
+
x: player.x,
|
|
856
|
+
y: player.y,
|
|
857
|
+
z: player.z,
|
|
858
|
+
vx: (Math.random() - 0.5) * 20,
|
|
859
|
+
vy: (Math.random() - 0.5) * 5,
|
|
860
|
+
vz: -Math.random() * 30 - 10,
|
|
861
|
+
life: 0.8,
|
|
862
|
+
maxLife: 0.8,
|
|
863
|
+
type: 'speed',
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function createBoostEffect() {
|
|
869
|
+
for (let i = 0; i < 15; i++) {
|
|
870
|
+
const particle = createSphere(0.4, 0x00ffaa, [
|
|
871
|
+
player.x + (Math.random() - 0.5) * 4,
|
|
872
|
+
player.y + (Math.random() - 0.5) * 2,
|
|
873
|
+
player.z - 2,
|
|
874
|
+
]);
|
|
875
|
+
|
|
876
|
+
particles.push({
|
|
877
|
+
mesh: particle,
|
|
878
|
+
x: player.x,
|
|
879
|
+
y: player.y,
|
|
880
|
+
z: player.z - 2,
|
|
881
|
+
vx: (Math.random() - 0.5) * 15,
|
|
882
|
+
vy: (Math.random() - 0.5) * 8,
|
|
883
|
+
vz: -Math.random() * 20 - 15,
|
|
884
|
+
life: 1.2,
|
|
885
|
+
maxLife: 1.2,
|
|
886
|
+
type: 'boost',
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function createCollectionEffect(x, y, z) {
|
|
892
|
+
for (let i = 0; i < 10; i++) {
|
|
893
|
+
const particle = createSphere(0.2, 0xffffff, [x, y, z]);
|
|
894
|
+
|
|
895
|
+
particles.push({
|
|
896
|
+
mesh: particle,
|
|
897
|
+
x: x,
|
|
898
|
+
y: y,
|
|
899
|
+
z: z,
|
|
900
|
+
vx: (Math.random() - 0.5) * 12,
|
|
901
|
+
vy: Math.random() * 8,
|
|
902
|
+
vz: (Math.random() - 0.5) * 12,
|
|
903
|
+
life: 1,
|
|
904
|
+
maxLife: 1,
|
|
905
|
+
type: 'collection',
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function drawRacingHUD() {
|
|
911
|
+
// Main race HUD
|
|
912
|
+
rect(16, 16, 600, 140, rgba8(0, 0, 40, 200), true);
|
|
913
|
+
rect(16, 16, 600, 140, rgba8(0, 150, 255, 120), false);
|
|
914
|
+
|
|
915
|
+
// Title
|
|
916
|
+
print('🏁 F-ZERO NOVA', 24, 24, rgba8(0, 255, 255, 255));
|
|
917
|
+
print('ULTIMATE N64 RACING EXPERIENCE', 24, 40, rgba8(255, 200, 0, 255));
|
|
918
|
+
|
|
919
|
+
// Race stats
|
|
920
|
+
print(`LAP: ${currentLap}/${maxLaps}`, 24, 60, rgba8(255, 255, 100, 255));
|
|
921
|
+
print(`POSITION: ${racePosition}/${OPPONENT_COUNT + 1}`, 24, 76, rgba8(255, 150, 50, 255));
|
|
922
|
+
print(`LAP TIME: ${lapTime.toFixed(1)}s`, 24, 92, rgba8(100, 255, 100, 255));
|
|
923
|
+
|
|
924
|
+
// Vehicle stats
|
|
925
|
+
print(`SPEED: ${speed.toFixed(0)} KM/H`, 200, 60, rgba8(255, 100, 100, 255));
|
|
926
|
+
print(`MAX SPEED: ${maxSpeed}`, 200, 76, rgba8(255, 200, 200, 255));
|
|
927
|
+
print(`BOOST: ${boost.toFixed(0)}%`, 200, 92, rgba8(0, 200, 255, 255));
|
|
928
|
+
print(`HEALTH: ${health.toFixed(0)}%`, 200, 108, rgba8(255, 255, 255, 255));
|
|
929
|
+
|
|
930
|
+
// Technical stats
|
|
931
|
+
print(`TRACK POS: ${player.trackPosition.toFixed(0)}`, 380, 60, rgba8(200, 200, 255, 255));
|
|
932
|
+
print(`LANE: ${player.lanePosition.toFixed(1)}`, 380, 76, rgba8(200, 200, 255, 255));
|
|
933
|
+
print(`TILT: ${player.tilt.toFixed(2)}`, 380, 92, rgba8(200, 200, 255, 255));
|
|
934
|
+
|
|
935
|
+
// 3D Performance & Effects
|
|
936
|
+
const stats = get3DStats();
|
|
937
|
+
if (stats) {
|
|
938
|
+
print(`3D MESHES: ${stats.meshes || 0}`, 500, 60, rgba8(150, 255, 150, 255));
|
|
939
|
+
print(`GPU: ${stats.renderer || 'ThreeJS'}`, 500, 76, rgba8(150, 255, 150, 255));
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Visual effects status
|
|
943
|
+
const speedFactor = Math.min(speed / maxSpeed, 1.0);
|
|
944
|
+
const effectsColor = speedFactor > 0.8 ? rgba8(255, 255, 0, 255) : rgba8(150, 255, 150, 255);
|
|
945
|
+
print(`EFFECTS: ${(speedFactor * 100).toFixed(0)}%`, 500, 92, effectsColor);
|
|
946
|
+
print(
|
|
947
|
+
`BLOOM: ${postProcessing.bloom.enabled ? 'ON' : 'OFF'}`,
|
|
948
|
+
500,
|
|
949
|
+
108,
|
|
950
|
+
rgba8(255, 150, 255, 255)
|
|
951
|
+
);
|
|
952
|
+
|
|
953
|
+
// Speed gauge (circular)
|
|
954
|
+
const gaugeX = 580;
|
|
955
|
+
const gaugeY = 200;
|
|
956
|
+
const gaugeRadius = 40;
|
|
957
|
+
|
|
958
|
+
// Gauge background
|
|
959
|
+
for (let i = 0; i < 32; i++) {
|
|
960
|
+
const angle = (i / 32) * Math.PI * 2 - Math.PI / 2;
|
|
961
|
+
const x1 = gaugeX + Math.cos(angle) * (gaugeRadius - 5);
|
|
962
|
+
const y1 = gaugeY + Math.sin(angle) * (gaugeRadius - 5);
|
|
963
|
+
const x2 = gaugeX + Math.cos(angle) * gaugeRadius;
|
|
964
|
+
const y2 = gaugeY + Math.sin(angle) * gaugeRadius;
|
|
965
|
+
|
|
966
|
+
const intensity = i < (speed / maxSpeed) * 32 ? 255 : 50;
|
|
967
|
+
const greenIntensity = Math.floor(intensity / 2); // Ensure integer division
|
|
968
|
+
line(x1, y1, x2, y2, rgba8(intensity, greenIntensity, 0, 255));
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Speed needle
|
|
972
|
+
const needleAngle = (speed / maxSpeed) * Math.PI * 2 - Math.PI / 2;
|
|
973
|
+
const needleX = gaugeX + Math.cos(needleAngle) * (gaugeRadius - 8);
|
|
974
|
+
const needleY = gaugeY + Math.sin(needleAngle) * (gaugeRadius - 8);
|
|
975
|
+
line(gaugeX, gaugeY, needleX, needleY, rgba8(255, 255, 255, 255));
|
|
976
|
+
|
|
977
|
+
print('SPEED', gaugeX - 15, gaugeY + gaugeRadius + 8, rgba8(255, 255, 255, 255));
|
|
978
|
+
|
|
979
|
+
// Mini-map (track layout)
|
|
980
|
+
const mapX = 500;
|
|
981
|
+
const mapY = 300;
|
|
982
|
+
const mapSize = 80;
|
|
983
|
+
|
|
984
|
+
rect(mapX - mapSize / 2, mapY - mapSize / 2, mapSize, mapSize, rgba8(0, 0, 50, 150), true);
|
|
985
|
+
|
|
986
|
+
// Draw track outline
|
|
987
|
+
for (let i = 0; i < 32; i++) {
|
|
988
|
+
const angle = (i / 32) * Math.PI * 2;
|
|
989
|
+
const x = mapX + Math.cos(angle) * mapSize * 0.3;
|
|
990
|
+
const y = mapY + Math.sin(angle) * mapSize * 0.3;
|
|
991
|
+
pset(x, y, rgba8(100, 100, 255, 200));
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Player position
|
|
995
|
+
const playerAngle = (player.trackPosition / (TRACK_SEGMENTS * 10)) * Math.PI * 2;
|
|
996
|
+
const playerMapX = mapX + Math.cos(playerAngle) * mapSize * 0.3;
|
|
997
|
+
const playerMapY = mapY + Math.sin(playerAngle) * mapSize * 0.3;
|
|
998
|
+
pset(playerMapX, playerMapY, rgba8(255, 255, 255, 255));
|
|
999
|
+
|
|
1000
|
+
// Opponent positions
|
|
1001
|
+
opponents.forEach(opponent => {
|
|
1002
|
+
const oppAngle = (opponent.trackPosition / (TRACK_SEGMENTS * 10)) * Math.PI * 2;
|
|
1003
|
+
const oppMapX = mapX + Math.cos(oppAngle) * mapSize * 0.3;
|
|
1004
|
+
const oppMapY = mapY + Math.sin(oppAngle) * mapSize * 0.3;
|
|
1005
|
+
pset(oppMapX, oppMapY, rgba8(255, 100, 100, 200));
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
print('TRACK', mapX - 15, mapY + mapSize / 2 + 8, rgba8(255, 255, 255, 255));
|
|
1009
|
+
|
|
1010
|
+
// Controls
|
|
1011
|
+
print('↑↓ ACCELERATE/BRAKE | ←→ STEER | X BOOST', 24, 340, rgba8(255, 255, 255, 200));
|
|
1012
|
+
print(
|
|
1013
|
+
'Experience ultimate Nintendo 64 / F-Zero style 3D racing!',
|
|
1014
|
+
24,
|
|
1015
|
+
360,
|
|
1016
|
+
rgba8(100, 255, 100, 180)
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// ============================================================================
|
|
1021
|
+
// ADVANCED LIGHTING & POST-PROCESSING SYSTEM
|
|
1022
|
+
// ============================================================================
|
|
1023
|
+
|
|
1024
|
+
let lightingSystem = {
|
|
1025
|
+
mainLight: { color: 0xaaaaff, intensity: 1.0, direction: [-0.4, -0.8, -0.3] },
|
|
1026
|
+
ambientLight: { color: 0x444466, intensity: 0.3 },
|
|
1027
|
+
speedLight: { color: 0x00aaff, intensity: 0.0 },
|
|
1028
|
+
boostLight: { color: 0xffaa00, intensity: 0.0 },
|
|
1029
|
+
trackLights: [],
|
|
1030
|
+
dynamicFog: { color: 0x220044, near: 50, far: 300, pulse: 0 },
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
let postProcessing = {
|
|
1034
|
+
bloom: { enabled: true, intensity: 0.8, threshold: 0.6 },
|
|
1035
|
+
motionBlur: { enabled: true, intensity: 0.8 },
|
|
1036
|
+
chromaticAberration: { enabled: true, intensity: 0.3 },
|
|
1037
|
+
vignette: { enabled: true, intensity: 0.4 },
|
|
1038
|
+
scanlines: { enabled: true, intensity: 0.2 },
|
|
1039
|
+
pixelation: { enabled: true, factor: 1.2 },
|
|
1040
|
+
colorGrading: { enabled: true, temperature: 0.1, tint: -0.05 },
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
function setupAdvancedLighting() {
|
|
1044
|
+
console.log('🌟 Setting up advanced lighting system...');
|
|
1045
|
+
|
|
1046
|
+
// Main directional light (sun/moon)
|
|
1047
|
+
if (typeof setLightDirection === 'function') {
|
|
1048
|
+
setLightDirection(...lightingSystem.mainLight.direction);
|
|
1049
|
+
}
|
|
1050
|
+
if (typeof setLightColor === 'function') {
|
|
1051
|
+
setLightColor(lightingSystem.mainLight.color);
|
|
1052
|
+
}
|
|
1053
|
+
if (typeof setLightIntensity === 'function') {
|
|
1054
|
+
setLightIntensity(lightingSystem.mainLight.intensity);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Enhanced ambient lighting
|
|
1058
|
+
if (typeof setAmbientLight === 'function') {
|
|
1059
|
+
setAmbientLight(lightingSystem.ambientLight.color, lightingSystem.ambientLight.intensity);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Add point lights for track illumination
|
|
1063
|
+
setupTrackLighting();
|
|
1064
|
+
|
|
1065
|
+
console.log('✅ Advanced lighting system initialized');
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
function setupTrackLighting() {
|
|
1069
|
+
// Create point lights along the track for dynamic illumination
|
|
1070
|
+
console.log('🔸 Setting up track point lights...');
|
|
1071
|
+
|
|
1072
|
+
for (let i = 0; i < 8; i++) {
|
|
1073
|
+
const angle = (i / 8) * Math.PI * 2;
|
|
1074
|
+
const x = Math.cos(angle) * TRACK_RADIUS;
|
|
1075
|
+
const z = Math.sin(angle) * TRACK_RADIUS;
|
|
1076
|
+
const y = 15; // Elevated light positions
|
|
1077
|
+
|
|
1078
|
+
const lightColor = i % 2 === 0 ? 0x00aaff : 0xaa00ff; // Alternating blue/purple
|
|
1079
|
+
|
|
1080
|
+
if (typeof addPointLight === 'function') {
|
|
1081
|
+
try {
|
|
1082
|
+
const lightId = addPointLight(x, y, z, lightColor, 2.0, 80);
|
|
1083
|
+
lightingSystem.trackLights.push({
|
|
1084
|
+
id: lightId,
|
|
1085
|
+
position: [x, y, z],
|
|
1086
|
+
color: lightColor,
|
|
1087
|
+
intensity: 2.0,
|
|
1088
|
+
baseIntensity: 2.0,
|
|
1089
|
+
pulsePhase: i * 0.5,
|
|
1090
|
+
});
|
|
1091
|
+
console.log(`✅ Point light ${i} created successfully`);
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
console.warn(`⚠️ Failed to create point light ${i}:`, error);
|
|
1094
|
+
}
|
|
1095
|
+
} else {
|
|
1096
|
+
console.log('🔸 Point lights not supported, using basic lighting');
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function setupDynamicFog() {
|
|
1102
|
+
console.log('🌫️ Setting up dynamic atmospheric fog...');
|
|
1103
|
+
|
|
1104
|
+
// Initial fog setup
|
|
1105
|
+
setFog(
|
|
1106
|
+
lightingSystem.dynamicFog.color,
|
|
1107
|
+
lightingSystem.dynamicFog.near,
|
|
1108
|
+
lightingSystem.dynamicFog.far
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
console.log('✅ Dynamic fog system initialized');
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function setupPostProcessingEffects() {
|
|
1115
|
+
console.log('🎪 Setting up post-processing pipeline...');
|
|
1116
|
+
|
|
1117
|
+
// Enable core post-processing effects
|
|
1118
|
+
if (typeof enableBloom === 'function' && postProcessing.bloom.enabled) {
|
|
1119
|
+
enableBloom(postProcessing.bloom.intensity);
|
|
1120
|
+
if (typeof setBloomThreshold === 'function') {
|
|
1121
|
+
setBloomThreshold(postProcessing.bloom.threshold);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (typeof enableMotionBlur === 'function' && postProcessing.motionBlur.enabled) {
|
|
1126
|
+
enableMotionBlur(postProcessing.motionBlur.intensity);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
if (typeof enablePixelation === 'function' && postProcessing.pixelation.enabled) {
|
|
1130
|
+
enablePixelation(postProcessing.pixelation.factor);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
if (typeof enableDithering === 'function') {
|
|
1134
|
+
enableDithering(true);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Advanced effects (if available)
|
|
1138
|
+
if (
|
|
1139
|
+
typeof enableChromaticAberration === 'function' &&
|
|
1140
|
+
postProcessing.chromaticAberration.enabled
|
|
1141
|
+
) {
|
|
1142
|
+
enableChromaticAberration(postProcessing.chromaticAberration.intensity);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
if (typeof enableVignette === 'function' && postProcessing.vignette.enabled) {
|
|
1146
|
+
enableVignette(postProcessing.vignette.intensity);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
if (typeof enableScanlines === 'function' && postProcessing.scanlines.enabled) {
|
|
1150
|
+
enableScanlines(postProcessing.scanlines.intensity);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (typeof enableColorGrading === 'function' && postProcessing.colorGrading.enabled) {
|
|
1154
|
+
enableColorGrading(postProcessing.colorGrading.temperature, postProcessing.colorGrading.tint);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
console.log('✅ Post-processing pipeline initialized');
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function updateAdvancedLighting(dt) {
|
|
1161
|
+
lightingSystem.dynamicFog.pulse += dt * 2;
|
|
1162
|
+
|
|
1163
|
+
// Dynamic fog color shifting based on speed and time
|
|
1164
|
+
const speedFactor = Math.min(speed / maxSpeed, 1.0);
|
|
1165
|
+
const timeFactor = Math.sin(gameTime * 0.5) * 0.5 + 0.5;
|
|
1166
|
+
|
|
1167
|
+
// Base fog color with speed-based intensity
|
|
1168
|
+
const baseR = 0x22 + Math.floor(speedFactor * 0x44);
|
|
1169
|
+
const baseG = 0x00 + Math.floor(timeFactor * 0x22);
|
|
1170
|
+
const baseB = 0x44 + Math.floor((speedFactor + timeFactor) * 0x33);
|
|
1171
|
+
|
|
1172
|
+
const fogColor = (baseR << 16) | (baseG << 8) | baseB;
|
|
1173
|
+
|
|
1174
|
+
// Dynamic fog distance based on speed
|
|
1175
|
+
const dynamicNear = 50 - speedFactor * 20;
|
|
1176
|
+
const dynamicFar = 300 + speedFactor * 100;
|
|
1177
|
+
|
|
1178
|
+
setFog(fogColor, dynamicNear, dynamicFar);
|
|
1179
|
+
|
|
1180
|
+
// Update track point lights with pulsing effects
|
|
1181
|
+
if (lightingSystem.trackLights && lightingSystem.trackLights.length > 0) {
|
|
1182
|
+
lightingSystem.trackLights.forEach((light, index) => {
|
|
1183
|
+
light.pulsePhase += dt * 3;
|
|
1184
|
+
const pulseIntensity = 0.7 + 0.3 * Math.sin(light.pulsePhase);
|
|
1185
|
+
const newIntensity = light.baseIntensity * pulseIntensity;
|
|
1186
|
+
|
|
1187
|
+
if (typeof updatePointLight === 'function') {
|
|
1188
|
+
try {
|
|
1189
|
+
updatePointLight(
|
|
1190
|
+
light.id,
|
|
1191
|
+
light.position[0],
|
|
1192
|
+
light.position[1],
|
|
1193
|
+
light.position[2],
|
|
1194
|
+
light.color,
|
|
1195
|
+
newIntensity,
|
|
1196
|
+
80
|
|
1197
|
+
);
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
// Silently ignore point light update errors
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// Speed-based lighting effects
|
|
1206
|
+
lightingSystem.speedLight.intensity = Math.min(speedFactor * 0.5, 0.5);
|
|
1207
|
+
lightingSystem.boostLight.intensity = Math.min((boost / 100) * 0.8, 0.8);
|
|
1208
|
+
|
|
1209
|
+
// Boost lighting effect
|
|
1210
|
+
if (boost > 50) {
|
|
1211
|
+
const boostPulse = Math.sin(gameTime * 8) * 0.5 + 0.5;
|
|
1212
|
+
if (typeof setAmbientLight === 'function') {
|
|
1213
|
+
const boostAmbient = 0x444466 + Math.floor(boostPulse * 0x222200);
|
|
1214
|
+
setAmbientLight(boostAmbient, 0.3 + boostPulse * 0.2);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Main light direction sway for dynamic feel
|
|
1219
|
+
const lightSway = Math.sin(gameTime * 0.3) * 0.1;
|
|
1220
|
+
if (typeof setLightDirection === 'function') {
|
|
1221
|
+
setLightDirection(-0.4 + lightSway, -0.8, -0.3 + lightSway * 0.5);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function updatePostProcessing(dt) {
|
|
1226
|
+
const speedFactor = Math.min(speed / maxSpeed, 1.0);
|
|
1227
|
+
|
|
1228
|
+
// Dynamic motion blur based on speed
|
|
1229
|
+
if (typeof setMotionBlurIntensity === 'function') {
|
|
1230
|
+
const motionBlurIntensity = 0.3 + speedFactor * 0.7;
|
|
1231
|
+
setMotionBlurIntensity(motionBlurIntensity);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// Dynamic bloom based on boost and speed
|
|
1235
|
+
if (typeof setBloomIntensity === 'function') {
|
|
1236
|
+
const boostFactor = boost / 100;
|
|
1237
|
+
const bloomIntensity = 0.5 + speedFactor * 0.3 + boostFactor * 0.4;
|
|
1238
|
+
setBloomIntensity(Math.min(bloomIntensity, 1.2));
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Chromatic aberration increases with speed
|
|
1242
|
+
if (typeof setChromaticAberrationIntensity === 'function') {
|
|
1243
|
+
const aberrationIntensity = 0.1 + speedFactor * 0.4;
|
|
1244
|
+
setChromaticAberrationIntensity(aberrationIntensity);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// Vignette intensity based on health and speed
|
|
1248
|
+
if (typeof setVignetteIntensity === 'function') {
|
|
1249
|
+
const healthFactor = (100 - health) / 100;
|
|
1250
|
+
const vignetteIntensity = 0.2 + healthFactor * 0.5 + speedFactor * 0.2;
|
|
1251
|
+
setVignetteIntensity(Math.min(vignetteIntensity, 0.8));
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Scanlines flicker with speed
|
|
1255
|
+
if (typeof setScanlineIntensity === 'function') {
|
|
1256
|
+
const scanlineFlicker = Math.sin(gameTime * 30) * 0.1 + 0.9;
|
|
1257
|
+
const scanlineIntensity = (0.1 + speedFactor * 0.2) * scanlineFlicker;
|
|
1258
|
+
setScanlineIntensity(scanlineIntensity);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// Dynamic pixelation for extreme speeds
|
|
1262
|
+
if (typeof setPixelationFactor === 'function') {
|
|
1263
|
+
let pixelationFactor = 1.0;
|
|
1264
|
+
if (speed > 100) {
|
|
1265
|
+
pixelationFactor = 1.2 + (speed - 100) / 100;
|
|
1266
|
+
}
|
|
1267
|
+
setPixelationFactor(Math.min(pixelationFactor, 2.0));
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// Color grading shifts for different racing conditions
|
|
1271
|
+
if (typeof updateColorGrading === 'function') {
|
|
1272
|
+
const temperature = -0.1 + speedFactor * 0.3; // Warmer at high speeds
|
|
1273
|
+
const tint = -0.05 + Math.sin(gameTime * 0.2) * 0.1; // Subtle color shifting
|
|
1274
|
+
updateColorGrading(temperature, tint);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// ============================================================================
|
|
1279
|
+
// ENHANCED TRACK LIGHTING SYSTEM
|
|
1280
|
+
// ============================================================================
|
|
1281
|
+
|
|
1282
|
+
function createTrackLighting(x, y, z, angle, segmentIndex) {
|
|
1283
|
+
// Create glowing track markers
|
|
1284
|
+
const markerHeight = y + 8;
|
|
1285
|
+
const markerSize = 0.8;
|
|
1286
|
+
|
|
1287
|
+
// Alternating neon colors for track sides
|
|
1288
|
+
const leftColor = segmentIndex % 4 === 0 ? 0x00ffff : 0xff00ff;
|
|
1289
|
+
const rightColor = segmentIndex % 4 === 0 ? 0xff00ff : 0x00ffff;
|
|
1290
|
+
|
|
1291
|
+
// Left side marker
|
|
1292
|
+
const leftX = x + Math.cos(angle + Math.PI / 2) * (TRACK_WIDTH / 2 + 3);
|
|
1293
|
+
const leftZ = z + Math.sin(angle + Math.PI / 2) * (TRACK_WIDTH / 2 + 3);
|
|
1294
|
+
const leftMarker = createCube(leftX, markerHeight, leftZ, markerSize, {
|
|
1295
|
+
material: 'emissive',
|
|
1296
|
+
color: leftColor,
|
|
1297
|
+
emissive: leftColor,
|
|
1298
|
+
transparent: true,
|
|
1299
|
+
opacity: 0.8,
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
// Right side marker
|
|
1303
|
+
const rightX = x + Math.cos(angle - Math.PI / 2) * (TRACK_WIDTH / 2 + 3);
|
|
1304
|
+
const rightZ = z + Math.sin(angle - Math.PI / 2) * (TRACK_WIDTH / 2 + 3);
|
|
1305
|
+
const rightMarker = createCube(rightX, markerHeight, rightZ, markerSize, {
|
|
1306
|
+
material: 'emissive',
|
|
1307
|
+
color: rightColor,
|
|
1308
|
+
emissive: rightColor,
|
|
1309
|
+
transparent: true,
|
|
1310
|
+
opacity: 0.8,
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
// Store markers for animation
|
|
1314
|
+
trackPieces[trackPieces.length - 1].leftMarker = leftMarker;
|
|
1315
|
+
trackPieces[trackPieces.length - 1].rightMarker = rightMarker;
|
|
1316
|
+
trackPieces[trackPieces.length - 1].markerColors = { left: leftColor, right: rightColor };
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
function createStartLineLighting(x, y, z, angle) {
|
|
1320
|
+
// Create spectacular start/finish line lighting
|
|
1321
|
+
for (let i = -3; i <= 3; i++) {
|
|
1322
|
+
const markerX = x + Math.cos(angle + Math.PI / 2) * i * 3;
|
|
1323
|
+
const markerZ = z + Math.sin(angle + Math.PI / 2) * i * 3;
|
|
1324
|
+
const markerY = y + 12;
|
|
1325
|
+
|
|
1326
|
+
const startMarker = createSphere(markerX, markerY, markerZ, 1.2, {
|
|
1327
|
+
material: 'holographic',
|
|
1328
|
+
color: 0xffff00,
|
|
1329
|
+
emissive: 0xaaaa00,
|
|
1330
|
+
transparent: true,
|
|
1331
|
+
opacity: 0.9,
|
|
1332
|
+
});
|
|
1333
|
+
|
|
1334
|
+
// Store for special animation
|
|
1335
|
+
if (!lightingSystem.startLineMarkers) {
|
|
1336
|
+
lightingSystem.startLineMarkers = [];
|
|
1337
|
+
}
|
|
1338
|
+
lightingSystem.startLineMarkers.push({
|
|
1339
|
+
mesh: startMarker,
|
|
1340
|
+
baseY: markerY,
|
|
1341
|
+
pulsePhase: i * 0.5,
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// Add rainbow light beams
|
|
1346
|
+
for (let i = 0; i < 5; i++) {
|
|
1347
|
+
const beamColor = [0xff0000, 0xff8800, 0xffff00, 0x00ff00, 0x0088ff][i];
|
|
1348
|
+
const beamX = x + Math.cos(angle + Math.PI / 2) * (i - 2) * 2;
|
|
1349
|
+
const beamZ = z + Math.sin(angle + Math.PI / 2) * (i - 2) * 2;
|
|
1350
|
+
|
|
1351
|
+
const lightBeam = createPlane(beamX, y + 15, beamZ, 1, 25, {
|
|
1352
|
+
material: 'emissive',
|
|
1353
|
+
color: beamColor,
|
|
1354
|
+
emissive: beamColor,
|
|
1355
|
+
transparent: true,
|
|
1356
|
+
opacity: 0.6,
|
|
1357
|
+
});
|
|
1358
|
+
rotateMesh(lightBeam, -Math.PI / 2, angle, 0);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function updateTrackLighting(dt) {
|
|
1363
|
+
// Animate track markers
|
|
1364
|
+
trackPieces.forEach((piece, index) => {
|
|
1365
|
+
if (piece.leftMarker && piece.rightMarker) {
|
|
1366
|
+
const pulsePhase = gameTime * 4 + index * 0.2;
|
|
1367
|
+
const pulseIntensity = 0.6 + 0.4 * Math.sin(pulsePhase);
|
|
1368
|
+
|
|
1369
|
+
// Update marker opacity based on pulse
|
|
1370
|
+
if (typeof setMeshOpacity === 'function') {
|
|
1371
|
+
setMeshOpacity(piece.leftMarker, pulseIntensity);
|
|
1372
|
+
setMeshOpacity(piece.rightMarker, pulseIntensity);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// Rotate markers for dynamic effect
|
|
1376
|
+
rotateMesh(piece.leftMarker, 0, dt * 2, 0);
|
|
1377
|
+
rotateMesh(piece.rightMarker, 0, -dt * 2, 0);
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
// Animate start line markers
|
|
1382
|
+
if (lightingSystem.startLineMarkers) {
|
|
1383
|
+
lightingSystem.startLineMarkers.forEach(marker => {
|
|
1384
|
+
marker.pulsePhase += dt * 3;
|
|
1385
|
+
const bounce = Math.sin(marker.pulsePhase) * 2;
|
|
1386
|
+
const currentPos = getPosition(marker.mesh);
|
|
1387
|
+
if (currentPos) {
|
|
1388
|
+
setPosition(marker.mesh, currentPos[0], marker.baseY + bounce, currentPos[2]);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// Rainbow color cycling
|
|
1392
|
+
const hue = (gameTime * 2 + marker.pulsePhase) % (Math.PI * 2);
|
|
1393
|
+
const r = Math.floor((Math.sin(hue) * 0.5 + 0.5) * 255);
|
|
1394
|
+
const g = Math.floor((Math.sin(hue + (Math.PI * 2) / 3) * 0.5 + 0.5) * 255);
|
|
1395
|
+
const b = Math.floor((Math.sin(hue + (Math.PI * 4) / 3) * 0.5 + 0.5) * 255);
|
|
1396
|
+
const rainbowColor = (r << 16) | (g << 8) | b;
|
|
1397
|
+
|
|
1398
|
+
if (typeof setMeshColor === 'function') {
|
|
1399
|
+
setMeshColor(marker.mesh, rainbowColor);
|
|
1400
|
+
}
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// ============================================================================
|
|
1406
|
+
// SPECTACULAR VISUAL EFFECTS SYSTEM
|
|
1407
|
+
// ============================================================================
|
|
1408
|
+
|
|
1409
|
+
function createSpectacularExplosion(x, y, z, size = 1.0, color = 0xff6600) {
|
|
1410
|
+
// Create multi-layered explosion effect
|
|
1411
|
+
const explosionLayers = [
|
|
1412
|
+
{ size: size * 0.5, color: 0xffffff, life: 0.3 },
|
|
1413
|
+
{ size: size * 1.0, color: 0xffff00, life: 0.6 },
|
|
1414
|
+
{ size: size * 1.5, color: 0xff6600, life: 0.9 },
|
|
1415
|
+
{ size: size * 2.0, color: 0xff0000, life: 1.2 },
|
|
1416
|
+
{ size: size * 2.5, color: 0x880000, life: 1.5 },
|
|
1417
|
+
];
|
|
1418
|
+
|
|
1419
|
+
explosionLayers.forEach((layer, index) => {
|
|
1420
|
+
setTimeout(() => {
|
|
1421
|
+
const explosionSphere = createSphere(x, y, z, layer.size, {
|
|
1422
|
+
material: 'emissive',
|
|
1423
|
+
color: layer.color,
|
|
1424
|
+
emissive: layer.color,
|
|
1425
|
+
transparent: true,
|
|
1426
|
+
opacity: 0.8,
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1429
|
+
particles.push({
|
|
1430
|
+
mesh: explosionSphere,
|
|
1431
|
+
x: x,
|
|
1432
|
+
y: y,
|
|
1433
|
+
z: z,
|
|
1434
|
+
vx: 0,
|
|
1435
|
+
vy: 0,
|
|
1436
|
+
vz: 0,
|
|
1437
|
+
life: layer.life,
|
|
1438
|
+
maxLife: layer.life,
|
|
1439
|
+
type: 'explosion',
|
|
1440
|
+
expandRate: layer.size * 2,
|
|
1441
|
+
});
|
|
1442
|
+
}, index * 50);
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
// Create particle shower
|
|
1446
|
+
for (let i = 0; i < 20; i++) {
|
|
1447
|
+
const sparkParticle = createSphere(0.1, 0xffff00, [x, y, z]);
|
|
1448
|
+
particles.push({
|
|
1449
|
+
mesh: sparkParticle,
|
|
1450
|
+
x: x,
|
|
1451
|
+
y: y,
|
|
1452
|
+
z: z,
|
|
1453
|
+
vx: (Math.random() - 0.5) * 20,
|
|
1454
|
+
vy: Math.random() * 15 + 5,
|
|
1455
|
+
vz: (Math.random() - 0.5) * 20,
|
|
1456
|
+
life: 2.0,
|
|
1457
|
+
maxLife: 2.0,
|
|
1458
|
+
type: 'spark',
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
function createPowerUpCollectionEffect(x, y, z, powerUpType) {
|
|
1464
|
+
const colors = {
|
|
1465
|
+
boost: [0xff6600, 0xffaa00, 0xffff00],
|
|
1466
|
+
health: [0x00ff00, 0x44ff44, 0x88ff88],
|
|
1467
|
+
shield: [0x0088ff, 0x44aaff, 0x88ccff],
|
|
1468
|
+
speed: [0xff0088, 0xff44aa, 0xff88cc],
|
|
1469
|
+
};
|
|
1470
|
+
|
|
1471
|
+
const typeColors = colors[powerUpType] || colors['boost'];
|
|
1472
|
+
|
|
1473
|
+
// Create expanding rings
|
|
1474
|
+
for (let ring = 0; ring < 3; ring++) {
|
|
1475
|
+
setTimeout(() => {
|
|
1476
|
+
const ringMesh = createSphere(x, y, z, ring + 1, {
|
|
1477
|
+
material: 'holographic',
|
|
1478
|
+
color: typeColors[ring],
|
|
1479
|
+
emissive: typeColors[ring],
|
|
1480
|
+
transparent: true,
|
|
1481
|
+
opacity: 0.6,
|
|
1482
|
+
wireframe: true,
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
particles.push({
|
|
1486
|
+
mesh: ringMesh,
|
|
1487
|
+
x: x,
|
|
1488
|
+
y: y,
|
|
1489
|
+
z: z,
|
|
1490
|
+
vx: 0,
|
|
1491
|
+
vy: 2,
|
|
1492
|
+
vz: 0,
|
|
1493
|
+
life: 1.5,
|
|
1494
|
+
maxLife: 1.5,
|
|
1495
|
+
type: 'collection_ring',
|
|
1496
|
+
expandRate: 3,
|
|
1497
|
+
});
|
|
1498
|
+
}, ring * 100);
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
// Create upward energy stream
|
|
1502
|
+
for (let i = 0; i < 10; i++) {
|
|
1503
|
+
const energyParticle = createSphere(0.2, typeColors[i % typeColors.length], [
|
|
1504
|
+
x + (Math.random() - 0.5) * 2,
|
|
1505
|
+
y,
|
|
1506
|
+
z + (Math.random() - 0.5) * 2,
|
|
1507
|
+
]);
|
|
1508
|
+
|
|
1509
|
+
particles.push({
|
|
1510
|
+
mesh: energyParticle,
|
|
1511
|
+
x: x,
|
|
1512
|
+
y: y,
|
|
1513
|
+
z: z,
|
|
1514
|
+
vx: (Math.random() - 0.5) * 2,
|
|
1515
|
+
vy: Math.random() * 8 + 4,
|
|
1516
|
+
vz: (Math.random() - 0.5) * 2,
|
|
1517
|
+
life: 2.0,
|
|
1518
|
+
maxLife: 2.0,
|
|
1519
|
+
type: 'energy_stream',
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
function createLapCompletionEffect() {
|
|
1525
|
+
// Spectacular lap completion celebration
|
|
1526
|
+
const celebrationColors = [0xff0000, 0xff8800, 0xffff00, 0x00ff00, 0x0088ff, 0x8800ff];
|
|
1527
|
+
|
|
1528
|
+
for (let i = 0; i < 30; i++) {
|
|
1529
|
+
setTimeout(() => {
|
|
1530
|
+
const fireworkParticle = createSphere(0.3, celebrationColors[i % celebrationColors.length], [
|
|
1531
|
+
player.x + (Math.random() - 0.5) * 20,
|
|
1532
|
+
player.y + Math.random() * 15 + 10,
|
|
1533
|
+
player.z + (Math.random() - 0.5) * 20,
|
|
1534
|
+
]);
|
|
1535
|
+
|
|
1536
|
+
particles.push({
|
|
1537
|
+
mesh: fireworkParticle,
|
|
1538
|
+
x: player.x,
|
|
1539
|
+
y: player.y + 15,
|
|
1540
|
+
z: player.z,
|
|
1541
|
+
vx: (Math.random() - 0.5) * 15,
|
|
1542
|
+
vy: Math.random() * 10 + 5,
|
|
1543
|
+
vz: (Math.random() - 0.5) * 15,
|
|
1544
|
+
life: 3.0,
|
|
1545
|
+
maxLife: 3.0,
|
|
1546
|
+
type: 'firework',
|
|
1547
|
+
});
|
|
1548
|
+
}, i * 20);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
// Screen flash effect
|
|
1552
|
+
if (typeof screenFlash === 'function') {
|
|
1553
|
+
screenFlash(0xffffff, 0.3);
|
|
1554
|
+
}
|
|
1555
|
+
}
|