awacke1 commited on
Commit
92c9ef3
·
verified ·
1 Parent(s): 00b2fd6

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +608 -244
index.html CHANGED
@@ -377,6 +377,8 @@
377
  this.isInExitLane = false;
378
  this.approachTarget = null;
379
  this.exitTarget = null;
 
 
380
  this.parkingAttempts = 0;
381
  this.maxParkingAttempts = 3;
382
  this.departureTime = 0;
@@ -1049,37 +1051,69 @@
1049
  }
1050
 
1051
  update(deltaTime) {
1052
- // Handle parked cars separately
1053
- if (this.isParked) {
1054
- this.handleParkedBehavior(deltaTime);
1055
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1056
  }
1057
-
1058
- if (this.crashed) return;
1059
-
1060
- this.timeAlive -= deltaTime;
1061
- if (this.timeAlive <= 0 || this.parkingAttempts >= this.maxParkingAttempts) {
1062
- if (!this.isParkingApproach) {
1063
- this.attemptParking();
 
 
 
 
 
 
 
 
 
 
 
1064
  }
1065
- return;
 
 
 
 
 
1066
  }
1067
-
1068
- this.updateSensors();
1069
- this.updateConvoyBehavior();
1070
- this.updateVisuals();
1071
-
1072
- // Get AI decision
1073
- const inputs = this.getEnhancedInputs();
1074
- const outputs = this.brain.activate(inputs);
1075
-
1076
- // Apply traffic-aware movement
1077
- this.applyTrafficMovement(outputs, deltaTime);
1078
- this.updateFitness(deltaTime);
1079
-
1080
- this.lastPosition.copy(this.mesh.position);
1081
- this.checkCollisions();
1082
- this.keepInBounds();
1083
  }
1084
 
1085
  applyTrafficMovement(outputs, deltaTime) {
@@ -1285,57 +1319,207 @@
1285
 
1286
  const queuePosition = this.targetParkingLot.queue.indexOf(this);
1287
 
1288
- // Check if there's space in approach lane
1289
- const approachLaneOccupancy = population.filter(car =>
1290
- car.isInApproachLane && car.targetParkingLot === this.targetParkingLot
1291
- ).length;
1292
 
1293
- return queuePosition < 3 && approachLaneOccupancy < this.targetParkingLot.approachLane.length;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1294
  }
1295
 
1296
  enterApproachLane(deltaTime) {
1297
- // Find first available approach lane position
1298
- const approachPositions = this.targetParkingLot.approachLane;
1299
  let targetPosition = null;
 
1300
 
1301
- for (let i = 0; i < approachPositions.length; i++) {
1302
- const pos = approachPositions[i];
1303
- const occupied = population.some(car =>
1304
- car !== this &&
1305
- car.isInApproachLane &&
1306
- car.mesh.position.distanceTo(pos) < 3
1307
- );
1308
-
1309
- if (!occupied) {
1310
- targetPosition = pos;
1311
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1312
  }
1313
  }
1314
 
1315
  if (targetPosition) {
1316
  this.isInApproachLane = true;
1317
  this.approachTarget = targetPosition;
 
1318
  this.moveToPosition(targetPosition, deltaTime, 4); // Slow approach
1319
  }
1320
  }
1321
 
1322
- handleApproachLane(deltaTime) {
1323
- // Check if we can proceed to actual parking
1324
- const availableSpot = this.targetParkingLot.spots.find(spot => !spot.occupied);
1325
- if (!availableSpot) {
1326
- // Wait in approach lane
1327
- this.velocity.multiplyScalar(0.95);
1328
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1329
  }
1330
 
1331
- const spotDistance = this.mesh.position.distanceTo(availableSpot.position);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1332
 
1333
- if (spotDistance < 3) {
1334
- // Successfully park
1335
- this.completeParkingProcess(availableSpot);
1336
- } else {
1337
- // Move toward spot
1338
- this.moveToPosition(availableSpot.position, deltaTime, 2);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1339
  }
1340
  }
1341
 
@@ -1644,22 +1828,53 @@
1644
  }
1645
 
1646
  destroy() {
1647
- // Clean up parking spot
1648
- if (this.parkingSpot) {
1649
- this.parkingSpot.occupied = false;
1650
- this.parkingSpot.car = null;
1651
- }
1652
-
1653
- // Remove from parking queue
1654
- this.leaveParkingQueue();
1655
-
1656
- // Clean up visual elements
1657
- this.flockLines.forEach(line => {
1658
- if (line.parent) scene.remove(line);
1659
- });
1660
-
1661
- if (this.mesh.parent) {
1662
- scene.remove(this.mesh);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1663
  }
1664
  }
1665
  }
@@ -2034,13 +2249,14 @@
2034
 
2035
  world.buildings.push({ mesh: building });
2036
 
2037
- // Create enhanced parking lot with approach lanes
2038
  const parkingLot = {
2039
  center: new THREE.Vector3(loc.x + width/2 + 25, 0.1, loc.z),
2040
  spots: [],
2041
  queue: [],
2042
- approachLane: [],
2043
- exitLane: []
 
2044
  };
2045
 
2046
  // Main parking lot surface (larger)
@@ -2050,40 +2266,64 @@
2050
  lot.position.copy(parkingLot.center);
2051
  scene.add(lot);
2052
 
