awacke1 commited on
Commit
2f8df45
·
verified ·
1 Parent(s): bdf0aea

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1160 -19
index.html CHANGED
@@ -1,19 +1,1160 @@
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>Interactive AI Traffic Simulator</title>
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ overflow: hidden;
11
+ font-family: Arial, sans-serif;
12
+ background: #000;
13
+ }
14
+ #ui {
15
+ position: absolute;
16
+ top: 10px;
17
+ left: 10px;
18
+ color: white;
19
+ background-color: rgba(0,0,0,0.9);
20
+ padding: 15px;
21
+ border-radius: 8px;
22
+ z-index: 100;
23
+ font-size: 14px;
24
+ min-width: 200px;
25
+ }
26
+ #controls {
27
+ position: absolute;
28
+ top: 10px;
29
+ right: 10px;
30
+ color: white;
31
+ background-color: rgba(0,0,0,0.9);
32
+ padding: 15px;
33
+ border-radius: 8px;
34
+ z-index: 100;
35
+ }
36
+ #drivingControls {
37
+ position: absolute;
38
+ bottom: 10px;
39
+ left: 50%;
40
+ transform: translateX(-50%);
41
+ color: white;
42
+ background-color: rgba(0,0,0,0.9);
43
+ padding: 15px;
44
+ border-radius: 8px;
45
+ z-index: 100;
46
+ text-align: center;
47
+ }
48
+ #cameraControls {
49
+ position: absolute;
50
+ bottom: 10px;
51
+ right: 10px;
52
+ color: white;
53
+ background-color: rgba(0,0,0,0.9);
54
+ padding: 15px;
55
+ border-radius: 8px;
56
+ z-index: 100;
57
+ }
58
+ button {
59
+ background-color: #4CAF50;
60
+ border: none;
61
+ color: white;
62
+ padding: 8px 16px;
63
+ margin: 5px;
64
+ cursor: pointer;
65
+ border-radius: 4px;
66
+ font-size: 12px;
67
+ }
68
+ button:hover {
69
+ background-color: #45a049;
70
+ }
71
+ button.active {
72
+ background-color: #ff6b6b;
73
+ }
74
+ #stats {
75
+ position: absolute;
76
+ bottom: 10px;
77
+ left: 10px;
78
+ color: white;
79
+ background-color: rgba(0,0,0,0.9);
80
+ padding: 15px;
81
+ border-radius: 8px;
82
+ z-index: 100;
83
+ font-size: 12px;
84
+ min-width: 200px;
85
+ }
86
+ #minimap {
87
+ position: absolute;
88
+ top: 10px;
89
+ right: 250px;
90
+ width: 200px;
91
+ height: 200px;
92
+ background-color: rgba(0,0,0,0.8);
93
+ border: 2px solid white;
94
+ border-radius: 8px;
95
+ z-index: 100;
96
+ }
97
+ .highlight { color: #ffcc00; font-weight: bold; }
98
+ .success { color: #00ff00; font-weight: bold; }
99
+ .player { color: #ff00ff; font-weight: bold; }
100
+ .crosshair {
101
+ position: absolute;
102
+ top: 50%;
103
+ left: 50%;
104
+ transform: translate(-50%, -50%);
105
+ width: 20px;
106
+ height: 20px;
107
+ border: 2px solid white;
108
+ border-radius: 50%;
109
+ z-index: 200;
110
+ opacity: 0.7;
111
+ display: none;
112
+ }
113
+ #speedometer {
114
+ position: absolute;
115
+ bottom: 100px;
116
+ right: 10px;
117
+ color: white;
118
+ background-color: rgba(0,0,0,0.9);
119
+ padding: 15px;
120
+ border-radius: 8px;
121
+ z-index: 100;
122
+ text-align: center;
123
+ display: none;
124
+ }
125
+ .speed-gauge {
126
+ width: 80px;
127
+ height: 80px;
128
+ border: 3px solid #4CAF50;
129
+ border-radius: 50%;
130
+ position: relative;
131
+ margin: 0 auto;
132
+ }
133
+ .speed-needle {
134
+ position: absolute;
135
+ top: 50%;
136
+ left: 50%;
137
+ width: 2px;
138
+ height: 35px;
139
+ background: #ff6b6b;
140
+ transform-origin: bottom center;
141
+ transform: translate(-50%, -100%) rotate(0deg);
142
+ }
143
+ </style>
144
+ </head>
145
+ <body>
146
+ <div id="ui">
147
+ <div class="highlight">Interactive AI Traffic Simulator</div>
148
+ <div>Mode: <span id="currentMode">AI Observer</span></div>
149
+ <div>Population: <span id="population">50</span></div>
150
+ <div>Speed: <span id="avgSpeed">0</span> km/h</div>
151
+ <div>Traffic Flow: <span id="trafficFlow">Normal</span></div>
152
+ <div>Active Routes: <span id="activeRoutes">0</span></div>
153
+ </div>
154
+
155
+ <div id="controls">
156
+ <button id="pauseBtn">Pause</button>
157
+ <button id="resetBtn">Reset</button>
158
+ <button id="speedBtn">Speed: 1x</button>
159
+ <button id="trafficBtn">Traffic: Normal</button>
160
+ <button id="weatherBtn">Weather: Clear</button>
161
+ </div>
162
+
163
+ <div id="drivingControls" style="display: none;">
164
+ <div class="highlight">Driving Controls</div>
165
+ <div>WASD - Drive | Mouse - Look | Space - Brake</div>
166
+ <div>Shift - Boost | Tab - Change Car | Esc - Exit</div>
167
+ </div>
168
+
169
+ <div id="cameraControls">
170
+ <div class="highlight">Camera Controls</div>
171
+ <button id="firstPersonBtn">First Person</button>
172
+ <button id="thirdPersonBtn">Third Person</button>
173
+ <button id="overviewBtn">Overview</button>
174
+ <button id="freeCamera">Free Cam</button>
175
+ <button id="nextCarBtn">Next Car</button>
176
+ </div>
177
+
178
+ <div id="stats">
179
+ <div><span class="highlight">Current Statistics:</span></div>
180
+ <div>Camera: <span id="cameraMode">Overview</span></div>
181
+ <div>Target: <span id="currentTarget">None</span></div>
182
+ <div>Distance: <span id="targetDistance">0</span>m</div>
183
+ <div>Destination: <span id="currentDestination">None</span></div>
184
+ <div>Flock Size: <span id="flockSize">0</span></div>
185
+ </div>
186
+
187
+ <div id="minimap"></div>
188
+
189
+ <div id="speedometer">
190
+ <div class="speed-gauge">
191
+ <div class="speed-needle" id="speedNeedle"></div>
192
+ </div>
193
+ <div>Speed: <span id="currentSpeed">0</span> km/h</div>
194
+ </div>
195
+
196
+ <div class="crosshair" id="crosshair"></div>
197
+
198
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
199
+ <script>
200
+ // Global variables
201
+ let scene, camera, renderer, clock;
202
+ let world = {
203
+ roads: [],
204
+ intersections: [],
205
+ buildings: [],
206
+ roadNetwork: new Map(),
207
+ trafficLights: []
208
+ };
209
+
210
+ // Camera and interaction
211
+ let cameraMode = 'overview'; // firstPerson, thirdPerson, overview, free
212
+ let currentTarget = null;
213
+ let isPlayerDriving = false;
214
+ let mouseControls = { x: 0, y: 0, sensitivity: 0.002 };
215
+ let keys = {};
216
+
217
+ // Traffic simulation
218
+ let population = [];
219
+ let playerCar = null;
220
+ let populationSize = 50;
221
+ let paused = false;
222
+ let speedMultiplier = 1;
223
+ let trafficDensity = 'normal';
224
+ let weatherCondition = 'clear';
225
+
226
+ // Enhanced AI parameters
227
+ const NEIGHBOR_RADIUS = 25;
228
+ const ROAD_WIDTH = 8;
229
+ const BUILDING_VISIT_DISTANCE = 15;
230
+
231
+ // Simple Neural Network for AI Cars
232
+ class SimpleNeuralNetwork {
233
+ constructor() {
234
+ this.weights = {
235
+ input: Array(16).fill().map(() => Array(8).fill().map(() => (Math.random() - 0.5) * 2)),
236
+ hidden: Array(8).fill().map(() => Array(4).fill().map(() => (Math.random() - 0.5) * 2)),
237
+ output: Array(4).fill().map(() => (Math.random() - 0.5) * 2)
238
+ };
239
+ }
240
+
241
+ activate(inputs) {
242
+ // Simple forward pass
243
+ let hidden = new Array(8).fill(0);
244
+ for (let i = 0; i < 8; i++) {
245
+ for (let j = 0; j < inputs.length && j < 16; j++) {
246
+ hidden[i] += inputs[j] * this.weights.input[j][i];
247
+ }
248
+ hidden[i] = Math.tanh(hidden[i]);
249
+ }
250
+
251
+ let outputs = new Array(4).fill(0);
252
+ for (let i = 0; i < 4; i++) {
253
+ for (let j = 0; j < hidden.length; j++) {
254
+ outputs[i] += hidden[j] * this.weights.hidden[j][i];
255
+ }
256
+ outputs[i] = this.weights.output[i];
257
+ outputs[i] = Math.tanh(outputs[i]);
258
+ }
259
+
260
+ return outputs;
261
+ }
262
+
263
+ mutate(rate = 0.1) {
264
+ // Simple mutation
265
+ Object.keys(this.weights).forEach(layer => {
266
+ if (Array.isArray(this.weights[layer][0])) {
267
+ this.weights[layer].forEach(neuron => {
268
+ neuron.forEach((weight, i) => {
269
+ if (Math.random() < rate) {
270
+ neuron[i] += (Math.random() - 0.5) * 0.5;
271
+ }
272
+ });
273
+ });
274
+ } else {
275
+ this.weights[layer].forEach((weight, i) => {
276
+ if (Math.random() < rate) {
277
+ this.weights[layer][i] += (Math.random() - 0.5) * 0.5;
278
+ }
279
+ });
280
+ }
281
+ });
282
+ }
283
+ }
284
+
285
+ // Enhanced AI Car with realistic navigation
286
+ class RealisticAICar {
287
+ constructor(x = 0, z = 0, isPlayer = false) {
288
+ this.isPlayer = isPlayer;
289
+ this.brain = isPlayer ? null : new SimpleNeuralNetwork();
290
+ this.mesh = this.createCarMesh();
291
+ this.mesh.position.set(x, 1, z);
292
+
293
+ // Movement properties
294
+ this.velocity = new THREE.Vector3(0, 0, 0);
295
+ this.acceleration = new THREE.Vector3();
296
+ this.maxSpeed = isPlayer ? 35 : 25;
297
+ this.currentSpeed = 0;
298
+ this.steering = 0;
299
+
300
+ // Navigation properties
301
+ this.destination = null;
302
+ this.currentPath = [];
303
+ this.pathIndex = 0;
304
+ this.isAtDestination = false;
305
+ this.lastDestinationTime = 0;
306
+
307
+ // Flocking properties
308
+ this.neighbors = [];
309
+ this.flockingForce = new THREE.Vector3();
310
+
311
+ // State tracking
312
+ this.sensors = Array(8).fill(0);
313
+ this.onRoad = false;
314
+ this.atIntersection = false;
315
+ this.waitingAtLight = false;
316
+
317
+ this.initializeDestination();
318
+ }
319
+
320
+ createCarMesh() {
321
+ const group = new THREE.Group();
322
+
323
+ // Car body
324
+ const bodyGeometry = new THREE.BoxGeometry(1.8, 1, 4);
325
+ const bodyMaterial = new THREE.MeshLambertMaterial({
326
+ color: this.isPlayer ? 0xff00ff : new THREE.Color().setHSL(Math.random(), 0.8, 0.6)
327
+ });
328
+ const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
329
+ body.position.y = 0.5;
330
+ body.castShadow = true;
331
+ group.add(body);
332
+
333
+ // Roof
334
+ const roofGeometry = new THREE.BoxGeometry(1.6, 0.8, 2.5);
335
+ const roof = new THREE.Mesh(roofGeometry, bodyMaterial);
336
+ roof.position.set(0, 1.4, -0.3);
337
+ group.add(roof);
338
+
339
+ // Wheels
340
+ const wheelGeometry = new THREE.CylinderGeometry(0.4, 0.4, 0.3, 8);
341
+ const wheelMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });
342
+
343
+ this.wheels = [];
344
+ const wheelPositions = [
345
+ [-1, 0, 1.5], [1, 0, 1.5],
346
+ [-1, 0, -1.5], [1, 0, -1.5]
347
+ ];
348
+
349
+ wheelPositions.forEach((pos, i) => {
350
+ const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
351
+ wheel.position.set(...pos);
352
+ wheel.rotation.z = Math.PI / 2;
353
+ this.wheels.push(wheel);
354
+ group.add(wheel);
355
+ });
356
+
357
+ // Player indicator
358
+ if (this.isPlayer) {
359
+ const indicatorGeometry = new THREE.ConeGeometry(0.5, 2, 4);
360
+ const indicator = new THREE.Mesh(indicatorGeometry,
361
+ new THREE.MeshLambertMaterial({ color: 0xffff00 }));
362
+ indicator.position.set(0, 3, 0);
363
+ group.add(indicator);
364
+ }
365
+
366
+ return group;
367
+ }
368
+
369
+ initializeDestination() {
370
+ if (world.buildings.length > 0) {
371
+ this.setRandomDestination();
372
+ }
373
+ }
374
+
375
+ setRandomDestination() {
376
+ const availableBuildings = world.buildings.filter(b =>
377
+ b.mesh.position.distanceTo(this.mesh.position) > 50
378
+ );
379
+
380
+ if (availableBuildings.length > 0) {
381
+ this.destination = availableBuildings[Math.floor(Math.random() * availableBuildings.length)];
382
+ this.calculatePath();
383
+ this.isAtDestination = false;
384
+ }
385
+ }
386
+
387
+ calculatePath() {
388
+ if (!this.destination) return;
389
+
390
+ // Simple pathfinding - find nearest road to destination
391
+ const start = this.mesh.position.clone();
392
+ const end = this.destination.mesh.position.clone();
393
+
394
+ // For now, create a simple path with waypoints
395
+ this.currentPath = this.generateWaypoints(start, end);
396
+ this.pathIndex = 0;
397
+ }
398
+
399
+ generateWaypoints(start, end) {
400
+ const waypoints = [];
401
+ const roadSpacing = 80;
402
+
403
+ // Navigate to road grid
404
+ const startRoadX = Math.round(start.x / roadSpacing) * roadSpacing;
405
+ const startRoadZ = Math.round(start.z / roadSpacing) * roadSpacing;
406
+ const endRoadX = Math.round(end.x / roadSpacing) * roadSpacing;
407
+ const endRoadZ = Math.round(end.z / roadSpacing) * roadSpacing;
408
+
409
+ waypoints.push(new THREE.Vector3(startRoadX, 1, start.z));
410
+ waypoints.push(new THREE.Vector3(startRoadX, 1, startRoadZ));
411
+
412
+ // Navigate along roads
413
+ if (startRoadX !== endRoadX) {
414
+ waypoints.push(new THREE.Vector3(endRoadX, 1, startRoadZ));
415
+ }
416
+ waypoints.push(new THREE.Vector3(endRoadX, 1, endRoadZ));
417
+ waypoints.push(end.clone().add(new THREE.Vector3(0, 1, 0)));
418
+
419
+ return waypoints;
420
+ }
421
+
422
+ updateSensors() {
423
+ const maxDistance = 15;
424
+ const raycaster = new THREE.Raycaster();
425
+
426
+ // 8-direction sensor array
427
+ for (let i = 0; i < 8; i++) {
428
+ const angle = (i * Math.PI * 2) / 8;
429
+ const direction = new THREE.Vector3(
430
+ Math.sin(angle), 0, Math.cos(angle)
431
+ );
432
+ direction.applyQuaternion(this.mesh.quaternion);
433
+
434
+ raycaster.set(this.mesh.position, direction);
435
+ const intersects = raycaster.intersectObjects(this.getObstacles(), true);
436
+
437
+ if (intersects.length > 0 && intersects[0].distance <= maxDistance) {
438
+ this.sensors[i] = 1 - (intersects[0].distance / maxDistance);
439
+ } else {
440
+ this.sensors[i] = 0;
441
+ }
442
+ }
443
+
444
+ this.updateRoadStatus();
445
+ this.updateFlocking();
446
+ }
447
+
448
+ updateRoadStatus() {
449
+ const pos = this.mesh.position;
450
+ const roadSpacing = 80;
451
+ const roadHalfWidth = ROAD_WIDTH / 2;
452
+
453
+ const nearestHorizontalRoad = Math.round(pos.z / roadSpacing) * roadSpacing;
454
+ const nearestVerticalRoad = Math.round(pos.x / roadSpacing) * roadSpacing;
455
+
456
+ const distToHorizontalRoad = Math.abs(pos.z - nearestHorizontalRoad);
457
+ const distToVerticalRoad = Math.abs(pos.x - nearestVerticalRoad);
458
+
459
+ this.onRoad = distToHorizontalRoad <= roadHalfWidth || distToVerticalRoad <= roadHalfWidth;
460
+
461
+ // Check if at intersection
462
+ this.atIntersection = distToHorizontalRoad <= roadHalfWidth && distToVerticalRoad <= roadHalfWidth;
463
+ }
464
+
465
+ updateFlocking() {
466
+ this.neighbors = [];
467
+ let separation = new THREE.Vector3();
468
+ let alignment = new THREE.Vector3();
469
+ let cohesion = new THREE.Vector3();
470
+ let neighborCount = 0;
471
+
472
+ population.forEach(other => {
473
+ if (other !== this) {
474
+ const distance = this.mesh.position.distanceTo(other.mesh.position);
475
+
476
+ if (distance < NEIGHBOR_RADIUS && distance > 0) {
477
+ this.neighbors.push(other);
478
+ cohesion.add(other.mesh.position);
479
+ alignment.add(other.velocity);
480
+ neighborCount++;
481
+
482
+ if (distance < 8) {
483
+ const diff = this.mesh.position.clone().sub(other.mesh.position);
484
+ diff.normalize().divideScalar(distance);
485
+ separation.add(diff);
486
+ }
487
+ }
488
+ }
489
+ });
490
+
491
+ if (neighborCount > 0) {
492
+ cohesion.divideScalar(neighborCount).sub(this.mesh.position).normalize();
493
+ alignment.divideScalar(neighborCount).normalize();
494
+ }
495
+
496
+ this.flockingForce = separation.multiplyScalar(1.5)
497
+ .add(alignment.multiplyScalar(1.0))
498
+ .add(cohesion.multiplyScalar(0.5));
499
+ }
500
+
501
+ update(deltaTime) {
502
+ this.updateSensors();
503
+
504
+ if (this.isPlayer) {
505
+ this.updatePlayerControls(deltaTime);
506
+ } else {
507
+ this.updateAI(deltaTime);
508
+ }
509
+
510
+ this.updateMovement(deltaTime);
511
+ this.updateDestination();
512
+ this.keepInBounds();
513
+ }
514
+
515
+ updatePlayerControls(deltaTime) {
516
+ const forwardForce = keys['KeyW'] ? 1 : 0;
517
+ const backwardForce = keys['KeyS'] ? -0.5 : 0;
518
+ const leftTurn = keys['KeyA'] ? -1 : 0;
519
+ const rightTurn = keys['KeyD'] ? 1 : 0;
520
+ const brake = keys['Space'] ? 1 : 0;
521
+ const boost = keys['ShiftLeft'] ? 1.5 : 1;
522
+
523
+ this.applyMovement(forwardForce + backwardForce, leftTurn + rightTurn, brake, boost, deltaTime);
524
+ }
525
+
526
+ updateAI(deltaTime) {
527
+ if (!this.brain) return;
528
+
529
+ const inputs = [
530
+ ...this.sensors,
531
+ this.onRoad ? 1 : 0,
532
+ this.atIntersection ? 1 : 0,
533
+ this.currentSpeed / this.maxSpeed,
534
+ this.getPathDirection(),
535
+ this.flockingForce.x,
536
+ this.flockingForce.z,
537
+ this.neighbors.length / 10,
538
+ this.getDestinationDirection()
539
+ ];
540
+
541
+ const outputs = this.brain.activate(inputs);
542
+ const [forward, turn, brake, formation] = outputs;
543
+
544
+ this.applyMovement(forward, turn, brake, 1, deltaTime);
545
+ }
546
+
547
+ getPathDirection() {
548
+ if (this.currentPath.length === 0 || this.pathIndex >= this.currentPath.length) {
549
+ return 0;
550
+ }
551
+
552
+ const target = this.currentPath[this.pathIndex];
553
+ const direction = target.clone().sub(this.mesh.position).normalize();
554
+ const forward = new THREE.Vector3(0, 0, 1).applyQuaternion(this.mesh.quaternion);
555
+
556
+ return direction.dot(forward);
557
+ }
558
+
559
+ getDestinationDirection() {
560
+ if (!this.destination) return 0;
561
+
562
+ const direction = this.destination.mesh.position.clone().sub(this.mesh.position).normalize();
563
+ const forward = new THREE.Vector3(0, 0, 1).applyQuaternion(this.mesh.quaternion);
564
+
565
+ return direction.dot(forward);
566
+ }
567
+
568
+ applyMovement(forwardForce, turnForce, brakeForce, boostMultiplier, deltaTime) {
569
+ // Steering
570
+ this.steering = turnForce * 0.03;
571
+ this.mesh.rotation.y += this.steering * this.currentSpeed * deltaTime;
572
+
573
+ // Acceleration
574
+ const maxAcceleration = 15 * boostMultiplier;
575
+ if (forwardForce > 0) {
576
+ this.currentSpeed += maxAcceleration * forwardForce * deltaTime;
577
+ } else if (forwardForce < 0) {
578
+ this.currentSpeed += maxAcceleration * forwardForce * deltaTime;
579
+ }
580
+
581
+ // Braking
582
+ if (brakeForce > 0) {
583
+ this.currentSpeed *= Math.pow(0.1, brakeForce * deltaTime);
584
+ }
585
+
586
+ // Natural deceleration
587
+ this.currentSpeed *= Math.pow(0.98, deltaTime * 60);
588
+
589
+ // Speed limits
590
+ this.currentSpeed = Math.max(-this.maxSpeed * 0.5,
591
+ Math.min(this.maxSpeed * boostMultiplier, this.currentSpeed));
592
+
593
+ // Apply velocity
594
+ const forward = new THREE.Vector3(0, 0, 1);
595
+ forward.applyQuaternion(this.mesh.quaternion);
596
+ this.velocity = forward.multiplyScalar(this.currentSpeed);
597
+
598
+ // Apply flocking forces for AI cars
599
+ if (!this.isPlayer && this.flockingForce.length() > 0) {
600
+ this.velocity.add(this.flockingForce.multiplyScalar(5 * deltaTime));
601
+ }
602
+
603
+ // Update position
604
+ this.mesh.position.add(this.velocity.clone().multiplyScalar(deltaTime));
605
+
606
+ // Wheel rotation
607
+ this.wheels.forEach(wheel => {
608
+ wheel.rotation.x += this.currentSpeed * deltaTime * 0.2;
609
+ });
610
+ }
611
+
612
+ updateMovement(deltaTime) {
613
+ // Road following behavior for AI
614
+ if (!this.isPlayer && this.onRoad) {
615
+ const roadBonus = this.getRoadFollowingForce();
616
+ this.mesh.position.add(roadBonus.multiplyScalar(deltaTime * 2));
617
+ }
618
+ }
619
+
620
+ getRoadFollowingForce() {
621
+ const pos = this.mesh.position;
622
+ const roadSpacing = 80;
623
+ const roadHalfWidth = ROAD_WIDTH / 2;
624
+
625
+ const nearestHorizontalRoad = Math.round(pos.z / roadSpacing) * roadSpacing;
626
+ const nearestVerticalRoad = Math.round(pos.x / roadSpacing) * roadSpacing;
627
+
628
+ const distToHorizontalRoad = pos.z - nearestHorizontalRoad;
629
+ const distToVerticalRoad = pos.x - nearestVerticalRoad;
630
+
631
+ const force = new THREE.Vector3();
632
+
633
+ if (Math.abs(distToHorizontalRoad) <= roadHalfWidth) {
634
+ force.z = -distToHorizontalRoad * 0.5;
635
+ }
636
+ if (Math.abs(distToVerticalRoad) <= roadHalfWidth) {
637
+ force.x = -distToVerticalRoad * 0.5;
638
+ }
639
+
640
+ return force;
641
+ }
642
+
643
+ updateDestination() {
644
+ if (!this.destination) return;
645
+
646
+ const distanceToDestination = this.mesh.position.distanceTo(this.destination.mesh.position);
647
+
648
+ if (distanceToDestination < BUILDING_VISIT_DISTANCE) {
649
+ if (!this.isAtDestination) {
650
+ this.isAtDestination = true;
651
+ this.lastDestinationTime = Date.now();
652
+ }
653
+
654
+ // Stay at destination for a while, then pick new one
655
+ if (Date.now() - this.lastDestinationTime > 3000) {
656
+ this.setRandomDestination();
657
+ }
658
+ }
659
+
660
+ // Update path following
661
+ if (this.currentPath.length > 0 && this.pathIndex < this.currentPath.length) {
662
+ const waypoint = this.currentPath[this.pathIndex];
663
+ const distanceToWaypoint = this.mesh.position.distanceTo(waypoint);
664
+
665
+ if (distanceToWaypoint < 10) {
666
+ this.pathIndex++;
667
+ }
668
+ }
669
+ }
670
+
671
+ getObstacles() {
672
+ let obstacles = [];
673
+
674
+ population.forEach(car => {
675
+ if (car !== this) {
676
+ obstacles.push(car.mesh);
677
+ }
678
+ });
679
+
680
+ world.buildings.forEach(building => {
681
+ obstacles.push(building.mesh);
682
+ });
683
+
684
+ return obstacles;
685
+ }
686
+
687
+ keepInBounds() {
688
+ const bounds = 300;
689
+ if (Math.abs(this.mesh.position.x) > bounds ||
690
+ Math.abs(this.mesh.position.z) > bounds) {
691
+ if (Math.abs(this.mesh.position.x) > bounds) {
692
+ this.mesh.position.x = Math.sign(this.mesh.position.x) * bounds;
693
+ this.velocity.x *= -0.5;
694
+ }
695
+ if (Math.abs(this.mesh.position.z) > bounds) {
696
+ this.mesh.position.z = Math.sign(this.mesh.position.z) * bounds;
697
+ this.velocity.z *= -0.5;
698
+ }
699
+ }
700
+ }
701
+ }
702
+
703
+ function init() {
704
+ scene = new THREE.Scene();
705
+ scene.background = new THREE.Color(0x87CEEB);
706
+ scene.fog = new THREE.Fog(0x87CEEB, 200, 800);
707
+
708
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
709
+ camera.position.set(0, 80, 80);
710
+
711
+ renderer = new THREE.WebGLRenderer({ antialias: true });
712
+ renderer.setSize(window.innerWidth, window.innerHeight);
713
+ renderer.shadowMap.enabled = true;
714
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
715
+ document.body.appendChild(renderer.domElement);
716
+
717
+ // Lighting
718
+ const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
719
+ scene.add(ambientLight);
720
+
721
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
722
+ directionalLight.position.set(50, 100, 50);
723
+ directionalLight.castShadow = true;
724
+ directionalLight.shadow.mapSize.width = 2048;
725
+ directionalLight.shadow.mapSize.height = 2048;
726
+ scene.add(directionalLight);
727
+
728
+ createWorld();
729
+ createPopulation();
730
+
731
+ clock = new THREE.Clock();
732
+
733
+ setupEventListeners();
734
+ setupKeyboardControls();
735
+ setupMouseControls();
736
+
737
+ animate();
738
+ }
739
+
740
+ function createWorld() {
741
+ // Ground
742
+ const groundGeometry = new THREE.PlaneGeometry(800, 800);
743
+ const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x228B22 });
744
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
745
+ ground.rotation.x = -Math.PI / 2;
746
+ ground.receiveShadow = true;
747
+ scene.add(ground);
748
+
749
+ createRoadNetwork();
750
+ createBuildings();
751
+ createTrafficLights();
752
+ }
753
+
754
+ function createRoadNetwork() {
755
+ const roadMaterial = new THREE.MeshLambertMaterial({ color: 0x444444 });
756
+ const roadSpacing = 80;
757
+
758
+ // Create road grid
759
+ for (let i = -240; i <= 240; i += roadSpacing) {
760
+ // Horizontal roads
761
+ const hRoadGeometry = new THREE.PlaneGeometry(480, ROAD_WIDTH);
762
+ const hRoad = new THREE.Mesh(hRoadGeometry, roadMaterial);
763
+ hRoad.rotation.x = -Math.PI / 2;
764
+ hRoad.position.set(0, 0.1, i);
765
+ scene.add(hRoad);
766
+
767
+ // Vertical roads
768
+ const vRoadGeometry = new THREE.PlaneGeometry(ROAD_WIDTH, 480);
769
+ const vRoad = new THREE.Mesh(vRoadGeometry, roadMaterial);
770
+ vRoad.rotation.x = -Math.PI / 2;
771
+ vRoad.position.set(i, 0.1, 0);
772
+ scene.add(vRoad);
773
+
774
+ // Road markings
775
+ createRoadMarkings(i);
776
+ }
777
+ }
778
+
779
+ function createRoadMarkings(position) {
780
+ const markingMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
781
+
782
+ // Center line markings
783
+ for (let j = -200; j <= 200; j += 20) {
784
+ const markingGeometry = new THREE.PlaneGeometry(1, 8);
785
+ const marking = new THREE.Mesh(markingGeometry, markingMaterial);
786
+ marking.rotation.x = -Math.PI / 2;
787
+ marking.position.set(j, 0.11, position);
788
+ scene.add(marking);
789
+
790
+ const vMarking = new THREE.Mesh(markingGeometry.clone(), markingMaterial);
791
+ vMarking.rotation.x = -Math.PI / 2;
792
+ vMarking.rotation.z = Math.PI / 2;
793
+ vMarking.position.set(position, 0.11, j);
794
+ scene.add(vMarking);
795
+ }
796
+ }
797
+
798
+ function createBuildings() {
799
+ world.buildings = [];
800
+ const buildingMaterial = new THREE.MeshLambertMaterial({ color: 0x666666 });
801
+
802
+ // Create buildings in grid pattern with some randomness
803
+ for (let x = -200; x <= 200; x += 80) {
804
+ for (let z = -200; z <= 200; z += 80) {
805
+ if (Math.random() > 0.3) { // Don't place building everywhere
806
+ const offsetX = x + (Math.random() - 0.5) * 40;
807
+ const offsetZ = z + (Math.random() - 0.5) * 40;
808
+
809
+ const width = 15 + Math.random() * 20;
810
+ const height = 10 + Math.random() * 30;
811
+ const depth = 15 + Math.random() * 20;
812
+
813
+ const buildingGeometry = new THREE.BoxGeometry(width, height, depth);
814
+ const building = new THREE.Mesh(buildingGeometry, buildingMaterial);
815
+ building.position.set(offsetX, height / 2, offsetZ);
816
+ building.castShadow = true;
817
+ scene.add(building);
818
+
819
+ world.buildings.push({ mesh: building });
820
+ }
821
+ }
822
+ }
823
+ }
824
+
825
+ function createTrafficLights() {
826
+ world.trafficLights = [];
827
+ const roadSpacing = 80;
828
+
829
+ // Place traffic lights at major intersections
830
+ for (let x = -160; x <= 160; x += roadSpacing) {
831
+ for (let z = -160; z <= 160; z += roadSpacing) {
832
+ if (Math.random() > 0.7) {
833
+ const light = createTrafficLight(x, z);
834
+ world.trafficLights.push(light);
835
+ }
836
+ }
837
+ }
838
+ }
839
+
840
+ function createTrafficLight(x, z) {
841
+ const poleGeometry = new THREE.CylinderGeometry(0.2, 0.2, 8);
842
+ const poleMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });
843
+ const pole = new THREE.Mesh(poleGeometry, poleMaterial);
844
+ pole.position.set(x + 6, 4, z + 6);
845
+ scene.add(pole);
846
+
847
+ const lightBoxGeometry = new THREE.BoxGeometry(1, 3, 1);
848
+ const lightBoxMaterial = new THREE.MeshLambertMaterial({ color: 0x222222 });
849
+ const lightBox = new THREE.Mesh(lightBoxGeometry, lightBoxMaterial);
850
+ lightBox.position.set(x + 6, 7, z + 6);
851
+ scene.add(lightBox);
852
+
853
+ // Light states
854
+ const states = ['red', 'yellow', 'green'];
855
+ const currentState = states[Math.floor(Math.random() * states.length)];
856
+
857
+ return {
858
+ pole: pole,
859
+ lightBox: lightBox,
860
+ position: new THREE.Vector3(x, 0, z),
861
+ state: currentState,
862
+ lastChange: Date.now()
863
+ };
864
+ }
865
+
866
+ function createPopulation() {
867
+ population = [];
868
+
869
+ // Create AI cars
870
+ for (let i = 0; i < populationSize - 1; i++) {
871
+ const angle = (i / populationSize) * Math.PI * 2;
872
+ const radius = 30 + Math.random() * 50;
873
+ const x = Math.cos(angle) * radius;
874
+ const z = Math.sin(angle) * radius;
875
+
876
+ const car = new RealisticAICar(x, z, false);
877
+ population.push(car);
878
+ scene.add(car.mesh);
879
+ }
880
+
881
+ // Create player car
882
+ playerCar = new RealisticAICar(0, 0, true);
883
+ population.push(playerCar);
884
+ scene.add(playerCar.mesh);
885
+ currentTarget = playerCar;
886
+ }
887
+
888
+ function animate() {
889
+ requestAnimationFrame(animate);
890
+
891
+ if (!paused) {
892
+ const deltaTime = Math.min(clock.getDelta() * speedMultiplier, 0.1);
893
+
894
+ updatePopulation(deltaTime);
895
+ updateTrafficLights();
896
+ updateCamera();
897
+ updateUI();
898
+ }
899
+
900
+ renderer.render(scene, camera);
901
+ }
902
+
903
+ function updatePopulation(deltaTime) {
904
+ population.forEach(car => car.update(deltaTime));
905
+ }
906
+
907
+ function updateTrafficLights() {
908
+ world.trafficLights.forEach(light => {
909
+ if (Date.now() - light.lastChange > 5000) {
910
+ const states = ['red', 'yellow', 'green'];
911
+ const currentIndex = states.indexOf(light.state);
912
+ light.state = states[(currentIndex + 1) % states.length];
913
+ light.lastChange = Date.now();
914
+
915
+ // Update light color
916
+ const colors = { red: 0xff0000, yellow: 0xffff00, green: 0x00ff00 };
917
+ light.lightBox.material.color.setHex(colors[light.state]);
918
+ }
919
+ });
920
+ }
921
+
922
+ function updateCamera() {
923
+ if (!currentTarget) return;
924
+
925
+ const targetPos = currentTarget.mesh.position;
926
+ const targetRot = currentTarget.mesh.rotation;
927
+
928
+ switch (cameraMode) {
929
+ case 'firstPerson':
930
+ // First person view
931
+ camera.position.copy(targetPos);
932
+ camera.position.y += 2;
933
+ camera.position.add(new THREE.Vector3(0, 0, 2).applyQuaternion(currentTarget.mesh.quaternion));
934
+
935
+ const lookDirection = new THREE.Vector3(0, 0, -1).applyQuaternion(currentTarget.mesh.quaternion);
936
+ lookDirection.add(new THREE.Vector3(mouseControls.x, mouseControls.y, 0));
937
+ camera.lookAt(targetPos.clone().add(lookDirection.multiplyScalar(10)));
938
+ break;
939
+
940
+ case 'thirdPerson':
941
+ // Third person view
942
+ const behindOffset = new THREE.Vector3(0, 8, 15).applyQuaternion(currentTarget.mesh.quaternion);
943
+ camera.position.lerp(targetPos.clone().sub(behindOffset), 0.1);
944
+ camera.lookAt(targetPos);
945
+ break;
946
+
947
+ case 'overview':
948
+ // Overview of the area
949
+ camera.position.lerp(new THREE.Vector3(0, 150, 150), 0.02);
950
+ camera.lookAt(targetPos);
951
+ break;
952
+
953
+ case 'free':
954
+ // Free camera movement
955
+ updateFreeCamera();
956
+ break;
957
+ }
958
+ }
959
+
960
+ function updateFreeCamera() {
961
+ const moveSpeed = 2;
962
+
963
+ if (keys['KeyW']) camera.position.add(new THREE.Vector3(0, 0, -moveSpeed));
964
+ if (keys['KeyS']) camera.position.add(new THREE.Vector3(0, 0, moveSpeed));
965
+ if (keys['KeyA']) camera.position.add(new THREE.Vector3(-moveSpeed, 0, 0));
966
+ if (keys['KeyD']) camera.position.add(new THREE.Vector3(moveSpeed, 0, 0));
967
+ if (keys['KeyQ']) camera.position.y += moveSpeed;
968
+ if (keys['KeyE']) camera.position.y -= moveSpeed;
969
+ }
970
+
971
+ function updateUI() {
972
+ if (currentTarget) {
973
+ document.getElementById('currentMode').textContent = currentTarget.isPlayer ? 'Player Driving' : 'AI Observer';
974
+ document.getElementById('currentTarget').textContent = currentTarget.isPlayer ? 'Player' : 'AI Car';
975
+ document.getElementById('currentSpeed').textContent = Math.round(Math.abs(currentTarget.currentSpeed * 3.6));
976
+ document.getElementById('flockSize').textContent = currentTarget.neighbors.length;
977
+
978
+ if (currentTarget.destination) {
979
+ const distance = currentTarget.mesh.position.distanceTo(currentTarget.destination.mesh.position);
980
+ document.getElementById('targetDistance').textContent = Math.round(distance);
981
+ document.getElementById('currentDestination').textContent = 'Building';
982
+ } else {
983
+ document.getElementById('targetDistance').textContent = '0';
984
+ document.getElementById('currentDestination').textContent = 'None';
985
+ }
986
+ }
987
+
988
+ document.getElementById('population').textContent = population.length;
989
+
990
+ const avgSpeed = population.reduce((sum, car) => sum + Math.abs(car.currentSpeed), 0) / population.length;
991
+ document.getElementById('avgSpeed').textContent = Math.round(avgSpeed * 3.6);
992
+
993
+ const activeRoutes = population.filter(car => car.currentPath.length > 0).length;
994
+ document.getElementById('activeRoutes').textContent = activeRoutes;
995
+
996
+ document.getElementById('cameraMode').textContent = cameraMode;
997
+
998
+ // Update speedometer
999
+ if (currentTarget && currentTarget.isPlayer) {
1000
+ const speedPercent = Math.abs(currentTarget.currentSpeed) / currentTarget.maxSpeed;
1001
+ const needleRotation = -90 + (speedPercent * 180);
1002
+ document.getElementById('speedNeedle').style.transform =
1003
+ `translate(-50%, -100%) rotate(${needleRotation}deg)`;
1004
+ }
1005
+ }
1006
+
1007
+ function setupEventListeners() {
1008
+ // UI controls
1009
+ document.getElementById('pauseBtn').addEventListener('click', togglePause);
1010
+ document.getElementById('resetBtn').addEventListener('click', resetSimulation);
1011
+ document.getElementById('speedBtn').addEventListener('click', toggleSpeed);
1012
+ document.getElementById('trafficBtn').addEventListener('click', toggleTraffic);
1013
+ document.getElementById('weatherBtn').addEventListener('click', toggleWeather);
1014
+
1015
+ // Camera controls
1016
+ document.getElementById('firstPersonBtn').addEventListener('click', () => setCameraMode('firstPerson'));
1017
+ document.getElementById('thirdPersonBtn').addEventListener('click', () => setCameraMode('thirdPerson'));
1018
+ document.getElementById('overviewBtn').addEventListener('click', () => setCameraMode('overview'));
1019
+ document.getElementById('freeCamera').addEventListener('click', () => setCameraMode('free'));
1020
+ document.getElementById('nextCarBtn').addEventListener('click', switchToNextCar);
1021
+
1022
+ window.addEventListener('resize', onWindowResize);
1023
+ }
1024
+
1025
+ function setupKeyboardControls() {
1026
+ document.addEventListener('keydown', (event) => {
1027
+ keys[event.code] = true;
1028
+
1029
+ if (event.code === 'Tab') {
1030
+ event.preventDefault();
1031
+ switchToNextCar();
1032
+ }
1033
+ if (event.code === 'Escape') {
1034
+ setCameraMode('overview');
1035
+ }
1036
+ });
1037
+
1038
+ document.addEventListener('keyup', (event) => {
1039
+ keys[event.code] = false;
1040
+ });
1041
+ }
1042
+
1043
+ function setupMouseControls() {
1044
+ let isMouseLocked = false;
1045
+
1046
+ document.addEventListener('click', () => {
1047
+ if (cameraMode === 'firstPerson' || cameraMode === 'free') {
1048
+ document.body.requestPointerLock();
1049
+ }
1050
+ });
1051
+
1052
+ document.addEventListener('pointerlockchange', () => {
1053
+ isMouseLocked = document.pointerLockElement === document.body;
1054
+ });
1055
+
1056
+ document.addEventListener('mousemove', (event) => {
1057
+ if (isMouseLocked) {
1058
+ mouseControls.x += event.movementX * mouseControls.sensitivity;
1059
+ mouseControls.y -= event.movementY * mouseControls.sensitivity;
1060
+
1061
+ mouseControls.x = Math.max(-Math.PI/3, Math.min(Math.PI/3, mouseControls.x));
1062
+ mouseControls.y = Math.max(-Math.PI/6, Math.min(Math.PI/6, mouseControls.y));
1063
+ }
1064
+ });
1065
+ }
1066
+
1067
+ function setCameraMode(mode) {
1068
+ cameraMode = mode;
1069
+
1070
+ // Update UI
1071
+ document.querySelectorAll('#cameraControls button').forEach(btn => {
1072
+ btn.classList.remove('active');
1073
+ });
1074
+
1075
+ const buttonMap = {
1076
+ 'firstPerson': 'firstPersonBtn',
1077
+ 'thirdPerson': 'thirdPersonBtn',
1078
+ 'overview': 'overviewBtn',
1079
+ 'free': 'freeCamera'
1080
+ };
1081
+
1082
+ if (buttonMap[mode]) {
1083
+ document.getElementById(buttonMap[mode]).classList.add('active');
1084
+ }
1085
+
1086
+ // Show/hide UI elements
1087
+ const showDriving = mode === 'firstPerson' && currentTarget && currentTarget.isPlayer;
1088
+ document.getElementById('drivingControls').style.display = showDriving ? 'block' : 'none';
1089
+ document.getElementById('speedometer').style.display = showDriving ? 'block' : 'none';
1090
+ document.getElementById('crosshair').style.display = mode === 'firstPerson' ? 'block' : 'none';
1091
+
1092
+ mouseControls.x = 0;
1093
+ mouseControls.y = 0;
1094
+ }
1095
+
1096
+ function switchToNextCar() {
1097
+ if (population.length === 0) return;
1098
+
1099
+ const currentIndex = population.indexOf(currentTarget);
1100
+ const nextIndex = (currentIndex + 1) % population.length;
1101
+ currentTarget = population[nextIndex];
1102
+
1103
+ // If switching to player car, enable driving controls
1104
+ if (currentTarget.isPlayer && cameraMode !== 'overview') {
1105
+ setCameraMode('firstPerson');
1106
+ }
1107
+ }
1108
+
1109
+ function togglePause() {
1110
+ paused = !paused;
1111
+ document.getElementById('pauseBtn').textContent = paused ? 'Resume' : 'Pause';
1112
+ }
1113
+
1114
+ function resetSimulation() {
1115
+ population.forEach(car => {
1116
+ if (car.mesh.parent) scene.remove(car.mesh);
1117
+ });
1118
+ createPopulation();
1119
+ currentTarget = playerCar;
1120
+ }
1121
+
1122
+ function toggleSpeed() {
1123
+ speedMultiplier = speedMultiplier === 1 ? 2 : speedMultiplier === 2 ? 5 : 1;
1124
+ document.getElementById('speedBtn').textContent = `Speed: ${speedMultiplier}x`;
1125
+ }
1126
+
1127
+ function toggleTraffic() {
1128
+ const levels = ['light', 'normal', 'heavy'];
1129
+ const currentIndex = levels.indexOf(trafficDensity);
1130
+ trafficDensity = levels[(currentIndex + 1) % levels.length];
1131
+ document.getElementById('trafficBtn').textContent = `Traffic: ${trafficDensity}`;
1132
+ document.getElementById('trafficFlow').textContent = trafficDensity;
1133
+ }
1134
+
1135
+ function toggleWeather() {
1136
+ const conditions = ['clear', 'rain', 'fog'];
1137
+ const currentIndex = conditions.indexOf(weatherCondition);
1138
+ weatherCondition = conditions[(currentIndex + 1) % conditions.length];
1139
+ document.getElementById('weatherBtn').textContent = `Weather: ${weatherCondition}`;
1140
+
1141
+ // Update visual effects
1142
+ if (weatherCondition === 'fog') {
1143
+ scene.fog.near = 50;
1144
+ scene.fog.far = 200;
1145
+ } else {
1146
+ scene.fog.near = 200;
1147
+ scene.fog.far = 800;
1148
+ }
1149
+ }
1150
+
1151
+ function onWindowResize() {
1152
+ camera.aspect = window.innerWidth / window.innerHeight;
1153
+ camera.updateProjectionMatrix();
1154
+ renderer.setSize(window.innerWidth, window.innerHeight);
1155
+ }
1156
+
1157
+ init();
1158
+ </script>
1159
+ </body>
1160
+ </html>