Spaces:
Running
Running
Update index.html
Browse files- 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 |
-
|
1053 |
-
|
1054 |
-
this.
|
1055 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1056 |
}
|
1057 |
-
|
1058 |
-
|
1059 |
-
|
1060 |
-
|
1061 |
-
|
1062 |
-
|
1063 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1064 |
}
|
1065 |
-
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
1290 |
-
|
1291 |
-
).length;
|
1292 |
|
1293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1294 |
}
|
1295 |
|
1296 |
enterApproachLane(deltaTime) {
|
1297 |
-
// Find first available approach lane position
|
1298 |
-
const approachPositions = this.targetParkingLot.approachLane;
|
1299 |
let targetPosition = null;
|
|
|
1300 |
|
1301 |
-
|
1302 |
-
|
1303 |
-
|
1304 |
-
|
1305 |
-
|
1306 |
-
|
1307 |
-
|
1308 |
-
|
1309 |
-
|
1310 |
-
|
1311 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
1323 |
-
|
1324 |
-
|
1325 |
-
|
1326 |
-
|
1327 |
-
|
1328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1329 |
}
|
1330 |
|
1331 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1332 |
|
1333 |
-
|
1334 |
-
|
1335 |
-
|
1336 |
-
|
1337 |
-
|
1338 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1339 |
}
|
1340 |
}
|
1341 |
|
@@ -1644,22 +1828,53 @@
|
|
1644 |
}
|
1645 |
|
1646 |
destroy() {
|
1647 |
-
|
1648 |
-
|
1649 |
-
this.parkingSpot
|
1650 |
-
|
1651 |
-
|
1652 |
-
|
1653 |
-
|
1654 |
-
|
1655 |
-
|
1656 |
-
|
1657 |
-
|
1658 |
-
if (
|
1659 |
-
|
1660 |
-
|
1661 |
-
|
1662 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1663 |
}
|
1664 |
}
|
1665 |
}
|
@@ -2034,13 +2249,14 @@
|
|
2034 |
|
2035 |
world.buildings.push({ mesh: building });
|
2036 |
|
2037 |
-
// Create enhanced parking lot with
|
2038 |
const parkingLot = {
|
2039 |
center: new THREE.Vector3(loc.x + width/2 + 25, 0.1, loc.z),
|
2040 |
spots: [],
|
2041 |
queue: [],
|
2042 |
-
|
2043 |
-
|
|
|
2044 |
};
|
2045 |
|
2046 |
// Main parking lot surface (larger)
|
@@ -2050,40 +2266,64 @@
|
|
2050 |
lot.position.copy(parkingLot.center);
|
2051 |
scene.add(lot);
|
2052 |
|
2053 |
-
//
|
2054 |
-
|
2055 |
-
|
2056 |
-
|
2057 |
-
|
2058 |
-
|
2059 |
-
|
2060 |
-
|
2061 |
-
|
2062 |
-
|
2063 |
-
|
2064 |
-
|
2065 |
-
|
2066 |
-
|
2067 |
-
|
2068 |
-
|
2069 |
-
|
2070 |
-
|
2071 |
-
|
2072 |
-
|
2073 |
-
|
2074 |
-
parkingLot.
|
2075 |
}
|
2076 |
|
2077 |
-
// Create exit
|
2078 |
-
for (let
|
2079 |
-
const
|
2080 |
-
|
2081 |
-
|
2082 |
-
|
2083 |
-
);
|
2084 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2140 |
-
|
2141 |
-
|
2142 |
-
|
2143 |
-
|
2144 |
-
|
2145 |
-
|
2146 |
-
|
2147 |
-
|
2148 |
-
|
2149 |
-
|
2150 |
-
for (let i = 0; i < tournamentCount; i++) {
|
2151 |
-
const tournamentSize = 5;
|
2152 |
-
let best = null;
|
2153 |
-
let bestFitness = -Infinity;
|
2154 |
|
2155 |
-
|
2156 |
-
|
2157 |
-
|
2158 |
-
|
2159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2196 |
-
|
2197 |
-
|
2198 |
-
|
2199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2200 |
|
2201 |
-
|
2202 |
-
child.brain.mutate(mutationRate);
|
2203 |
|
2204 |
-
|
2205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2258 |
-
|
2259 |
-
|
2260 |
-
|
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.
|
2268 |
-
stats.
|
2269 |
-
|
2270 |
-
stats.
|
2271 |
-
|
2272 |
-
stats.
|
2273 |
-
|
2274 |
-
|
2275 |
-
|
2276 |
-
if (car.
|
2277 |
-
stats.
|
2278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2279 |
}
|
2280 |
-
} else {
|
2281 |
-
stats.solo++;
|
2282 |
}
|
|
|
|
|
|
|
|
|
2283 |
}
|
2284 |
});
|
2285 |
|
@@ -2287,39 +2584,66 @@
|
|
2287 |
}
|
2288 |
|
2289 |
function updateCamera() {
|
2290 |
-
|
2291 |
-
|
2292 |
-
|
2293 |
-
|
2294 |
-
|
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 |
-
|
2303 |
-
|
2304 |
-
|
2305 |
-
|
2306 |
-
|
2307 |
-
|
2308 |
-
|
2309 |
-
|
2310 |
-
|
2311 |
-
|
2312 |
-
|
2313 |
-
|
2314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2315 |
|
2316 |
-
|
2317 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2431 |
-
spot
|
2432 |
-
|
2433 |
-
|
2434 |
-
|
2435 |
-
|
2436 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2437 |
});
|
2438 |
|
2439 |
-
population
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|