it-swarm.com.de

Wie finde ich den Schnittpunkt zwischen einer Linie und einem Rechteck?

Ich habe eine Linie, die von Punkt A nach B geht; Ich habe (x, y) von beiden Punkten. Ich habe auch ein Rechteck, das bei B zentriert ist, sowie die Breite und Höhe des Rechtecks.

Ich muss den Punkt in der Linie finden, die das Rechteck schneidet. Gibt es eine Formel, die mir das (x, y) dieses Punktes gibt?

48
John Petterson

Vielleicht möchten Sie Graphics Gems - ausprobieren -/- Dies ist ein klassischer Satz von Routinen für Grafiken, der viele der erforderlichen Algorithmen enthält. Obwohl es in C und etwas veraltet ist, funkeln die Algorithmen immer noch und es sollte trivial sein, auf andere Sprachen zu übertragen. 

Für Ihr aktuelles Problem erstellen Sie einfach die vier Linien für das Rechteck und sehen, welche Schnittlinie Ihre gegebene Linie ist.

19

Der Punkt A befindet sich immer außerhalb des Rechtecks ​​und der Punkt B befindet sich immer in der Mitte des Rechtecks

Angenommen, das Rechteck ist achsausgerichtet, macht dies ziemlich einfach:

Die Steigung der Linie ist s = (Ay - By)/(Ax - Bx).

  • Wenn -h/2 <= s * w/2 <= h/2, dann schneidet die Linie:
    • Die rechte Kante, wenn Axt> Bx
    • Der linke Rand, wenn Ax <Bx.
  • Wenn -w/2 <= (h/2)/s <= w/2, dann schneidet die Linie:
    • Der obere Rand, wenn Ay> By
    • Der untere Rand, wenn Ay <By.

Sobald Sie die Kante kennen, die sie schneidet, kennen Sie eine Koordinate: x = Bx ± w/2 oder y = By ± h/2, je nachdem, welche Kante Sie treffen. Die andere Koordinate ist gegeben durch y = By + s * w/2 oder x = Bx + (h/2)/s. 

19
Joren

/**
 * Finds the intersection point between
 *     * the rectangle
 *       with parallel sides to the x and y axes 
 *     * the half-line pointing towards (x,y)
 *       originating from the middle of the rectangle
 *
 * Note: the function works given min[XY] <= max[XY],
 *       even though minY may not be the "top" of the rectangle
 *       because the coordinate system is flipped.
 * Note: if the input is inside the rectangle,
 *       the line segment wouldn't have an intersection with the rectangle,
 *       but the projected half-line does.
 * Warning: passing in the middle of the rectangle will return the midpoint itself
 *          there are infinitely many half-lines projected in all directions,
 *          so let's just shortcut to midpoint (GIGO).
 *
 * @param x:Number x coordinate of point to build the half-line from
 * @param y:Number y coordinate of point to build the half-line from
 * @param minX:Number the "left" side of the rectangle
 * @param minY:Number the "top" side of the rectangle
 * @param maxX:Number the "right" side of the rectangle
 * @param maxY:Number the "bottom" side of the rectangle
 * @param validate:boolean (optional) whether to treat point inside the rect as error
 * @return an object with x and y members for the intersection
 * @throws if validate == true and (x,y) is inside the rectangle
 * @author TWiStErRob
 * @licence Dual CC0/WTFPL/Unlicence, whatever floats your boat
 * @see <a href="http://stackoverflow.com/a/31254199/253468">source</a>
 * @see <a href="http://stackoverflow.com/a/18292964/253468">based on</a>
 */
