humphuk commited on
Commit
b298fd7
·
verified ·
1 Parent(s): 9a8699c

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +936 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: 3d Cycling
3
- emoji: 🌍
4
- colorFrom: red
5
- colorTo: green
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: 3d-cycling
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,936 @@
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">
6
+ <title>3D Cycling Adventure</title>
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ padding: 0;
11
+ overflow: hidden;
12
+ font-family: 'Arial', sans-serif;
13
+ background-color: #333;
14
+ color: white;
15
+ }
16
+
17
+ #container {
18
+ position: absolute;
19
+ width: 100%;
20
+ height: 100%;
21
+ }
22
+
23
+ #ui {
24
+ position: absolute;
25
+ top: 0;
26
+ left: 0;
27
+ width: 100%;
28
+ height: 100%;
29
+ pointer-events: none;
30
+ z-index: 100;
31
+ }
32
+
33
+ #speedometer {
34
+ position: absolute;
35
+ bottom: 30px;
36
+ right: 30px;
37
+ width: 150px;
38
+ height: 150px;
39
+ background: rgba(0, 0, 0, 0.5);
40
+ border-radius: 50%;
41
+ border: 3px solid #fff;
42
+ display: flex;
43
+ flex-direction: column;
44
+ justify-content: center;
45
+ align-items: center;
46
+ }
47
+
48
+ #speed {
49
+ font-size: 28px;
50
+ font-weight: bold;
51
+ margin-bottom: 5px;
52
+ }
53
+
54
+ #speed-label {
55
+ font-size: 14px;
56
+ text-transform: uppercase;
57
+ letter-spacing: 2px;
58
+ }
59
+
60
+ #distance {
61
+ position: absolute;
62
+ top: 30px;
63
+ left: 30px;
64
+ font-size: 24px;
65
+ background: rgba(0, 0, 0, 0.5);
66
+ padding: 10px 20px;
67
+ border-radius: 5px;
68
+ }
69
+
70
+ #controls {
71
+ position: absolute;
72
+ bottom: 30px;
73
+ left: 30px;
74
+ background: rgba(0, 0, 0, 0.5);
75
+ padding: 15px;
76
+ border-radius: 5px;
77
+ pointer-events: all;
78
+ }
79
+
80
+ #controls h3 {
81
+ margin-top: 0;
82
+ margin-bottom: 10px;
83
+ }
84
+
85
+ button {
86
+ background: #4CAF50;
87
+ border: none;
88
+ color: white;
89
+ padding: 8px 15px;
90
+ text-align: center;
91
+ text-decoration: none;
92
+ display: inline-block;
93
+ font-size: 14px;
94
+ margin: 4px 2px;
95
+ cursor: pointer;
96
+ border-radius: 4px;
97
+ }
98
+
99
+ button:hover {
100
+ background: #45a049;
101
+ }
102
+
103
+ #start-screen {
104
+ position: absolute;
105
+ top: 0;
106
+ left: 0;
107
+ width: 100%;
108
+ height: 100%;
109
+ background: rgba(0, 0, 0, 0.8);
110
+ display: flex;
111
+ flex-direction: column;
112
+ justify-content: center;
113
+ align-items: center;
114
+ z-index: 200;
115
+ }
116
+
117
+ #start-screen h1 {
118
+ font-size: 48px;
119
+ color: #4CAF50;
120
+ margin-bottom: 30px;
121
+ text-shadow: 0 0 10px rgba(76, 175, 80, 0.7);
122
+ }
123
+
124
+ #start-button {
125
+ padding: 15px 40px;
126
+ font-size: 20px;
127
+ background: #4CAF50;
128
+ border: none;
129
+ border-radius: 8px;
130
+ cursor: pointer;
131
+ transition: all 0.3s;
132
+ }
133
+
134
+ #start-button:hover {
135
+ transform: scale(1.05);
136
+ box-shadow: 0 0 20px rgba(76, 175, 80, 0.7);
137
+ }
138
+
139
+ #terrain-info {
140
+ position: absolute;
141
+ top: 30px;
142
+ right: 30px;
143
+ background: rgba(0, 0, 0, 0.5);
144
+ padding: 10px 20px;
145
+ border-radius: 5px;
146
+ }
147
+
148
+ #gear {
149
+ position: absolute;
150
+ bottom: 190px;
151
+ right: 30px;
152
+ background: rgba(0, 0, 0, 0.5);
153
+ padding: 8px 15px;
154
+ border-radius: 5px;
155
+ }
156
+ </style>
157
+ </head>
158
+ <body>
159
+ <div id="container"></div>
160
+
161
+ <div id="ui">
162
+ <div id="distance">Distance: 0 m</div>
163
+ <div id="terrain-info">Terrain: Flat</div>
164
+ <div id="gear">Gear: 3</div>
165
+ <div id="speedometer">
166
+ <div id="speed">0</div>
167
+ <div id="speed-label">KM/H</div>
168
+ </div>
169
+
170
+ <div id="controls">
171
+ <h3>Controls</h3>
172
+ <p>W: Pedal Faster</p>
173
+ <p>A/D: Steer Left/Right</p>
174
+ <p>S: Brake</p>
175
+ <p>Space: Jump</p>
176
+ <p>Q/E: Gear Down/Up</p>
177
+ <button id="reset-btn">Reset</button>
178
+ </div>
179
+ </div>
180
+
181
+ <div id="start-screen">
182
+ <h1>3D CYCLING ADVENTURE</h1>
183
+ <button id="start-button">START RIDE</button>
184
+ </div>
185
+
186
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
187
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script>
188
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.min.js"></script>
189
+ <script>
190
+ // Game variables
191
+ let scene, camera, renderer, bike, terrain, road, controls;
192
+ let speed = 0;
193
+ let maxSpeed = 40;
194
+ let acceleration = 0.05;
195
+ let deceleration = 0.1;
196
+ let brakePower = 0.2;
197
+ let distance = 0;
198
+ let upDownSpeed = 0;
199
+ let jumpPower = 15;
200
+ let isJumping = false;
201
+ let isOnGround = true;
202
+ let gameStarted = false;
203
+ let currentGear = 3;
204
+ let terrainType = 'flat';
205
+ let keyState = {};
206
+ let clock = new THREE.Clock();
207
+ let roadPath = [];
208
+ let roadWidth = 3;
209
+
210
+ // Initialize the game
211
+ function init() {
212
+ // Create scene
213
+ scene = new THREE.Scene();
214
+ scene.background = new THREE.Color(0x87CEEB); // Sky blue
215
+
216
+ // Setup camera
217
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
218
+ camera.position.set(0, 2, -5);
219
+ camera.lookAt(0, 2, 0);
220
+
221
+ // Setup renderer
222
+ renderer = new THREE.WebGLRenderer({ antialias: true });
223
+ renderer.setSize(window.innerWidth, window.innerHeight);
224
+ document.getElementById('container').appendChild(renderer.domElement);
225
+
226
+ // Add lights
227
+ const ambientLight = new THREE.AmbientLight(0x404040);
228
+ scene.add(ambientLight);
229
+
230
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
231
+ directionalLight.position.set(1, 1, 1);
232
+ scene.add(directionalLight);
233
+
234
+ // Create terrain
235
+ createTerrain();
236
+
237
+ // Create road
238
+ createRoad();
239
+
240
+ // Create simple bike placeholder
241
+ createBike();
242
+
243
+ // Add event listeners
244
+ window.addEventListener('resize', onWindowResize);
245
+ document.addEventListener('keydown', onKeyDown);
246
+ document.addEventListener('keyup', onKeyUp);
247
+
248
+ // Start button
249
+ document.getElementById('start-button').addEventListener('click', startGame);
250
+ document.getElementById('reset-btn').addEventListener('click', resetGame);
251
+
252
+ // Start animation loop
253
+ animate();
254
+ }
255
+
256
+ function createTerrain() {
257
+ // Create a large ground plane
258
+ const groundGeometry = new THREE.PlaneGeometry(10000, 10000, 50, 50);
259
+
260
+ // Modify vertices to create hills and valleys
261
+ const vertices = groundGeometry.attributes.position;
262
+ for (let i = 0; i < vertices.count; i++) {
263
+ const x = vertices.getX(i);
264
+ const z = vertices.getZ(i);
265
+
266
+ // Create interesting terrain with sine and cosine waves
267
+ let y = Math.sin(x * 0.02) * 5 +
268
+ Math.cos(z * 0.03) * 3 +
269
+ Math.sin(x * 0.005 + z * 0.005) * 10;
270
+
271
+ // Flatten the area where the bike starts
272
+ if (Math.abs(x) < 100 && Math.abs(z) < 100) {
273
+ y = 0;
274
+ }
275
+
276
+ vertices.setY(i, y);
277
+ }
278
+
279
+ groundGeometry.computeVertexNormals();
280
+
281
+ // Create different materials for different terrains
282
+ const grassTexture = new THREE.TextureLoader().load('https://threejs.org/examples/textures/grass/grass.png');
283
+ grassTexture.wrapS = grassTexture.wrapT = THREE.RepeatWrapping;
284
+ grassTexture.repeat.set(50, 50);
285
+
286
+ const groundMaterial = new THREE.MeshStandardMaterial({
287
+ map: grassTexture,
288
+ side: THREE.DoubleSide
289
+ });
290
+
291
+ terrain = new THREE.Mesh(groundGeometry, groundMaterial);
292
+ terrain.rotation.x = -Math.PI / 2;
293
+ terrain.receiveShadow = true;
294
+ scene.add(terrain);
295
+
296
+ // Add some trees and obstacles
297
+ createObstacles();
298
+
299
+ // Add skybox
300
+ const skyboxGeometry = new THREE.BoxGeometry(5000, 5000, 5000);
301
+ const skyboxMaterial = new THREE.MeshBasicMaterial({
302
+ color: 0x87CEEB,
303
+ side: THREE.BackSide
304
+ });
305
+ const skybox = new THREE.Mesh(skyboxGeometry, skyboxMaterial);
306
+ scene.add(skybox);
307
+ }
308
+
309
+ function createRoad() {
310
+ // Generate a path for the road that winds through the terrain
311
+ generateRoadPath();
312
+
313
+ // Create road geometry
314
+ const roadGeometry = new THREE.BufferGeometry();
315
+ const vertices = [];
316
+ const normals = [];
317
+ const uv = [];
318
+
319
+ // Road resolution (how many segments)
320
+ const segments = roadPath.length * 2;
321
+
322
+ // Create road by extruding along the path
323
+ for (let i = 0; i < roadPath.length - 1; i++) {
324
+ const p1 = roadPath[i];
325
+ const p2 = roadPath[i + 1];
326
+
327
+ // Calculate perpendicular vector
328
+ const dir = new THREE.Vector3(
329
+ p2.x - p1.x,
330
+ 0,
331
+ p2.z - p1.z
332
+ ).normalize();
333
+
334
+ // Get perpendicular vector
335
+ const perp = new THREE.Vector3(-dir.z, 0, dir.x);
336
+
337
+ // Get road height from terrain
338
+ const h1 = getTerrainHeight(p1.x, p1.z) + 0.05;
339
+ const h2 = getTerrainHeight(p2.x, p2.z) + 0.05;
340
+
341
+ // Create vertices for left and right edges
342
+ const v1 = new THREE.Vector3(
343
+ p1.x - perp.x * roadWidth/2,
344
+ h1,
345
+ p1.z - perp.z * roadWidth/2
346
+ );
347
+
348
+ const v2 = new THREE.Vector3(
349
+ p1.x + perp.x * roadWidth/2,
350
+ h1,
351
+ p1.z + perp.z * roadWidth/2
352
+ );
353
+
354
+ const v3 = new THREE.Vector3(
355
+ p2.x - perp.x * roadWidth/2,
356
+ h2,
357
+ p2.z - perp.z * roadWidth/2
358
+ );
359
+
360
+ const v4 = new THREE.Vector3(
361
+ p2.x + perp.x * roadWidth/2,
362
+ h2,
363
+ p2.z + perp.z * roadWidth/2
364
+ );
365
+
366
+ // Add vertices for two triangles per segment
367
+ // Triangle 1
368
+ vertices.push(v1.x, v1.y, v1.z);
369
+ vertices.push(v2.x, v2.y, v2.z);
370
+ vertices.push(v3.x, v3.y, v3.z);
371
+
372
+ // Triangle 2
373
+ vertices.push(v2.x, v2.y, v2.z);
374
+ vertices.push(v4.x, v4.y, v4.z);
375
+ vertices.push(v3.x, v3.y, v3.z);
376
+
377
+ // Add normals (pointing up)
378
+ for (let j = 0; j < 6; j++) {
379
+ normals.push(0, 1, 0);
380
+ }
381
+
382
+ // Add UV coordinates for texture mapping
383
+ uv.push(0, 0);
384
+ uv.push(1, 0);
385
+ uv.push(0, 1);
386
+
387
+ uv.push(1, 0);
388
+ uv.push(1, 1);
389
+ uv.push(0, 1);
390
+ }
391
+
392
+ // Set geometry attributes
393
+ roadGeometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
394
+ roadGeometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
395
+ roadGeometry.setAttribute('uv', new THREE.Float32BufferAttribute(uv, 2));
396
+
397
+ // Road material
398
+ const roadTexture = new THREE.TextureLoader().load('https://threejs.org/examples/textures/terrain/asphalt.jpg');
399
+ roadTexture.wrapS = roadTexture.wrapT = THREE.RepeatWrapping;
400
+ roadTexture.repeat.set(roadPath.length / 10, 1);
401
+
402
+ const roadMaterial = new THREE.MeshStandardMaterial({
403
+ map: roadTexture,
404
+ roughness: 0.8,
405
+ metalness: 0.2
406
+ });
407
+
408
+ road = new THREE.Mesh(roadGeometry, roadMaterial);
409
+ scene.add(road);
410
+
411
+ // Add white road markings
412
+ createRoadMarkings();
413
+ }
414
+
415
+ function generateRoadPath() {
416
+ // Create a winding road that follows terrain contours
417
+ const startPoint = new THREE.Vector3(0, 0, 0);
418
+ roadPath.push(startPoint);
419
+
420
+ // Generate random waypoints
421
+ const segments = 100;
422
+ let currentAngle = 0;
423
+
424
+ for (let i = 0; i < segments; i++) {
425
+ // Random angle change to make the road wind
426
+ currentAngle += (Math.random() - 0.5) * 0.5;
427
+
428
+ // Calculate next point
429
+ const distance = 20 + Math.random() * 10;
430
+ const lastPoint = roadPath[roadPath.length - 1];
431
+
432
+ const newPoint = new THREE.Vector3(
433
+ lastPoint.x + Math.sin(currentAngle) * distance,
434
+ 0,
435
+ lastPoint.z + Math.cos(currentAngle) * distance
436
+ );
437
+
438
+ // Adjust y position to terrain
439
+ newPoint.y = getTerrainHeight(newPoint.x, newPoint.z);
440
+
441
+ roadPath.push(newPoint);
442
+ }
443
+
444
+ // Smooth the path
445
+ smoothRoadPath();
446
+ }
447
+
448
+ function smoothRoadPath() {
449
+ // Apply simple smoothing to the road path
450
+ const smoothedPath = [];
451
+
452
+ for (let i = 0; i < roadPath.length; i++) {
453
+ if (i === 0 || i === roadPath.length - 1) {
454
+ smoothedPath.push(roadPath[i].clone());
455
+ continue;
456
+ }
457
+
458
+ // Average with previous and next points
459
+ const avgX = (roadPath[i-1].x + roadPath[i].x + roadPath[i+1].x) / 3;
460
+ const avgZ = (roadPath[i-1].z + roadPath[i].z + roadPath[i+1].z) / 3;
461
+ const avgY = getTerrainHeight(avgX, avgZ);
462
+
463
+ smoothedPath.push(new THREE.Vector3(avgX, avgY, avgZ));
464
+ }
465
+
466
+ roadPath = smoothedPath;
467
+ }
468
+
469
+ function createRoadMarkings() {
470
+ const centerLineGeometry = new THREE.BufferGeometry();
471
+ const centerVertices = [];
472
+
473
+ for (let i = 1; i < roadPath.length; i++) {
474
+ const p1 = roadPath[i-1];
475
+ const p2 = roadPath[i];
476
+
477
+ // Midpoint between p1 and p2
478
+ const mid = new THREE.Vector3(
479
+ (p1.x + p2.x) / 2,
480
+ (p1.y + p2.y) / 2 + 0.01, // Slightly above road
481
+ (p1.z + p2.z) / 2
482
+ );
483
+
484
+ // Direction between points
485
+ const dir = new THREE.Vector3(
486
+ p2.x - p1.x,
487
+ 0,
488
+ p2.z - p1.z
489
+ ).normalize();
490
+
491
+ // Perpendicular vector
492
+ const perp = new THREE.Vector3(-dir.z, 0, dir.x);
493
+
494
+ // Create a small line segment
495
+ const lineLength = 2;
496
+ const gapLength = 3;
497
+
498
+ // Total segment length
499
+ const segLength = p1.distanceTo(p2);
500
+ const segments = Math.floor(segLength / (lineLength + gapLength));
501
+
502
+ for (let j = 0; j < segments; j++) {
503
+ const startPos = j / segments;
504
+ const endPos = (j + lineLength/(lineLength+gapLength)) / segments;
505
+
506
+ const start = new THREE.Vector3().lerpVectors(p1, p2, startPos);
507
+ const end = new THREE.Vector3().lerpVectors(p1, p2, endPos);
508
+
509
+ start.y = getTerrainHeight(start.x, start.z) + 0.01;
510
+ end.y = getTerrainHeight(end.x, end.z) + 0.01;
511
+
512
+ centerVertices.push(start.x, start.y, start.z);
513
+ centerVertices.push(end.x, end.y, end.z);
514
+ }
515
+ }
516
+
517
+ centerLineGeometry.setAttribute('position', new THREE.Float32BufferAttribute(centerVertices, 3));
518
+
519
+ const centerLineMaterial = new THREE.LineBasicMaterial({
520
+ color: 0xffffff,
521
+ linewidth: 3
522
+ });
523
+
524
+ const centerLine = new THREE.LineSegments(centerLineGeometry, centerLineMaterial);
525
+ scene.add(centerLine);
526
+ }
527
+
528
+ function createBike() {
529
+ // Create a simple bike with a frame and wheels
530
+ const bikeGroup = new THREE.Group();
531
+
532
+ // Frame
533
+ const frameGeometry = new THREE.BoxGeometry(2, 0.5, 0.8);
534
+ const frameMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
535
+ const frame = new THREE.Mesh(frameGeometry, frameMaterial);
536
+ frame.position.y = 0.7;
537
+ bikeGroup.add(frame);
538
+
539
+ // Handlebar
540
+ const handlebarGeometry = new THREE.BoxGeometry(0.3, 0.05, 1);
541
+ const handlebarMaterial = new THREE.MeshStandardMaterial({ color: 0x222222 });
542
+ const handlebar = new THREE.Mesh(handlebarGeometry, handlebarMaterial);
543
+ handlebar.position.set(0, 1.2, 0.3);
544
+ bikeGroup.add(handlebar);
545
+
546
+ // Front wheel
547
+ const wheelGeometry = new THREE.CylinderGeometry(0.4, 0.4, 0.1, 32);
548
+ const wheelMaterial = new THREE.MeshStandardMaterial({ color: 0x666666 });
549
+ const frontWheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
550
+ frontWheel.position.set(0, 0.4, 0.8);
551
+ frontWheel.rotation.z = Math.PI / 2;
552
+ bikeGroup.add(frontWheel);
553
+
554
+ // Rear wheel
555
+ const rearWheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
556
+ rearWheel.position.set(0, 0.4, -0.8);
557
+ rearWheel.rotation.z = Math.PI / 2;
558
+ bikeGroup.add(rearWheel);
559
+
560
+ // Rider (simple representation)
561
+ const headGeometry = new THREE.SphereGeometry(0.3, 16, 16);
562
+ const headMaterial = new THREE.MeshStandardMaterial({ color: 0xFFD700 });
563
+ const head = new THREE.Mesh(headGeometry, headMaterial);
564
+ head.position.set(0, 1.5, 0);
565
+ bikeGroup.add(head);
566
+
567
+ bikeGroup.position.y = 1;
568
+ bike = bikeGroup;
569
+ scene.add(bike);
570
+ }
571
+
572
+ function createObstacles() {
573
+ // Add some trees outside the road
574
+ for (let i = 0; i < 100; i++) {
575
+ const angle = Math.random() * Math.PI * 2;
576
+ const radius = 50 + Math.random() * 2950;
577
+
578
+ const x = Math.cos(angle) * radius;
579
+ const z = Math.sin(angle) * radius;
580
+
581
+ // Skip if too close to road
582
+ let nearRoad = false;
583
+ for (let j = 0; j < roadPath.length; j++) {
584
+ const p = roadPath[j];
585
+ if (Math.sqrt((x - p.x) * (x - p.x) + (z - p.z) * (z - p.z)) < 10) {
586
+ nearRoad = true;
587
+ break;
588
+ }
589
+ }
590
+
591
+ if (nearRoad) continue;
592
+
593
+ const y = getTerrainHeight(x, z);
594
+
595
+ if (y > 2 || y < -2) continue; // Don't place trees on steep slopes
596
+
597
+ const treeHeight = 5 + Math.random() * 10;
598
+
599
+ // Trunk
600
+ const trunkGeometry = new THREE.CylinderGeometry(0.3, 0.4, treeHeight, 8);
601
+ const trunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });
602
+ const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
603
+ trunk.position.set(x, y + treeHeight/2, z);
604
+ scene.add(trunk);
605
+
606
+ // Leaves
607
+ const leavesGeometry = new THREE.SphereGeometry(treeHeight/3, 8, 8);
608
+ const leavesMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });
609
+ const leaves = new THREE.Mesh(leavesGeometry, leavesMaterial);
610
+ leaves.position.set(x, y + treeHeight, z);
611
+ scene.add(leaves);
612
+ }
613
+
614
+ // Add some roadside objects (cones, signs, etc.)
615
+ for (let i = 0; i < 50; i++) {
616
+ // Pick a random point along the road
617
+ const segIdx = Math.floor(Math.random() * (roadPath.length - 1));
618
+ const p1 = roadPath[segIdx];
619
+ const p2 = roadPath[segIdx + 1];
620
+
621
+ // Get direction vector
622
+ const dir = new THREE.Vector3(
623
+ p2.x - p1.x,
624
+ 0,
625
+ p2.z - p1.z
626
+ ).normalize();
627
+
628
+ // Get perpendicular vector
629
+ const perp = new THREE.Vector3(-dir.z, 0, dir.x);
630
+
631
+ // Place obstacle slightly off the road
632
+ const t = Math.random();
633
+ const alongRoad = new THREE.Vector3().lerpVectors(p1, p2, t);
634
+ const offset = 1.5 + Math.random() * 3; // Random offset from road edge
635
+ const position = new THREE.Vector3(
636
+ alongRoad.x + perp.x * (roadWidth/2 + offset),
637
+ getTerrainHeight(alongRoad.x, alongRoad.z) + 0.1,
638
+ alongRoad.z + perp.z * (roadWidth/2 + offset)
639
+ );
640
+
641
+ // Create cone
642
+ const coneGeometry = new THREE.ConeGeometry(0.3, 1, 8);
643
+ const coneMaterial = new THREE.MeshStandardMaterial({ color: 0xFFA500 });
644
+ const cone = new THREE.Mesh(coneGeometry, coneMaterial);
645
+ cone.position.copy(position);
646
+ scene.add(cone);
647
+ }
648
+ }
649
+
650
+ function onWindowResize() {
651
+ camera.aspect = window.innerWidth / window.innerHeight;
652
+ camera.updateProjectionMatrix();
653
+ renderer.setSize(window.innerWidth, window.innerHeight);
654
+ }
655
+
656
+ function onKeyDown(event) {
657
+ keyState[event.key.toLowerCase()] = true;
658
+
659
+ // Space for jump
660
+ if (event.key === ' ' && isOnGround && !isJumping && gameStarted) {
661
+ isJumping = true;
662
+ isOnGround = false;
663
+ upDownSpeed = jumpPower;
664
+ }
665
+ }
666
+
667
+ function onKeyUp(event) {
668
+ keyState[event.key.toLowerCase()] = false;
669
+ }
670
+
671
+ function startGame() {
672
+ document.getElementById('start-screen').style.display = 'none';
673
+ gameStarted = true;
674
+ }
675
+
676
+ function resetGame() {
677
+ speed = 0;
678
+ distance = 0;
679
+ currentGear = 3;
680
+ bike.position.set(0, 1, 0);
681
+ bike.rotation.y = 0;
682
+ camera.position.set(0, 2, -5);
683
+ camera.lookAt(0, 2, 0);
684
+ updateUI();
685
+ }
686
+
687
+ function animate() {
688
+ requestAnimationFrame(animate);
689
+
690
+ const delta = clock.getDelta();
691
+
692
+ if (gameStarted) {
693
+ // Physics and controls
694
+ handleControls(delta);
695
+
696
+ // Update bike position and rotation
697
+ updateBike(delta);
698
+
699
+ // Update camera to follow the bike
700
+ updateCamera();
701
+
702
+ // Check terrain under bike
703
+ checkTerrain();
704
+
705
+ // Update distance
706
+ distance += speed * delta;
707
+ updateUI();
708
+ }
709
+
710
+ renderer.render(scene, camera);
711
+ }
712
+
713
+ function handleControls(delta) {
714
+ // Change gears
715
+ if (keyState['q'] && currentGear > 1) {
716
+ currentGear--;
717
+ updateUI();
718
+ }
719
+ if (keyState['e'] && currentGear < 7) {
720
+ currentGear++;
721
+ updateUI();
722
+ }
723
+
724
+ // Calculate gear multiplier
725
+ const gearMultiplier = 0.5 + currentGear * 0.2;
726
+
727
+ // Acceleration
728
+ if (keyState['w']) {
729
+ speed += acceleration * gearMultiplier * delta * 60;
730
+ if (speed > maxSpeed) speed = maxSpeed;
731
+ }
732
+
733
+ // Braking
734
+ if (keyState['s']) {
735
+ speed -= brakePower * delta * 60;
736
+ if (speed < 0) speed = 0;
737
+ }
738
+
739
+ // Natural deceleration
740
+ if (speed > 0 && !keyState['w']) {
741
+ speed -= deceleration * delta * 60;
742
+ if (speed < 0) speed = 0;
743
+ }
744
+
745
+ // Steering
746
+ if (keyState['a']) {
747
+ bike.rotation.y += 0.03 * (1 - speed / maxSpeed) * delta * 60;
748
+ }
749
+ if (keyState['d']) {
750
+ bike.rotation.y -= 0.03 * (1 - speed / maxSpeed) * delta * 60;
751
+ }
752
+
753
+ // Apply gravity
754
+ if (!isOnGround) {
755
+ upDownSpeed -= 9.8 * delta;
756
+ bike.position.y += upDownSpeed * delta;
757
+
758
+ // Check if bike hits the ground
759
+ const terrainHeight = getTerrainHeight(bike.position.x, bike.position.z);
760
+ if (bike.position.y - 0.5 < terrainHeight) {
761
+ bike.position.y = terrainHeight + 0.5;
762
+ isOnGround = true;
763
+ isJumping = false;
764
+ upDownSpeed = 0;
765
+ }
766
+ }
767
+ }
768
+
769
+ function updateBike(delta) {
770
+ // Move bike forward based on speed
771
+ if (speed > 0) {
772
+ const direction = new THREE.Vector3(
773
+ Math.sin(bike.rotation.y),
774
+ 0,
775
+ Math.cos(bike.rotation.y)
776
+ ).normalize();
777
+
778
+ bike.position.add(direction.multiplyScalar(speed * delta));
779
+ }
780
+
781
+ // Keep bike on terrain
782
+ const terrainHeight = getTerrainHeight(bike.position.x, bike.position.z);
783
+ if (isOnGround && !isJumping) {
784
+ bike.position.y = terrainHeight + 0.5;
785
+ }
786
+
787
+ // Rotate wheels when moving
788
+ const wheels = bike.children.filter(child => child.geometry instanceof THREE.CylinderGeometry);
789
+ wheels.forEach(wheel => {
790
+ wheel.rotation.x -= speed * delta * 0.1;
791
+ });
792
+ }
793
+
794
+ function updateCamera() {
795
+ // Third-person camera that follows the bike
796
+ const targetPosition = new THREE.Vector3(
797
+ bike.position.x - Math.sin(bike.rotation.y) * 5,
798
+ bike.position.y + 2,
799
+ bike.position.z - Math.cos(bike.rotation.y) * 5
800
+ );
801
+
802
+ // Smooth camera movement
803
+ camera.position.lerp(targetPosition, 0.1);
804
+ camera.lookAt(bike.position.x, bike.position.y + 1, bike.position.z);
805
+ }
806
+
807
+ function checkTerrain() {
808
+ const terrainHeight = getTerrainHeight(bike.position.x, bike.position.z);
809
+
810
+ // Check if bike is on the ground
811
+ if (bike.position.y - 0.5 <= terrainHeight && !isJumping) {
812
+ bike.position.y = terrainHeight + 0.5;
813
+ isOnGround = true;
814
+ } else {
815
+ isOnGround = false;
816
+ }
817
+
818
+ // Check if bike is on the road
819
+ let onRoad = false;
820
+ for (let i = 0; i < roadPath.length; i++) {
821
+ const p = roadPath[i];
822
+ const dist = Math.sqrt((bike.position.x - p.x) * (bike.position.x - p.x) +
823
+ (bike.position.z - p.z) * (bike.position.z - p.z));
824
+ if (dist < roadWidth/2) {
825
+ onRoad = true;
826
+ break;
827
+ }
828
+ }
829
+
830
+ // Determine terrain type
831
+ const gradient = getTerrainGradient(bike.position.x, bike.position.z);
832
+ const slopeAngle = Math.atan(Math.sqrt(gradient.x * gradient.x + gradient.z * gradient.z)) * (180 / Math.PI);
833
+
834
+ if (onRoad) {
835
+ terrainType = 'road';
836
+ maxSpeed = 40;
837
+ } else if (slopeAngle > 20) {
838
+ terrainType = 'steep climb';
839
+ maxSpeed = 15;
840
+ } else if (slopeAngle > 10) {
841
+ terrainType = 'uphill';
842
+ maxSpeed = 25;
843
+ } else if (slopeAngle < -10) {
844
+ terrainType = 'downhill';
845
+ maxSpeed = 60;
846
+ } else {
847
+ terrainType = 'flat';
848
+ maxSpeed = 40;
849
+ }
850
+
851
+ // Apply terrain effects to speed
852
+ if (terrainType === 'uphill' || terrainType === 'steep climb') {
853
+ if (!keyState['w']) {
854
+ speed -= 0.05 * slopeAngle * 0.1;
855
+ }
856
+ } else if (terrainType === 'downhill') {
857
+ speed += 0.05 * -slopeAngle * 0.1;
858
+ } else if (!onRoad) {
859
+ // Off-road penalty
860
+ speed *= 0.95;
861
+ }
862
+
863
+ // Update UI for terrain
864
+ if (onRoad) {
865
+ document.getElementById('terrain-info').textContent = 'Terrain: Road';
866
+ } else {
867
+ document.getElementById('terrain-info').textContent = `Terrain: ${terrainType}`;
868
+ }
869
+ }
870
+
871
+ function getTerrainHeight(x, z) {
872
+ // Check road first since it's slightly above terrain
873
+ // Get closest point on road
874
+ let closestRoadPoint = null;
875
+ let closestRoadDist = Infinity;
876
+
877
+ for (let i = 0; i < roadPath.length; i++) {
878
+ const p = roadPath[i];
879
+ const dist = Math.sqrt((x - p.x) * (x - p.x) + (z - p.z) * (z - p.z));
880
+
881
+ if (dist < closestRoadDist) {
882
+ closestRoadDist = dist;
883
+ closestRoadPoint = p;
884
+ }
885
+ }
886
+
887
+ // If point is within road width, use road height
888
+ if (closestRoadDist < roadWidth/2) {
889
+ return closestRoadPoint.y + 0.05; // Road is slightly above terrain
890
+ }
891
+
892
+ // Otherwise check terrain height
893
+ const terrainGeometry = terrain.geometry;
894
+ const vertices = terrainGeometry.attributes.position;
895
+
896
+ let closestVertex = 0;
897
+ let closestDistance = Infinity;
898
+
899
+ for (let i = 0; i < vertices.count; i++) {
900
+ const vx = vertices.getX(i);
901
+ const vz = vertices.getZ(i);
902
+ const distance = Math.sqrt((vx - x) * (vx - x) + (vz - z) * (vz - z));
903
+
904
+ if (distance < closestDistance) {
905
+ closestDistance = distance;
906
+ closestVertex = i;
907
+ }
908
+ }
909
+
910
+ return vertices.getY(closestVertex);
911
+ }
912
+
913
+ function getTerrainGradient(x, z) {
914
+ const delta = 0.5;
915
+
916
+ const height = getTerrainHeight(x, z);
917
+ const heightX = getTerrainHeight(x + delta, z);
918
+ const heightZ = getTerrainHeight(x, z + delta);
919
+
920
+ return {
921
+ x: (heightX - height) / delta,
922
+ z: (heightZ - height) / delta
923
+ };
924
+ }
925
+
926
+ function updateUI() {
927
+ document.getElementById('speed').textContent = Math.round(speed * 3.6); // Convert m/s to km/h
928
+ document.getElementById('distance').textContent = `Distance: ${Math.round(distance)} m`;
929
+ document.getElementById('gear').textContent = `Gear: ${currentGear}`;
930
+ }
931
+
932
+ // Initialize the game when the page loads
933
+ window.onload = init;
934
+ </script>
935
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
936
+ </html>