2053
- // Approach lane (single file entry)
2054
- const approachGeometry = new THREE.PlaneGeometry(6, 60);
2055
- const approachLane = new THREE.Mesh(approachGeometry, queueMaterial);
2056
- approachLane.rotation.x = -Math.PI / 2;
2057
- approachLane.position.set(parkingLot.center.x - 30, 0.08, parkingLot.center.z);
2058
- scene.add(approachLane);
2059
-
2060
- // Exit lane (single file exit)
2061
- const exitGeometry = new THREE.PlaneGeometry(6, 60);
2062
- const exitLane = new THREE.Mesh(exitGeometry, queueMaterial);
2063
- exitLane.rotation.x = -Math.PI / 2;
2064
- exitLane.position.set(parkingLot.center.x + 30, 0.08, parkingLot.center.z);
2065
- scene.add(exitLane);
2066
-
2067
- // Create approach queue positions
2068
- for (let q = 0; q < 12; q++) {
2069
- const queuePos = new THREE.Vector3(
2070
- parkingLot.center.x - 30,
2071
- 1,
2072
- parkingLot.center.z - 25 + (q * 4.5) // 4.5m spacing for tight queuing
2073
- );
2074
- parkingLot.approachLane.push(queuePos);
2075
  }
2076
 
2077
- // Create exit queue positions
2078
- for (let q = 0; q < 8; q++) {
2079
- const exitPos = new THREE.Vector3(
2080
- parkingLot.center.x + 30,
2081
- 1,
2082
- parkingLot.center.z - 15 + (q * 4) // Tighter exit spacing
2083
- );
2084
- parkingLot.exitLane.push(exitPos);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2085
  }
2086
 
 
 
 
 
 
 
 
 
 
 
2087
  // Create parking spots (5x8 grid = 40 spots)
2088
  for (let row = 0; row < 5; row++) {
2089
  for (let col = 0; col < 8; col++) {
@@ -2136,86 +2376,135 @@
2136
  }
2137
 
2138
  function evolvePopulation() {
2139
- // Sort by fitness
2140
- population.sort((a, b) => b.fitness - a.fitness);
2141
-
2142
- // Advanced selection
2143
- const eliteCount = Math.floor(populationSize * 0.15);
2144
- const tournamentCount = Math.floor(populationSize * 0.25);
2145
- const mutatedCount = populationSize - eliteCount - tournamentCount;
2146
-
2147
- const survivors = population.slice(0, eliteCount);
2148
-
2149
- // Tournament selection
2150
- for (let i = 0; i < tournamentCount; i++) {
2151
- const tournamentSize = 5;
2152
- let best = null;
2153
- let bestFitness = -Infinity;
2154
 
2155
- for (let j = 0; j < tournamentSize; j++) {
2156
- const candidate = population[Math.floor(Math.random() * Math.min(population.length, populationSize * 0.5))];
2157
- if (candidate.fitness > bestFitness) {
2158
- best = candidate;
2159
- bestFitness = candidate.fitness;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2160
  }
2161
  }
2162
- if (best) survivors.push(best);
2163
- }
2164
-
2165
- // Clean up old population
2166
- population.forEach(car => car.destroy());
2167
-
2168
- // Create new population
2169
- const newPopulation = [];
2170
- const roadPositions = [
2171
- { x: -280, z: 0 }, { x: 280, z: 0 },
2172
- { x: 0, z: -280 }, { x: 0, z: 280 },
2173
- { x: -130, z: 0 }, { x: 130, z: 0 },
2174
- { x: 0, z: -130 }, { x: 0, z: 130 }
2175
- ];
2176
-
2177
- // Elite reproduction
2178
- survivors.forEach((parent, index) => {
2179
- const startPos = roadPositions[index % roadPositions.length];
2180
- const newCar = new TrafficCar(
2181
- startPos.x + (Math.random() - 0.5) * 10,
2182
- startPos.z + (Math.random() - 0.5) * 10
2183
- );
2184
- newCar.brain = parent.brain.copy();
2185
- newPopulation.push(newCar);
2186
- scene.add(newCar.mesh);
2187
- });
2188
-
2189
- // Mutated offspring
2190
- while (newPopulation.length < populationSize) {
2191
- const parentIndex = Math.floor(Math.random() * Math.min(survivors.length, eliteCount * 2));
2192
- const parent = survivors[parentIndex];
2193
- const startPos = roadPositions[newPopulation.length % roadPositions.length];
2194
 
2195
- const child = new TrafficCar(
2196
- startPos.x + (Math.random() - 0.5) * 10,
2197
- startPos.z + (Math.random() - 0.5) * 10
2198
- );
2199
- child.brain = parent.brain.copy();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2200
 
2201
- const mutationRate = parent.fitness > bestFitness * 0.8 ? 0.05 : 0.15;
2202
- child.brain.mutate(mutationRate);
2203
 
2204
- newPopulation.push(child);
2205
- scene.add(child.mesh);
 
 
 
 
 
 
2206
  }
2207
-
2208
- population = newPopulation;
2209
-
2210
- // Update epoch
2211
- epoch++;
2212
- timeLeft = epochTime;
2213
- bestFitness = Math.max(bestFitness, survivors[0]?.fitness || 0);
2214
- crashCount = 0;
2215
- parkingEvents = 0;
2216
- laneViolations = 0;
2217
-
2218
- console.log(`Epoch ${epoch}: Best fitness: ${bestFitness.toFixed(1)}, Parking events: ${parkingEvents}`);
2219
  }
2220
 
2221
  function animate() {
@@ -2254,32 +2543,40 @@
2254
  approaching: 0 // Cars approaching parking
2255
  };
2256
 
2257
- population.forEach(car => {
2258
- car.update(deltaTime);
2259
-
2260
- if (!car.crashed) {
2261
- stats.alive++;
2262
- stats.totalRoadTime += car.roadTime;
2263
- stats.totalConvoyTime += car.convoyTime;
2264
- stats.totalParkingScore += car.parkingScore;
2265
- stats.totalViolations += car.trafficViolations;
2266
 
2267
- if (car.isParked) {
2268
- stats.parked++;
2269
- } else if (car.isParkingApproach) {
2270
- stats.approaching++;
2271
- } else if (car.role === 'leader') {
2272
- stats.leaders++;
2273
- stats.maxConvoySize = Math.max(stats.maxConvoySize, car.convoyFollowers.length + 1);
2274
- } else if (car.convoyPosition >= 0) {
2275
- stats.convoy++;
2276
- if (car.followingDistance > 0) {
2277
- stats.totalFollowingDistance += car.followingDistance;
2278
- stats.followingCount++;
 
 
 
 
 
 
 
 
 
 
 
2279
  }
2280
- } else {
2281
- stats.solo++;
2282
  }
 
 
 
 
2283
  }
2284
  });
2285
 
@@ -2287,39 +2584,66 @@
2287
  }