function pointOnRect(x, y, minX, minY, maxX, maxY, validate) {
	//assert minX <= maxX;
	//assert minY <= maxY;
	if (validate && (minX < x && x < maxX) && (minY < y && y < maxY))
		throw "Point " + [x,y] + "cannot be inside "
		    + "the rectangle: " + [minX, minY] + " - " + [maxX, maxY] + ".";
	var midX = (minX + maxX) / 2;
	var midY = (minY + maxY) / 2;
	// if (midX - x == 0) -> m == ±Inf -> minYx/maxYx == x (because value / ±Inf = ±0)
	var m = (midY - y) / (midX - x);

	if (x <= midX) { // check "left" side
		var minXy = m * (minX - x) + y;
		if (minY <= minXy && minXy <= maxY)
			return {x: minX, y: minXy};
	}

	if (x >= midX) { // check "right" side
		var maxXy = m * (maxX - x) + y;
		if (minY <= maxXy && maxXy <= maxY)
			return {x: maxX, y: maxXy};
	}

	if (y <= midY) { // check "top" side
		var minYx = (minY - y) / m + x;
		if (minX <= minYx && minYx <= maxX)
			return {x: minYx, y: minY};
	}

	if (y >= midY) { // check "bottom" side
		var maxYx = (maxY - y) / m + x;
		if (minX <= maxYx && maxYx <= maxX)
			return {x: maxYx, y: maxY};
	}

	// Edge case when finding midpoint intersection: m = 0/0 = NaN
	if (x === midX && y === midY) return {x: x, y: y};

	// Should never happen :) If it does, please tell me!
	throw "Cannot find intersection for " + [x,y]
	    + " inside rectangle " + [minX, minY] + " - " + [maxX, maxY] + ".";
}

