Tim13ekd commited on
Commit
af0eefd
·
verified ·
1 Parent(s): a41e852

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +857 -19
index.html CHANGED
@@ -1,19 +1,857 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <title>Neon Drift: 3D Physics Racer</title>
7
+
8
+ <!-- Import FontAwesome for Icons -->
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+
11
+ <style>
12
+ :root {
13
+ --primary-color: #00f3ff;
14
+ --secondary-color: #ff00ff;
15
+ --bg-dark: #0a0a12;
16
+ --glass-bg: rgba(20, 20, 30, 0.7);
17
+ --glass-border: rgba(255, 255, 255, 0.1);
18
+ --text-color: #ffffff;
19
+ --font-main: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
20
+ }
21
+
22
+ * {
23
+ box-sizing: border-box;
24
+ user-select: none;
25
+ -webkit-user-select: none;
26
+ touch-action: none; /* Prevent scrolling on mobile */
27
+ }
28
+
29
+ body {
30
+ margin: 0;
31
+ padding: 0;
32
+ overflow: hidden;
33
+ background-color: var(--bg-dark);
34
+ font-family: var(--font-main);
35
+ color: var(--text-color);
36
+ }
37
+
38
+ /* --- Canvas Container --- */
39
+ #game-container {
40
+ position: absolute;
41
+ top: 0;
42
+ left: 0;
43
+ width: 100vw;
44
+ height: 100vh;
45
+ z-index: 1;
46
+ }
47
+
48
+ /* --- UI Overlay Layer --- */
49
+ #ui-layer {
50
+ position: absolute;
51
+ top: 0;
52
+ left: 0;
53
+ width: 100%;
54
+ height: 100%;
55
+ z-index: 10;
56
+ pointer-events: none; /* Let clicks pass through to controls/canvas */
57
+ display: flex;
58
+ flex-direction: column;
59
+ justify-content: space-between;
60
+ }
61
+
62
+ /* --- Header / Top Bar --- */
63
+ .top-bar {
64
+ display: flex;
65
+ justify-content: space-between;
66
+ align-items: center;
67
+ padding: 15px 20px;
68
+ background: linear-gradient(to bottom, rgba(0,0,0,0.8), transparent);
69
+ pointer-events: auto;
70
+ }
71
+
72
+ .brand {
73
+ font-size: 1.2rem;
74
+ font-weight: 800;
75
+ text-transform: uppercase;
76
+ letter-spacing: 2px;
77
+ text-shadow: 0 0 10px var(--primary-color);
78
+ }
79
+
80
+ .brand a {
81
+ color: var(--primary-color);
82
+ text-decoration: none;
83
+ transition: color 0.3s;
84
+ }
85
+ .brand a:hover {
86
+ color: var(--secondary-color);
87
+ }
88
+
89
+ .stats {
90
+ display: flex;
91
+ gap: 20px;
92
+ font-family: 'Courier New', monospace;
93
+ font-weight: bold;
94
+ }
95
+
96
+ .stat-box {
97
+ background: var(--glass-bg);
98
+ border: 1px solid var(--glass-border);
99
+ padding: 8px 15px;
100
+ border-radius: 8px;
101
+ backdrop-filter: blur(5px);
102
+ box-shadow: 0 4px 6px rgba(0,0,0,0.3);
103
+ }
104
+
105
+ .stat-label {
106
+ font-size: 0.7rem;
107
+ color: #aaa;
108
+ display: block;
109
+ }
110
+
111
+ .stat-value {
112
+ font-size: 1.2rem;
113
+ color: var(--primary-color);
114
+ }
115
+
116
+ /* --- Main Menu (Start Screen) --- */
117
+ #start-screen {
118
+ position: absolute;
119
+ top: 0;
120
+ left: 0;
121
+ width: 100%;
122
+ height: 100%;
123
+ background: rgba(10, 10, 18, 0.85);
124
+ backdrop-filter: blur(10px);
125
+ display: flex;
126
+ flex-direction: column;
127
+ justify-content: center;
128
+ align-items: center;
129
+ z-index: 20;
130
+ pointer-events: auto;
131
+ transition: opacity 0.5s ease;
132
+ }
133
+
134
+ #start-screen.hidden {
135
+ opacity: 0;
136
+ pointer-events: none;
137
+ }
138
+
139
+ h1 {
140
+ font-size: 4rem;
141
+ margin-bottom: 0.5rem;
142
+ background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
143
+ -webkit-background-clip: text;
144
+ -webkit-text-fill-color: transparent;
145
+ text-shadow: 0 0 20px rgba(0, 243, 255, 0.3);
146
+ text-align: center;
147
+ }
148
+
149
+ p.subtitle {
150
+ font-size: 1.2rem;
151
+ color: #ccc;
152
+ margin-bottom: 3rem;
153
+ text-align: center;
154
+ max-width: 600px;
155
+ line-height: 1.5;
156
+ }
157
+
158
+ .btn {
159
+ background: linear-gradient(90deg, var(--primary-color), #00a8ff);
160
+ border: none;
161
+ padding: 15px 40px;
162
+ color: #000;
163
+ font-size: 1.2rem;
164
+ font-weight: bold;
165
+ border-radius: 50px;
166
+ cursor: pointer;
167
+ transition: transform 0.2s, box-shadow 0.2s;
168
+ box-shadow: 0 0 15px var(--primary-color);
169
+ text-transform: uppercase;
170
+ }
171
+
172
+ .btn:hover {
173
+ transform: scale(1.05);
174
+ box-shadow: 0 0 25px var(--secondary-color);
175
+ }
176
+
177
+ .controls-info {
178
+ margin-top: 40px;
179
+ display: grid;
180
+ grid-template-columns: repeat(3, 1fr);
181
+ gap: 20px;
182
+ text-align: center;
183
+ }
184
+
185
+ .control-item {
186
+ display: flex;
187
+ flex-direction: column;
188
+ align-items: center;
189
+ color: #888;
190
+ }
191
+
192
+ .control-item i {
193
+ font-size: 1.5rem;
194
+ margin-bottom: 10px;
195
+ color: var(--text-color);
196
+ }
197
+
198
+ /* --- Mobile Controls --- */
199
+ #mobile-controls {
200
+ display: none; /* Hidden on desktop by default */
201
+ width: 100%;
202
+ height: 100%;
203
+ position: absolute;
204
+ top: 0;
205
+ left: 0;
206
+ pointer-events: none;
207
+ padding: 20px;
208
+ padding-bottom: 40px;
209
+ }
210
+
211
+ .touch-zone {
212
+ pointer-events: auto;
213
+ position: absolute;
214
+ bottom: 20px;
215
+ display: flex;
216
+ gap: 15px;
217
+ }
218
+
219
+ .d-pad {
220
+ left: 20px;
221
+ display: flex;
222
+ flex-direction: column;
223
+ align-items: center;
224
+ gap: 10px;
225
+ }
226
+
227
+ .pedals {
228
+ right: 20px;
229
+ display: flex;
230
+ flex-direction: row-reverse; /* Brake on left, Gas on right relative to hand */
231
+ gap: 15px;
232
+ }
233
+
234
+ .touch-btn {
235
+ width: 70px;
236
+ height: 70px;
237
+ background: rgba(255, 255, 255, 0.1);
238
+ border: 2px solid rgba(255, 255, 255, 0.2);
239
+ border-radius: 50%;
240
+ color: white;
241
+ font-size: 1.5rem;
242
+ display: flex;
243
+ justify-content: center;
244
+ align-items: center;
245
+ backdrop-filter: blur(4px);
246
+ transition: background 0.1s, transform 0.1s;
247
+ }
248
+
249
+ .touch-btn:active, .touch-btn.active {
250
+ background: rgba(0, 243, 255, 0.3);
251
+ transform: scale(0.95);
252
+ border-color: var(--primary-color);
253
+ }
254
+
255
+ .touch-btn.brake:active, .touch-btn.brake.active {
256
+ background: rgba(255, 0, 100, 0.3);
257
+ border-color: var(--secondary-color);
258
+ }
259
+
260
+ /* --- Reset Button --- */
261
+ #reset-btn {
262
+ position: absolute;
263
+ bottom: 20px;
264
+ left: 50%;
265
+ transform: translateX(-50%);
266
+ background: rgba(0,0,0,0.6);
267
+ border: 1px solid white;
268
+ color: white;
269
+ padding: 10px 20px;
270
+ border-radius: 20px;
271
+ pointer-events: auto;
272
+ cursor: pointer;
273
+ font-size: 0.9rem;
274
+ display: none;
275
+ }
276
+
277
+ #reset-btn:hover {
278
+ background: var(--secondary-color);
279
+ border-color: var(--secondary-color);
280
+ }
281
+
282
+ /* --- Media Queries --- */
283
+ @media (max-width: 768px) {
284
+ h1 { font-size: 2.5rem; }
285
+ .controls-info { display: none; } /* Hide keyboard info on mobile */
286
+ #mobile-controls { display: block; }
287
+ #reset-btn { display: block; }
288
+ }
289
+ </style>
290
+
291
+ <!-- Three.js & Cannon-es via Import Map -->
292
+ <script type="importmap">
293
+ {
294
+ "imports": {
295
+ "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
296
+ "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/",
297
+ "cannon-es": "https://unpkg.com/cannon-es@0.20.0/dist/cannon-es.js"
298
+ }
299
+ }
300
+ </script>
301
+ </head>
302
+ <body>
303
+
304
+ <!-- Game Canvas -->
305
+ <div id="game-container"></div>
306
+
307
+ <!-- UI Overlay -->
308
+ <div id="ui-layer">
309
+ <div class="top-bar">
310
+ <div class="brand">
311
+ <i class="fa-solid fa-gamepad"></i> Neon Drift
312
+ <span style="font-size: 0.7em; opacity: 0.7; margin-left: 10px;">| Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a></span>
313
+ </div>
314
+ <div class="stats">
315
+ <div class="stat-box">
316
+ <span class="stat-label">SPEED</span>
317
+ <span class="stat-value" id="speed-display">0 <small>km/h</small></span>
318
+ </div>
319
+ </div>
320
+ </div>
321
+
322
+ <div id="reset-btn" onclick="resetCar()">
323
+ <i class="fa-solid fa-rotate-right"></i> Flip Car
324
+ </div>
325
+
326
+ <!-- Mobile Controls -->
327
+ <div id="mobile-controls">
328
+ <div class="touch-zone d-pad">
329
+ <div class="touch-btn" id="btn-left"><i class="fa-solid fa-arrow-left"></i></div>
330
+ <div class="touch-btn" id="btn-right"><i class="fa-solid fa-arrow-right"></i></div>
331
+ </div>
332
+ <div class="touch-zone pedals">
333
+ <div class="touch-btn" id="btn-brake" style="border-color: #ff6666;"><i class="fa-solid fa-stop"></i></div>
334
+ <div class="touch-btn" id="btn-gas"><i class="fa-solid fa-gas-pump"></i></div>
335
+ </div>
336
+ </div>
337
+ </div>
338
+
339
+ <!-- Start Screen -->
340
+ <div id="start-screen">
341
+ <h1>NEON DRIFT</h1>
342
+ <p class="subtitle">Physics-based driving experience. Jump ramps, drift, and explore the cyber-grid.</p>
343
+
344
+ <button class="btn" id="start-btn">Start Engine</button>
345
+
346
+ <div class="controls-info">
347
+ <div class="control-item">
348
+ <i class="fa-solid fa-keyboard"></i>
349
+ <span>WASD / Arrows</span>
350
+ </div>
351
+ <div class="control-item">
352
+ <i class="fa-solid fa-gamepad"></i>
353
+ <span>Controller</span>
354
+ </div>
355
+ <div class="control-item">
356
+ <i class="fa-solid fa-mobile-screen"></i>
357
+ <span>Touch</span>
358
+ </div>
359
+ </div>
360
+ </div>
361
+
362
+ <script type="module">
363
+ import * as THREE from 'three';
364
+ import * as CANNON from 'cannon-es';
365
+
366
+ // --- Configuration ---
367
+ const CONFIG = {
368
+ shadows: true,
369
+ maxSteerVal: 0.5,
370
+ maxForce: 1000,
371
+ brakeForce: 20,
372
+ colors: {
373
+ bg: 0x0a0a12,
374
+ grid: 0x00f3ff,
375
+ car: 0xff00ff,
376
+ obstacle: 0x222222
377
+ }
378
+ };
379
+
380
+ // --- Globals ---
381
+ let scene, camera, renderer, world;
382
+ let vehicle, chassisBody, wheelBodies = [];
383
+ let meshHelper;
384
+ let lastTime;
385
+ let isGameActive = false;
386
+
387
+ // Input State
388
+ const input = {
389
+ forward: false,
390
+ backward: false,
391
+ left: false,
392
+ right: false,
393
+ brake: false
394
+ };
395
+
396
+ // --- Initialization ---
397
+ function init() {
398
+ // 1. Setup Three.js Scene
399
+ scene = new THREE.Scene();
400
+ scene.background = new THREE.Color(CONFIG.colors.bg);
401
+ scene.fog = new THREE.FogExp2(CONFIG.colors.bg, 0.015);
402
+
403
+ // 2. Camera
404
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
405
+
406
+ // 3. Renderer
407
+ renderer = new THREE.WebGLRenderer({ antialias: true });
408
+ renderer.setSize(window.innerWidth, window.innerHeight);
409
+ renderer.shadowMap.enabled = true;
410
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
411
+ document.getElementById('game-container').appendChild(renderer.domElement);
412
+
413
+ // 4. Lights
414
+ const ambientLight = new THREE.AmbientLight(0x404040, 1.5); // Soft white light
415
+ scene.add(ambientLight);
416
+
417
+ const dirLight = new THREE.DirectionalLight(0xffffff, 1);
418
+ dirLight.position.set(50, 100, 50);
419
+ dirLight.castShadow = true;
420
+ dirLight.shadow.mapSize.width = 2048;
421
+ dirLight.shadow.mapSize.height = 2048;
422
+ dirLight.shadow.camera.near = 0.5;
423
+ dirLight.shadow.camera.far = 500;
424
+ dirLight.shadow.camera.left = -50;
425
+ dirLight.shadow.camera.right = 50;
426
+ dirLight.shadow.camera.top = 50;
427
+ dirLight.shadow.camera.bottom = -50;
428
+ scene.add(dirLight);
429
+
430
+ // Neon Point Lights
431
+ const pLight1 = new THREE.PointLight(CONFIG.colors.grid, 2, 50);
432
+ pLight1.position.set(0, 5, 0);
433
+ scene.add(pLight1);
434
+
435
+ // 5. Physics World
436
+ world = new CANNON.World();
437
+ world.gravity.set(0, -9.82, 0);
438
+ world.broadphase = new CANNON.SAPBroadphase(world);
439
+
440
+ // Materials
441
+ const groundMat = new CANNON.Material();
442
+ const wheelMat = new CANNON.Material();
443
+ const wheelGroundContact = new CANNON.ContactMaterial(wheelMat, groundMat, {
444
+ friction: 0.3,
445
+ restitution: 0,
446
+ contactEquationStiffness: 1000
447
+ });
448
+ world.addContactMaterial(wheelGroundContact);
449
+
450
+ // 6. Create Environment
451
+ createEnvironment(groundMat);
452
+
453
+ // 7. Create Car
454
+ createCar(wheelMat);
455
+
456
+ // 8. Event Listeners
457
+ window.addEventListener('resize', onWindowResize);
458
+ setupInputs();
459
+
460
+ // Start Loop
461
+ lastTime = performance.now();
462
+ requestAnimationFrame(animate);
463
+ }
464
+
465
+ // --- Environment Generation ---
466
+ function createEnvironment(material) {
467
+ // Floor
468
+ const groundGeo = new THREE.PlaneGeometry(500, 500);
469
+ const groundTex = createGridTexture();
470
+ groundTex.wrapS = THREE.RepeatWrapping;
471
+ groundTex.wrapT = THREE.RepeatWrapping;
472
+ groundTex.repeat.set(100, 100);
473
+
474
+ const groundMesh = new THREE.Mesh(
475
+ groundGeo,
476
+ new THREE.MeshStandardMaterial({
477
+ map: groundTex,
478
+ roughness: 0.8,
479
+ metalness: 0.2
480
+ })
481
+ );
482
+ groundMesh.rotation.x = -Math.PI / 2;
483
+ groundMesh.receiveShadow = true;
484
+ scene.add(groundMesh);
485
+
486
+ // Physics Ground
487
+ const groundBody = new CANNON.Body({
488
+ mass: 0, // static
489
+ shape: new CANNON.Plane(),
490
+ material: material
491
+ });
492
+ groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0);
493
+ world.addBody(groundBody);
494
+
495
+ // Obstacles & Ramps
496
+ createObstacle(10, 1, 10, -20, 0.5, -20); // Box
497
+ createObstacle(2, 1, 8, 15, 0.5, -30); // Wall
498
+ createRamp(8, 2, 10, -15, 1, 15); // Ramp
499
+ createRamp(8, 2, 10, 20, 1, 30); // Ramp 2
500
+ }
501
+
502
+ function createGridTexture() {
503
+ const canvas = document.createElement('canvas');
504
+ canvas.width = 512;
505
+ canvas.height = 512;
506
+ const ctx = canvas.getContext('2d');
507
+
508
+ // Background
509
+ ctx.fillStyle = '#0a0a12';
510
+ ctx.fillRect(0, 0, 512, 512);
511
+
512
+ // Grid Lines
513
+ ctx.strokeStyle = '#00f3ff';
514
+ ctx.lineWidth = 2;
515
+
516
+ // Glow effect
517
+ ctx.shadowBlur = 10;
518
+ ctx.shadowColor = '#00f3ff';
519
+
520
+ ctx.beginPath();
521
+ // Vertical lines
522
+ for(let i=0; i<=512; i+=64) {
523
+ ctx.moveTo(i, 0);
524
+ ctx.lineTo(i, 512);
525
+ }
526
+ // Horizontal lines
527
+ for(let i=0; i<=512; i+=64) {
528
+ ctx.moveTo(0, i);
529
+ ctx.lineTo(512, i);
530
+ }
531
+ ctx.stroke();
532
+
533
+ const tex = new THREE.CanvasTexture(canvas);
534
+ tex.anisotropy = 16;
535
+ return tex;
536
+ }
537
+
538
+ function createObstacle(w, h, d, x, y, z) {
539
+ // Visual
540
+ const geo = new THREE.BoxGeometry(w, h, d);
541
+ const mat = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.1 });
542
+ const mesh = new THREE.Mesh(geo, mat);
543
+ mesh.position.set(x, y, z);
544
+ mesh.castShadow = true;
545
+ mesh.receiveShadow = true;
546
+ scene.add(mesh);
547
+
548
+ // Neon Edges
549
+ const edges = new THREE.EdgesGeometry(geo);
550
+ const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xff00ff }));
551
+ mesh.add(line);
552
+
553
+ // Physics
554
+ const shape = new CANNON.Box(new CANNON.Vec3(w/2, h/2, d/2));
555
+ const body = new CANNON.Body({ mass: 0 });
556
+ body.addShape(shape);
557
+ body.position.set(x, y, z);
558
+ world.addBody(body);
559
+ }
560
+
561
+ function createRamp(w, h, d, x, y, z) {
562
+ // Visual
563
+ const geo = new THREE.BoxGeometry(w, h, d);
564
+ const mat = new THREE.MeshStandardMaterial({ color: 0x222222 });
565
+ const mesh = new THREE.Mesh(geo, mat);
566
+ mesh.position.set(x, y, z);
567
+ mesh.rotation.x = -0.2; // Tilt
568
+ mesh.castShadow = true;
569
+ mesh.receiveShadow = true;
570
+ scene.add(mesh);
571
+
572
+ // Physics
573
+ const shape = new CANNON.Box(new CANNON.Vec3(w/2, h/2, d/2));
574
+ const body = new CANNON.Body({ mass: 0 });
575
+ body.addShape(shape);
576
+ body.position.set(x, y, z);
577
+ body.quaternion.setFromEuler(-0.2, 0, 0);
578
+ world.addBody(body);
579
+ }
580
+
581
+ // --- Car Creation ---
582
+ function createCar(material) {
583
+ // Chassis Dimensions
584
+ const chassisWidth = 1.8;
585
+ const chassisHeight = 0.8;
586
+ const chassisDepth = 4;
587
+ const mass = 150;
588
+
589
+ // Visual Chassis
590
+ const chassisGeo = new THREE.BoxGeometry(chassisWidth, chassisHeight, chassisDepth);
591
+ const chassisMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.2, metalness: 0.8 });
592
+ const chassisMesh = new THREE.Mesh(chassisGeo, chassisMat);
593
+ chassisMesh.castShadow = true;
594
+
595
+ // Add glowing details to car
596
+ const cabinGeo = new THREE.BoxGeometry(1.4, 0.6, 2);
597
+ const cabinMat = new THREE.MeshStandardMaterial({ color: CONFIG.colors.car, emissive: CONFIG.colors.secondary, emissiveIntensity: 0.5 });
598
+ const cabinMesh = new THREE.Mesh(cabinGeo, cabinMat);
599
+ cabinMesh.position.y = 0.5;
600
+ cabinMesh.position.z = -0.2;
601
+ chassisMesh.add(cabinMesh);
602
+
603
+ // Tail lights
604
+ const tailGeo = new THREE.BoxGeometry(0.6, 0.2, 0.1);
605
+ const tailMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
606
+ const tl = new THREE.Mesh(tailGeo, tailMat);
607
+ tl.position.set(-0.5, 0, 2);
608
+ const tr = new THREE.Mesh(tailGeo, tailMat);
609
+ tr.position.set(0.5, 0, 2);
610
+ chassisMesh.add(tl);
611
+ chassisMesh.add(tr);
612
+
613
+ scene.add(chassisMesh);
614
+
615
+ // Physics Chassis
616
+ const chassisShape = new CANNON.Box(new CANNON.Vec3(chassisWidth/2, chassisHeight/2, chassisDepth/2));
617
+ chassisBody = new CANNON.Body({ mass: mass, material: material });
618
+ chassisBody.addShape(chassisShape);
619
+ chassisBody.position.set(0, 4, 0); // Drop from sky
620
+ chassisBody.angularDamping = 0.5;
621
+ world.addBody(chassisBody);
622
+
623
+ // Vehicle Setup
624
+ vehicle = new CANNON.RaycastVehicle({
625
+ chassisBody: chassisBody,
626
+ });
627
+
628
+ const wheelOptions = {
629
+ radius: 0.5,
630
+ directionLocal: new CANNON.Vec3(0, -1, 0),
631
+ suspensionStiffness: 30,
632
+ suspensionRestLength: 0.3,
633
+ frictionSlip: 1.4,
634
+ dampingRelaxation: 2.3,
635
+ dampingCompression: 4.4,
636
+ maxSuspensionForce: 100000,
637
+ rollInfluence: 0.01,
638
+ axleLocal: new CANNON.Vec3(-1, 0, 0),
639
+ chassisConnectionPointLocal: new CANNON.Vec3(1, 1, 0),
640
+ maxSuspensionTravel: 0.3,
641
+ customSlidingRotationalSpeed: -30,
642
+ useCustomSlidingRotationalSpeed: true
643
+ };
644
+
645
+ // Add wheels
646
+ const axleWidth = 1.3;
647
+ const frontPos = 1.3;
648
+ const rearPos = -1.2;
649
+ const wheelHeight = -0.5;
650
+
651
+ // Front Left
652
+ wheelOptions.chassisConnectionPointLocal.set(axleWidth, wheelHeight, frontPos);
653
+ vehicle.addWheel(wheelOptions);
654
+
655
+ // Front Right
656
+ wheelOptions.chassisConnectionPointLocal.set(-axleWidth, wheelHeight, frontPos);
657
+ vehicle.addWheel(wheelOptions);
658
+
659
+ // Rear Left
660
+ wheelOptions.chassisConnectionPointLocal.set(axleWidth, wheelHeight, rearPos);
661
+ vehicle.addWheel(wheelOptions);
662
+
663
+ // Rear Right
664
+ wheelOptions.chassisConnectionPointLocal.set(-axleWidth, wheelHeight, rearPos);
665
+ vehicle.addWheel(wheelOptions);
666
+
667
+ vehicle.addToWorld(world);
668
+
669
+ // Wheel Visuals
670
+ const wheelGeo = new THREE.CylinderGeometry(wheelOptions.radius, wheelOptions.radius, wheelOptions.radius / 2, 24);
671
+ wheelGeo.rotateZ(Math.PI / 2);
672
+ const wheelMeshMat = new THREE.MeshStandardMaterial({ color: 0x333333 });
673
+ const rimMat = new THREE.MeshBasicMaterial({ color: CONFIG.colors.primary });
674
+
675
+ vehicle.wheelInfos.forEach((wheel) => {
676
+ const cylinder = new THREE.Mesh(wheelGeo, wheelMeshMat);
677
+ cylinder.castShadow = true;
678
+
679
+ // Add Rim visual
680
+ const rim = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.8, 0.8), rimMat);
681
+ rim.rotation.x = Math.PI/2;
682
+ cylinder.add(rim);
683
+
684
+ scene.add(cylinder);
685
+ wheelBodies.push(cylinder);
686
+ });
687
+
688
+ // Sync Chassis Mesh with Body
689
+ chassisBody.visualMesh = chassisMesh;
690
+ }
691
+
692
+ // --- Inputs Handling ---
693
+ function setupInputs() {
694
+ // Keyboard
695
+ document.addEventListener('keydown', (e) => {
696
+ switch(e.key.toLowerCase()) {
697
+ case 'w': case 'arrowup': input.forward = true; break;
698
+ case 's': case 'arrowdown': input.backward = true; break;
699
+ case 'a': case 'arrowleft': input.left = true; break;
700
+ case 'd': case 'arrowright': input.right = true; break;
701
+ case ' ': input.brake = true; break;
702
+ }
703
+ });
704
+
705
+ document.addEventListener('keyup', (e) => {
706
+ switch(e.key.toLowerCase()) {
707
+ case 'w': case 'arrowup': input.forward = false; break;
708
+ case 's': case 'arrowdown': input.backward = false; break;
709
+ case 'a': case 'arrowleft': input.left = false; break;
710
+ case 'd': case 'arrowright': input.right = false; break;
711
+ case ' ': input.brake = false; break;
712
+ }
713
+ });
714
+
715
+ // Touch (Mobile)
716
+ const addTouch = (id, key) => {
717
+ const el = document.getElementById(id);
718
+ el.addEventListener('touchstart', (e) => { e.preventDefault(); input[key] = true; el.classList.add('active'); });
719
+ el.addEventListener('touchend', (e) => { e.preventDefault(); input[key] = false; el.classList.remove('active'); });
720
+ };
721
+
722
+ addTouch('btn-gas', 'forward');
723
+ addTouch('btn-brake', 'backward'); // Using backward for brake/reverse logic
724
+ addTouch('btn-left', 'left');
725
+ addTouch('btn-right', 'right');
726
+
727
+ // Start Button
728
+ document.getElementById('start-btn').addEventListener('click', () => {
729
+ document.getElementById('start-screen').classList.add('hidden');
730
+ isGameActive = true;
731
+ });
732
+ }
733
+
734
+ function handleGamepad() {
735
+ const gamepads = navigator.getGamepads();
736
+ if (gamepads[0]) {
737
+ const gp = gamepads[0];
738
+
739
+ // Deadzone check
740
+ const deadzone = 0.1;
741
+
742
+ // Left Stick Axis 0 (Steering) or D-Pad Axis 0
743
+ let steerAxis = gp.axes[0];
744
+ if (Math.abs(steerAxis) < deadzone) steerAxis = 0;
745
+
746
+ if (steerAxis < -0.1) { input.left = true; input.right = false; }
747
+ else if (steerAxis > 0.1) { input.left = false; input.right = true; }
748
+ else { input.left = false; input.right = false; }
749
+
750
+ // Triggers or Buttons for Gas/Brake
751
+ // Button 0 (A/Cross), Button 1 (B/Circle)
752
+ input.forward = gp.buttons[0].pressed;
753
+ input.backward = gp.buttons[1].pressed || gp.buttons[2].pressed; // B or X
754
+ }
755
+ }
756
+
757
+ // --- Game Logic ---
758
+ function updatePhysics() {
759
+ if (!vehicle) return;
760
+
761
+ // Steering
762
+ const steer = input.left ? CONFIG.maxSteerVal : (input.right ? -CONFIG.maxSteerVal : 0);
763
+ vehicle.setSteeringValue(steer, 0);
764
+ vehicle.setSteeringValue(steer, 1);
765
+
766
+ // Engine Force
767
+ let force = 0;
768
+ if (input.forward) force = -CONFIG.maxForce;
769
+ if (input.backward) force = CONFIG.maxForce / 2; // Reverse is slower
770
+
771
+ vehicle.applyEngineForce(force, 2);
772
+ vehicle.applyEngineForce(force, 3);
773
+
774
+ // Brakes
775
+ const brakeForce = input.brake ? CONFIG.brakeForce : 0;
776
+ vehicle.setBrake(brakeForce, 0);
777
+ vehicle.setBrake(brakeForce, 1);
778
+ vehicle.setBrake(brakeForce, 2);
779
+ vehicle.setBrake(brakeForce, 3);
780
+
781
+ // Update Physics World
782
+ world.step(1 / 60);
783
+
784
+ // Sync Visuals
785
+ for (let i = 0; i < vehicle.wheelInfos.length; i++) {
786
+ vehicle.updateWheelTransform(i);
787
+ const t = vehicle.wheelInfos[i].worldTransform;
788
+ const wheelMesh = wheelBodies[i];
789
+ wheelMesh.position.copy(t.position);
790
+ wheelMesh.quaternion.copy(t.quaternion);
791
+ }
792
+
793
+ if (chassisBody && chassisBody.visualMesh) {
794
+ chassisBody.visualMesh.position.copy(chassisBody.position);
795
+ chassisBody.visualMesh.quaternion.copy(chassisBody.quaternion);
796
+ }
797
+ }
798
+
799
+ function updateCamera() {
800
+ if (!chassisBody) return;
801
+
802
+ // Camera Follow Logic (Smooth Lerp)
803
+ const relativeCameraOffset = new THREE.Vector3(0, 5, 10);
804
+ const cameraOffset = relativeCameraOffset.applyMatrix4(chassisBody.visualMesh.matrixWorld);
805
+
806
+ // Simple lerp
807
+ camera.position.lerp(cameraOffset, 0.1);
808
+
809
+ // Look slightly ahead of the car
810
+ const carPos = chassisBody.visualMesh.position;
811
+ const lookTarget = new THREE.Vector3(carPos.x, carPos.y, carPos.z - 10);
812
+ // Apply rotation of car to look target? No, just look at car generally
813
+ camera.lookAt(carPos);
814
+ }
815
+
816
+ function updateUI() {
817
+ if (!chassisBody) return;
818
+ // Speed calculation (approximate)
819
+ const velocity = chassisBody.velocity;
820
+ const speed = Math.sqrt(velocity.x**2 + velocity.z**2) * 3.6; // m/s to km/h
821
+ document.getElementById('speed-display').innerHTML = Math.round(speed) + ' <small>km/h</small>';
822
+ }
823
+
824
+ // Global Reset Function
825
+ window.resetCar = function() {
826
+ if (!chassisBody) return;
827
+ chassisBody.position.set(0, 2, 0);
828
+ chassisBody.quaternion.set(0, 0, 0, 1);
829
+ chassisBody.velocity.set(0, 0, 0);
830
+ chassisBody.angularVelocity.set(0, 0, 0);
831
+ };
832
+
833
+ function animate() {
834
+ requestAnimationFrame(animate);
835
+
836
+ if (isGameActive) {
837
+ handleGamepad();
838
+ updatePhysics();
839
+ updateCamera();
840
+ updateUI();
841
+ }
842
+
843
+ renderer.render(scene, camera);
844
+ }
845
+
846
+ function onWindowResize() {
847
+ camera.aspect = window.innerWidth / window.innerHeight;
848
+ camera.updateProjectionMatrix();
849
+ renderer.setSize(window.innerWidth, window.innerHeight);
850
+ }
851
+
852
+ // Start
853
+ init();
854
+
855
+ </script>
856
+ </body>
857
+ </html>