2288
 
2289
  function updateCamera() {
2290
- if (cameraMode === 'follow_best') {
2291
- // Follow best performing car
2292
- let bestCar = population.reduce((best, car) => {
2293
- if (car.crashed || car.isParked) return best;
2294
- return !best || car.fitness > best.fitness ? car : best;
2295
- }, null);
2296
-
2297
- if (bestCar) {
2298
- const targetPos = bestCar.mesh.position.clone();
2299
- targetPos.y += 40;
2300
- targetPos.add(bestCar.velocity.clone().normalize().multiplyScalar(25));
2301
 
2302
- camera.position.lerp(targetPos, 0.03);
2303
- camera.lookAt(bestCar.mesh.position);
2304
- }
2305
- } else if (cameraMode === 'follow_convoy') {
2306
- // Follow largest convoy
2307
- let largestConvoy = population.find(car =>
2308
- car.role === 'leader' && car.convoyFollowers.length > 0
2309
- );
2310
-
2311
- if (largestConvoy) {
2312
- const targetPos = largestConvoy.mesh.position.clone();
2313
- targetPos.y += 50;
2314
- targetPos.add(largestConvoy.velocity.clone().normalize().multiplyScalar(30));
 
 
 
 
 
 
 
 
2315
 
2316
- camera.position.lerp(targetPos, 0.03);
2317
- camera.lookAt(largestConvoy.mesh.position);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2318
  }
2319
- } else {
2320
- // Overview mode
2321
- camera.position.lerp(new THREE.Vector3(0, 180, 180), 0.02);
2322
- camera.lookAt(0, 0, 0);
2323
  }
2324
  }
2325
 
@@ -2425,18 +2749,58 @@
2425
  parkingEvents = 0;
2426
  laneViolations = 0;
2427
 
2428
- // Reset parking lots and queues
2429
  world.parkingLots.forEach(lot => {
2430
- lot.spots.forEach(spot => {
2431
- spot.occupied = false;
2432
- spot.car = null;
2433
- });
2434
- lot.queue = []; // Clear parking queues
2435
- lot.approachLane = lot.approachLane || [];
2436
- lot.exitLane = lot.exitLane || [];
 
 
 
 
 
 
 
 
 
 
 
 
 
2437
  });
2438
 
2439
- population.forEach(car => car.destroy());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2440
  createInitialPopulation();
2441
  }
2442
 
 
377
  this.isInExitLane = false;
378
  this.approachTarget = null;
379
  this.exitTarget = null;
380
+ this.selectedApproachLane = -1;
381
+ this.selectedExitLane = -1;
382
  this.parkingAttempts = 0;
383
  this.maxParkingAttempts = 3;
384
  this.departureTime = 0;
 
1051
  }
1052
 
