From 550159fb713afb1c322e2a0bf7f81457921b332c Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Sat, 27 Sep 2025 14:01:25 +0200 Subject: [PATCH] =?UTF-8?q?=C3=84nderung:=20Optimierung=20der=20Kollisions?= =?UTF-8?q?erkennung=20und=20Debugging=20im=20Taxi-Spiel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ä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. --- frontend/src/data/streetCoordinates.json | 151 +++------ frontend/src/views/minigames/TaxiGame.vue | 393 +++++++++++++++------- 2 files changed, 326 insertions(+), 218 deletions(-) diff --git a/frontend/src/data/streetCoordinates.json b/frontend/src/data/streetCoordinates.json index d2f2316..a429692 100644 --- a/frontend/src/data/streetCoordinates.json +++ b/frontend/src/data/streetCoordinates.json @@ -4,8 +4,6 @@ "cornerBottomRight": { "regions": [ [ - {"x": 0, "y": 0}, - {"x": 0, "y": 1}, {"x": 0.375, "y": 1}, {"x": 0.375, "y": 0.427}, {"x": 0.38, "y": 0.409}, @@ -14,8 +12,7 @@ {"x": 0.408, "y": 0.397}, {"x": 0.417, "y": 0.38}, {"x": 0.434, "y": 0.375}, - {"x": 1, "y": 0.375}, - {"x": 1, "y": 0} + {"x": 1, "y": 0.375} ], [ {"x": 0.625, "y": 1}, @@ -26,8 +23,7 @@ {"x": 0.641, "y": 0.636}, {"x": 0.648, "y": 0.632}, {"x": 0.656, "y": 0.625}, - {"x": 1, "y": 0.625}, - {"x": 1, "y": 1} + {"x": 1, "y": 0.625} ] ] }, @@ -41,10 +37,7 @@ {"x": 0.611, "y": 0.395}, {"x": 0.619, "y": 0.406}, {"x": 0.625, "y": 0.422}, - {"x": 0.625, "y": 1}, - {"x": 1, "y": 1}, - {"x": 1, "y": 0}, - {"x": 0, "y": 0} + {"x": 0.625, "y": 1} ], [ {"x": 0, "y": 0.625}, @@ -54,8 +47,7 @@ {"x": 0.366, "y": 0.642}, {"x": 0.373, "y": 0.651}, {"x": 0.375, "y": 0.659}, - {"x": 0.375, "y": 1}, - {"x": 0, "y": 1} + {"x": 0.375, "y": 1} ] ] }, @@ -70,8 +62,7 @@ {"x": 0.356, "y": 0.37}, {"x": 0.348, "y": 0.373}, {"x": 0.336, "y": 0.375}, - {"x": 0, "y": 0.375}, - {"x": 0, "y": 0} + {"x": 0, "y": 0.375} ], [ {"x": 0.625, "y": 0}, @@ -81,10 +72,7 @@ {"x": 0.605, "y": 0.614}, {"x": 0.594, "y": 0.621}, {"x": 0.584, "y": 0.625}, - {"x": 0, "y": 0.625}, - {"x": 0, "y": 1}, - {"x": 1, "y": 1}, - {"x": 1, "y": 0} + {"x": 0, "y": 0.625} ] ] }, @@ -98,10 +86,7 @@ {"x": 0.395, "y": 0.614}, {"x": 0.406, "y": 0.621}, {"x": 0.416, "y": 0.625}, - {"x": 1, "y": 0.625}, - {"x": 1, "y": 1}, - {"x": 0, "y": 1}, - {"x": 0, "y": 0} + {"x": 1, "y": 0.625} ], [ {"x": 0.625, "y": 0}, @@ -112,8 +97,7 @@ {"x": 0.644, "y": 0.37}, {"x": 0.652, "y": 0.373}, {"x": 0.664, "y": 0.375}, - {"x": 1, "y": 0.375}, - {"x": 1, "y": 0} + {"x": 1, "y": 0.375} ] ] }, @@ -121,15 +105,11 @@ "regions": [ [ {"x": 0, "y": 0.375}, - {"x": 1, "y": 0.375}, - {"x": 1, "y": 0}, - {"x": 0, "y": 0} + {"x": 1, "y": 0.375} ], [ {"x": 0, "y": 0.625}, - {"x": 1, "y": 0.625}, - {"x": 1, "y": 1}, - {"x": 0, "y": 1} + {"x": 1, "y": 0.625} ] ] }, @@ -137,15 +117,11 @@ "regions": [ [ {"x": 0.375, "y": 0}, - {"x": 0.375, "y": 1}, - {"x": 0, "y": 1}, - {"x": 0, "y": 0} + {"x": 0.375, "y": 1} ], [ {"x": 0.625, "y": 0}, - {"x": 0.625, "y": 1}, - {"x": 1, "y": 1}, - {"x": 1, "y": 0} + {"x": 0.625, "y": 1} ] ] }, @@ -154,26 +130,22 @@ [ {"x": 0.375, "y": 0}, {"x": 0.375, "y": 0.375}, - {"x": 0, "y": 0.375}, - {"x": 0, "y": 0} + {"x": 0, "y": 0.375} ], [ {"x": 0.625, "y": 0}, {"x": 0.625, "y": 0.375}, - {"x": 1, "y": 0.375}, - {"x": 1, "y": 0} + {"x": 1, "y": 0.375} ], [ {"x": 0.375, "y": 1}, {"x": 0.375, "y": 0.625}, - {"x": 0, "y": 0.625}, - {"x": 0, "y": 1} + {"x": 0, "y": 0.625} ], [ {"x": 0.625, "y": 1}, {"x": 0.625, "y": 0.625}, - {"x": 1, "y": 0.625}, - {"x": 1, "y": 1} + {"x": 1, "y": 0.625} ] ] }, @@ -185,9 +157,7 @@ {"x": 0.384, "y": 0.195}, {"x": 0.615, "y": 0.195}, {"x": 0.925, "y": 0.375}, - {"x": 1, "y": 0.375}, - {"x": 1, "y": 0}, - {"x": 0, "y": 0} + {"x": 1, "y": 0.375} ], [ {"x": 0.25, "y": 0.375}, @@ -198,9 +168,7 @@ ], [ {"x": 0, "y": 0.625}, - {"x": 1, "y": 0.625}, - {"x": 1, "y": 1}, - {"x": 0, "y": 1} + {"x": 1, "y": 0.625} ] ] }, @@ -212,9 +180,7 @@ {"x": 0.805, "y": 0.384}, {"x": 0.805, "y": 0.615}, {"x": 0.625, "y": 0.925}, - {"x": 0.625, "y": 1}, - {"x": 1, "y": 1}, - {"x": 1, "y": 0} + {"x": 0.625, "y": 1} ], [ {"x": 0.625, "y": 0.25}, @@ -225,9 +191,7 @@ ], [ {"x": 0.375, "y": 0}, - {"x": 0.375, "y": 1}, - {"x": 0, "y": 1}, - {"x": 0, "y": 0} + {"x": 0.375, "y": 1} ] ] }, @@ -236,86 +200,71 @@ [ {"x": 0, "y": 0.375}, {"x": 0.375, "y": 0.375}, - {"x": 0.375, "y": 0}, - {"x": 0, "y": 0} + {"x": 0.375, "y": 0} ], [ {"x": 0, "y": 0.625}, {"x": 0.375, "y": 0.625}, - {"x": 0.375, "y": 1}, - {"x": 0, "y": 1} + {"x": 0.375, "y": 1} ], [ {"x": 0.625, "y": 0}, - {"x": 0.625, "y": 1}, - {"x": 1, "y": 1}, - {"x": 1, "y": 0} + {"x": 0.625, "y": 1} ] ] }, "tRight": { "regions": [ [ - {"x": 0.375, "y": 0}, - {"x": 0.375, "y": 1}, - {"x": 0, "y": 1}, - {"x": 0, "y": 0} - ], - [ - {"x": 0.625, "y": 0}, - {"x": 0.625, "y": 0.375}, {"x": 1, "y": 0.375}, - {"x": 1, "y": 0} + {"x": 0.625, "y": 0.375}, + {"x": 0.625, "y": 0} ], [ - {"x": 0.625, "y": 1}, - {"x": 0.625, "y": 0.625}, {"x": 1, "y": 0.625}, - {"x": 1, "y": 1} + {"x": 0.625, "y": 0.625}, + {"x": 0.625, "y": 1} + ], + [ + {"x": 0.375, "y": 0}, + {"x": 0.375, "y": 1} ] ] - }, + } + , "tUp": { - "regions": [ + "regions": [ [ - {"x": 0, "y": 0.375}, - {"x": 0.375, "y": 0.375}, {"x": 0.375, "y": 0}, - {"x": 0, "y": 0} + {"x": 0.375, "y": 0.375}, + {"x": 0, "y": 0.375} ], [ - {"x": 1, "y": 0.375}, - {"x": 0.625, "y": 0.375}, {"x": 0.625, "y": 0}, - {"x": 1, "y": 0} + {"x": 0.625, "y": 0.375}, + {"x": 1, "y": 0.375} ], [ {"x": 0, "y": 0.625}, - {"x": 1, "y": 0.625}, - {"x": 1, "y": 1}, - {"x": 0, "y": 1} + {"x": 1, "y": 0.625} ] ] }, "tDown": { "regions": [ [ - {"x": 0, "y": 0.375}, - {"x": 1, "y": 0.375}, - {"x": 1, "y": 0}, - {"x": 0, "y": 0} - ], - [ - {"x": 0, "y": 0.625}, - {"x": 0.375, "y": 0.625}, - {"x": 0.375, "y": 1}, - {"x": 0, "y": 1} - ], - [ - {"x": 1, "y": 0.625}, - {"x": 0.625, "y": 0.625}, {"x": 0.625, "y": 1}, - {"x": 1, "y": 1} + {"x": 0.625, "y": 0.625}, + {"x": 1, "y": 0.625} + ], + [ + {"x": 0.375, "y": 1}, + {"x": 0.375, "y": 0.625}, + {"x": 0, "y": 0.625} + ], + [ + {"x": 1, "y": 0.375}, + {"x": 0, "y": 0.375} ] ] } diff --git a/frontend/src/views/minigames/TaxiGame.vue b/frontend/src/views/minigames/TaxiGame.vue index 0e10ed1..75b30b8 100644 --- a/frontend/src/views/minigames/TaxiGame.vue +++ b/frontend/src/views/minigames/TaxiGame.vue @@ -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)