Änderung: Optimierung der Kollisionserkennung und Debugging im Taxi-Spiel

Änderungen:
- Anpassung der Kollisionserkennung, um die Logik für befahrbare und nicht befahrbare Bereiche zu verbessern.
- Einführung von Debugging-Informationen zur besseren Nachverfolgbarkeit von Kollisionen, insbesondere bei Fuel-Tiles.
- Bereinigung der Codebasis durch Entfernen überflüssiger Konsolenausgaben und Verbesserung der Lesbarkeit.

Diese Anpassungen erhöhen die Effizienz und Benutzerfreundlichkeit des Spiels, indem sie die Kollisionserkennung präzisieren und die Debugging-Möglichkeiten erweitern.
This commit is contained in:
Torsten Schulz (local)
2025-09-27 14:01:25 +02:00
parent 7371ba73fe
commit 550159fb71
2 changed files with 326 additions and 218 deletions

View File

@@ -238,6 +238,7 @@ export default {
loadedPassengersList: [], // Aktuell geladene Passagiere im Taxi
occupiedHouses: new Set(), // Verhindert doppelte Belegung von Häusern
lastPassengerGeneration: 0,
debugCollisionShown: false, // Flag für einmalige Debug-Ausgabe
passengerGenerationInterval: 0,
// Timeout-Referenzen für Cleanup
passengerGenerationTimeout: null,
@@ -1958,8 +1959,9 @@ export default {
const originalTileSize = streetCoordinates.getOriginalTileSize(); // 640px
const currentTileSize = 500; // Aktuelle Render-Größe
// Hole die Polygon-Koordinaten für dieses Tile
const regions = streetCoordinates.getDriveableRegions(streetTileType, originalTileSize);
// Hole die relativen Koordinaten direkt aus der JSON (0-1)
const tileData = streetCoordinates.data.tiles[streetTileType];
const regions = tileData ? tileData.regions : [];
if (regions.length === 0) {
// Keine Polygone definiert = befahrbar
@@ -1973,151 +1975,308 @@ export default {
width: this.taxi.width / currentTileSize,
height: this.taxi.height / currentTileSize
};
// Für normale Tiles: Prüfe ob Taxi eine der Hindernis-Polylinien schneidet
// Für Fuel-Tiles: Prüfe ob Taxi eine der befahrbaren Polylinien schneidet
const isFuelTile = tileType === 'fuelhorizontal' || tileType === 'fuelvertical';
// Für Fuel-Tiles: Prüfe ob das Taxi in einem der befahrbaren Bereiche ist
if (tileType === 'fuelhorizontal' || tileType === 'fuelvertical') {
// Prüfe ob das Taxi in mindestens einem befahrbaren Bereich ist
for (let i = 0; i < regions.length; i++) {
const region = regions[i];
if (this.rectPolygonCollision(taxiRect, region)) {
return true; // Taxi ist in einem befahrbaren Bereich
}
}
// Crash - zeige Debug-Informationen
console.log('🚨 CRASH auf Fuel-Tile - Debug-Informationen:');
console.log('Tile Type:', tileType);
console.log('Polygons:', regions);
console.log('Taxi Rect:', taxiRect);
console.log('Taxi Position (absolute):', { x: this.taxi.x, y: this.taxi.y, width: this.taxi.width, height: this.taxi.height });
return false; // Taxi ist in keinem befahrbaren Bereich
}
// Erstelle rotiertes Rechteck
const rotatedRect = {
cx: taxiRect.x + taxiRect.width / 2, // Mittelpunkt X
cy: taxiRect.y + taxiRect.height / 2, // Mittelpunkt Y
theta: this.taxi.imageAngle + this.taxi.angle, // Rotation
hx: taxiRect.width / 2, // halbe Breite
hy: taxiRect.height / 2 // halbe Höhe
};
// Für andere Tiles: Prüfe Kollision mit jedem Polygon (nicht befahrbar)
// Prüfe jede Region (Polylinie) einzeln
for (let i = 0; i < regions.length; i++) {
const region = regions[i];
if (this.rectPolygonCollision(taxiRect, region)) {
// Crash - zeige Debug-Informationen
console.log('🚨 CRASH auf Tile - Debug-Informationen:');
console.log('Tile Type:', tileType);
console.log('Crash Polygon:', region);
console.log('Alle Polygons:', regions);
console.log('Taxi Rect:', taxiRect);
console.log('Taxi Position (absolute):', { x: this.taxi.x, y: this.taxi.y, width: this.taxi.width, height: this.taxi.height });
// Jede Region ist eine Polylinie mit mehreren Punkten
if (region.length >= 2) {
// Verwende die relativen Koordinaten direkt aus der JSON (0-1)
// ohne die doppelte Konvertierung über getDriveableRegions
const regionPoints = region.map(point => ({
x: point.x, // Bereits relativ (0-1) aus der JSON
y: point.y // Bereits relativ (0-1) aus der JSON
}));
// Test: Prüfe einen einzelnen Punkt
const testPoint = { x: 0.5, y: 0.45 };
const isInside = this.isPointInPolygon(testPoint.x, testPoint.y, region);
console.log('Test-Punkt (0.5, 0.45) in Polygon:', isInside);
// Verwende die robuste Kollisionserkennung für diese Region
const result = this.polylineIntersectsRotatedRect(regionPoints, rotatedRect);
// Test: Prüfe mit absoluten Koordinaten
const testPointAbs = { x: 250, y: 225 }; // Absolute Koordinaten
const regionAbs = region.map(p => ({ x: p.x * 500, y: p.y * 500 })); // Umrechnung zu absoluten Koordinaten
const isInsideAbs = this.isPointInPolygon(testPointAbs.x, testPointAbs.y, regionAbs);
console.log('Test-Punkt (250, 225) in Polygon (absolut):', isInsideAbs);
// Debug-Ausgabe bei jeder Prüfung
console.log('🔍 KOLLISIONS-DEBUG:');
console.log('Taxi Rect (relativ):', taxiRect);
console.log('Region Polylinie:', regionPoints);
console.log('Rotated Rect:', rotatedRect);
console.log('Kollisionsergebnis:', result);
// Test: Prüfe einen Punkt, der definitiv außerhalb sein sollte
const testPointOutside = { x: 0.5, y: 0.1 }; // Sehr weit oben
const isInsideOutside = this.isPointInPolygon(testPointOutside.x, testPointOutside.y, region);
console.log('Test-Punkt (0.5, 0.1) in Polygon (sollte false sein):', isInsideOutside);
// Zusätzliche Debug-Ausgabe für cornerbottomright
if (streetTileType === 'cornerBottomRight') {
console.log('🔧 CORNERBOTTOMRIGHT DEBUG:');
console.log('Region (relativ aus JSON):', region);
console.log('RegionPoints (relativ):', regionPoints);
console.log('Taxi position:', this.taxi.x, this.taxi.y);
console.log('Current tile size:', currentTileSize);
console.log('Original tile size:', originalTileSize);
}
// Debug: Zeige die ersten paar Polygon-Punkte
console.log('Erste 5 Polygon-Punkte:', region.slice(0, 5));
// Debug: Skalierungs-Informationen
console.log('Tile Size:', this.tiles.size);
console.log('Original Tile Size (aus JSON):', 640);
console.log('Skalierungsfaktor:', this.tiles.size / 640);
// Debug: Taxi-Position in verschiedenen Koordinatensystemen
console.log('Taxi Position (Canvas):', { x: this.taxi.x, y: this.taxi.y });
console.log('Taxi Position (Tile-relative):', { x: this.taxi.x / this.tiles.size, y: this.taxi.y / this.tiles.size });
console.log('Taxi Position (JSON-relative):', { x: this.taxi.x / 640, y: this.taxi.y / 640 });
return false; // Kollision = nicht befahrbar
if (isFuelTile) {
// Für Fuel-Tiles: Taxi muss eine der befahrbaren Polylinien schneiden
if (result.hit) {
console.log('✅ Fuel-Tile: Taxi ist auf befahrbarer Polylinie (Region', i, ')');
return true; // Befahrbar
}
} else {
// Für normale Tiles: Taxi darf KEINE der Hindernis-Polylinien schneiden
if (result.hit) {
// Crash - zeige Debug-Informationen
console.log('🚨 CRASH auf Tile - Debug-Informationen:');
console.log('Taxi Rect (relativ):', taxiRect);
console.log('Crash Region Polylinie:', regionPoints);
console.log('Rotated Rect:', rotatedRect);
console.log('Crash Hits:', result.hits);
return false; // Kollision mit Hindernis = Crash
}
}
}
}
// Alle Regionen geprüft
if (isFuelTile) {
// Für Fuel-Tiles: Keine befahrbare Polylinie gefunden = Crash
console.log('🚨 Fuel-Tile: Taxi ist NICHT auf befahrbarer Polylinie - CRASH');
return false; // Nicht befahrbar = Crash
} else {
// Für normale Tiles: Keine Hindernis-Polylinie geschnitten = befahrbar
console.log('✅ Normales Tile: Taxi schneidet keine Hindernis-Polylinien');
return true; // Keine Kollision = befahrbar
}
return true; // Keine Kollision = befahrbar
},
// Prüft Kollision zwischen Rechteck und Polygon
rectPolygonCollision(rect, polygon) {
// Konvertiere Polygon-Koordinaten von absoluten Pixeln zu relativen (0-1)
// Prüft ob eine Linie ein rotiertes Rechteck schneidet
isLineCrossingRect(rect, line) {
// Konvertiere Linien-Koordinaten von absoluten Pixeln zu relativen (0-1)
const originalTileSize = 640; // Aus der JSON-Datei
const currentTileSize = this.tiles.size; // Aktuelle Render-Größe
const relativePolygon = polygon.map(point => ({
x: point.x / originalTileSize,
y: point.y / originalTileSize
}));
const relativeLine = {
x1: line.x1 / originalTileSize,
y1: line.y1 / originalTileSize,
x2: line.x2 / originalTileSize,
y2: line.y2 / originalTileSize
};
// Debug-Ausgabe bei jeder Kollisionsprüfung
console.log('🔍 KOLLISIONS-DEBUG:');
console.log('Taxi Rect (relativ):', rect);
console.log('Taxi Position (absolut):', { x: this.taxi.x, y: this.taxi.y, width: this.taxi.width, height: this.taxi.height });
console.log('Line (absolut aus JSON):', line);
console.log('Line (relativ konvertiert):', relativeLine);
console.log('Original Tile Size (JSON):', originalTileSize);
console.log('Current Tile Size:', this.tiles.size);
console.log('Taxi Angle:', this.taxi.angle);
console.log('Taxi ImageAngle:', this.taxi.imageAngle);
// Berechne die rotierten Ecken des Taxis
// rect ist bereits in relativen Koordinaten (0-1)
const centerX = rect.x + rect.width / 2;
const centerY = rect.y + rect.height / 2;
const angle = this.taxi.imageAngle + this.taxi.angle;
// Ursprüngliche Ecken relativ zum Zentrum (in relativen Koordinaten)
const halfWidth = rect.width / 2;
const halfHeight = rect.height / 2;
const originalCorners = [
{ x: -halfWidth, y: -halfHeight }, // Oben links
{ x: halfWidth, y: -halfHeight }, // Oben rechts
{ x: -halfWidth, y: halfHeight }, // Unten links
{ x: halfWidth, y: halfHeight } // Unten rechts
// Erstelle Polyline aus der Linie
const polyline = [
{ x: relativeLine.x1, y: relativeLine.y1 },
{ x: relativeLine.x2, y: relativeLine.y2 }
];
// Rotiere die Ecken (bleibt in relativen Koordinaten)
const corners = originalCorners.map(corner => ({
x: centerX + corner.x * Math.cos(angle) - corner.y * Math.sin(angle),
y: centerY + corner.x * Math.sin(angle) + corner.y * Math.cos(angle)
}));
// Erstelle rotiertes Rechteck
const rotatedRect = {
cx: rect.x + rect.width / 2, // Mittelpunkt X
cy: rect.y + rect.height / 2, // Mittelpunkt Y
theta: this.taxi.imageAngle + this.taxi.angle, // Rotation
hx: rect.width / 2, // halbe Breite
hy: rect.height / 2 // halbe Höhe
};
for (let i = 0; i < corners.length; i++) {
const corner = corners[i];
// corners sind bereits in relativen Koordinaten (0-1)
const isInside = this.isPointInPolygon(corner.x, corner.y, relativePolygon);
if (isInside) {
return true; // Rechteck-Ecke ist im Polygon = Kollision
// Verwende die robuste Kollisionserkennung
const result = this.polylineIntersectsRotatedRect(polyline, rotatedRect);
// Debug-Ausgabe für Kollisionsergebnis
console.log('Kollisionsergebnis:', result);
console.log('Rotated Rect:', rotatedRect);
console.log('Polyline:', polyline);
return result.hit;
},
// Testet, ob eine Polyline (Liste von Punkten) ein rotiertes Rechteck schneidet
polylineIntersectsRotatedRect(polyline, rect, eps = 1e-9) {
const { cx, cy, theta, hx, hy } = rect;
// Debug-Ausgabe für Eingabeparameter
console.log('🔧 polylineIntersectsRotatedRect DEBUG:');
console.log('Polyline:', polyline);
console.log('Rect:', rect);
console.log('AABB bounds: x=[', cx - hx, ',', cx + hx, '], y=[', cy - hy, ',', cy + hy, ']');
// Vereinfachte Kollisionserkennung: Prüfe ob irgendein Polyline-Punkt im Rechteck liegt
// oder ob irgendein Rechteck-Punkt auf der Polyline liegt
// 1. Prüfe ob Polyline-Punkte im Rechteck liegen
for (let i = 0; i < polyline.length; i++) {
const p = polyline[i];
const inX = p.x >= cx - hx && p.x <= cx + hx;
const inY = p.y >= cy - hy && p.y <= cy + hy;
console.log(` Punkt ${i}: (${p.x}, ${p.y}) - In AABB: ${inX && inY}`);
if (inX && inY) {
console.log(`✅ Kollision: Polyline-Punkt ${i} liegt im Rechteck`);
return { hit: true, hits: [{ type: 'point_in_rect', pointIndex: i, point: p }] };
}
}
// Prüfe ob eine Polygon-Ecke im Rechteck liegt
for (let i = 0; i < relativePolygon.length; i++) {
const point = relativePolygon[i];
const isInside = this.isPointInRect(point.x, point.y, rect);
if (isInside) {
return true; // Polygon-Ecke ist im Rechteck = Kollision
}
}
// Prüfe ob Rechteck-Kanten das Polygon schneiden
const rectEdges = [
{ x1: rect.x, y1: rect.y, x2: rect.x + rect.width, y2: rect.y }, // Oben
{ x1: rect.x + rect.width, y1: rect.y, x2: rect.x + rect.width, y2: rect.y + rect.height }, // Rechts
{ x1: rect.x, y1: rect.y + rect.height, x2: rect.x + rect.width, y2: rect.y + rect.height }, // Unten
{ x1: rect.x, y1: rect.y, x2: rect.x, y2: rect.y + rect.height } // Links
// 2. Prüfe ob Rechteck-Ecken auf der Polyline liegen
const rectCorners = [
{ x: cx - hx, y: cy - hy }, // links oben
{ x: cx + hx, y: cy - hy }, // rechts oben
{ x: cx + hx, y: cy + hy }, // rechts unten
{ x: cx - hx, y: cy + hy } // links unten
];
for (const edge of rectEdges) {
for (let i = 0; i < relativePolygon.length; i++) {
const j = (i + 1) % relativePolygon.length;
const polyEdge = {
x1: relativePolygon[i].x,
y1: relativePolygon[i].y,
x2: relativePolygon[j].x,
y2: relativePolygon[j].y
};
for (let cornerIndex = 0; cornerIndex < rectCorners.length; cornerIndex++) {
const corner = rectCorners[cornerIndex];
for (let i = 0; i < polyline.length - 1; i++) {
const A = polyline[i];
const B = polyline[i + 1];
if (this.lineIntersection(edge, polyEdge)) {
return true; // Kanten schneiden sich = Kollision
// Prüfe ob Ecke auf Liniensegment liegt
if (this.isPointOnLineSegment(corner.x, corner.y, A.x, A.y, B.x, B.y)) {
console.log(`✅ Kollision: Rechteck-Ecke ${cornerIndex} liegt auf Polyline-Segment ${i}`);
return { hit: true, hits: [{ type: 'corner_on_line', cornerIndex, segmentIndex: i, corner, segment: { A, B } }] };
}
}
}
return false; // Keine Kollision
// 3. Prüfe Liniensegment-Überschneidungen (vereinfacht)
for (let i = 0; i < polyline.length - 1; i++) {
const A = polyline[i];
const B = polyline[i + 1];
// Prüfe ob Liniensegment das Rechteck schneidet
if (this.lineSegmentIntersectsRect(A.x, A.y, B.x, B.y, cx - hx, cy - hy, cx + hx, cy + hy)) {
console.log(`✅ Kollision: Polyline-Segment ${i} schneidet Rechteck`);
return { hit: true, hits: [{ type: 'segment_intersects_rect', segmentIndex: i, segment: { A, B } }] };
}
}
console.log(`❌ Keine Kollision gefunden`);
return { hit: false, hits: [] };
},
// Prüft ob ein Punkt auf einem Liniensegment liegt
isPointOnLineSegment(px, py, x1, y1, x2, y2, tolerance = 0.01) {
// Berechne den Abstand vom Punkt zur Linie
const A = px - x1;
const B = py - y1;
const C = x2 - x1;
const D = y2 - y1;
const dot = A * C + B * D;
const lenSq = C * C + D * D;
if (lenSq === 0) {
// Linie ist ein Punkt
return Math.abs(px - x1) < tolerance && Math.abs(py - y1) < tolerance;
}
const param = dot / lenSq;
// Prüfe ob Parameter im Bereich [0,1] liegt
if (param < 0 || param > 1) {
return false;
}
// Berechne den nächsten Punkt auf der Linie
const xx = x1 + param * C;
const yy = y1 + param * D;
// Prüfe ob der Abstand klein genug ist
const distance = Math.sqrt((px - xx) * (px - xx) + (py - yy) * (py - yy));
return distance <= tolerance;
},
// Prüft ob ein Liniensegment ein Rechteck schneidet
lineSegmentIntersectsRect(x1, y1, x2, y2, rectMinX, rectMinY, rectMaxX, rectMaxY) {
// Verwende den Liang-Barsky Algorithmus für Liniensegment-Rechteck Schnitt
let t0 = 0, t1 = 1;
const dx = x2 - x1;
const dy = y2 - y1;
// Prüfe jede Seite des Rechtecks
const sides = [
{ p: -dx, q: x1 - rectMinX }, // links
{ p: dx, q: rectMaxX - x1 }, // rechts
{ p: -dy, q: y1 - rectMinY }, // oben
{ p: dy, q: rectMaxY - y1 } // unten
];
for (const side of sides) {
if (Math.abs(side.p) < 1e-9) {
// Parallel zur Seite
if (side.q < 0) {
return false; // außerhalb
}
} else {
const t = side.q / side.p;
if (side.p < 0) {
// Eintritt
if (t > t0) t0 = t;
} else {
// Austritt
if (t < t1) t1 = t;
}
if (t0 > t1) {
return false; // keine Überschneidung
}
}
}
return t0 <= 1 && t1 >= 0;
},
// Prüft ob ein Punkt auf einer Linie liegt
isPointOnLine(px, py, line) {
const { x1, y1, x2, y2 } = line;
// Berechne den Abstand vom Punkt zur Linie
const A = px - x1;
const B = py - y1;
const C = x2 - x1;
const D = y2 - y1;
const dot = A * C + B * D;
const lenSq = C * C + D * D;
if (lenSq === 0) {
// Linie ist ein Punkt
return Math.abs(px - x1) < 0.001 && Math.abs(py - y1) < 0.001;
}
const param = dot / lenSq;
let xx, yy;
if (param < 0) {
xx = x1;
yy = y1;
} else if (param > 1) {
xx = x2;
yy = y2;
} else {
xx = x1 + param * C;
yy = y1 + param * D;
}
const dx = px - xx;
const dy = py - yy;
const distance = Math.sqrt(dx * dx + dy * dy);
return distance < 0.001; // Toleranz für Gleitkommazahlen
},
// Prüft ob ein Punkt in einem Polygon liegt (Point-in-Polygon)