1053
  update(deltaTime) {
1054
+ try {
1055
+ // Handle parked cars separately
1056
+ if (this.isParked) {
1057
+ this.handleParkedBehavior(deltaTime);
1058
+ return;
1059
+ }
1060
+
1061
+ if (this.crashed) return;
1062
+
1063
+ this.timeAlive -= deltaTime;
1064
+ if (this.timeAlive <= 0 || this.parkingAttempts >= this.maxParkingAttempts) {
1065
+ if (!this.isParkingApproach) {
1066
+ this.attemptParking();
1067
+ }
1068
+ return;
1069
+ }
1070
+
1071
+ this.updateSensors();
1072
+ this.updateConvoyBehavior();
1073
+ this.updateVisuals();
1074
+
1075
+ // Get AI decision
1076
+ const inputs = this.getEnhancedInputs();
1077
+ const outputs = this.brain.activate(inputs);
1078
+
1079
+ // Apply traffic-aware movement
1080
+ this.applyTrafficMovement(outputs, deltaTime);
1081
+ this.updateFitness(deltaTime);
1082
+
1083
+ this.lastPosition.copy(this.mesh.position);
1084
+ this.checkCollisions();
1085
+ this.keepInBounds();
1086
+ } catch (error) {
1087
+ console.warn('Error in car update:', error);
1088
+ // Try to recover by resetting car state
1089
+ this.recoverFromError();
1090
  }
1091
+ }
1092
+
1093
+ recoverFromError() {
1094
+ try {
1095
+ // Reset to basic driving state
1096
+ this.role = 'driver';
1097
+ this.isParked = false;
1098
+ this.isParkingApproach = false;
1099
+ this.isInApproachLane = false;
1100
+ this.isInExitLane = false;
1101
+ this.parkingQueue = -1;
1102
+ this.convoyPosition = -1;
1103
+ this.convoyLeader = null;
1104
+ this.followTarget = null;
1105
+
1106
+ // Set safe velocity
1107
+ if (this.velocity.length() < 1) {
1108
+ this.velocity.set(0, 0, 5);
1109
  }
1110
+
1111
+ // Reset time
1112
+ this.timeAlive = 30 + Math.random() * 20;
1113
+ } catch (recoveryError) {
1114
+ console.warn('Error during recovery:', recoveryError);
1115
+ this.crashed = true;
1116
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1117
  }
1118
 
1119
  applyTrafficMovement(outputs, deltaTime) {
 
1319
 
1320
  const queuePosition = this.targetParkingLot.queue.indexOf(this);
1321
 
1322
+ // Check if there's space in any approach lane
1323
+ let totalApproachCapacity = 0;
1324
+ let currentApproachOccupancy = 0;
 
1325
 
1326
+ if (this.targetParkingLot.approachLanes) {
1327
+ // Multiple lanes system
1328
+ this.targetParkingLot.approachLanes.forEach(lane => {
1329
+ totalApproachCapacity += lane.length;
1330
+ });
1331
+
1332
+ currentApproachOccupancy = population.filter(car =>
1333
+ car.isInApproachLane &&
1334
+ car.targetParkingLot === this.targetParkingLot
1335
+ ).length;
1336
+ } else {
1337
+ // Single lane fallback
1338
+ totalApproachCapacity = this.targetParkingLot.approachLane ?
1339
+ this.targetParkingLot.approachLane.length : 10;
1340
+
1341
+ currentApproachOccupancy = population.filter(car =>
1342
+ car.isInApproachLane &&
1343
+ car.targetParkingLot === this.targetParkingLot
1344
+ ).length;
1345
+ }
1346
+
1347
+ return queuePosition < 5 && currentApproachOccupancy < totalApproachCapacity;
1348
  }
1349
 
1350
  enterApproachLane(deltaTime) {
1351
+ // Find first available approach lane position from any lane
 
1352
  let targetPosition = null;
1353
+ let selectedLane = -1;
1354
 
1355
+ if (this.targetParkingLot.approachLanes) {
1356
+ for (let laneIndex = 0; laneIndex < this.targetParkingLot.approachLanes.length; laneIndex++) {
1357
+ const lanePositions = this.targetParkingLot.approachLanes[laneIndex];
1358
+
1359
+ for (let posIndex = 0; posIndex < lanePositions.length; posIndex++) {
1360
+ const pos = lanePositions[posIndex];
1361
+ const occupied = population.some(car =>
1362
+ car !== this &&
1363
+ car.isInApproachLane &&
1364
+ car.mesh.position.distanceTo(pos) < 4
1365
+ );
1366
+
1367
+ if (!occupied) {
1368
+ targetPosition = pos;
1369
+ selectedLane = laneIndex;
1370
+ break;
1371
+ }
1372
+ }
1373
+
1374
+ if (targetPosition) break;
1375
+ }
1376
+ } else {
1377
+ // Fallback to single lane system for compatibility
1378
+ const approachPositions = this.targetParkingLot.approachLane || [];
1379
+ for (let i = 0; i < approachPositions.length; i++) {
1380
+ const pos = approachPositions[i];
1381
+ const occupied = population.some(car =>
1382
+ car !== this &&
1383
+ car.isInApproachLane &&
1384
+ car.mesh.position.distanceTo(pos) < 4
1385
+ );
1386
+
1387
+ if (!occupied) {
1388
+ targetPosition = pos;
1389
+ break;
1390
+ }
1391
  }
1392
  }
1393
 
1394
  if (targetPosition) {
1395
  this.isInApproachLane = true;
1396
  this.approachTarget = targetPosition;
1397
+ this.selectedApproachLane = selectedLane;
1398
  this.moveToPosition(targetPosition, deltaTime, 4); // Slow approach
1399
  }
1400
  }
1401
 
1402
+ useExitLane() {
1403
+ let exitTarget = null;
1404
+ let selectedExitLane = -1;
1405
+
1406
+ // Try multiple exit lanes if available
1407
+ if (this.targetParkingLot.exitLanes) {
1408
+ for (let laneIndex = 0; laneIndex < this.targetParkingLot.exitLanes.length; laneIndex++) {
1409
+ const exitPositions = this.targetParkingLot.exitLanes[laneIndex];
1410
+
1411
+ for (let posIndex = 0; posIndex < exitPositions.length; posIndex++) {
1412
+ const pos = exitPositions[posIndex];
1413
+ const occupied = population.some(car =>
1414
+ car !== this &&
1415
+ car.mesh.position.distanceTo(pos) < 4
1416
+ );
1417
+
1418
+ if (!occupied) {
1419
+ exitTarget = pos;
1420
+ selectedExitLane = laneIndex;
1421
+ break;
1422
+ }
1423
+ }
1424
+
1425
+ if (exitTarget) break;
1426
+ }
1427
+ } else {
1428
+ // Fallback to single lane system
1429
+ const exitPositions = this.targetParkingLot.exitLane || [];
1430
+ for (let i = 0; i < exitPositions.length; i++) {
1431
+ const pos = exitPositions[i];
1432
+ const occupied = population.some(car =>
1433
+ car !== this &&
1434
+ car.mesh.position.distanceTo(pos) < 4
1435
+ );
1436
+
1437
+ if (!occupied) {
1438
+ exitTarget = pos;
1439
+ break;
1440
+ }
1441
+ }
1442
  }
1443
 
1444
+ if (exitTarget) {
1445
+ this.isInExitLane = true;
1446
+ this.exitTarget = exitTarget;
1447
+ this.selectedExitLane = selectedExitLane;
1448
+ this.mesh.position.copy(exitTarget);
1449
+
1450
+ // Set exit velocity toward nearest road or access point
1451
+ const exitDirection = this.getBestExitDirection();
1452
+ this.velocity.copy(exitDirection.multiplyScalar(7));
1453
+
1454
+ // Schedule exit lane departure
1455
+ setTimeout(() => {
1456
+ if (this.isInExitLane) {
1457
+ this.isInExitLane = false;
1458
+ this.role = 'driver';
1459
+ this.timeAlive = 50 + Math.random() * 30;
1460
+ this.updateCarColor();
1461
+ }
1462
+ }, 2000 + Math.random() * 2000); // 2-4 seconds variation
1463
+
1464
+ return true;
1465
+ }
1466
 
1467
+ return false;
1468
+ }
1469
+
1470
+ getBestExitDirection() {
1471
+ // Choose exit direction based on available access points
1472
+ if (this.targetParkingLot.accessPoints && this.targetParkingLot.accessPoints.length > 0) {
1473
+ const accessPoint = this.targetParkingLot.accessPoints[
1474
+ Math.floor(Math.random() * this.targetParkingLot.accessPoints.length)
1475
+ ];
1476
+
1477
+ const direction = accessPoint.pos.clone().sub(this.mesh.position).normalize();
1478
+ return direction;
1479
+ }
1480
+
1481
+ // Fallback directions
1482
+ const directions = [
1483
+ new THREE.Vector3(0, 0, 1), // South
1484
+ new THREE.Vector3(0, 0, -1), // North
1485
+ new THREE.Vector3(1, 0, 0), // East
1486
+ new THREE.Vector3(-1, 0, 0) // West
1487
+ ];
1488
+
1489
+ return directions[Math.floor(Math.random() * directions.length)];
1490
+ }
1491
+
1492
+ handleApproachLane(deltaTime) {
1493
+ try {
1494
+ // Check if we can proceed to actual parking
1495
+ if (!this.targetParkingLot || !this.targetParkingLot.spots) {
1496
+ this.isInApproachLane = false;
1497
+ this.role = 'driver';
1498
+ return;
1499
+ }
1500
+
1501
+ const availableSpot = this.targetParkingLot.spots.find(spot => !spot.occupied);
1502
+ if (!availableSpot) {
1503
+ // Wait in approach lane
1504
+ this.velocity.multiplyScalar(0.95);
1505
+ return;
1506
+ }
1507
+
1508
+ const spotDistance = this.mesh.position.distanceTo(availableSpot.position);
1509
+
1510
+ if (spotDistance < 3) {
1511
+ // Successfully park
1512
+ this.completeParkingProcess(availableSpot);
1513
+ } else {
1514
+ // Move toward spot
1515
+ this.moveToPosition(availableSpot.position, deltaTime, 2);
1516
+ }
1517
+ } catch (error) {
1518
+ console.warn('Error in handleApproachLane:', error);
1519
+ // Recover by leaving approach lane
1520
+ this.isInApproachLane = false;
1521
+ this.role = 'driver';
1522
+ this.timeAlive = 30;
1523
  }
1524
  }
1525
 
 
1828
  }
