awacke1 commited on
Commit
c5d5414
·
verified ·
1 Parent(s): 6e1f693

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +837 -19
index.html CHANGED
@@ -1,19 +1,837 @@
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 City Scene</title>
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ overflow: hidden;
11
+ font-family: Arial, sans-serif;
12
+ }
13
+ canvas {
14
+ display: block;
15
+ }
16
+ .ui-container {
17
+ position: absolute;
18
+ top: 10px;
19
+ left: 10px;
20
+ color: white;
21
+ background-color: rgba(0, 0, 0, 0.5);
22
+ padding: 10px;
23
+ border-radius: 5px;
24
+ user-select: none;
25
+ }
26
+ .controls {
27
+ position: absolute;
28
+ bottom: 10px;
29
+ left: 10px;
30
+ color: white;
31
+ background-color: rgba(0, 0, 0, 0.5);
32
+ padding: 10px;
33
+ border-radius: 5px;
34
+ }
35
+ </style>
36
+ </head>
37
+ <body>
38
+ <div class="ui-container">
39
+ <h2>3D City Explorer</h2>
40
+ <div id="score">Score: 0</div>
41
+ <div id="time">Time: 60</div>
42
+ </div>
43
+ <div class="controls">
44
+ <p>Controls: W to move backward, S to move forward, A/D to move left/right, Mouse to look, Space to jump</p>
45
+ <p>Collect the floating cubes to score points!</p>
46
+ </div>
47
+
48
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
49
+ <script>
50
+ // Polyfill for tqdm since it's not defined
51
+ const tqdm = {
52
+ tqdm: function(desc, total) {
53
+ return {
54
+ update: function() {},
55
+ // Other methods as needed
56
+ };
57
+ }
58
+ };
59
+ </script>
60
+ <script>
61
+ // Game variables
62
+ let score = 0;
63
+ let timeRemaining = 60;
64
+ let gameActive = true;
65
+
66
+ // Set up scene
67
+ const scene = new THREE.Scene();
68
+ scene.background = new THREE.Color(0x87CEEB); // Sky blue background
69
+
70
+ // Set up camera
71
+ const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
72
+ camera.position.set(0, 5, 15);
73
+
74
+ // Set up renderer
75
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
76
+ renderer.setSize(window.innerWidth, window.innerHeight);
77
+ renderer.shadowMap.enabled = true;
78
+ document.body.appendChild(renderer.domElement);
79
+
80
+ // Add lights
81
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
82
+ scene.add(ambientLight);
83
+
84
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
85
+ directionalLight.position.set(50, 50, 50);
86
+ directionalLight.castShadow = true;
87
+ directionalLight.shadow.mapSize.width = 2048;
88
+ directionalLight.shadow.mapSize.height = 2048;
89
+ directionalLight.shadow.camera.near = 1;
90
+ directionalLight.shadow.camera.far = 500;
91
+ directionalLight.shadow.camera.left = -100;
92
+ directionalLight.shadow.camera.right = 100;
93
+ directionalLight.shadow.camera.top = 100;
94
+ directionalLight.shadow.camera.bottom = -100;
95
+ scene.add(directionalLight);
96
+
97
+ // Create ground
98
+ const groundGeometry = new THREE.PlaneGeometry(200, 200);
99
+ const groundMaterial = new THREE.MeshStandardMaterial({
100
+ color: 0x1a5e1a, // Green
101
+ roughness: 0.8,
102
+ metalness: 0.2
103
+ });
104
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
105
+ ground.rotation.x = -Math.PI / 2;
106
+ ground.receiveShadow = true;
107
+ scene.add(ground);
108
+
109
+ // Buildings array
110
+ const buildings = [];
111
+ const collectibles = [];
112
+
113
+ // Building color palette
114
+ const buildingColors = [
115
+ 0x888888, 0x666666, 0x999999, 0xaaaaaa, 0x555555,
116
+ 0x334455, 0x445566, 0x223344, 0x556677, 0x667788,
117
+ 0x993333, 0x884422, 0x553333, 0x772222, 0x664433
118
+ ];
119
+
120
+ // L-system grammar rules for buildings
121
+ const buildingRules = [
122
+ // Colonial style - symmetrical with central features
123
+ {
124
+ name: "Colonial",
125
+ axiom: "A",
126
+ rules: {
127
+ "A": "B[+F][-F]",
128
+ "B": "F[-C][+C]F",
129
+ "C": "D[-E][+E]",
130
+ "D": "F[+F][-F]F",
131
+ "E": "F[-F][+F]"
132
+ },
133
+ iterations: 2,
134
+ baseHeight: 10,
135
+ baseWidth: 6,
136
+ baseDepth: 6,
137
+ angle: Math.PI/6,
138
+ probability: 0.2
139
+ },
140
+ // Victorian style - complex with many decorative elements
141
+ {
142
+ name: "Victorian",
143
+ axiom: "A",
144
+ rules: {
145
+ "A": "B[+C][-C][/D][\\D]",
146
+ "B": "F[+F][-F][/F][\\F]",
147
+ "C": "F[++F][--F]",
148
+ "D": "F[+\F][-/F]"
149
+ },
150
+ iterations: 3,
151
+ baseHeight: 15,
152
+ baseWidth: 5,
153
+ baseDepth: 5,
154
+ angle: Math.PI/5,
155
+ probability: 0.15
156
+ },
157
+ // Modern style - clean lines, boxy but with variations
158
+ {
159
+ name: "Modern",
160
+ axiom: "A",
161
+ rules: {
162
+ "A": "B[+B][-B]",
163
+ "B": "F[/C][\\C]",
164
+ "C": "F"
165
+ },
166
+ iterations: 2,
167
+ baseHeight: 20,
168
+ baseWidth: 8,
169
+ baseDepth: 8,
170
+ angle: Math.PI/2,
171
+ probability: 0.25
172
+ },
173
+ // Skyscraper - tall vertical structures
174
+ {
175
+ name: "Skyscraper",
176
+ axiom: "A",
177
+ rules: {
178
+ "A": "FB[+C][-C]",
179
+ "B": "FB",
180
+ "C": "F[+F][-F]"
181
+ },
182
+ iterations: 4,
183
+ baseHeight: 30,
184
+ baseWidth: 10,
185
+ baseDepth: 10,
186
+ angle: Math.PI/8,
187
+ probability: 0.15
188
+ },
189
+ // Simple box building - for variety and filling space
190
+ {
191
+ name: "Simple",
192
+ axiom: "F",
193
+ rules: {
194
+ "F": "F[+F][-F]"
195
+ },
196
+ iterations: 1,
197
+ baseHeight: 8,
198
+ baseWidth: 6,
199
+ baseDepth: 6,
200
+ angle: Math.PI/4,
201
+ probability: 0.25
202
+ }
203
+ ];
204
+
205
+ // L-system interpreter
206
+ function interpretLSystem(rule, position, rotation) {
207
+ // Start with the axiom
208
+ let currentString = rule.axiom;
209
+
210
+ // Apply rules for the specified number of iterations
211
+ for (let i = 0; i < rule.iterations; i++) {
212
+ let newString = "";
213
+
214
+ // Apply rules to each character
215
+ for (let j = 0; j < currentString.length; j++) {
216
+ const char = currentString[j];
217
+ newString += rule.rules[char] || char;
218
+ }
219
+
220
+ currentString = newString;
221
+ }
222
+
223
+ // Now interpret the L-system string to create building parts
224
+ let buildingGroup = new THREE.Group();
225
+ buildingGroup.position.copy(position);
226
+
227
+ // Stack to keep track of transformations
228
+ const stack = [];
229
+ let currentPosition = new THREE.Vector3(0, 0, 0);
230
+ let currentRotation = rotation || new THREE.Euler();
231
+ let scale = new THREE.Vector3(1, 1, 1);
232
+
233
+ // Select a material for this building
234
+ const color = buildingColors[Math.floor(Math.random() * buildingColors.length)];
235
+ const material = new THREE.MeshStandardMaterial({
236
+ color: color,
237
+ roughness: 0.7,
238
+ metalness: 0.2
239
+ });
240
+
241
+ // Interpret each character in the final string
242
+ for (let i = 0; i < currentString.length; i++) {
243
+ const char = currentString[i];
244
+
245
+ switch (char) {
246
+ case 'F': // Forward and create a building part
247
+ // Randomize dimensions with constraints based on rule
248
+ const width = rule.baseWidth * (0.5 + Math.random() * 0.5) * scale.x;
249
+ const height = rule.baseHeight * (0.5 + Math.random() * 0.5) * scale.y;
250
+ const depth = rule.baseDepth * (0.5 + Math.random() * 0.5) * scale.z;
251
+
252
+ // Create geometry
253
+ const geometry = new THREE.BoxGeometry(width, height, depth);
254
+ const buildingPart = new THREE.Mesh(geometry, material);
255
+
256
+ // Position and add to group
257
+ buildingPart.position.copy(currentPosition);
258
+ buildingPart.rotation.copy(currentRotation);
259
+ buildingPart.castShadow = true;
260
+ buildingPart.receiveShadow = true;
261
+
262
+ // Add windows if part is large enough
263
+ if (height > 5 && width > 2 && depth > 2) {
264
+ addWindowsToBuilding(buildingPart, width, height, depth);
265
+ }
266
+
267
+ buildingGroup.add(buildingPart);
268
+
269
+ // Move forward in the direction of current rotation
270
+ const direction = new THREE.Vector3(0, height/2, 0);
271
+ direction.applyEuler(currentRotation);
272
+ currentPosition.add(direction);
273
+ break;
274
+
275
+ case '+': // Rotate right around Y axis
276
+ currentRotation.y += rule.angle;
277
+ break;
278
+
279
+ case '-': // Rotate left around Y axis
280
+ currentRotation.y -= rule.angle;
281
+ break;
282
+
283
+ case '/': // Rotate around X axis
284
+ currentRotation.x += rule.angle;
285
+ break;
286
+
287
+ case '\\': // Rotate around X axis (opposite)
288
+ currentRotation.x -= rule.angle;
289
+ break;
290
+
291
+ case '^': // Rotate around Z axis
292
+ currentRotation.z += rule.angle;
293
+ break;
294
+
295
+ case '&': // Rotate around Z axis (opposite)
296
+ currentRotation.z -= rule.angle;
297
+ break;
298
+
299
+ case '[': // Push state
300
+ stack.push({
301
+ position: currentPosition.clone(),
302
+ rotation: currentRotation.clone(),
303
+ scale: scale.clone()
304
+ });
305
+ break;
306
+
307
+ case ']': // Pop state
308
+ if (stack.length > 0) {
309
+ const state = stack.pop();
310
+ currentPosition = state.position;
311
+ currentRotation = state.rotation;
312
+ scale = state.scale;
313
+ }
314
+ break;
315
+
316
+ case '>': // Scale up
317
+ scale.multiplyScalar(1.2);
318
+ break;
319
+
320
+ case '<': // Scale down
321
+ scale.multiplyScalar(0.8);
322
+ break;
323
+ }
324
+ }
325
+
326
+ return buildingGroup;
327
+ }
328
+
329
+ // Create a city
330
+ function createCity() {
331
+ // Create grid of buildings
332
+ const citySize = 5; // Size of the city grid
333
+ const spacing = 15; // Spacing between building centers
334
+
335
+ for (let x = -citySize; x <= citySize; x++) {
336
+ for (let z = -citySize; z <= citySize; z++) {
337
+ // Skip sometimes to create spaces
338
+ if (Math.random() < 0.2) continue;
339
+
340
+ // Position with slight randomization
341
+ const position = new THREE.Vector3(
342
+ x * spacing + (Math.random() * 2 - 1), // Add slight randomness
343
+ 0, // Will be adjusted by the L-system
344
+ z * spacing + (Math.random() * 2 - 1)
345
+ );
346
+
347
+ // Select a building style based on probability
348
+ let selectedRule = null;
349
+ let random = Math.random();
350
+ let cumulativeProbability = 0;
351
+
352
+ for (const rule of buildingRules) {
353
+ cumulativeProbability += rule.probability;
354
+ if (random <= cumulativeProbability) {
355
+ selectedRule = rule;
356
+ break;
357
+ }
358
+ }
359
+
360
+ if (!selectedRule) {
361
+ selectedRule = buildingRules[0]; // Default to first rule if somehow none selected
362
+ }
363
+
364
+ // Create building using L-system
365
+ const building = interpretLSystem(selectedRule, position, new THREE.Euler());
366
+ scene.add(building);
367
+ buildings.push(building);
368
+ }
369
+ }
370
+
371
+ // Create streets
372
+ const roadWidth = 8;
373
+ const roadColor = 0x333333;
374
+
375
+ // X-axis roads
376
+ for (let x = -citySize; x <= citySize; x++) {
377
+ const roadGeometry = new THREE.PlaneGeometry(roadWidth, citySize * 2 * spacing + roadWidth);
378
+ const roadMaterial = new THREE.MeshStandardMaterial({
379
+ color: roadColor,
380
+ roughness: 0.9,
381
+ metalness: 0.1
382
+ });
383
+ const road = new THREE.Mesh(roadGeometry, roadMaterial);
384
+ road.rotation.x = -Math.PI / 2;
385
+ road.position.set(x * spacing, 0.01, 0); // Slightly above ground to prevent z-fighting
386
+ scene.add(road);
387
+
388
+ // Add road markings
389
+ const markingGeometry = new THREE.PlaneGeometry(0.5, citySize * 2 * spacing + roadWidth);
390
+ const markingMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff });
391
+ const marking = new THREE.Mesh(markingGeometry, markingMaterial);
392
+ marking.rotation.x = -Math.PI / 2;
393
+ marking.position.set(x * spacing, 0.02, 0);
394
+ scene.add(marking);
395
+ }
396
+
397
+ // Z-axis roads
398
+ for (let z = -citySize; z <= citySize; z++) {
399
+ const roadGeometry = new THREE.PlaneGeometry(citySize * 2 * spacing + roadWidth, roadWidth);
400
+ const roadMaterial = new THREE.MeshStandardMaterial({
401
+ color: roadColor,
402
+ roughness: 0.9,
403
+ metalness: 0.1
404
+ });
405
+ const road = new THREE.Mesh(roadGeometry, roadMaterial);
406
+ road.rotation.x = -Math.PI / 2;
407
+ road.position.set(0, 0.01, z * spacing);
408
+ scene.add(road);
409
+
410
+ // Add road markings
411
+ const markingGeometry = new THREE.PlaneGeometry(citySize * 2 * spacing + roadWidth, 0.5);
412
+ const markingMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff });
413
+ const marking = new THREE.Mesh(markingGeometry, markingMaterial);
414
+ marking.rotation.x = -Math.PI / 2;
415
+ marking.position.set(0, 0.02, z * spacing);
416
+ scene.add(marking);
417
+ }
418
+ }
419
+
420
+ // Add windows to a building
421
+ function addWindowsToBuilding(building, width, height, depth) {
422
+ const windowSize = 0.5;
423
+ const windowSpacing = 1.5;
424
+ const windowGeometry = new THREE.PlaneGeometry(windowSize, windowSize);
425
+ const windowMaterial = new THREE.MeshStandardMaterial({
426
+ color: 0xffffcc,
427
+ emissive: 0xffffcc,
428
+ emissiveIntensity: 0.5,
429
+ transparent: true,
430
+ opacity: 0.8
431
+ });
432
+
433
+ // Calculate how many levels of windows to add based on building height
434
+ const numLevels = Math.floor((height - 2) / windowSpacing);
435
+
436
+ // Front and back windows
437
+ const frontZ = depth / 2 + 0.01;
438
+ const backZ = -depth / 2 - 0.01;
439
+
440
+ for (let level = 0; level < numLevels; level++) {
441
+ // Calculate y position for this level, starting from near the bottom
442
+ const y = 1 + level * windowSpacing;
443
+
444
+ for (let x = -width / 2 + windowSpacing; x < width / 2 - windowSpacing / 2; x += windowSpacing) {
445
+ // Only add some windows randomly
446
+ if (Math.random() < 0.3) continue;
447
+
448
+ // Front window
449
+ const frontWindow = new THREE.Mesh(windowGeometry, windowMaterial);
450
+ frontWindow.position.set(x, y, frontZ);
451
+ frontWindow.rotation.y = Math.PI;
452
+ building.add(frontWindow);
453
+
454
+ // Back window
455
+ const backWindow = new THREE.Mesh(windowGeometry, windowMaterial);
456
+ backWindow.position.set(x, y, backZ);
457
+ building.add(backWindow);
458
+ }
459
+ }
460
+
461
+ // Side windows
462
+ const rightX = width / 2 + 0.01;
463
+ const leftX = -width / 2 - 0.01;
464
+
465
+ for (let level = 0; level < numLevels; level++) {
466
+ // Calculate y position for this level, starting from near the bottom
467
+ const y = 1 + level * windowSpacing;
468
+
469
+ for (let z = -depth / 2 + windowSpacing; z < depth / 2 - windowSpacing / 2; z += windowSpacing) {
470
+ // Only add some windows randomly
471
+ if (Math.random() < 0.3) continue;
472
+
473
+ // Right window
474
+ const rightWindow = new THREE.Mesh(windowGeometry, windowMaterial);
475
+ rightWindow.position.set(rightX, y, z);
476
+ rightWindow.rotation.y = Math.PI / 2;
477
+ building.add(rightWindow);
478
+
479
+ // Left window
480
+ const leftWindow = new THREE.Mesh(windowGeometry, windowMaterial);
481
+ leftWindow.position.set(leftX, y, z);
482
+ leftWindow.rotation.y = -Math.PI / 2;
483
+ building.add(leftWindow);
484
+ }
485
+ }
486
+ }
487
+
488
+ // Create collectible items
489
+ function createCollectibles() {
490
+ const citySize = 5;
491
+ const spacing = 15;
492
+
493
+ for (let i = 0; i < 20; i++) {
494
+ const x = (Math.random() * 2 - 1) * citySize * spacing;
495
+ const z = (Math.random() * 2 - 1) * citySize * spacing;
496
+ const y = 1 + Math.random() * 20;
497
+
498
+ const collectibleGeometry = new THREE.BoxGeometry(1, 1, 1);
499
+ const collectibleMaterial = new THREE.MeshStandardMaterial({
500
+ color: 0xffff00,
501
+ emissive: 0xffff00,
502
+ emissiveIntensity: 0.5,
503
+ transparent: true,
504
+ opacity: 0.8
505
+ });
506
+
507
+ const collectible = new THREE.Mesh(collectibleGeometry, collectibleMaterial);
508
+ collectible.position.set(x, y, z);
509
+ collectible.userData.id = i;
510
+ collectible.userData.rotationSpeed = 0.01 + Math.random() * 0.02;
511
+ collectible.userData.floatSpeed = 0.5 + Math.random() * 0.5;
512
+ collectible.userData.floatRange = 0.5 + Math.random() * 0.5;
513
+ collectible.userData.initialY = y;
514
+
515
+ scene.add(collectible);
516
+ collectibles.push(collectible);
517
+ }
518
+ }
519
+
520
+ // Create skybox with clouds
521
+ function createSkybox() {
522
+ const skyGeometry = new THREE.SphereGeometry(400, 32, 32);
523
+ const skyMaterial = new THREE.MeshBasicMaterial({
524
+ color: 0x87CEEB,
525
+ side: THREE.BackSide
526
+ });
527
+ const sky = new THREE.Mesh(skyGeometry, skyMaterial);
528
+ scene.add(sky);
529
+
530
+ // Add clouds
531
+ for (let i = 0; i < 50; i++) {
532
+ const radius = 350;
533
+ const phi = Math.random() * Math.PI;
534
+ const theta = Math.random() * Math.PI * 2;
535
+
536
+ const x = radius * Math.sin(phi) * Math.cos(theta);
537
+ const y = radius * Math.cos(phi) + 50; // Keep clouds higher in the sky
538
+ const z = radius * Math.sin(phi) * Math.sin(theta);
539
+
540
+ const cloudSize = 10 + Math.random() * 20;
541
+ const cloudGeometry = new THREE.SphereGeometry(cloudSize, 8, 8);
542
+ const cloudMaterial = new THREE.MeshStandardMaterial({
543
+ color: 0xffffff,
544
+ roughness: 1,
545
+ metalness: 0,
546
+ transparent: true,
547
+ opacity: 0.8
548
+ });
549
+
550
+ const cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
551
+ cloud.position.set(x, y, z);
552
+ cloud.userData.rotationSpeed = 0.0001 + Math.random() * 0.0002;
553
+
554
+ scene.add(cloud);
555
+ }
556
+ }
557
+
558
+ // Player object
559
+ const playerHeight = 2;
560
+ const playerRadius = 0.5;
561
+ // Use cylinder instead of capsule for compatibility with r128
562
+ const playerGeometry = new THREE.CylinderGeometry(playerRadius, playerRadius, playerHeight, 8);
563
+ const playerMaterial = new THREE.MeshStandardMaterial({ color: 0x0000ff });
564
+ const player = new THREE.Mesh(playerGeometry, playerMaterial);
565
+ player.position.set(0, playerHeight / 2, 0);
566
+ player.castShadow = true;
567
+ scene.add(player);
568
+
569
+ // Player physics
570
+ const playerVelocity = new THREE.Vector3();
571
+ const playerDirection = new THREE.Vector3();
572
+ let isJumping = false;
573
+ const GRAVITY = 0.2;
574
+ const JUMP_FORCE = 0.7;
575
+ const MOVE_SPEED = 0.2;
576
+
577
+ // Player collision detection
578
+ function checkPlayerCollisions() {
579
+ // Collectible collisions
580
+ for (let i = collectibles.length - 1; i >= 0; i--) {
581
+ const collectible = collectibles[i];
582
+ const distance = player.position.distanceTo(collectible.position);
583
+
584
+ if (distance < playerRadius + 1) {
585
+ scene.remove(collectible);
586
+ collectibles.splice(i, 1);
587
+ score += 10;
588
+ document.getElementById('score').textContent = `Score: ${score}`;
589
+ }
590
+ }
591
+
592
+ // Building collisions - updated for composite buildings
593
+ for (const building of buildings) {
594
+ // For each building group, we need to check collision with each child
595
+ building.traverse((child) => {
596
+ if (child.isMesh) {
597
+ const buildingBox = new THREE.Box3().setFromObject(child);
598
+ const playerPos = player.position.clone();
599
+
600
+ // Check if player is inside building bounds but add some margin for the radius
601
+ if (playerPos.x + playerRadius > buildingBox.min.x &&
602
+ playerPos.x - playerRadius < buildingBox.max.x &&
603
+ playerPos.z + playerRadius > buildingBox.min.z &&
604
+ playerPos.z - playerRadius < buildingBox.max.z) {
605
+
606
+ // Find the closest edge to push the player away from
607
+ const edgeDistances = [
608
+ {edge: 'left', dist: Math.abs(playerPos.x - buildingBox.min.x)},
609
+ {edge: 'right', dist: Math.abs(playerPos.x - buildingBox.max.x)},
610
+ {edge: 'front', dist: Math.abs(playerPos.z - buildingBox.min.z)},
611
+ {edge: 'back', dist: Math.abs(playerPos.z - buildingBox.max.z)}
612
+ ];
613
+
614
+ edgeDistances.sort((a, b) => a.dist - b.dist);
615
+ const closestEdge = edgeDistances[0].edge;
616
+
617
+ // Push player away from the closest edge
618
+ switch (closestEdge) {
619
+ case 'left':
620
+ player.position.x = buildingBox.min.x - playerRadius;
621
+ break;
622
+ case 'right':
623
+ player.position.x = buildingBox.max.x + playerRadius;
624
+ break;
625
+ case 'front':
626
+ player.position.z = buildingBox.min.z - playerRadius;
627
+ break;
628
+ case 'back':
629
+ player.position.z = buildingBox.max.z + playerRadius;
630
+ break;
631
+ }
632
+ }
633
+ }
634
+ });
635
+ }
636
+
637
+ // Floor collision and jumping physics
638
+ if (player.position.y <= playerHeight / 2) {
639
+ player.position.y = playerHeight / 2;
640
+ playerVelocity.y = 0;
641
+ isJumping = false;
642
+ }
643
+ }
644
+
645
+ // Controls
646
+ const keys = {
647
+ w: false,
648
+ a: false,
649
+ s: false,
650
+ d: false,
651
+ space: false
652
+ };
653
+
654
+ // Mouse controls for looking around
655
+ const mousePosition = {
656
+ x: 0,
657
+ y: 0
658
+ };
659
+
660
+ let cameraRotation = 0;
661
+ let cameraPitch = 0;
662
+
663
+ document.addEventListener('keydown', (event) => {
664
+ switch (event.key.toLowerCase()) {
665
+ case 'w': keys.w = true; break;
666
+ case 'a': keys.a = true; break;
667
+ case 's': keys.s = true; break;
668
+ case 'd': keys.d = true; break;
669
+ case ' ': keys.space = true; break;
670
+ }
671
+ });
672
+
673
+ document.addEventListener('keyup', (event) => {
674
+ switch (event.key.toLowerCase()) {
675
+ case 'w': keys.w = false; break;
676
+ case 'a': keys.a = false; break;
677
+ case 's': keys.s = false; break;
678
+ case 'd': keys.d = false; break;
679
+ case ' ': keys.space = false; break;
680
+ }
681
+ });
682
+
683
+ document.addEventListener('mousemove', (event) => {
684
+ // Only capture mouse if pointer is locked
685
+ if (document.pointerLockElement === renderer.domElement) {
686
+ cameraRotation -= event.movementX * 0.002;
687
+ cameraPitch -= event.movementY * 0.002;
688
+
689
+ // Limit pitch to prevent camera flipping
690
+ cameraPitch = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, cameraPitch));
691
+ }
692
+ });
693
+
694
+ // Lock pointer when clicking on the game
695
+ renderer.domElement.addEventListener('click', () => {
696
+ if (!document.pointerLockElement) {
697
+ renderer.domElement.requestPointerLock();
698
+ }
699
+ });
700
+
701
+ // Update player position based on input
702
+ function updatePlayer() {
703
+ // Apply gravity
704
+ playerVelocity.y -= GRAVITY;
705
+
706
+ // Handle jumping
707
+ if (keys.space && !isJumping) {
708
+ playerVelocity.y = JUMP_FORCE;
709
+ isJumping = true;
710
+ }
711
+
712
+ // Get movement direction based on camera rotation
713
+ // Switching W and S keys (W now moves backward, S moves forward)
714
+ playerDirection.z = Number(keys.s) - Number(keys.w);
715
+ playerDirection.x = Number(keys.d) - Number(keys.a);
716
+ playerDirection.normalize();
717
+
718
+ // Rotate direction based on camera rotation
719
+ playerDirection.applyAxisAngle(new THREE.Vector3(0, 1, 0), cameraRotation);
720
+
721
+ // Apply movement
722
+ player.position.x += playerDirection.x * MOVE_SPEED;
723
+ player.position.z += playerDirection.z * MOVE_SPEED;
724
+ player.position.y += playerVelocity.y;
725
+
726
+ // Update camera position to follow player
727
+ camera.position.x = player.position.x;
728
+ camera.position.z = player.position.z;
729
+ camera.position.y = player.position.y + 1.5; // Eye level
730
+
731
+ // Update camera rotation
732
+ camera.rotation.order = 'YXZ'; // Important for proper rotation
733
+ camera.rotation.y = cameraRotation;
734
+ camera.rotation.x = cameraPitch;
735
+
736
+ // Collision detection
737
+ checkPlayerCollisions();
738
+ }
739
+
740
+ // Timer function
741
+ function updateTimer() {
742
+ if (gameActive && timeRemaining > 0) {
743
+ timeRemaining--;
744
+ document.getElementById('time').textContent = `Time: ${timeRemaining}`;
745
+
746
+ if (timeRemaining === 0) {
747
+ gameActive = false;
748
+ endGame();
749
+ }
750
+ }
751
+ }
752
+
753
+ // End game
754
+ function endGame() {
755
+ const endScreen = document.createElement('div');
756
+ endScreen.style.position = 'absolute';
757
+ endScreen.style.top = '50%';
758
+ endScreen.style.left = '50%';
759
+ endScreen.style.transform = 'translate(-50%, -50%)';
760
+ endScreen.style.background = 'rgba(0, 0, 0, 0.8)';
761
+ endScreen.style.color = 'white';
762
+ endScreen.style.padding = '20px';
763
+ endScreen.style.borderRadius = '10px';
764
+ endScreen.style.textAlign = 'center';
765
+ endScreen.innerHTML = `
766
+ <h2>Game Over!</h2>
767
+ <p>Your final score: ${score}</p>
768
+ <button id="restart-btn" style="padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px;">Play Again</button>
769
+ `;
770
+ document.body.appendChild(endScreen);
771
+
772
+ document.getElementById('restart-btn').addEventListener('click', () => {
773
+ document.body.removeChild(endScreen);
774
+ score = 0;
775
+ timeRemaining = 60;
776
+ gameActive = true;
777
+ document.getElementById('score').textContent = `Score: ${score}`;
778
+ document.getElementById('time').textContent = `Time: ${timeRemaining}`;
779
+
780
+ // Reset player position
781
+ player.position.set(0, playerHeight / 2, 0);
782
+ playerVelocity.set(0, 0, 0);
783
+
784
+ // Reset camera
785
+ cameraRotation = 0;
786
+ cameraPitch = 0;
787
+
788
+ // Reset collectibles
789
+ for (const collectible of collectibles) {
790
+ scene.remove(collectible);
791
+ }
792
+ collectibles.length = 0;
793
+ createCollectibles();
794
+ });
795
+ }
796
+
797
+ // Animation and game loop
798
+ function animate() {
799
+ requestAnimationFrame(animate);
800
+
801
+ if (gameActive) {
802
+ updatePlayer();
803
+
804
+ // Animate collectibles
805
+ for (const collectible of collectibles) {
806
+ collectible.rotation.x += collectible.userData.rotationSpeed;
807
+ collectible.rotation.y += collectible.userData.rotationSpeed * 1.5;
808
+
809
+ // Float up and down
810
+ const floatOffset = Math.sin(Date.now() * 0.001 * collectible.userData.floatSpeed) * collectible.userData.floatRange;
811
+ collectible.position.y = collectible.userData.initialY + floatOffset;
812
+ }
813
+ }
814
+
815
+ renderer.render(scene, camera);
816
+ }
817
+
818
+ // Handle window resize
819
+ window.addEventListener('resize', () => {
820
+ camera.aspect = window.innerWidth / window.innerHeight;
821
+ camera.updateProjectionMatrix();
822
+ renderer.setSize(window.innerWidth, window.innerHeight);
823
+ });
824
+
825
+ // Initialize
826
+ createCity();
827
+ createCollectibles();
828
+ createSkybox();
829
+
830
+ // Start game timer
831
+ setInterval(updateTimer, 1000);
832
+
833
+ // Start animation loop
834
+ animate();
835
+ </script>
836
+ </body>
837
+ </html>