(function tests() {
	var left = 100, right = 200, top = 50, bottom = 150; // a square, really
	var hMiddle = (left + right) / 2, vMiddle = (top + bottom) / 2;
	function intersectTestRect(x, y) { return pointOnRect(x,y, left,top, right,bottom, true); }
	function intersectTestRectNoValidation(x, y) { return pointOnRect(x,y, left,top, right,bottom, false); }
	function checkTestRect(x, y) { return function() { return pointOnRect(x,y, left,top, right,bottom, true); }; }
	QUnit.test("intersects left side", function(assert) {
		var leftOfRect = 0, closerLeftOfRect = 25;
		assert.deepEqual(intersectTestRect(leftOfRect, 25), {x:left, y:75}, "point above top");
		assert.deepEqual(intersectTestRect(closerLeftOfRect, top), {x:left, y:80}, "point in line with top");
		assert.deepEqual(intersectTestRect(leftOfRect, 70), {x:left, y:90}, "point above middle");
		assert.deepEqual(intersectTestRect(leftOfRect, vMiddle), {x:left, y:100}, "point exact middle");
		assert.deepEqual(intersectTestRect(leftOfRect, 130), {x:left, y:110}, "point below middle");
		assert.deepEqual(intersectTestRect(closerLeftOfRect, bottom), {x:left, y:120}, "point in line with bottom");
		assert.deepEqual(intersectTestRect(leftOfRect, 175), {x:left, y:125}, "point below bottom");
	});
	QUnit.test("intersects right side", function(assert) {
		var rightOfRect = 300, closerRightOfRect = 250;
		assert.deepEqual(intersectTestRect(rightOfRect, 25), {x:right, y:75}, "point above top");
		assert.deepEqual(intersectTestRect(closerRightOfRect, top), {x:right, y:75}, "point in line with top");
		assert.deepEqual(intersectTestRect(rightOfRect, 70), {x:right, y:90}, "point above middle");
		assert.deepEqual(intersectTestRect(rightOfRect, vMiddle), {x:right, y:100}, "point exact middle");
		assert.deepEqual(intersectTestRect(rightOfRect, 130), {x:right, y:110}, "point below middle");
		assert.deepEqual(intersectTestRect(closerRightOfRect, bottom), {x:right, y:125}, "point in line with bottom");
		assert.deepEqual(intersectTestRect(rightOfRect, 175), {x:right, y:125}, "point below bottom");
	});
	QUnit.test("intersects top side", function(assert) {
		var aboveRect = 0;
		assert.deepEqual(intersectTestRect(80, aboveRect), {x:115, y:top}, "point left of left");
		assert.deepEqual(intersectTestRect(left, aboveRect), {x:125, y:top}, "point in line with left");
		assert.deepEqual(intersectTestRect(120, aboveRect), {x:135, y:top}, "point left of middle");
		assert.deepEqual(intersectTestRect(hMiddle, aboveRect), {x:150, y:top}, "point exact middle");
		assert.deepEqual(intersectTestRect(180, aboveRect), {x:165, y:top}, "point right of middle");
		assert.deepEqual(intersectTestRect(right, aboveRect), {x:175, y:top}, "point in line with right");
		assert.deepEqual(intersectTestRect(220, aboveRect), {x:185, y:top}, "point right of right");
	});
	QUnit.test("intersects bottom side", function(assert) {
		var belowRect = 200;
		assert.deepEqual(intersectTestRect(80, belowRect), {x:115, y:bottom}, "point left of left");
		assert.deepEqual(intersectTestRect(left, belowRect), {x:125, y:bottom}, "point in line with left");
		assert.deepEqual(intersectTestRect(120, belowRect), {x:135, y:bottom}, "point left of middle");
		assert.deepEqual(intersectTestRect(hMiddle, belowRect), {x:150, y:bottom}, "point exact middle");
		assert.deepEqual(intersectTestRect(180, belowRect), {x:165, y:bottom}, "point right of middle");
		assert.deepEqual(intersectTestRect(right, belowRect), {x:175, y:bottom}, "point in line with right");
		assert.deepEqual(intersectTestRect(220, belowRect), {x:185, y:bottom}, "point right of right");
	});
	QUnit.test("intersects a corner", function(assert) {
		assert.deepEqual(intersectTestRect(left-50, top-50), {x:left, y:top}, "intersection line aligned with top-left corner");
		assert.deepEqual(intersectTestRect(right+50, top-50), {x:right, y:top}, "intersection line aligned with top-right corner");
		assert.deepEqual(intersectTestRect(left-50, bottom+50), {x:left, y:bottom}, "intersection line aligned with bottom-left corner");
		assert.deepEqual(intersectTestRect(right+50, bottom+50), {x:right, y:bottom}, "intersection line aligned with bottom-right corner");
	});
	QUnit.test("on the corners", function(assert) {
		assert.deepEqual(intersectTestRect(left, top), {x:left, y:top}, "top-left corner");
		assert.deepEqual(intersectTestRect(right, top), {x:right, y:top}, "top-right corner");
		assert.deepEqual(intersectTestRect(right, bottom), {x:right, y:bottom}, "bottom-right corner");
		assert.deepEqual(intersectTestRect(left, bottom), {x:left, y:bottom}, "bottom-left corner");
	});
	QUnit.test("on the edges", function(assert) {
		assert.deepEqual(intersectTestRect(hMiddle, top), {x:hMiddle, y:top}, "top Edge");
		assert.deepEqual(intersectTestRect(right, vMiddle), {x:right, y:vMiddle}, "right Edge");
		assert.deepEqual(intersectTestRect(hMiddle, bottom), {x:hMiddle, y:bottom}, "bottom Edge");
		assert.deepEqual(intersectTestRect(left, vMiddle), {x:left, y:vMiddle}, "left Edge");
	});
	QUnit.test("validates inputs", function(assert) {
		assert.throws(checkTestRect(hMiddle, vMiddle), /cannot be inside/, "center");
		assert.throws(checkTestRect(hMiddle-10, vMiddle-10), /cannot be inside/, "top left of center");
		assert.throws(checkTestRect(hMiddle-10, vMiddle), /cannot be inside/, "left of center");
		assert.throws(checkTestRect(hMiddle-10, vMiddle+10), /cannot be inside/, "bottom left of center");
		assert.throws(checkTestRect(hMiddle, vMiddle-10), /cannot be inside/, "above center");
		assert.throws(checkTestRect(hMiddle, vMiddle), /cannot be inside/, "center");
		assert.throws(checkTestRect(hMiddle, vMiddle+10), /cannot be inside/, "below center");
		assert.throws(checkTestRect(hMiddle+10, vMiddle-10), /cannot be inside/, "top right of center");
		assert.throws(checkTestRect(hMiddle+10, vMiddle), /cannot be inside/, "right of center");
		assert.throws(checkTestRect(hMiddle+10, vMiddle+10), /cannot be inside/, "bottom right of center");
		assert.throws(checkTestRect(left+10, vMiddle-10), /cannot be inside/, "right of left Edge");
		assert.throws(checkTestRect(left+10, vMiddle), /cannot be inside/, "right of left Edge");
		assert.throws(checkTestRect(left+10, vMiddle+10), /cannot be inside/, "right of left Edge");
		assert.throws(checkTestRect(right-10, vMiddle-10), /cannot be inside/, "left of right Edge");
		assert.throws(checkTestRect(right-10, vMiddle), /cannot be inside/, "left of right Edge");
		assert.throws(checkTestRect(right-10, vMiddle+10), /cannot be inside/, "left of right Edge");
		assert.throws(checkTestRect(hMiddle-10, top+10), /cannot be inside/, "below top Edge");
		assert.throws(checkTestRect(hMiddle, top+10), /cannot be inside/, "below top Edge");
		assert.throws(checkTestRect(hMiddle+10, top+10), /cannot be inside/, "below top Edge");
		assert.throws(checkTestRect(hMiddle-10, bottom-10), /cannot be inside/, "above bottom Edge");
		assert.throws(checkTestRect(hMiddle, bottom-10), /cannot be inside/, "above bottom Edge");
		assert.throws(checkTestRect(hMiddle+10, bottom-10), /cannot be inside/, "above bottom Edge");
	});
	QUnit.test("doesn't validate inputs", function(assert) {
		assert.deepEqual(intersectTestRectNoValidation(hMiddle-10, vMiddle-10), {x:left, y:top}, "top left of center");
		assert.deepEqual(intersectTestRectNoValidation(hMiddle-10, vMiddle), {x:left, y:vMiddle}, "left of center");
		assert.deepEqual(intersectTestRectNoValidation(hMiddle-10, vMiddle+10), {x:left, y:bottom}, "bottom left of center");
		assert.deepEqual(intersectTestRectNoValidation(hMiddle, vMiddle-10), {x:hMiddle, y:top}, "above center");
		assert.deepEqual(intersectTestRectNoValidation(hMiddle, vMiddle), {x:hMiddle, y:vMiddle}, "center");
		assert.deepEqual(intersectTestRectNoValidation(hMiddle, vMiddle+10), {x:hMiddle, y:bottom}, "below center");
		assert.deepEqual(intersectTestRectNoValidation(hMiddle+10, vMiddle-10), {x:right, y:top}, "top right of center");
		assert.deepEqual(intersectTestRectNoValidation(hMiddle+10, vMiddle), {x:right, y:vMiddle}, "right of center");
		assert.deepEqual(intersectTestRectNoValidation(hMiddle+10, vMiddle+10), {x:right, y:bottom}, "bottom right of center");
	});
})();
<link href="https://code.jquery.com/qunit/qunit-2.3.2.css" rel="stylesheet"/>
<script src="https://code.jquery.com/qunit/qunit-2.3.2.js"></script>
<div id="qunit"></div>

