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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +867 -116
index.html CHANGED
@@ -354,6 +354,11 @@
354
  this.targetLane = null;
355
  this.lanePosition = 0; // -1 to 1 within lane
356
 
 
 
 
 
 
357
  // Convoy and flock behavior
358
  this.flockId = -1;
359
  this.convoyPosition = -1; // Position in convoy (-1 = not in convoy)
@@ -362,10 +367,19 @@
362
  this.followTarget = null;
363
  this.role = 'driver'; // driver, leader, parker
364
 
365
- // Traffic behavior
366
  this.isParked = false;
367
  this.parkingSpot = null;
368
  this.targetParkingLot = null;
 
 
 
 
 
 
 
 
 
369
  this.turnSignal = 'none'; // left, right, none
370
  this.laneDiscipline = 0;
371
  this.followingDistance = FOLLOW_DISTANCE;
@@ -510,37 +524,174 @@
510
  let nearestRoad = null;
511
  let minDistance = Infinity;
512
 
513
- // Check horizontal roads
514
- for (let roadZ = -300; roadZ <= 300; roadZ += ROAD_SPACING) {
515
- const distToRoad = Math.abs(pos.z - roadZ);
516
- if (distToRoad < ROAD_WIDTH / 2 && distToRoad < minDistance) {
517
- minDistance = distToRoad;
518
- const laneCenter = roadZ + (pos.x > 0 ? -LANE_WIDTH/2 : LANE_WIDTH/2);
 
 
519
  nearestRoad = {
520
- lane: 'horizontal',
521
  center: laneCenter,
522
- direction: pos.x > 0 ? Math.PI : 0 // Left side driving
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  };
524
  }
525
  }
526
 
527
- // Check vertical roads
528
- for (let roadX = -300; roadX <= 300; roadX += ROAD_SPACING) {
529
- const distToRoad = Math.abs(pos.x - roadX);
530
- if (distToRoad < ROAD_WIDTH / 2 && distToRoad < minDistance) {
531
- minDistance = distToRoad;
532
- const laneCenter = roadX + (pos.z > 0 ? -LANE_WIDTH/2 : LANE_WIDTH/2);
533
  nearestRoad = {
534
- lane: 'vertical',
 
 
 
 
 
 
 
 
 
 
 
 
535
  center: laneCenter,
536
- direction: pos.z > 0 ? -Math.PI/2 : Math.PI/2 // Left side driving
 
537
  };
538
  }
539
  }
540
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  return nearestRoad;
542
  }
543
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  updateSensors() {
545
  const maxDistance = 10;
546
  const raycaster = new THREE.Raycaster();
@@ -746,11 +897,108 @@
746
  }
747
  });
748
 
 
 
 
749
  // Determine role and convoy behavior
750
  this.updateRole();
751
  this.updateConvoyFormation();
752
  }
753
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
754
  updateRole() {
755
  const roadPos = this.getRoadPosition();
756
 
@@ -801,11 +1049,19 @@
801
  }
802
 