1829
 
1830
  destroy() {
1831
+ try {
1832
+ // Clean up parking spot
1833
+ if (this.parkingSpot) {
1834
+ this.parkingSpot.occupied = false;
1835
+ this.parkingSpot.car = null;
1836
+ }
1837
+
1838
+ // Remove from parking queue
1839
+ this.leaveParkingQueue();
1840
+
1841
+ // Clean up convoy relationships
1842
+ if (this.convoyFollowers && this.convoyFollowers.length > 0) {
1843
+ this.convoyFollowers.forEach(follower => {
1844
+ if (follower) {
1845
+ follower.convoyLeader = null;
1846
+ follower.followTarget = null;
1847
+ follower.convoyPosition = -1;
1848
+ }
1849
+ });
1850
+ }
1851
+
1852
+ if (this.convoyLeader) {
1853
+ const index = this.convoyLeader.convoyFollowers.indexOf(this);
1854
+ if (index !== -1) {
1855
+ this.convoyLeader.convoyFollowers.splice(index, 1);
1856
+ }
1857
+ }
1858
+
1859
+ // Clean up visual elements
1860
+ if (this.flockLines) {
1861
+ this.flockLines.forEach(line => {
1862
+ try {
1863
+ if (line && line.parent) {
1864
+ scene.remove(line);
1865
+ }
1866
+ } catch (error) {
1867
+ console.warn('Error removing flock line:', error);
1868
+ }
1869
+ });
1870
+ }
1871
+
1872
+ // Remove mesh from scene
1873
+ if (this.mesh && this.mesh.parent) {
1874
+ scene.remove(this.mesh);
1875
+ }
1876
+ } catch (error) {
1877
+ console.warn('Error in car destroy method:', error);
1878
  }
1879
  }