12
TWiStErRob

Hier ist eine Lösung in Java, die true zurückgibt, wenn ein Liniensegment (die ersten 4 Parameter) ein Achsenrechteck (die letzten 4 Parameter) schneidet. Es wäre trivial, den Schnittpunkt anstelle eines Booleschen Werts zurückzugeben. Es funktioniert, indem zuerst geprüft wird, ob es ganz außen ist, ansonsten mit der Liniengleichung y=m*x+b. Wir wissen, dass die Linien, aus denen das Rechteck besteht, achsenorientiert sind, so dass die Überprüfungen einfach sind.

public boolean aabbContainsSegment (float x1, float y1, float x2, float y2, float minX, float minY, float maxX, float maxY) {  
    // Completely outside.
    if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
        return false;

    float m = (y2 - y1) / (x2 - x1);

    float y = m * (minX - x1) + y1;
    if (y > minY && y < maxY) return true;

    y = m * (maxX - x1) + y1;
    if (y > minY && y < maxY) return true;

    float x = (minY - y1) / m + x1;
    if (x > minX && x < maxX) return true;

    x = (maxY - y1) / m + x1;
    if (x > minX && x < maxX) return true;

    return false;
}

Es ist möglich, eine Abkürzung zu verwenden, wenn der Anfang oder das Ende des Segments innerhalb des Rechtecks ​​liegt. Wahrscheinlich ist es jedoch besser, nur die Mathematik auszuführen, die immer true zurückgibt, wenn sich eines oder beide Segmentenden im Inneren befinden. Wenn Sie die Verknüpfung trotzdem möchten, geben Sie den folgenden Code nach der Überprüfung "vollständig außerhalb" ein.