803
  update(deltaTime) {
804
- if (this.crashed || this.isParked) return;
 
 
 
 
 
 
805
 
806
  this.timeAlive -= deltaTime;
807
- if (this.timeAlive <= 0) {
808
- this.attemptParking();
 
 
809
  return;
810
  }
811
 
@@ -897,17 +1153,38 @@
897
 
898
  followRoad(deltaTime) {
899
  const roadInfo = this.findNearestRoad();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
900
  if (!roadInfo) return;
901
 
902
- // Lane-keeping force
903
  const laneKeepingForce = this.brain.trafficTraits.laneKeeping;
904
  let targetDirection = roadInfo.direction;
905
 
906
- // Adjust for lane position
907
  if (roadInfo.lane === 'horizontal') {
908
  const laneOffset = this.mesh.position.z - roadInfo.center;
909
  if (Math.abs(laneOffset) > LANE_WIDTH / 4) {
910
- const correction = -laneOffset * 0.02 * laneKeepingForce;
911
  this.velocity.z += correction;
912
  if (Math.abs(laneOffset) > LANE_WIDTH / 2) {
913
  this.trafficViolations++;
@@ -917,7 +1194,7 @@
917
  } else {
918
  const laneOffset = this.mesh.position.x - roadInfo.center;
919
  if (Math.abs(laneOffset) > LANE_WIDTH / 4) {
920
- const correction = -laneOffset * 0.02 * laneKeepingForce;
921
  this.velocity.x += correction;
922
  if (Math.abs(laneOffset) > LANE_WIDTH / 2) {
923
  this.trafficViolations++;
@@ -926,14 +1203,14 @@
926
  }
927
  }
928
 
929
- // Direction alignment
930
  const currentDirection = Math.atan2(this.velocity.x, this.velocity.z);
931
  let angleDiff = targetDirection - currentDirection;
932
  while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
933
  while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
934
 
935
  if (Math.abs(angleDiff) > 0.1) {
936
- this.mesh.rotation.y += angleDiff * 0.05 * laneKeepingForce;
937
  }
938
 
939
  this.roadTime += deltaTime;
@@ -971,48 +1248,204 @@
971
  }
972
 
973
  executeParking(deltaTime) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
974
  if (!this.targetParkingLot) return;
975
 
976
- // Find available parking spot
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
977
  const availableSpot = this.targetParkingLot.spots.find(spot => !spot.occupied);
978
- if (!availableSpot) return;
979
-
980
- const spotPos = availableSpot.position;
981
- const distance = this.mesh.position.distanceTo(spotPos);
982
-
983
- if (distance < 2) {
984
- // Park successfully
985
- this.isParked = true;
986
- this.parkingSpot = availableSpot;
987
- availableSpot.occupied = true;
988
- availableSpot.car = this;
989
- this.mesh.position.copy(spotPos);
990
- this.velocity.set(0, 0, 0);
991
- this.parkingScore += 100;
992
- parkingEvents++;
993
- this.updateCarColor();
994
  } else {
995
- // Move toward parking spot
996
- const direction = spotPos.clone().sub(this.mesh.position).normalize();
997
- this.velocity.copy(direction.multiplyScalar(5));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
998
  this.mesh.position.add(this.velocity.clone().multiplyScalar(deltaTime));
999
 
1000
- // Align with parking spot
1001
  const targetAngle = Math.atan2(direction.x, direction.z);
1002
  this.mesh.rotation.y = targetAngle;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1003
  }
1004
  }
1005
 
1006
  attemptParking() {
 
 
 
1007
  if (!this.targetParkingLot) {
1008
- this.crashed = true;
 
 
1009
  return;
1010
  }
1011
 
1012
- this.executeParking(0.016); // Try one more time
1013
- if (!this.isParked) {
1014
- this.crashed = true;
1015
- }
1016
  }
1017
 
1018
  updateFitness(deltaTime) {
@@ -1144,27 +1577,47 @@
1144
  checkCollisions() {
1145
  const carBox = new THREE.Box3().setFromObject(this.mesh);
1146
 
 
 
 
 
1147
  // Soft collision with other cars
1148
  population.forEach(otherCar => {
1149
  if (otherCar !== this && !otherCar.crashed && !otherCar.isParked) {
1150
  const otherBox = new THREE.Box3().setFromObject(otherCar.mesh);
1151
- if (carBox.intersectsBox(otherBox)) {
 
 
 
 
 
 
 
 
1152
  const separation = new THREE.Vector3()
1153
  .subVectors(this.mesh.position, otherCar.mesh.position)
1154
- .normalize()
1155
- .multiplyScalar(3);
1156
-
1157
- this.velocity.add(separation.multiplyScalar(0.3));
1158
- otherCar.velocity.sub(separation.multiplyScalar(0.3));
1159
 
1160
- this.fitness -= 20;
1161
- otherCar.fitness -= 20;
1162
- this.trafficViolations++;
 
 
 
 
 
 
 
 
 
 
 
 
1163
  }
1164
  }
1165
  });
1166
 
1167
- // Building collisions
1168
  world.buildings.forEach(building => {
1169
  const buildingBox = new THREE.Box3().setFromObject(building.mesh);
1170
  if (carBox.intersectsBox(buildingBox)) {
@@ -1191,11 +1644,16 @@
1191
  }
1192
 
1193
  destroy() {
 
1194
  if (this.parkingSpot) {
1195
  this.parkingSpot.occupied = false;
1196
  this.parkingSpot.car = null;
1197
  }
1198
 
 
 
 
 
1199
  this.flockLines.forEach(line => {
1200
  if (line.parent) scene.remove(line);
1201
  });
@@ -1246,52 +1704,300 @@
1246
  }
1247
 
1248
  function createTrafficWorld() {
1249
- // Ground
1250
- const groundGeometry = new THREE.PlaneGeometry(1000, 1000);
1251
  const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x228B22 });
1252
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
1253
  ground.rotation.x = -Math.PI / 2;
1254
  ground.receiveShadow = true;
1255
  scene.add(ground);
1256
 
 
1257
  createRoadNetwork();
 
 
1258
  createBuildingsWithParkingLots();
1259
  }
1260
 
1261
  function createRoadNetwork() {
1262
  const roadMaterial = new THREE.MeshLambertMaterial({ color: 0x444444 });
 
1263
  const lineMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1264
 
1265
- // Create road grid
1266
- for (let i = -300; i <= 300; i += ROAD_SPACING) {
1267
- // Horizontal roads
1268
- const hRoadGeometry = new THREE.PlaneGeometry(600, ROAD_WIDTH);
1269
- const hRoad = new THREE.Mesh(hRoadGeometry, roadMaterial);
1270
- hRoad.rotation.x = -Math.PI / 2;
1271
- hRoad.position.set(0, 0.1, i);
1272
- scene.add(hRoad);
1273
-
1274
- // Lane dividers for horizontal roads
1275
- const hLineGeometry = new THREE.PlaneGeometry(600, 0.3);
1276
- const hLine = new THREE.Mesh(hLineGeometry, lineMaterial);
1277
- hLine.rotation.x = -Math.PI / 2;
1278
- hLine.position.set(0, 0.15, i);
1279
- scene.add(hLine);
1280
-
1281
- // Vertical roads
1282
- const vRoadGeometry = new THREE.PlaneGeometry(ROAD_WIDTH, 600);
1283
- const vRoad = new THREE.Mesh(vRoadGeometry, roadMaterial);
1284
- vRoad.rotation.x = -Math.PI / 2;
1285
- vRoad.position.set(i, 0.1, 0);
1286
- scene.add(vRoad);
1287
-
1288
- // Lane dividers for vertical roads
1289
- const vLineGeometry = new THREE.PlaneGeometry(0.3, 600);
1290
- const vLine = new THREE.Mesh(vLineGeometry, lineMaterial);
1291
- vLine.rotation.x = -Math.PI / 2;
1292
- vLine.position.set(i, 0.15, 0);
1293
- scene.add(vLine);
1294
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1295
  }
1296
 
1297
  function createBuildingsWithParkingLots() {
@@ -1301,22 +2007,24 @@
1301
  const buildingMaterial = new THREE.MeshLambertMaterial({ color: 0x666666 });
1302
  const parkingMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });
1303
  const spotMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
 
1304
 
1305
- // Create buildings at strategic locations
1306
  const buildingLocations = [
1307
- { x: -75, z: -75 }, { x: 75, z: -75 },
1308
- { x: -75, z: 75 }, { x: 75, z: 75 },
1309
- { x: -225, z: -225 }, { x: 225, z: -225 },
1310
- { x: -225, z: 225 }, { x: 225, z: 225 },
1311
- { x: 0, z: -150 }, { x: 0, z: 150 },
1312
- { x: -150, z: 0 }, { x: 150, z: 0 }
 
1313
  ];
1314
 
1315
- buildingLocations.forEach(loc => {
1316
  // Create building
1317
- const width = 25 + Math.random() * 15;
1318
- const height = 15 + Math.random() * 25;
1319
- const depth = 25 + Math.random() * 15;
1320
 
1321
  const buildingGeometry = new THREE.BoxGeometry(width, height, depth);
1322
  const building = new THREE.Mesh(buildingGeometry, buildingMaterial);
@@ -1326,24 +2034,61 @@
1326
 
1327
  world.buildings.push({ mesh: building });
1328
 
1329
- // Create large parking lot next to building
1330
  const parkingLot = {
1331
- center: new THREE.Vector3(loc.x + width/2 + 20, 0.1, loc.z),
1332
- spots: []
 
 
 
1333
  };
1334
 
1335
- // Parking lot ground
1336
- const lotGeometry = new THREE.PlaneGeometry(40, 30);
1337
  const lot = new THREE.Mesh(lotGeometry, parkingMaterial);
1338
  lot.rotation.x = -Math.PI / 2;
1339
  lot.position.copy(parkingLot.center);
1340
  scene.add(lot);
1341
 
1342
- // Create parking spots (4x6 grid = 24 spots)
1343
- for (let row = 0; row < 4; row++) {
1344
- for (let col = 0; col < 6; col++) {
1345
- const spotX = parkingLot.center.x + (col - 2.5) * 6;
1346
- const spotZ = parkingLot.center.z + (row - 1.5) * 7;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1347
 
1348
  // Spot marking
1349
  const spotGeometry = new THREE.PlaneGeometry(PARKING_SPOT_SIZE.width, PARKING_SPOT_SIZE.length);
@@ -1505,7 +2250,8 @@
1505
  totalParkingScore: 0,
1506
  totalViolations: 0,
1507
  totalFollowingDistance: 0,
1508
- followingCount: 0
 
1509
  };
1510
 
1511
  population.forEach(car => {
@@ -1520,6 +2266,8 @@
1520
 
1521
  if (car.isParked) {
1522
  stats.parked++;
 
 
1523
  } else if (car.role === 'leader') {
1524
  stats.leaders++;
1525
  stats.maxConvoySize = Math.max(stats.maxConvoySize, car.convoyFollowers.length + 1);
@@ -1677,12 +2425,15 @@
1677
  parkingEvents = 0;
1678
  laneViolations = 0;
1679
 
1680
- // Reset parking lots
1681
  world.parkingLots.forEach(lot => {
1682
  lot.spots.forEach(spot => {
1683
  spot.occupied = false;
1684
  spot.car = null;
1685
  });
 
 
 
1686
  });
1687
 
1688
  population.forEach(car => car.destroy());
 
354
  this.targetLane = null;
355
  this.lanePosition = 0; // -1 to 1 within lane
356
 
357
+ // Road transition tracking
358
+ this.lastRoadPosition = 0;
359
+ this.isTransitioningToRoad = false;
360
+ this.roadTransitionTime = 0;
361
+
362
  // Convoy and flock behavior
363
  this.flockId = -1;
364
  this.convoyPosition = -1; // Position in convoy (-1 = not in convoy)
 
367
  this.followTarget = null;
368
  this.role = 'driver'; // driver, leader, parker
369
 
370
+ // Enhanced parking system
371
  this.isParked = false;
372
  this.parkingSpot = null;
373
  this.targetParkingLot = null;
374
+ this.parkingQueue = -1; // Position in parking queue (-1 = not queued)
375
+ this.isParkingApproach = false;
376
+ this.isInApproachLane = false;
377
+ this.isInExitLane = false;
378
+ this.approachTarget = null;
379
+ this.exitTarget = null;
380
+ this.parkingAttempts = 0;
381
+ this.maxParkingAttempts = 3;
382
+ this.departureTime = 0;
383
  this.turnSignal = 'none'; // left, right, none
384
  this.laneDiscipline = 0;
385
  this.followingDistance = FOLLOW_DISTANCE;
 
524
  let nearestRoad = null;
525
  let minDistance = Infinity;
526
 
527
+ // Check major highways (8 lanes - 48 units wide)
528
+ const mainHighwayPositions = [0]; // Main cross highways
529
+ mainHighwayPositions.forEach(roadPos => {
530
+ // Horizontal highway
531
+ const distToHorizontal = Math.abs(pos.z - roadPos);
532
+ if (distToHorizontal < 24 && distToHorizontal < minDistance) {
533
+ minDistance = distToHorizontal;
534
+ const laneCenter = roadPos + (pos.x > 0 ? -12 : 12); // 4 lanes each direction
535
  nearestRoad = {
536
+ lane: 'highway_horizontal',
537
  center: laneCenter,
538
+ direction: pos.x > 0 ? Math.PI : 0,
539
+ width: 48
540
+ };
541
+ }
542
+
543
+ // Vertical highway
544
+ const distToVertical = Math.abs(pos.x - roadPos);
545
+ if (distToVertical < 24 && distToVertical < minDistance) {
546
+ minDistance = distToVertical;
547
+ const laneCenter = roadPos + (pos.z > 0 ? -12 : 12);
548
+ nearestRoad = {
549
+ lane: 'highway_vertical',
550
+ center: laneCenter,
551
+ direction: pos.z > 0 ? -Math.PI/2 : Math.PI/2,
552
+ width: 48
553
+ };
554
+ }
555
+ });
556
+
557
+ // Check secondary highways (4 lanes - 24 units wide)
558
+ for (let roadPos = -300; roadPos <= 300; roadPos += 150) {
559
+ if (roadPos === 0) continue; // Skip main highway
560
+
561
+ const distToHorizontal = Math.abs(pos.z - roadPos);
562
+ if (distToHorizontal < 12 && distToHorizontal < minDistance) {
563
+ minDistance = distToHorizontal;
564
+ const laneCenter = roadPos + (pos.x > 0 ? -6 : 6);
565
+ nearestRoad = {
566
+ lane: 'secondary_horizontal',
567
+ center: laneCenter,
568
+ direction: pos.x > 0 ? Math.PI : 0,
569
+ width: 24
570
+ };
571
+ }
572
+
573
+ const distToVertical = Math.abs(pos.x - roadPos);
574
+ if (distToVertical < 12 && distToVertical < minDistance) {
575
+ minDistance = distToVertical;
576
+ const laneCenter = roadPos + (pos.z > 0 ? -6 : 6);
577
+ nearestRoad = {
578
+ lane: 'secondary_vertical',
579
+ center: laneCenter,
580
+ direction: pos.z > 0 ? -Math.PI/2 : Math.PI/2,
581
+ width: 24
582
  };
583
  }
584
  }
585
 
586
+ // Check local roads (2 lanes - 12 units wide)
587
+ for (let roadPos = -375; roadPos <= 375; roadPos += 75) {
588
+ const distToHorizontal = Math.abs(pos.z - roadPos);
589
+ if (distToHorizontal < 6 && distToHorizontal < minDistance) {
590
+ minDistance = distToHorizontal;
591
+ const laneCenter = roadPos + (pos.x > 0 ? -3 : 3);
592
  nearestRoad = {
593
+ lane: 'local_horizontal',
594
+ center: laneCenter,
595
+ direction: pos.x > 0 ? Math.PI : 0,
596
+ width: 12
597
+ };
598
+ }
599
+
600
+ const distToVertical = Math.abs(pos.x - roadPos);
601
+ if (distToVertical < 6 && distToVertical < minDistance) {
602
+ minDistance = distToVertical;
603
+ const laneCenter = roadPos + (pos.z > 0 ? -3 : 3);
604
+ nearestRoad = {
605
+ lane: 'local_vertical',
606
  center: laneCenter,
607
+ direction: pos.z > 0 ? -Math.PI/2 : Math.PI/2,
608
+ width: 12
609
  };
610
  }
611
  }
612
 
613
+ // Check building access roads (less precise matching)
614
+ if (!nearestRoad || minDistance > 8) {
615
+ world.buildings.forEach(building => {
616
+ const buildingPos = building.mesh.position;
617
+ const buildingBox = new THREE.Box3().setFromObject(building.mesh);
618
+ const size = buildingBox.getSize(new THREE.Vector3());
619
+
620
+ // Check proximity to building access roads
621
+ const accessRoadPositions = [
622
+ { x: buildingPos.x, z: buildingPos.z + size.z/2 + 10, dir: 'horizontal' },
623
+ { x: buildingPos.x, z: buildingPos.z - size.z/2 - 10, dir: 'horizontal' },
624
+ { x: buildingPos.x + size.x/2 + 10, z: buildingPos.z, dir: 'vertical' },
625
+ { x: buildingPos.x - size.x/2 - 10, z: buildingPos.z, dir: 'vertical' }
626
+ ];
627
+
628
+ accessRoadPositions.forEach(accessRoad => {
629
+ const dist = pos.distanceTo(new THREE.Vector3(accessRoad.x, 0, accessRoad.z));
630
+ if (dist < 8 && dist < minDistance) {
631
+ minDistance = dist;
632
+ nearestRoad = {
633
+ lane: `access_${accessRoad.dir}`,
634
+ center: accessRoad.dir === 'horizontal' ? accessRoad.z : accessRoad.x,
635
+ direction: accessRoad.dir === 'horizontal' ?
636
+ (pos.x > accessRoad.x ? Math.PI : 0) :
637
+ (pos.z > accessRoad.z ? -Math.PI/2 : Math.PI/2),
638
+ width: 8
639
+ };
640
+ }
641
+ });
642
+ });
643
+ }
644
+
645
  return nearestRoad;
646
  }
647
 
648
+ getRoadPosition() {
649
+ const pos = this.mesh.position;
650
+ let maxRoadScore = 0;
651
+
652
+ // Check all road types for best road position score
653
+
654
+ // Major highways
655
+ const mainHighwayDist = Math.abs(pos.z);
656
+ if (mainHighwayDist <= 24) {
657
+ maxRoadScore = Math.max(maxRoadScore, 1 - (mainHighwayDist / 24));
658
+ }
659
+ const mainVerticalDist = Math.abs(pos.x);
660
+ if (mainVerticalDist <= 24) {
661
+ maxRoadScore = Math.max(maxRoadScore, 1 - (mainVerticalDist / 24));
662
+ }
663
+
664
+ // Secondary highways
665
+ for (let roadPos = -300; roadPos <= 300; roadPos += 150) {
666
+ if (roadPos === 0) continue;
667
+
668
+ const hDist = Math.abs(pos.z - roadPos);
669
+ if (hDist <= 12) {
670
+ maxRoadScore = Math.max(maxRoadScore, 1 - (hDist / 12));
671
+ }
672
+
673
+ const vDist = Math.abs(pos.x - roadPos);
674
+ if (vDist <= 12) {
675
+ maxRoadScore = Math.max(maxRoadScore, 1 - (vDist / 12));
676
+ }
677
+ }
678
+
679
+ // Local roads
680
+ for (let roadPos = -375; roadPos <= 375; roadPos += 75) {
681
+ const hDist = Math.abs(pos.z - roadPos);
682
+ if (hDist <= 6) {
683
+ maxRoadScore = Math.max(maxRoadScore, 1 - (hDist / 6));
684
+ }
685
+
686
+ const vDist = Math.abs(pos.x - roadPos);
687
+ if (vDist <= 6) {
688
+ maxRoadScore = Math.max(maxRoadScore, 1 - (vDist / 6));
689
+ }
690
+ }
691
+
692
+ return maxRoadScore;
693
+ }
694
+
695
  updateSensors() {
696
  const maxDistance = 10;
697
  const raycaster = new THREE.Raycaster();
 
897
  }
898
  });
899
 
900
+ // Calculate flock centroid and check if moving away
901
+ this.updateFlockCohesion();
902
+
903
  // Determine role and convoy behavior
904
  this.updateRole();
905
  this.updateConvoyFormation();
906
  }
907
 
908
+ updateFlockCohesion() {
909
+ if (this.neighbors.length === 0) return;
910
+
911
+ // Calculate flock centroid
912
+ const centroid = new THREE.Vector3();
913
+ this.neighbors.forEach(neighbor => {
914
+ centroid.add(neighbor.mesh.position);
915
+ });
916
+ centroid.divideScalar(this.neighbors.length);
917
+
918
+ // Check if moving away from centroid
919
+ const currentPos = this.mesh.position.clone();
920
+ const futurePos = currentPos.clone().add(this.velocity.clone().normalize().multiplyScalar(5));
921
+
922
+ const currentDistToCentroid = currentPos.distanceTo(centroid);
923
+ const futureDistToCentroid = futurePos.distanceTo(centroid);
924
+
925
+ // If moving away from flock, apply gentle correction
926
+ if (futureDistToCentroid > currentDistToCentroid && currentDistToCentroid > 15) {
927
+ const returnForce = centroid.clone().sub(currentPos).normalize().multiplyScalar(0.3);
928
+ this.velocity.add(returnForce);
929
+
930
+ // Gentle turning toward centroid
931
+ const targetDirection = Math.atan2(returnForce.x, returnForce.z);
932
+ const currentDirection = Math.atan2(this.velocity.x, this.velocity.z);
933
+ let angleDiff = targetDirection - currentDirection;
934
+
935
+ while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
936
+ while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
937
+
938
+ // Apply gentle turning
939
+ this.mesh.rotation.y += angleDiff * 0.02;
940
+
941
+ // Slow down for turning
942
+ this.velocity.multiplyScalar(0.98);
943
+ }
944
+
945
+ // Move flock centroid toward roads
946
+ this.guideFlockToRoads(centroid);
947
+ }
948
+
949
+ guideFlockToRoads(centroid) {
950
+ if (this.role !== 'leader') return;
951
+
952
+ // Find nearest road from centroid
953
+ const nearestRoadInfo = this.findNearestRoadFromPosition(centroid);
954
+ if (!nearestRoadInfo) return;
955
+
956
+ const roadDistance = nearestRoadInfo.distance;
957
+ if (roadDistance > 5) {
958
+ // Guide flock toward road
959
+ const roadDirection = nearestRoadInfo.direction;
960
+ const roadForce = roadDirection.clone().multiplyScalar(0.2);
961
+ this.velocity.add(roadForce);
962
+ }
963
+ }
964
+
965
+ findNearestRoadFromPosition(position) {
966
+ let nearestRoad = null;
967
+ let minDistance = Infinity;
968
+
969
+ // Check horizontal roads
970
+ for (let roadZ = -300; roadZ <= 300; roadZ += ROAD_SPACING) {
971
+ const distToRoad = Math.abs(position.z - roadZ);
972
+ if (distToRoad < minDistance) {
973
+ minDistance = distToRoad;
974
+ const direction = new THREE.Vector3(0, 0, roadZ - position.z).normalize();
975
+ nearestRoad = {
976
+ distance: distToRoad,
977
+ direction: direction,
978
+ lane: 'horizontal',
979
+ center: roadZ
980
+ };
981
+ }
982
+ }
983
+
984
+ // Check vertical roads
985
+ for (let roadX = -300; roadX <= 300; roadX += ROAD_SPACING) {
986
+ const distToRoad = Math.abs(position.x - roadX);
987
+ if (distToRoad < minDistance) {
988
+ minDistance = distToRoad;
989
+ const direction = new THREE.Vector3(roadX - position.x, 0, 0).normalize();
990
+ nearestRoad = {
991
+ distance: distToRoad,
992
+ direction: direction,
993
+ lane: 'vertical',
994
+ center: roadX
995
+ };
996
+ }
997
+ }
998
+
999
+ return nearestRoad;
1000
+ }
1001
+
1002
  updateRole() {
1003
  const roadPos = this.getRoadPosition();
1004
 
 
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
 
 
1153
 
1154
  followRoad(deltaTime) {
1155
  const roadInfo = this.findNearestRoad();
1156
+ const currentRoadPos = this.getRoadPosition();
1157
+
1158
+ // Check if transitioning from grass to road
1159
+ if (currentRoadPos > 0.3 && this.lastRoadPosition <= 0.3) {
1160
+ // Just touched road after being on grass - slow down
1161
+ this.velocity.multiplyScalar(0.7);
1162
+ this.isTransitioningToRoad = true;
1163
+ this.roadTransitionTime = 2.0; // 2 seconds to stabilize
1164
+ }
1165
+
1166
+ if (this.roadTransitionTime > 0) {
1167
+ this.roadTransitionTime -= deltaTime;
1168
+ // Maintain slower speed during transition
1169
+ const currentSpeed = this.velocity.length();
1170
+ if (currentSpeed > this.maxSpeed * 0.6) {
1171
+ this.velocity.normalize().multiplyScalar(this.maxSpeed * 0.6);
1172
+ }
1173
+ }
1174
+
1175
+ this.lastRoadPosition = currentRoadPos;
1176
+
1177
  if (!roadInfo) return;
1178
 
1179
+ // Enhanced lane-keeping force
1180
  const laneKeepingForce = this.brain.trafficTraits.laneKeeping;
1181
  let targetDirection = roadInfo.direction;
1182
 
1183
+ // Adjust for lane position with stronger correction
1184
  if (roadInfo.lane === 'horizontal') {
1185
  const laneOffset = this.mesh.position.z - roadInfo.center;
1186
  if (Math.abs(laneOffset) > LANE_WIDTH / 4) {
1187
+ const correction = -laneOffset * 0.05 * laneKeepingForce;
1188
  this.velocity.z += correction;
1189
  if (Math.abs(laneOffset) > LANE_WIDTH / 2) {
1190
  this.trafficViolations++;
 
1194
  } else {
1195
  const laneOffset = this.mesh.position.x - roadInfo.center;
1196
  if (Math.abs(laneOffset) > LANE_WIDTH / 4) {
1197
+ const correction = -laneOffset * 0.05 * laneKeepingForce;
1198
  this.velocity.x += correction;
1199
  if (Math.abs(laneOffset) > LANE_WIDTH / 2) {
1200
  this.trafficViolations++;
 
1203
  }
1204
  }
1205
 
1206
+ // Stronger direction alignment for road following
1207
  const currentDirection = Math.atan2(this.velocity.x, this.velocity.z);
1208
  let angleDiff = targetDirection - currentDirection;
1209
  while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
1210
  while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
1211
 
1212
  if (Math.abs(angleDiff) > 0.1) {
1213
+ this.mesh.rotation.y += angleDiff * 0.08 * laneKeepingForce;
1214
  }
1215
 
1216
  this.roadTime += deltaTime;
 
1248
  }
1249
 
1250
  executeParking(deltaTime) {
1251
+ if (!this.targetParkingLot) {
1252
+ this.findNearestParkingLot();
1253
+ return;
1254
+ }
1255
+
1256
+ // Enhanced parking state machine
1257
+ if (this.parkingQueue === -1) {
1258
+ this.joinParkingQueue();
1259
+ }
1260
+
1261
+ if (this.isInApproachLane) {
1262
+ this.handleApproachLane(deltaTime);
1263
+ } else if (this.canProceedToApproach()) {
1264
+ this.enterApproachLane(deltaTime);
1265
+ } else {
1266
+ this.waitForApproachAccess(deltaTime);
1267
+ }
1268
+ }
1269
+
1270
+ joinParkingQueue() {
1271
  if (!this.targetParkingLot) return;
1272
 
1273
+ if (!this.targetParkingLot.queue) {
1274
+ this.targetParkingLot.queue = [];
1275
+ }
1276
+
1277
+ if (!this.targetParkingLot.queue.includes(this)) {
1278
+ this.targetParkingLot.queue.push(this);
1279
+ this.parkingQueue = this.targetParkingLot.queue.length - 1;
1280
+ }
1281
+ }
1282
+
1283
+ canProceedToApproach() {
1284
+ if (!this.targetParkingLot || !this.targetParkingLot.queue) return false;
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
+
1342
+ completeParkingProcess(spot) {
1343
+ this.isParked = true;
1344
+ this.parkingSpot = spot;
1345
+ spot.occupied = true;
1346
+ spot.car = this;
1347
+ this.mesh.position.copy(spot.position);
1348
+ this.velocity.set(0, 0, 0);
1349
+ this.parkingScore += 100;
1350
+ this.leaveParkingQueue();
1351
+ this.isInApproachLane = false;
1352
+
1353
+ parkingEvents++;
1354
+
1355
+ this.departureTime = 15 + Math.random() * 25;
1356
+ this.updateCarColor();
1357
+ }
1358
+
1359
+ waitForApproachAccess(deltaTime) {
1360
+ // Line up end-to-end near parking lot
1361
+ const queuePosition = Math.min(this.parkingQueue, 5);
1362
+ const queueTarget = this.targetParkingLot.center.clone();
1363
+ queueTarget.add(new THREE.Vector3(-50, 0, -15 + queuePosition * 5)); // 5m spacing
1364
+
1365
+ this.moveToPosition(queueTarget, deltaTime, 3);
1366
+ }
1367
+
1368
+ moveToPosition(targetPos, deltaTime, speed) {
1369
+ const direction = targetPos.clone().sub(this.mesh.position);
1370
+ const distance = direction.length();
1371
+
1372
+ if (distance > 1) {
1373
+ direction.normalize();
1374
+ this.velocity.copy(direction.multiplyScalar(speed));
1375
  this.mesh.position.add(this.velocity.clone().multiplyScalar(deltaTime));
1376
 
1377
+ // Align rotation with movement
1378
  const targetAngle = Math.atan2(direction.x, direction.z);
1379
  this.mesh.rotation.y = targetAngle;
1380
+ } else {
1381
+ this.velocity.multiplyScalar(0.9);
1382
+ }
1383
+ }
1384
+
1385
+ leaveParking() {
1386
+ if (!this.isParked || !this.parkingSpot) return;
1387
+
1388
+ // Free the parking spot
1389
+ this.parkingSpot.occupied = false;
1390
+ this.parkingSpot.car = null;
1391
+ this.parkingSpot = null;
1392
+ this.isParked = false;
1393
+ this.isInApproachLane = false;
1394
+
1395
+ // Use exit lane for organized departure
1396
+ this.useExitLane();
1397
+ }
1398
+
1399
+ useExitLane() {
1400
+ // Find exit lane position
1401
+ const exitPositions = this.targetParkingLot.exitLane;
1402
+ let exitTarget = null;
1403
+
1404
+ for (let i = 0; i < exitPositions.length; i++) {
1405
+ const pos = exitPositions[i];
1406
+ const occupied = population.some(car =>
1407
+ car !== this &&
1408
+ car.mesh.position.distanceTo(pos) < 4
1409
+ );
1410
+
1411
+ if (!occupied) {
1412
+ exitTarget = pos;
1413
+ break;
1414
+ }
1415
+ }
1416
+
1417
+ if (exitTarget) {
1418
+ this.isInExitLane = true;
1419
+ this.exitTarget = exitTarget;
1420
+ this.mesh.position.copy(exitTarget);
1421
+
1422
+ // Set exit velocity
1423
+ const exitDirection = new THREE.Vector3(0, 0, 1); // Move south to exit
1424
+ this.velocity.copy(exitDirection.multiplyScalar(6));
1425
+
1426
+ // Schedule exit lane departure
1427
+ setTimeout(() => {
1428
+ this.isInExitLane = false;
1429
+ this.role = 'driver';
1430
+ this.timeAlive = 50 + Math.random() * 30;
1431
+ this.updateCarColor();
1432
+ }, 2000);
1433
  }
1434
  }
1435
 
1436
  attemptParking() {
1437
+ this.role = 'parker';
1438
+ this.findNearestParkingLot();
1439
+
1440
  if (!this.targetParkingLot) {
1441
+ // No parking available, become a wanderer
1442
+ this.timeAlive = 20;
1443
+ this.role = 'driver';
1444
  return;
1445
  }
1446
 
1447
+ // Start parking process
1448
+ this.isParkingApproach = true;
 
 
1449
  }
1450
 
1451
  updateFitness(deltaTime) {
 
1577
  checkCollisions() {
1578
  const carBox = new THREE.Box3().setFromObject(this.mesh);
1579
 
1580
+ // Modified collision behavior for parking areas
1581
+ const isParkingMode = this.isInApproachLane || this.isInExitLane ||
1582
+ this.isParkingApproach || this.role === 'parker';
1583
+
1584
  // Soft collision with other cars
1585
  population.forEach(otherCar => {
1586
  if (otherCar !== this && !otherCar.crashed && !otherCar.isParked) {
1587
  const otherBox = new THREE.Box3().setFromObject(otherCar.mesh);
1588
+ const distance = this.mesh.position.distanceTo(otherCar.mesh.position);
1589
+
1590
+ // Relaxed collision detection for parking behavior
1591
+ const minDistance = isParkingMode || otherCar.isInApproachLane ? 2.5 : 4.0;
1592
+ const bothInParkingMode = isParkingMode &&
1593
+ (otherCar.isInApproachLane || otherCar.isParkingApproach || otherCar.role === 'parker');
1594
+
1595
+ if (carBox.intersectsBox(otherBox) || (distance < minDistance && !bothInParkingMode)) {
1596
+ // Gentle separation instead of hard collision
1597
  const separation = new THREE.Vector3()
1598
  .subVectors(this.mesh.position, otherCar.mesh.position)
1599
+ .normalize();
 
 
 
 
1600
 
1601
+ if (isParkingMode) {
1602
+ // Very gentle separation for parking
1603
+ separation.multiplyScalar(1.5);
1604
+ this.velocity.add(separation.multiplyScalar(0.1));
1605
+ this.velocity.multiplyScalar(0.95); // Slow down
1606
+ } else {
1607
+ // Normal separation
1608
+ separation.multiplyScalar(3);
1609
+ this.velocity.add(separation.multiplyScalar(0.3));
1610
+ otherCar.velocity.sub(separation.multiplyScalar(0.3));
1611
+
1612
+ this.fitness -= 10;
1613
+ otherCar.fitness -= 10;
1614
+ this.trafficViolations++;
1615
+ }
1616
  }
1617
  }
1618
  });
1619
 
1620
+ // Building collisions (unchanged)
1621
  world.buildings.forEach(building => {
1622
  const buildingBox = new THREE.Box3().setFromObject(building.mesh);
1623
  if (carBox.intersectsBox(buildingBox)) {
 
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
  });
 
1704
  }
1705
 
1706
  function createTrafficWorld() {
1707
+ // Enhanced ground with road texture hints
1708
+ const groundGeometry = new THREE.PlaneGeometry(1200, 1200);
1709
  const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x228B22 });
1710
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
1711
  ground.rotation.x = -Math.PI / 2;
1712
  ground.receiveShadow = true;
1713
  scene.add(ground);
1714
 
1715
+ // Create comprehensive road network first
1716
  createRoadNetwork();
1717
+
1718
+ // Then create buildings with parking lots
1719
  createBuildingsWithParkingLots();
1720
  }
1721
 
1722
  function createRoadNetwork() {
1723
  const roadMaterial = new THREE.MeshLambertMaterial({ color: 0x444444 });
1724
+ const highwayMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });
1725
  const lineMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
1726
+ const yellowLineMaterial = new THREE.MeshLambertMaterial({ color: 0xffff00 });
1727
+
1728
+ // Create comprehensive road network
1729
+
1730
+ // 1. MAJOR HIGHWAYS (8 lanes) - Cross patterns
1731
+ createHighway(-400, 400, 0, 'horizontal', 48, highwayMaterial, yellowLineMaterial); // North highway
1732
+ createHighway(-400, 400, 0, 'vertical', 48, highwayMaterial, yellowLineMaterial); // Main highway
1733
+
1734
+ // 2. SECONDARY HIGHWAYS (4 lanes) - Grid system
1735
+ for (let i = -300; i <= 300; i += 150) {
1736
+ if (i !== 0) { // Don't overlap main highway
1737
+ createHighway(-400, 400, i, 'horizontal', 24, roadMaterial, yellowLineMaterial);
1738
+ createHighway(-400, 400, i, 'vertical', 24, roadMaterial, yellowLineMaterial);
1739
+ }
1740
+ }
1741
 
1742
+ // 3. LOCAL ROADS (2 lanes) - Connecting roads
1743
+ for (let i = -375; i <= 375; i += 75) {
1744
+ createLocalRoad(-400, 400, i, 'horizontal', 12, roadMaterial, lineMaterial);
1745
+ createLocalRoad(-400, 400, i, 'vertical', 12, roadMaterial, lineMaterial);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1746
  }
1747
+
1748
+ // 4. BUILDING ACCESS ROADS - Around all buildings
1749
+ createBuildingAccessRoads();
1750
+ }
1751
+
1752
+ function createHighway(start, end, position, direction, width, material, linemat) {
1753
+ if (direction === 'horizontal') {
1754
+ // Main road surface
1755
+ const roadGeometry = new THREE.PlaneGeometry(end - start, width);
1756
+ const road = new THREE.Mesh(roadGeometry, material);
1757
+ road.rotation.x = -Math.PI / 2;
1758
+ road.position.set((start + end) / 2, 0.1, position);
1759
+ scene.add(road);
1760
+
1761
+ // Lane dividers
1762
+ const laneCount = width / 6; // 6 units per lane
1763
+ for (let lane = 1; lane < laneCount; lane++) {
1764
+ const lineY = position - width/2 + (lane * 6);
1765
+
1766
+ // Center divider (yellow)
1767
+ if (lane === Math.floor(laneCount / 2)) {
1768
+ createDashedLine(start, end, lineY, 'horizontal', linemat, true);
1769
+ } else {
1770
+ // Regular lane dividers (white)
1771
+ createDashedLine(start, end, lineY, 'horizontal', linemat, false);
1772
+ }
1773
+ }
1774
+ } else {
1775
+ // Vertical highway
1776
+ const roadGeometry = new THREE.PlaneGeometry(width, end - start);
1777
+ const road = new THREE.Mesh(roadGeometry, material);
1778
+ road.rotation.x = -Math.PI / 2;
1779
+ road.position.set(position, 0.1, (start + end) / 2);
1780
+ scene.add(road);
1781
+
1782
+ // Lane dividers
1783
+ const laneCount = width / 6;
1784
+ for (let lane = 1; lane < laneCount; lane++) {
1785
+ const lineX = position - width/2 + (lane * 6);
1786
+
1787
+ if (lane === Math.floor(laneCount / 2)) {
1788
+ createDashedLine(start, end, lineX, 'vertical', linemat, true);
1789
+ } else {
1790
+ createDashedLine(start, end, lineX, 'vertical', linemat, false);
1791
+ }
1792
+ }
1793
+ }
1794
+ }
1795
+
1796
+ function createLocalRoad(start, end, position, direction, width, material, linemat) {
1797
+ if (direction === 'horizontal') {
1798
+ const roadGeometry = new THREE.PlaneGeometry(end - start, width);
1799
+ const road = new THREE.Mesh(roadGeometry, material);
1800
+ road.rotation.x = -Math.PI / 2;
1801
+ road.position.set((start + end) / 2, 0.1, position);
1802
+ scene.add(road);
1803
+
1804
+ // Center line
1805
+ createDashedLine(start, end, position, 'horizontal', linemat, false);
1806
+ } else {
1807
+ const roadGeometry = new THREE.PlaneGeometry(width, end - start);
1808
+ const road = new THREE.Mesh(roadGeometry, material);
1809
+ road.rotation.x = -Math.PI / 2;
1810
+ road.position.set(position, 0.1, (start + end) / 2);
1811
+ scene.add(road);
1812
+
1813
+ // Center line
1814
+ createDashedLine(start, end, position, 'vertical', linemat, false);
1815
+ }
1816
+ }
1817
+
1818
+ function createDashedLine(start, end, position, direction, material, isDouble) {
1819
+ const dashLength = 8;
1820
+ const gapLength = 4;
1821
+ const totalLength = end - start;
1822
+ const segments = Math.floor(totalLength / (dashLength + gapLength));
1823
+
1824
+ for (let i = 0; i < segments; i++) {
1825
+ const segmentStart = start + i * (dashLength + gapLength);
1826
+
1827
+ if (direction === 'horizontal') {
1828
+ const lineGeometry = new THREE.PlaneGeometry(dashLength, 0.3);
1829
+ const line = new THREE.Mesh(lineGeometry, material);
1830
+ line.rotation.x = -Math.PI / 2;
1831
+ line.position.set(segmentStart + dashLength/2, 0.12, position);
1832
+ scene.add(line);
1833
+
1834
+ // Double line for highway center
1835
+ if (isDouble) {
1836
+ const line2 = new THREE.Mesh(lineGeometry, material);
1837
+ line2.rotation.x = -Math.PI / 2;
1838
+ line2.position.set(segmentStart + dashLength/2, 0.12, position + 1);
1839
+ scene.add(line2);
1840
+ }
1841
+ } else {
1842
+ const lineGeometry = new THREE.PlaneGeometry(0.3, dashLength);
1843
+ const line = new THREE.Mesh(lineGeometry, material);
1844
+ line.rotation.x = -Math.PI / 2;
1845
+ line.position.set(position, 0.12, segmentStart + dashLength/2);
1846
+ scene.add(line);
1847
+
1848
+ if (isDouble) {
1849
+ const line2 = new THREE.Mesh(lineGeometry, material);
1850
+ line2.rotation.x = -Math.PI / 2;
1851
+ line2.position.set(position + 1, 0.12, segmentStart + dashLength/2);
1852
+ scene.add(line2);
1853
+ }
1854
+ }
1855
+ }
1856
+ }
1857
+
1858
+ function createBuildingAccessRoads() {
1859
+ const accessRoadMaterial = new THREE.MeshLambertMaterial({ color: 0x555555 });
1860
+ const lineMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
1861
+
1862
+ world.buildings.forEach((building, index) => {
1863
+ const pos = building.mesh.position;
1864
+ const buildingBox = new THREE.Box3().setFromObject(building.mesh);
1865
+ const size = buildingBox.getSize(new THREE.Vector3());
1866
+
1867
+ // Create access roads around each building (rectangular loop)
1868
+ const roadWidth = 8;
1869
+ const buffer = 5; // Distance from building
1870
+
1871
+ // North road
1872
+ createBuildingRoad(
1873
+ pos.x - size.x/2 - buffer - 10,
1874
+ pos.x + size.x/2 + buffer + 10,
1875
+ pos.z + size.z/2 + buffer + roadWidth/2,
1876
+ 'horizontal', roadWidth, accessRoadMaterial, lineMaterial
1877
+ );
1878
+
1879
+ // South road
1880
+ createBuildingRoad(
1881
+ pos.x - size.x/2 - buffer - 10,
1882
+ pos.x + size.x/2 + buffer + 10,
1883
+ pos.z - size.z/2 - buffer - roadWidth/2,
1884
+ 'horizontal', roadWidth, accessRoadMaterial, lineMaterial
1885
+ );
1886
+
1887
+ // East road
1888
+ createBuildingRoad(
1889
+ pos.x + size.x/2 + buffer + roadWidth/2,
1890
+ pos.z - size.z/2 - buffer - 10,
1891
+ pos.z + size.z/2 + buffer + 10,
1892
+ 'vertical', roadWidth, accessRoadMaterial, lineMaterial
1893
+ );
1894
+
1895
+ // West road
1896
+ createBuildingRoad(
1897
+ pos.x - size.x/2 - buffer - roadWidth/2,
1898
+ pos.z - size.z/2 - buffer - 10,
1899
+ pos.z + size.z/2 + buffer + 10,
1900
+ 'vertical', roadWidth, accessRoadMaterial, lineMaterial
1901
+ );
1902
+
1903
+ // Connecting roads to main network
1904
+ createConnectorRoads(pos, size, roadWidth, accessRoadMaterial);
1905
+ });
1906
+ }
1907
+
1908
+ function createBuildingRoad(startOrX, endOrStartZ, positionOrEndZ, direction, width, material, linemat) {
1909
+ if (direction === 'horizontal') {
1910
+ const roadGeometry = new THREE.PlaneGeometry(endOrStartZ - startOrX, width);
1911
+ const road = new THREE.Mesh(roadGeometry, material);
1912
+ road.rotation.x = -Math.PI / 2;
1913
+ road.position.set((startOrX + endOrStartZ) / 2, 0.08, positionOrEndZ);
1914
+ scene.add(road);
1915
+
1916
+ // Center line
1917
+ const lineGeometry = new THREE.PlaneGeometry(endOrStartZ - startOrX, 0.2);
1918
+ const line = new THREE.Mesh(lineGeometry, linemat);
1919
+ line.rotation.x = -Math.PI / 2;
1920
+ line.position.set((startOrX + endOrStartZ) / 2, 0.1, positionOrEndZ);
1921
+ scene.add(line);
1922
+ } else {
1923
+ const roadGeometry = new THREE.PlaneGeometry(width, positionOrEndZ - endOrStartZ);
1924
+ const road = new THREE.Mesh(roadGeometry, material);
1925
+ road.rotation.x = -Math.PI / 2;
1926
+ road.position.set(startOrX, 0.08, (endOrStartZ + positionOrEndZ) / 2);
1927
+ scene.add(road);
1928
+
1929
+ // Center line
1930
+ const lineGeometry = new THREE.PlaneGeometry(0.2, positionOrEndZ - endOrStartZ);
1931
+ const line = new THREE.Mesh(lineGeometry, linemat);
1932
+ line.rotation.x = -Math.PI / 2;
1933
+ line.position.set(startOrX, 0.1, (endOrStartZ + positionOrEndZ) / 2);
1934
+ scene.add(line);
1935
+ }
1936
+ }
1937
+
1938
+ function createConnectorRoads(buildingPos, buildingSize, roadWidth, material) {
1939
+ // Connect building access roads to nearest main roads
1940
+ const nearestMainRoads = findNearestMainRoads(buildingPos);
1941
+
1942
+ nearestMainRoads.forEach(mainRoad => {
1943
+ // Create connector from building to main road
1944
+ const startPos = buildingPos.clone();
1945
+ const endPos = mainRoad.position.clone();
1946
+
1947
+ // Create straight connector road
1948
+ const distance = startPos.distanceTo(endPos);
1949
+ const direction = endPos.clone().sub(startPos).normalize();
1950
+
1951
+ const connectorGeometry = new THREE.PlaneGeometry(roadWidth, distance);
1952
+ const connector = new THREE.Mesh(connectorGeometry, material);
1953
+ connector.rotation.x = -Math.PI / 2;
1954
+ connector.rotation.z = Math.atan2(direction.x, direction.z);
1955
+ connector.position.copy(startPos.clone().add(endPos).multiplyScalar(0.5));
1956
+ connector.position.y = 0.08;
1957
+ scene.add(connector);
1958
+ });
1959
+ }
1960
+
1961
+ function findNearestMainRoads(position) {
1962
+ const mainRoads = [];
1963
+
1964
+ // Find nearest horizontal main road
1965
+ let nearestHorizontalDist = Infinity;
1966
+ let nearestHorizontalZ = 0;
1967
+ for (let z = -300; z <= 300; z += 150) {
1968
+ const dist = Math.abs(position.z - z);
1969
+ if (dist < nearestHorizontalDist) {
1970
+ nearestHorizontalDist = dist;
1971
+ nearestHorizontalZ = z;
1972
+ }
1973
+ }
1974
+
1975
+ if (nearestHorizontalDist < 100) {
1976
+ mainRoads.push({
1977
+ position: new THREE.Vector3(position.x, 0, nearestHorizontalZ),
1978
+ type: 'horizontal'
1979
+ });
1980
+ }
1981
+
1982
+ // Find nearest vertical main road
1983
+ let nearestVerticalDist = Infinity;
1984
+ let nearestVerticalX = 0;
1985
+ for (let x = -300; x <= 300; x += 150) {
1986
+ const dist = Math.abs(position.x - x);
1987
+ if (dist < nearestVerticalDist) {
1988
+ nearestVerticalDist = dist;
1989
+ nearestVerticalX = x;
1990
+ }
1991
+ }
1992
+
1993
+ if (nearestVerticalDist < 100) {
1994
+ mainRoads.push({
1995
+ position: new THREE.Vector3(nearestVerticalX, 0, position.z),
1996
+ type: 'vertical'
1997
+ });
1998
+ }
1999
+
2000
+ return mainRoads.slice(0, 2); // Maximum 2 connections per building
2001
  }
2002
 
2003
  function createBuildingsWithParkingLots() {
 
2007
  const buildingMaterial = new THREE.MeshLambertMaterial({ color: 0x666666 });
2008
  const parkingMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });
2009
  const spotMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
2010
+ const queueMaterial = new THREE.MeshLambertMaterial({ color: 0x222222 });
2011
 
2012
+ // Create buildings at strategic locations with better spacing
2013
  const buildingLocations = [
2014
+ { x: -200, z: -200 }, { x: 0, z: -200 }, { x: 200, z: -200 },
2015
+ { x: -200, z: 0 }, { x: 200, z: 0 },
2016
+ { x: -200, z: 200 }, { x: 0, z: 200 }, { x: 200, z: 200 },
2017
+ { x: -100, z: -100 }, { x: 100, z: -100 },
2018
+ { x: -100, z: 100 }, { x: 100, z: 100 },
2019
+ { x: -300, z: -300 }, { x: 300, z: -300 },
2020
+ { x: -300, z: 300 }, { x: 300, z: 300 }
2021
  ];
2022
 
2023
+ buildingLocations.forEach((loc, index) => {
2024
  // Create building
2025
+ const width = 20 + Math.random() * 10;
2026
+ const height = 12 + Math.random() * 20;
2027
+ const depth = 20 + Math.random() * 10;
2028
 
2029
  const buildingGeometry = new THREE.BoxGeometry(width, height, depth);
2030
  const building = new THREE.Mesh(buildingGeometry, buildingMaterial);
 
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)
2047
+ const lotGeometry = new THREE.PlaneGeometry(50, 40);
2048
  const lot = new THREE.Mesh(lotGeometry, parkingMaterial);
2049
  lot.rotation.x = -Math.PI / 2;
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++) {
2090
+ const spotX = parkingLot.center.x + (col - 3.5) * 6;
2091
+ const spotZ = parkingLot.center.z + (row - 2) * 7;
2092
 
2093
  // Spot marking
2094
  const spotGeometry = new THREE.PlaneGeometry(PARKING_SPOT_SIZE.width, PARKING_SPOT_SIZE.length);
 
2250
  totalParkingScore: 0,
2251
  totalViolations: 0,
2252
  totalFollowingDistance: 0,
2253
+ followingCount: 0,
2254
+ approaching: 0 // Cars approaching parking
2255
  };
2256
 
2257
  population.forEach(car => {
 
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);
 
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());