1880
  }
 
2249
 
2250
  world.buildings.push({ mesh: building });
2251
 
2252
+ // Create enhanced parking lot with multiple access points
2253
  const parkingLot = {
2254
  center: new THREE.Vector3(loc.x + width/2 + 25, 0.1, loc.z),
2255
  spots: [],
2256
  queue: [],
2257
+ approachLanes: [], // Multiple approach lanes
2258
+ exitLanes: [], // Multiple exit lanes
2259
+ accessPoints: [] // Multiple access points
2260
  };
2261
 
2262
  // Main parking lot surface (larger)
 
2266
  lot.position.copy(parkingLot.center);
2267
  scene.add(lot);
2268
 
2269
+ // Create multiple approach lanes (3 lanes)
2270
+ for (let laneNum = 0; laneNum < 3; laneNum++) {
2271
+ const laneOffset = (laneNum - 1) * 10; // -10, 0, 10 offset
2272
+
2273
+ // Approach lane
2274
+ const approachGeometry = new THREE.PlaneGeometry(6, 60);
2275
+ const approachLane = new THREE.Mesh(approachGeometry, queueMaterial);
2276
+ approachLane.rotation.x = -Math.PI / 2;
2277
+ approachLane.position.set(parkingLot.center.x - 35, 0.08, parkingLot.center.z + laneOffset);
2278
+ scene.add(approachLane);
2279
+
2280
+ // Create approach queue positions for this lane
2281
+ const lanePositions = [];
2282
+ for (let q = 0; q < 10; q++) {
2283
+ const queuePos = new THREE.Vector3(
2284
+ parkingLot.center.x - 35,
2285
+ 1,
2286
+ parkingLot.center.z + laneOffset - 25 + (q * 5) // 5m spacing
2287
+ );
2288
+ lanePositions.push(queuePos);
2289
+ }
2290
+ parkingLot.approachLanes.push(lanePositions);
2291
  }
2292
 
2293
+ // Create multiple exit lanes (2 lanes)
2294
+ for (let exitNum = 0; exitNum < 2; exitNum++) {
2295
+ const exitOffset = (exitNum - 0.5) * 15; // -7.5, 7.5 offset
2296
+
2297
+ // Exit lane
2298
+ const exitGeometry = new THREE.PlaneGeometry(6, 50);
2299
+ const exitLane = new THREE.Mesh(exitGeometry, queueMaterial);
2300
+ exitLane.rotation.x = -Math.PI / 2;
2301
+ exitLane.position.set(parkingLot.center.x + 35, 0.08, parkingLot.center.z + exitOffset);
2302
+ scene.add(exitLane);
2303
+
2304
+ // Create exit positions for this lane
2305
+ const exitPositions = [];
2306
+ for (let q = 0; q < 8; q++) {
2307
+ const exitPos = new THREE.Vector3(
2308
+ parkingLot.center.x + 35,
2309
+ 1,
2310
+ parkingLot.center.z + exitOffset - 20 + (q * 5)
2311
+ );
2312
+ exitPositions.push(exitPos);
2313
+ }
2314
+ parkingLot.exitLanes.push(exitPositions);
2315
  }
2316
 
2317
+ // Create multiple access points with road connections
2318
+ const accessPoints = [
2319
+ { pos: new THREE.Vector3(loc.x + width/2 + 10, 1, loc.z + depth/2 + 15), name: 'north' },
2320
+ { pos: new THREE.Vector3(loc.x + width/2 + 10, 1, loc.z - depth/2 - 15), name: 'south' },
2321
+ { pos: new THREE.Vector3(loc.x + width + 15, 1, loc.z), name: 'east' },
2322
+ { pos: new THREE.Vector3(loc.x - 15, 1, loc.z), name: 'west' }
2323
+ ];
2324
+
2325
+ parkingLot.accessPoints = accessPoints;
2326
+
2327
  // Create parking spots (5x8 grid = 40 spots)