// Start or end inside.
if ((x1 > minX && x1 < maxX && y1 > minY && y1 < maxY) || (x2 > minX && x2 < maxX && y2 > minY && y2 < maxY)) return true;
7
NateS

Ich gebe dir kein Programm dafür, aber so kannst du es machen:

  • berechnen Sie den Winkel der Linie
  • berechnen Sie den Winkel einer Linie vom Mittelpunkt des Rechtecks ​​zu einer seiner Ecken
  • anhand der Winkel bestimmen Sie, auf welcher Seite die Linie das Rechteck schneidet
  • berechnen Sie den Schnittpunkt zwischen der Seite des Rechtecks ​​und der Linie
3

Ich bin kein Mathematikfan und mag es auch nicht besonders, Sachen aus anderen Sprachen zu übersetzen, wenn andere dies bereits getan haben. Wenn ich also eine langweilige Übersetzungsaufgabe erledige, füge ich sie dem Artikel hinzu, der mich zum Code geführt hat. Um zu verhindern, dass jemand doppelte Arbeit leistet.

Wenn Sie diesen Kreuzungscode in C # haben möchten, schauen Sie hier http://dotnetbyexample.blogspot.nl/2013/09/utility-classes-to-check-if-lines-andor.html

2
LocalJoost

Eine weitere Option, die Sie in Betracht ziehen können, insbesondere wenn Sie viele Linien mit demselben Rechteck testen möchten, besteht darin, Ihr Koordinatensystem so zu transformieren, dass die Achsen mit den Diagonalen des Rechtecks ​​ausgerichtet werden. Da Ihre Linie oder Ihr Strahl in der Mitte des Rechtecks ​​beginnt, können Sie den Winkel bestimmen. Dann können Sie feststellen, welches Segment sich durch den Winkel schneidet (d. H. <90 Grad Seg 1, 90 Grad <<180 Grad Seg 2 usw.). Dann müssen Sie natürlich wieder zum ursprünglichen Koordinatensystem zurückkehren

Obwohl dies nach mehr Arbeit aussieht, können die Transformationsmatrix und ihre Umkehrung einmal berechnet und erneut verwendet werden. Dies gilt auch für höher dimensionierte Rechtecke, bei denen Quadranten und Schnittpunkte mit Flächen in 3D usw. berücksichtigt werden müssten.

2
Ivajlo Donev

Hier ist eine Lösung, die für mich funktioniert. Ich gehe davon aus, dass das Rect auf die Achsen ausgerichtet ist.

Daten:

// Center of the Rectangle
let Cx: number
let Cy: number
// Width
let w: number
// Height
let h: number

// Other Point
let Ax: number
let Ay: number

Verschieben Sie nun Punkt A um die Mitte des Rechtecks, sodass das Rechteck in O (0,0) zentriert ist, und betrachten Sie das Problem im ersten Viertel (d. H. X> 0 und y> 0). 

// Coordinates Translated
let Px = Math.abs(Ax - Cx)
let Py = Math.abs(Ay - Cy)

// Slope of line from Point P to Center
let Pm = Py / Px

// Slope of rectangle Diagonal
let Rm = h / w

// If the point is inside the rectangle, return the center
let res: [number, number] = [0, 0]

// Check if the point is inside and if so do not calculate
if (!(Px < w / 2 && Py < h / 2)) {

    // Calculate point in first quarter: Px >= 0 && Py >= 0
    if (Pm <= Rm) {
        res[0] = w / 2
        res[1] = (w * Pm) / 2
    } else {
        res[0] = h / (Pm * 2)
        res[1] = h / 2
    }

    // Set original sign 
    if (Ax - Cx < 0) res[0] *= -1
    if (Ay - Cy < 0) res[1] *= -1
}