2328
  for (let row = 0; row < 5; row++) {
2329
  for (let col = 0; col < 8; col++) {
 
2376
  }
2377
 
2378
  function evolvePopulation() {
2379
+ try {
2380
+ // Sort by fitness with error handling
2381
+ population.sort((a, b) => {
2382
+ const fitnessA = a.fitness || 0;
2383
+ const fitnessB = b.fitness || 0;
2384
+ return fitnessB - fitnessA;
2385
+ });
2386
+
2387
+ // Advanced selection
2388
+ const eliteCount = Math.floor(populationSize * 0.15);
2389
+ const tournamentCount = Math.floor(populationSize * 0.25);
 
 
 
 
2390
 
2391
+ const survivors = population.slice(0, eliteCount);
2392
+
2393
+ // Tournament selection with error handling
2394
+ for (let i = 0; i < tournamentCount; i++) {
2395
+ try {
2396
+ const tournamentSize = 5;
2397
+ let best = null;
2398
+ let bestFitness = -Infinity;
2399
+
2400
+ for (let j = 0; j < tournamentSize; j++) {
2401
+ const candidateIndex = Math.floor(Math.random() * Math.min(population.length, populationSize * 0.5));
2402
+ const candidate = population[candidateIndex];
2403
+ if (candidate && (candidate.fitness || 0) > bestFitness) {
2404
+ best = candidate;
2405
+ bestFitness = candidate.fitness || 0;
2406
+ }
2407
+ }
2408
+ if (best) survivors.push(best);
2409
+ } catch (error) {
2410
+ console.warn('Error in tournament selection:', error);
2411
  }
2412
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2413
 
2414
+ // Clean up old population
2415
+ population.forEach(car => {
2416
+ try {
2417
+ car.destroy();
2418
+ } catch (error) {
2419
+ console.warn('Error destroying car during evolution:', error);
2420
+ }
2421
+ });
2422
+
2423
+ // Create new population
2424
+ const newPopulation = [];
2425
+ const roadPositions = [
2426
+ { x: -280, z: 0 }, { x: 280, z: 0 },
2427
+ { x: 0, z: -280 }, { x: 0, z: 280 },
2428
+ { x: -130, z: 0 }, { x: 130, z: 0 },
2429
+ { x: 0, z: -130 }, { x: 0, z: 130 }
2430
+ ];
2431
+
2432
+ // Elite reproduction
2433
+ survivors.forEach((parent, index) => {
2434
+ try {
2435
+ const startPos = roadPositions[index % roadPositions.length];
2436
+ const newCar = new TrafficCar(
2437
+ startPos.x + (Math.random() - 0.5) * 10,
2438
+ startPos.z + (Math.random() - 0.5) * 10
2439
+ );
2440
+ if (parent.brain) {
2441
+ newCar.brain = parent.brain.copy();
2442
+ }
2443
+ newPopulation.push(newCar);
2444
+ scene.add(newCar.mesh);
2445
+ } catch (error) {
2446
+ console.warn('Error creating elite offspring:', error);
2447
+ }
2448
+ });
2449
+
2450
+ // Mutated offspring
2451
+ while (newPopulation.length < populationSize) {
2452
+ try {
2453
+ const parentIndex = Math.floor(Math.random() * Math.min(survivors.length, eliteCount * 2));
2454
+ const parent = survivors[parentIndex];
2455
+ const startPos = roadPositions[newPopulation.length % roadPositions.length];
2456
+
2457
+ const child = new TrafficCar(
2458
+ startPos.x + (Math.random() - 0.5) * 10,
2459
+ startPos.z + (Math.random() - 0.5) * 10
2460
+ );
2461
+
2462
+ if (parent && parent.brain) {
2463
+ child.brain = parent.brain.copy();
2464
+ const mutationRate = (parent.fitness || 0) > bestFitness * 0.8 ? 0.05 : 0.15;
2465
+ child.brain.mutate(mutationRate);
2466
+ }
2467
+
2468
+ newPopulation.push(child);
2469
+ scene.add(child.mesh);
2470
+ } catch (error) {
2471
+ console.warn('Error creating mutated offspring:', error);
2472
+ // Create a basic car as fallback
2473
+ try {
2474
+ const startPos = roadPositions[newPopulation.length % roadPositions.length];
2475
+ const basicCar = new TrafficCar(
2476
+ startPos.x + (Math.random() - 0.5) * 10,
2477
+ startPos.z + (Math.random() - 0.5) * 10
2478
+ );
2479
+ newPopulation.push(basicCar);
2480
+ scene.add(basicCar.mesh);
2481
+ } catch (fallbackError) {
2482
+ console.error('Error creating fallback car:', fallbackError);
2483
+ }
2484
+ }
2485
+ }
2486
+
2487
+ population = newPopulation;
2488
+
2489
+ // Update epoch
2490
+ epoch++;
2491
+ timeLeft = epochTime;
2492
+ bestFitness = Math.max(bestFitness, survivors[0]?.fitness || 0);
2493
+ crashCount = 0;
2494
+ parkingEvents = 0;
2495
+ laneViolations = 0;
2496
 
2497
+ console.log(`Epoch ${epoch}: Best fitness: ${bestFitness.toFixed(1)}, Population: ${population.length}`);
 
2498
 
2499
+ } catch (error) {
2500
+ console.error('Critical error during evolution:', error);
2501
+ // Try to recover by creating a new basic population
2502
+ try {
2503
+ createInitialPopulation();
2504
+ } catch (recoveryError) {
2505
+ console.error('Failed to recover from evolution error:', recoveryError);
2506
+ }
2507
  }
 
 
 
 
 
 
 
 
 
 
 
 
2508
  }
2509
 
2510
  function animate() {
 
2543
  approaching: 0 // Cars approaching parking
2544
  };
2545
 
2546
+ // Update each car with error handling
2547
+ population.forEach((car, index) => {
2548
+ try {
2549
+ car.update(deltaTime);
 
 
 
 
 
2550
 
2551
+ if (!car.crashed) {
2552
+ stats.alive++;
2553
+ stats.totalRoadTime += car.roadTime || 0;
2554
+ stats.totalConvoyTime += car.convoyTime || 0;
2555
+ stats.totalParkingScore += car.parkingScore || 0;
2556
+ stats.totalViolations += car.trafficViolations || 0;
2557
+
2558
+ if (car.isParked) {
2559
+ stats.parked++;
2560
+ } else if (car.isParkingApproach) {
2561
+ stats.approaching++;
2562
+ } else if (car.role === 'leader') {
2563
+ stats.leaders++;
2564
+ const followerCount = car.convoyFollowers ? car.convoyFollowers.length : 0;
2565
+ stats.maxConvoySize = Math.max(stats.maxConvoySize, followerCount + 1);
2566
+ } else if (car.convoyPosition >= 0) {
2567
+ stats.convoy++;
2568
+ if (car.followingDistance > 0) {
2569
+ stats.totalFollowingDistance += car.followingDistance;
2570
+ stats.followingCount++;
2571
+ }
2572
+ } else {
2573
+ stats.solo++;
2574
  }
 
 
2575
  }
2576
+ } catch (error) {
2577
+ console.warn(`Error updating car ${index}:`, error);
2578
+ // Mark problematic car as crashed to prevent further errors
2579
+ car.crashed = true;
2580
  }
2581
  });
2582
 
 
2584
  }
2585
 
2586
  function updateCamera() {
2587
+ try {
2588
+ if (cameraMode === 'follow_best') {
2589
+ // Follow best performing car
2590
+ let bestCar = null;
2591
+ let bestFitness = -1;
 
 
 
 
 
 
2592
 
2593
+ population.forEach(car => {
2594
+ if (car && !car.crashed && !car.isParked && car.fitness > bestFitness) {
2595
+ bestCar = car;
2596
+ bestFitness = car.fitness;
2597
+ }
2598
+ });
2599
+
2600
+ if (bestCar && bestCar.mesh) {
2601
+ const targetPos = bestCar.mesh.position.clone();
2602
+ targetPos.y += 40;
2603
+ if (bestCar.velocity && bestCar.velocity.length() > 0.1) {
2604
+ targetPos.add(bestCar.velocity.clone().normalize().multiplyScalar(25));
2605
+ }
2606
+
2607
+ camera.position.lerp(targetPos, 0.03);
2608
+ camera.lookAt(bestCar.mesh.position);
2609
+ }
2610
+ } else if (cameraMode === 'follow_convoy') {
2611
+ // Follow largest convoy
2612
+ let largestConvoy = null;
2613
+ let maxFollowers = 0;
2614
 
2615
+ population.forEach(car => {
2616
+ if (car && car.role === 'leader' &&
2617
+ car.convoyFollowers && car.convoyFollowers.length > maxFollowers) {
2618
+ largestConvoy = car;
2619
+ maxFollowers = car.convoyFollowers.length;
2620
+ }
2621
+ });
2622
+
2623
+ if (largestConvoy && largestConvoy.mesh) {
2624
+ const targetPos = largestConvoy.mesh.position.clone();
2625
+ targetPos.y += 50;
2626
+ if (largestConvoy.velocity && largestConvoy.velocity.length() > 0.1) {
2627
+ targetPos.add(largestConvoy.velocity.clone().normalize().multiplyScalar(30));
2628
+ }
2629
+
2630
+ camera.position.lerp(targetPos, 0.03);
2631
+ camera.lookAt(largestConvoy.mesh.position);
2632
+ }
2633
+ } else {
2634
+ // Overview mode
2635
+ camera.position.lerp(new THREE.Vector3(0, 180, 180), 0.02);
2636
+ camera.lookAt(0, 0, 0);
2637
+ }
2638
+ } catch (error) {
2639
+ console.warn('Error updating camera:', error);
2640
+ // Fallback to overview mode
2641
+ try {
2642
+ camera.position.lerp(new THREE.Vector3(0, 180, 180), 0.02);
2643
+ camera.lookAt(0, 0, 0);
2644
+ } catch (fallbackError) {
2645
+ console.error('Critical camera error:', fallbackError);
2646
  }
 
 
 
 
2647
  }
2648
  }
2649
 
 
2749
  parkingEvents = 0;
2750
  laneViolations = 0;
2751
 
2752
+ // Reset parking lots and queues with error handling
2753
  world.parkingLots.forEach(lot => {
2754
+ try {
2755
+ lot.spots.forEach(spot => {
2756
+ spot.occupied = false;
2757
+ spot.car = null;
2758
+ });
2759
+ lot.queue = []; // Clear parking queues
2760
+
2761
+ // Initialize multiple lanes if they don't exist
2762
+ if (!lot.approachLanes) {
2763
+ lot.approachLanes = [lot.approachLane || []];
2764
+ }
2765
+ if (!lot.exitLanes) {
2766
+ lot.exitLanes = [lot.exitLane || []];
2767
+ }
2768
+ if (!lot.accessPoints) {
2769
+ lot.accessPoints = [];
2770
+ }
2771
+ } catch (error) {
2772
+ console.warn('Error resetting parking lot:', error);
2773
+ }
2774
  });
2775
 
2776
+ // Clean up population with error handling
2777
+ population.forEach(car => {
2778
+ try {
2779
+ car.destroy();
2780
+ } catch (error) {
2781
+ console.warn('Error destroying car:', error);
2782
+ }
2783
+ });
2784
+
2785
+ // Clear any remaining visual elements
2786
+ try {
2787
+ const linesToRemove = [];
2788
+ scene.traverse(child => {
2789
+ if (child.isLine && child.material && child.material.color) {
2790
+ // Check if it's a flock line (green color)
2791
+ if (child.material.color.g > 0.8) {
2792
+ linesToRemove.push(child);
2793
+ }
2794
+ }
2795
+ });
2796
+
2797
+ linesToRemove.forEach(line => {
2798
+ scene.remove(line);
2799
+ });
2800
+ } catch (error) {
2801
+ console.warn('Error cleaning up visual elements:', error);
2802
+ }
2803
+
2804
  createInitialPopulation();
2805
  }
2806