// Translate back
return [res[0] + Cx, res[1] + Cy]
1
ivanross

Ich weiß nicht, ob dies der beste Weg ist, aber Sie können den Anteil der Linie innerhalb des Rechtecks ​​ermitteln. Sie können dies aus der Breite des Rechtecks ​​und der Differenz zwischen den x-Koordinaten von A und B (oder aus Höhe und y-Koordinaten) ablesen. Je nach Breite und Höhe können Sie überprüfen, welcher Fall gilt. Der andere Fall wird sich auf die Erweiterung beziehen einer Seite des Rechtecks). Wenn Sie dies haben, nehmen Sie einfach diesen Anteil des Vektors von B nach A und Sie haben die Koordinaten Ihres Schnittpunkts.

1
JaakkoK

Hier ist eine etwas verbale Methode, die die Schnittintervalle zwischen einer (unendlichen) Linie und einem Rechteck nur mit grundlegender Mathematik zurückgibt:

// Line2      - 2D line with Origin (= offset from 0,0) and direction
// Rectangle2 - 2D rectangle by min and max points
// Contacts   - Stores entry and exit times of a line through a convex shape

Contacts findContacts(const Line2 &line, const Rectangle2 &rect) {
  Contacts contacts;

  // If the line is not parallel to the Y axis, find out when it will cross
  // the limits of the rectangle horizontally
  if(line.Direction.X != 0.0f) {
    float leftTouch = (rect.Min.X - line.Origin.X) / line.Direction.X;
    float rightTouch = (rect.Max.X - line.Origin.X) / line.Direction.X;
    contacts.Entry = std::fmin(leftTouch, rightTouch);
    contacts.Exit = std::fmax(leftTouch, rightTouch);
  } else if((line.Offset.X < rect.Min.X) || (line.Offset.X >= rect.Max.X)) {
    return Contacts::None; // Rectangle missed by vertical line
  }

  // If the line is not parallel to the X axis, find out when it will cross
  // the limits of the rectangle vertically
  if(line.Direction.Y != 0.0f) {
    float topTouch = (rectangle.Min.Y - line.Offset.Y) / line.Direction.Y;
    float bottomTouch = (rectangle.Max.Y - line.Offset.Y) / line.Direction.Y;

    // If the line is parallel to the Y axis (and it goes through
    // the rectangle), only the Y axis needs to be taken into account.
    if(line.Direction.X == 0.0f) {
      contacts.Entry = std::fmin(topTouch, bottomTouch);
      contacts.Exit = std::fmax(topTouch, bottomTouch);
    } else {
      float verticalEntry = std::fmin(topTouch, bottomTouch);
      float verticalExit = std::fmax(topTouch, bottomTouch);

      // If the line already left the rectangle on one axis before entering it
      // on the other, it has missed the rectangle.
      if((verticalExit < contacts.Entry) || (contacts.Exit < verticalEntry)) {
        return Contacts::None;
      }

      // Restrict the intervals from the X axis of the rectangle to where
      // the line is also within the limits of the rectangle on the Y axis
      contacts.Entry = std::fmax(verticalEntry, contacts.Entry);
      contacts.Exit = std::fmin(verticalExit, contacts.Exit);
    }
  } else if((line.Offset.Y < rect.Min.Y) || (line.Offset.Y > rect.Max.Y)) {
    return Contacts::None; // Rectangle missed by horizontal line
  }

  return contacts;
}

Dieser Ansatz bietet ein hohes Maß an numerischer Stabilität (die Intervalle sind in allen Fällen das Ergebnis einer einzigen Subtraktion und Division), beinhalten jedoch einige Verzweigungen.

Für ein Liniensegment (mit Start- und Endpunkten) müssen Sie den Startpunkt des Segments als Ursprung und für die Richtung end - start angeben. Die Berechnung der Koordinaten der beiden Kreuzungen ist einfach als entryPoint = Origin + direction * contacts.Entry und exitPoint = Origin + direction * contacts.Exit.

0
Cygon