Frage

Wie man den Bereich der Kreuzung zwischen einem Dreieck (angegeben als drei (X, Y) Paare) und ein Kreis (X, Y, R) nicht berechnen? Ich habe einige der Suche ohne Erfolg getan. Dies ist für die Arbeit, keine Schule. :)

Es wäre so etwas wie dies in C # aussehen:

struct { PointF vert[3]; } Triangle;
struct { PointF center; float radius; } Circle;

// returns the area of intersection, e.g.:
// if the circle contains the triangle, return area of triangle
// if the triangle contains the circle, return area of circle
// if partial intersection, figure that out
// if no intersection, return 0
double AreaOfIntersection(Triangle t, Circle c)
{
 ...
}
War es hilfreich?

Lösung

Wenn Sie eine exakte Lösung (oder zumindest so genau wie möglich Gleitkommaarithmetik bekommen verwenden) dann dies eine Menge Lauferei beteiligen wird, weil es so viele Fälle sind zu berücksichtigen.

Ich zähle neun verschiedene Fälle (in der Abbildung unten durch die Anzahl der Eckpunkt des Dreiecks innerhalb des Kreises kategorisierten, und die Anzahl der Kanten des Dreiecks, die in dem Kreis schneiden oder enthalten sind):

Neun Fälle für Kreuzung: 1, 2. keine Ecken, keine Kanten, 3. keine Scheitelpunkte, eine Kante; 4. keine Ecken, zwei Kanten, 5. keine Scheitelpunkte, drei Kanten, 6. einen Scheitel, zwei Flanken, 7. einen Scheitel, drei Kanten, 8. zwei Ecken, drei Kanten;. 9. drei Ecken, drei Kanten

(Allerdings ist diese Art der Aufzählung von geometrischen Fällen ist bekannt, schwierig zu sein, und es würde mich nicht überraschen, wenn ich ein oder zwei verpasst!)

So ist der Ansatz:

  1. für jeden Eckpunkt des Dreiecks bestimmen, ob sie innerhalb des Kreises ist. Ich werde Sie wissen, zu übernehmen, wie das zu tun.

  2. für jede Kante des Dreiecks bestimmen, ob er den Kreis schneidet. (Ich schrieb auf eine Methode hier oder sehen jede Computational Geometry Buch .) Sie werden den Punkt oder die Schnittpunkte (falls vorhanden) für die Verwendung in Schritt 4.

  3. berechnen müssen
  4. Sie fest, welche der neun Fälle, die Sie haben.

  5. Berechnen Sie den Bereich der Kreuzung. Fälle 1, 2 und 9 sind einfach. in Dreiecken und Kreissegmente In den übrigen sechs Fällen habe ich gestrichelten Linien gezeichnet zu zeigen, wie die Schnittfläche partitionieren bezogen auf den ursprünglichen Eckpunkt des Dreiecks, und auf dem Schnittpunkt Sie berechnet in Schritt 2.

Dieser Algorithmus sein wird ziemlich empfindlich und anfällig für Fehler, die nur einer der Fälle betreffen, so stellen Sie sicher, dass Sie Testfälle, die alle neun Fälle abdecken (und ich schlage vor, Permutation die Eckpunkte der Testdreiecke auch). Achten Sie besonders auf Fälle, in denen einer der Eckpunkte des Dreiecks am Rand des Kreises ist.

Wenn Sie nicht eine exakte Lösung benötigen, dann die Zahlen Rastern und die Pixel in der Kreuzung zu zählen (wie durch ein paar anderen Befragten vorgeschlagen) scheint wie ein viel einfacher Ansatz, um Code und entsprechend weniger fehleranfällig.

Andere Tipps

Zuerst werden wir erinnern uns daran, wie die Fläche eines Polygons zu finden. Sobald wir dies getan haben, zu dem Algorithmus die Kreuzung zwischen einem Polygon zu finden und einem Kreis soll leicht zu verstehen sein.

Wie wird die Fläche eines Polygons finden

ist im Fall eines Dreiecks aussehen lassen, weil alle wesentlichen Logik erscheint. Nehmen wir an, wir mit den Eckpunkten eines Dreiecks haben (x1, y1), (x2, y2) und (x3, y3), wie Sie rund um das Dreieck gegen den Uhrzeigersinn gehen, wie in der folgenden Abbildung dargestellt: triangleFigure

Dann können Sie den Bereich, der durch die Formel berechnen

A = (x1 y2 + x2 y3 + x3 y1 - x2y1- x3 y2 - x1y3). / 2

Um zu sehen, warum diese Formel funktioniert, machen sie es neu anordnen, so dass es in der Form

A = (x1 y2 - x2 y1) / 2 + (x2 y3 - x3 y2) / 2 +. (X3 y1 - x1y3) / 2

Nun ist der erste Term ist die folgende Fläche, die in unserem Fall positiv ist: eingeben Bild Beschreibung hier

Wenn es nicht klar ist, dass der Bereich der grünen Region in der Tat ist (x1 y2 - x2 y1) / 2, dann lesen Sie diese .

Der zweite Term ist dieser Bereich, was wiederum positiv ist:

eingeben Bild Beschreibung hier

Und der dritte Bereich ist in der folgenden Abbildung dargestellt. Dieses Mal ist der Bereich negativ

eingeben Bild Beschreibung hier

diese drei bis Hinzufügen erhalten wir folgendes Bild

eingeben Bild Beschreibung hier

Wir sehen, dass die grüne Fläche, die außerhalb des Dreiecks war durch den roten Bereich aufgehoben wird, so dass die Nettofläche nur die Fläche des Dreiecks ist, und das zeigt, warum unsere Formel in diesem Fall zutraf.

Was ich oben gesagt war die intuitive Erklärung, warum der Bereich Formel richtig war. Eine rigorosere Erklärung wäre zu beachten, dass, wenn wir den Bereich von einer Kante zu berechnen, der Bereich, erhalten wir die Umgebung, die wir von der Integration r ^ 2dθ / 2 erhalten würden, so dass wir die Integration effektiv r ^ 2dθ / 2 um die Grenze des Polygons, und schürt Satz ergibt dies das gleiche Ergebnis wie die Integration von rdrdθ über die Region um das Polygon begrenzt. Da die Integration von rdrdθ über den durch das Polygon begrenzten Bereich der Fläche gibt, schließen wir, dass unser Verfahren richtig das Gebiet geben muss.

Bereich der Kreuzung eines Kreises mit einem Polygon

Lassen Sie sich nun darüber diskutieren, wie die Fläche des Schnittpunktes eines Kreises mit dem Radius R mit einem Polygon zu finden, wie zeigt in der folgenden Abbildung:

eingeben Bild Beschreibung hier

Wir interessieren uns für den Bereich der grünen Region. Wir können, wie im Fall des einzelnen Polygon, brechen unsere Berechnung für jede Seite des Polygons eine Fläche in der Suche, und dann die Bereiche addieren.

Unser erster Bereich wird wie folgt aussehen: eingeben Bild Beschreibung hier

Der zweite Bereich wird wie folgt aussehen eingeben Bild Beschreibung hier

Und der dritte Bereich wird eingeben Bild Beschreibung hier

Auch hier sind die ersten beiden Bereiche positiv in unserem Fall, während der dritte negativ sein wird. Hoffentlich wird die Stornierungen arbeiten, so dass die Nettofläche ist in der Tat das Gebiet, das wir interessiert sind. Mal sehen.

eingeben Bild Beschreibung hier

Tatsächlich ist die Summe der Flächen wird Bereich sind wir interessiert an.

Auch hier können wir eine strengere Erklärung geben, warum das funktioniert. Lasse ich die Region durch den Schnittpunkt definiert sein und P das Polygon sein. Dann aus der vorherigen Diskussion wissen wir, dass wir an den Computer wollen das Integral von r ^ 2dθ /2 um die Grenze von I. dies schwierig jedoch zu tun, weil es die Kreuzung zu finden, erfordert.

Stattdessen haben wir ein Integral über das Polygon. Wir integrierten max (R, R) ^ 2 d & thgr; / 2 über die Grenze des Polygons. Um zu sehen, warum dies die richtige Antwort gibt, lassen Sie sich eine Funktion π definieren, die einen Punkt in Polarkoordinaten nimmt (r, θ) zu dem Punkt (max (r, R), θ). Es sollte nicht verwirrend sein, auf denen die Koordinatenfunktionen von π (r) = max (R, R) und π (θ) = θ zu beziehen. Dann, was wir taten, war π (r) ^ 2 dR / 2 über die Grenze des Polygons zu integrieren.

Auf der anderen Seite, da π (θ) = θ, das ist das gleiche wie die Integration von π (r) ^ 2 dπ (θ) / 2 über die Grenze des Polygons.

Jetzt eine Veränderung der Variablen tun, finden wir, dass wir die gleiche Antwort bekommen würde, wenn wir r ^ 2 dR / 2 über die Grenze von π (P) integriert, wobei π (P), um das Bild von P unter π ist.

Stokes Theorem Mit wieder wissen wir, dass r Integration ^ 2 dR / 2 über die Grenze von π (P) gibt uns die Fläche von π (P). Mit anderen Worten gibt es die gleiche Antwort wie die Integration von dxdy über π (P).

Mit einer Änderung der Variable wieder, wissen wir, dass die Integration von dxdy über π (P) ist die gleiche wie Jdxdy über P Integration, wobei J die Jacobi von π ist.

Jetzt können wir das Integral von Jdxdy in zwei Bereiche aufgeteilt: der Teil in dem Kreis und der Teil außerhalb des Kreises. Nun π läßt Punkte im Kreis allein so J = 1 gibt, so ist der Beitrag von diesem Teil von P ist der Bereich des Teils von P, die im Kreis liegt das heißt, der Bereich der Kreuzung. Der zweite Bereich ist der Bereich außerhalb des Kreises. Es J = 0, da π diesen Teil an die Grenze des Kreises kollabiert nach unten.

So was wir berechnen ist in der Tat der Bereich der Kreuzung.

Nun, da wir relativ sicher sind, wir wissen, konzeptionell, wie die Gegend zu finden, lassen Sie uns genauer darüber sprechen, wie der Beitrag von einem einzelnen Segment zu berechnen. Lassen Sie uns, indem Sie auf ein Segment in beginnen, was ich nennen „Standardgeometrie“. Es wird unten gezeigt.

eingeben Bild Beschreibung hier

In der Standardgeometrie, geht die Kante horizontal von links nach rechts. Es wird durch drei Zahlen beschrieben: xi, die x-Koordinate, wo die Kante beginnt, xf, die x-Koordinate, wo die Kante endet, und y, das y des Randes koordinieren.

Nun sehen wir, dass, wenn | y |

Der Bereich der Region 2 ist nur die Fläche eines Dreiecks. Allerdings müssen wir vorsichtig Zeichen sein. Wir wollen, dass der Bereich, positiv sein gezeigt, so dass wir das Gebiet werden sagen - (Xint - (-Xint)). Y / 2

Eine andere Sache im Auge zu behalten ist, dass im Allgemeinen, xi nicht weniger sein muss als -Xint und xf nicht als Xint größer sein muss.

Der andere Fall zu prüfen ist, | y | > R. Dieser Fall einfacher, weil es nur ein Stück ist, der Region 1 in der Figur ähnlich ist.

Nun, da wir wissen, wie das Gebiet von einer Kante in Standardgeometrie zu berechnen, das einzige, was zu tun ist, beschreiben, wie man jede Kante in Standard-Geometrie zu verwandeln.

Aber das ist nur eine einfache Änderung der Koordinaten. Gegeben einig mit Anfangspunkt vi und letzten Vertex vf, der neue x-Einheitsvektor ist der Einheitsvektor zeigt von vi zu vf. Dann xi ist nur die Verschiebung des VI von der Mitte des Kreises in x punktiert, und xf ist nur xi plus dem Abstand zwischen VI und vf. Inzwischen y wird durch den Keil Produkt von x mit der Verschiebung von vi von der Mitte des Kreises gegeben.

Code

Das ist die Beschreibung des Algorithmus, jetzt ist es Zeit, einen Code zu schreiben. Ich werde Java verwenden.

Zunächst einmal, da wir mit Kreisen arbeiten, sollten wir einen Kreis Klasse

public class Circle {

    final Point2D center;
    final double radius;

    public Circle(double x, double y, double radius) {
        center = new Point2D.Double(x, y);
        this.radius = radius;
    }

    public Circle(Point2D.Double center, double radius) {
        this(center.getX(), center.getY(), radius);
    }

    public Point2D getCenter() {
        return new Point2D.Double(getCenterX(), getCenterY());
    }

    public double getCenterX() {
        return center.getX();
    }

    public double getCenterY() {
        return center.getY();
    }

    public double getRadius() {
        return radius;
    }

}

Für Polygone, werde ich Javas Shape Klasse. Shapes hat eine PathIterator, die ich verwenden kann, durch die Kanten des Polygons iterieren.

Jetzt für die eigentliche Arbeit. Ich werde die Logik der Iterieren durch die Kanten trennen, um die Kanten in Standardgeometrie usw. setzen, aus der Logik der Berechnung der Fläche Sobald dies erledigt ist. Der Grund dafür ist, dass Sie in der Zukunft etwas anderes neben oder zusätzlich zu dem Bereich berechnen wollen, und Sie wollen in der Lage sein, den Code wieder zu verwenden, die mit Iterieren durch die Kanten zu behandeln.

Also ich habe eine generische Klasse, die eine Eigenschaft der Klasse T über unsere Polygon Kreis Kreuzung berechnet.

public abstract class CircleShapeIntersectionFinder<T> {

Es hat drei statische Methoden, die nur Rechen Geometrie helfen:

private static double[] displacment2D(final double[] initialPoint, final double[] finalPoint) {
    return new double[]{finalPoint[0] - initialPoint[0], finalPoint[1] - initialPoint[1]};
}

private static double wedgeProduct2D(final double[] firstFactor, final double[] secondFactor) {
    return firstFactor[0] * secondFactor[1] - firstFactor[1] * secondFactor[0];
}

static private double dotProduct2D(final double[] firstFactor, final double[] secondFactor) {
    return firstFactor[0] * secondFactor[0] + firstFactor[1] * secondFactor[1];
}

Es gibt zwei Instanzfelder, ein Circle, die nur eine Kopie des Kreises hält, und die currentSquareRadius, die eine Kopie des quadratischen Radius hält. Dies mag seltsam erscheinen, aber die Klasse verwende ich tatsächlich ausgestattet, die Bereiche einer ganzen Sammlung von Kreis-Polygon-Kreuzungen zu finden. Deshalb habe ich mich auf einen der Kreise als „aktuell“.

private Circle currentCircle;
private double currentSquareRadius;

Als nächstes kommt die Methode zur Berechnung, was wir berechnen möchten:

public final T computeValue(Circle circle, Shape shape) {
    initialize();
    processCircleShape(circle, shape);
    return getValue();
}

initialize() und getValue() sind abstrakt. initialize() würde die Variable, die insgesamt der Bereich auf Null zu halten, und getValue() würde nur den Bereich zurück. Die Definition für processCircleShape ist

private void processCircleShape(Circle circle, final Shape cellBoundaryPolygon) {
    initializeForNewCirclePrivate(circle);
    if (cellBoundaryPolygon == null) {
        return;
    }
    PathIterator boundaryPathIterator = cellBoundaryPolygon.getPathIterator(null);
    double[] firstVertex = new double[2];
    double[] oldVertex = new double[2];
    double[] newVertex = new double[2];
    int segmentType = boundaryPathIterator.currentSegment(firstVertex);
    if (segmentType != PathIterator.SEG_MOVETO) {
        throw new AssertionError();
    }
    System.arraycopy(firstVertex, 0, newVertex, 0, 2);
    boundaryPathIterator.next();
    System.arraycopy(newVertex, 0, oldVertex, 0, 2);
    segmentType = boundaryPathIterator.currentSegment(newVertex);
    while (segmentType != PathIterator.SEG_CLOSE) {
        processSegment(oldVertex, newVertex);
        boundaryPathIterator.next();
        System.arraycopy(newVertex, 0, oldVertex, 0, 2);
        segmentType = boundaryPathIterator.currentSegment(newVertex);
    }
    processSegment(newVertex, firstVertex);
}

Lassen Sie uns einen zweiten nehmen schnell an initializeForNewCirclePrivate zu suchen. Diese Methode nur setzt die Instanzfelder und ermöglicht die abgeleitete Klasse jede Eigenschaft des Kreises zu speichern. Seine Definition ist

private void initializeForNewCirclePrivate(Circle circle) {
    currentCircle = circle;
    currentSquareRadius = currentCircle.getRadius() * currentCircle.getRadius();
    initializeForNewCircle(circle);
}

initializeForNewCircle ist abstrakt und eine Implementierung wäre für die Kreise Radius speichern zu vermeiden, Quadratwurzeln zu tun. Wie auch immer wieder processCircleShape. Nach initializeForNewCirclePrivate rufen, überprüfen wir, ob das Polygon null (die ich als ein leeres Polygon bin Interpretation), und wir zurück, wenn es null ist. In diesem Fall würde unsere berechnete Fläche gleich Null sein. Wenn das Polygon nicht ist null dann erhalten wir die PathIterator des Polygons. Das Argument für die getPathIterator Methode I nennen, ist eine affine Transformation, die auf den Weg angewendet werden können. Ich will nicht beantragen, obwohl, so gebe ich null nur.

Als nächstes Ich erkläre die double[]s die Spur der Eckpunkte halten. Ich muß den ersten Eckpunkt erinnern, weil die PathIterator mir nur einmal jede Ecke gibt, also muß ich zurückgehen, nachdem sie mir die letzte Ecke gegeben hat, und mit diesem letzten Scheitelpunkt und dem ersten Scheitelpunkt eine Kante bilden.

Die currentSegment Methode in der nächsten Zeile setzt den nächsten Scheitelpunkt in seinem Argument. Es gibt einen Code, der Ihnen sagt, wenn er aus Eckpunkten ist. Deshalb ist der Steuerausdruck für meine while-Schleife ist, was es ist.

Die meisten der Rest des Codes dieser Methode ist uninteressant Logik im Zusammenhang durch die Knoten iterieren. Die wichtige Sache ist, dass einmal pro Iteration der while-Schleife nenne ich processSegment und dann nenne ich processSegment wieder am Ende des Verfahrens die Kanten zu verarbeiten, die die letzte Ecke mit dem ersten Scheitelpunkt verbunden ist.

auf den Code für processSegment Werfen wir einen Blick:

private void processSegment(double[] initialVertex, double[] finalVertex) {
    double[] segmentDisplacement = displacment2D(initialVertex, finalVertex);
    if (segmentDisplacement[0] == 0 && segmentDisplacement[1] == 0) {
        return;
    }
    double segmentLength = Math.sqrt(dotProduct2D(segmentDisplacement, segmentDisplacement));
    double[] centerToInitialDisplacement = new double[]{initialVertex[0] - getCurrentCircle().getCenterX(), initialVertex[1] - getCurrentCircle().getCenterY()};
    final double leftX = dotProduct2D(centerToInitialDisplacement, segmentDisplacement) / segmentLength;
    final double rightX = leftX + segmentLength;
    final double y = wedgeProduct2D(segmentDisplacement, centerToInitialDisplacement) / segmentLength;
    processSegmentStandardGeometry(leftX, rightX, y);
}

Bei diesem Verfahren implementieren I die Schritte eine Kante in die Standardgeometrie zu transformieren, wie oben beschrieben. Zuerst berechnen segmentDisplacement I, wobei die Verschiebung von dem Anfangsscheitelpunkt zu dem endgültigen Scheitelpunkt. Dies definiert die x-Achse des Standard geometry. Ich habe eine baldige Rückkehr, wenn diese Verschiebung gleich Null ist.

Als nächstes berechne ich die Länge der Verschiebung, da dies notwendig ist, um das x-Einheitsvektor zu erhalten. Sobald ich diese Informationen vorliegen, berechne ich die Verschiebung von der Mitte des Kreises auf den Anfangspunkt. Das Skalarprodukt dieses mit segmentDisplacement gibt mir leftX, die ich hatte xi angerufen. Dann rightX, die ich hatte xf angerufen, nur leftX + segmentLength. Schließlich habe ich den Keil Produkt y zu erhalten, wie oben beschrieben.

Nun, da ich das Problem in die Standardgeometrie verwandelt habe, wird es leicht sein, zu beschäftigen. Das ist, was die processSegmentStandardGeometry Verfahren der Fall ist. Schauen wir uns den Code aussehen

private void processSegmentStandardGeometry(double leftX, double rightX, double y) {
    if (y * y > getCurrentSquareRadius()) {
        processNonIntersectingRegion(leftX, rightX, y);
    } else {
        final double intersectionX = Math.sqrt(getCurrentSquareRadius() - y * y);
        if (leftX < -intersectionX) {
            final double leftRegionRightEndpoint = Math.min(-intersectionX, rightX);
            processNonIntersectingRegion(leftX, leftRegionRightEndpoint, y);
        }
        if (intersectionX < rightX) {
            final double rightRegionLeftEndpoint = Math.max(intersectionX, leftX);
            processNonIntersectingRegion(rightRegionLeftEndpoint, rightX, y);
        }
        final double middleRegionLeftEndpoint = Math.max(-intersectionX, leftX);
        final double middleRegionRightEndpoint = Math.min(intersectionX, rightX);
        final double middleRegionLength = Math.max(middleRegionRightEndpoint - middleRegionLeftEndpoint, 0);
        processIntersectingRegion(middleRegionLength, y);
    }
}

Die erste if unterscheidet die Fälle, in denen y klein genug ist, dass der Rand des Kreises schneiden. Wenn y groß ist, und es gibt keine Möglichkeit der Kreuzung, dann rufe ich die Methode, diesen Fall zu behandeln. Ansonsten behandeln ich den Fall, in dem Schnittpunkt möglich ist.

Wenn Schnittpunkt möglich ist, berechnen I die x-Koordinate des Schnitt, intersectionX und I teilen den Rand in drei Abschnitte, die Bereichen entsprechen 1, 2 und 3 der Standardgeometrie obigen Abbildung. Zuerst habe ich Griff Region 1.

Region 1 zu handhaben, ich überprüfen, ob leftX in der Tat weniger als -intersectionX sonst dort 1. keine Region wäre, wenn es eine Region 1 ist, dann muß ich wissen, wann es endet. Es endet mit dem Minimum von rightX und -intersectionX. Nachdem ich diese x-Koordinaten gefunden habe, beschäftige ich mich mit diesem Nicht-Kreuzungsbereich.

Ich habe eine ähnliche Sache zu handhaben Bereich 3.

Für Bereich 2, ich habe eine gewisse Logik zu tun, dass leftX und rightX überprüfen Sie tatsächlich etwas Region zwischen -intersectionX und intersectionX umklammern. Nachdem die Region zu finden, ich brauche nur die Länge der Region und y, so gebe ich diese beiden Zahlen auf eine abstrakte Methode, die die Region 2 behandelt.

Lassen Sie uns auf den Code für processNonIntersectingRegion Betrachten Sie nun

private void processNonIntersectingRegion(double leftX, double rightX, double y) {
    final double initialTheta = Math.atan2(y, leftX);
    final double finalTheta = Math.atan2(y, rightX);
    double deltaTheta = finalTheta - initialTheta;
    if (deltaTheta < -Math.PI) {
        deltaTheta += 2 * Math.PI;
    } else if (deltaTheta > Math.PI) {
        deltaTheta -= 2 * Math.PI;
    }
    processNonIntersectingRegion(deltaTheta);
}

ich einfach atan2 verwenden, um die Winkeldifferenz zwischen leftX und rightX zu berechnen. Dann füge ich Code mit der Diskontinuität in atan2 zu behandeln, aber das ist wahrscheinlich nicht notwendig, weil die Unterbrechung entweder bei 180 Grad oder 0 Grad auftritt. Dann gebe ich den Unterschied in Winkeln auf eine abstrakte Methode. Schließlich haben wir nur abstrakte Methoden und Getter:

    protected abstract void initialize();

    protected abstract void initializeForNewCircle(Circle circle);

    protected abstract void processNonIntersectingRegion(double deltaTheta);

    protected abstract void processIntersectingRegion(double length, double y);

    protected abstract T getValue();

    protected final Circle getCurrentCircle() {
        return currentCircle;
    }

    protected final double getCurrentSquareRadius() {
        return currentSquareRadius;
    }

}

Lassen Sie uns auf der erweiterten Klasse aussehen Nun CircleAreaFinder

public class CircleAreaFinder extends CircleShapeIntersectionFinder<Double> {

public static double findAreaOfCircle(Circle circle, Shape shape) {
    CircleAreaFinder circleAreaFinder = new CircleAreaFinder();
    return circleAreaFinder.computeValue(circle, shape);
}

double area;

@Override
protected void initialize() {
    area = 0;
}

@Override
protected void processNonIntersectingRegion(double deltaTheta) {
    area += getCurrentSquareRadius() * deltaTheta / 2;
}

@Override
protected void processIntersectingRegion(double length, double y) {
    area -= length * y / 2;
}

@Override
protected Double getValue() {
    return area;
}

@Override
protected void initializeForNewCircle(Circle circle) {

}

}

Es hat ein Feld area Spur des Bereichs zu halten. initialize setzt Fläche auf Null, wie erwartet. Wenn wir eine nicht schneidende Kante verarbeitet, inkrementieren wir den Bereich, der durch R ^ 2 Δθ / 2, wie wir uns oben geschlossen sein sollten. Für eine schneidende Kante, dekrementieren wir den Bereich von y*length/2. Das war so, dass negative Werte für y zu positiven Flächen entsprechen, als wir beschlossen, sie sollten.

Nun ist die nette Sache ist, wenn wir den Überblick über den Umfang halten, wollen wir müssen mehr Arbeit nicht tun, dass viel. Ich definiert eine AreaPerimeter Klasse:

public class AreaPerimeter {

    final double area;
    final double perimeter;

    public AreaPerimeter(double area, double perimeter) {
        this.area = area;
        this.perimeter = perimeter;
    }

    public double getArea() {
        return area;
    }

    public double getPerimeter() {
        return perimeter;
    }

}

und jetzt brauchen wir nur noch einmal unsere abstrakte Klasse erweitern AreaPerimeter als Typen verwendet wird.

public class CircleAreaPerimeterFinder extends CircleShapeIntersectionFinder<AreaPerimeter> {

    public static AreaPerimeter findAreaPerimeterOfCircle(Circle circle, Shape shape) {
        CircleAreaPerimeterFinder circleAreaPerimeterFinder = new CircleAreaPerimeterFinder();
        return circleAreaPerimeterFinder.computeValue(circle, shape);
    }

    double perimeter;
    double radius;
    CircleAreaFinder circleAreaFinder;

    @Override
    protected void initialize() {
        perimeter = 0;
        circleAreaFinder = new CircleAreaFinder();
    }

    @Override
    protected void initializeForNewCircle(Circle circle) {
        radius = Math.sqrt(getCurrentSquareRadius());
    }

    @Override
    protected void processNonIntersectingRegion(double deltaTheta) {
        perimeter += deltaTheta * radius;
        circleAreaFinder.processNonIntersectingRegion(deltaTheta);
    }

    @Override
    protected void processIntersectingRegion(double length, double y) {
        perimeter += Math.abs(length);
        circleAreaFinder.processIntersectingRegion(length, y);
    }

    @Override
    protected AreaPerimeter getValue() {
        return new AreaPerimeter(circleAreaFinder.getValue(), perimeter);
    }

}

Wir haben eine variable perimeter Spur des Umfangs zu halten, erinnern wir uns, den Wert des radius Math.sqrt eine Menge rufen zu vermeiden, und wir die Berechnung der Fläche in unseren CircleAreaFinder delegieren. Wir können sehen, dass die Formeln für den Umfang sind einfach.

Als Referenz hier ist der vollständige Code von CircleShapeIntersectionFinder

private static double[] displacment2D(final double[] initialPoint, final double[] finalPoint) {
        return new double[]{finalPoint[0] - initialPoint[0], finalPoint[1] - initialPoint[1]};
    }

    private static double wedgeProduct2D(final double[] firstFactor, final double[] secondFactor) {
        return firstFactor[0] * secondFactor[1] - firstFactor[1] * secondFactor[0];
    }

    static private double dotProduct2D(final double[] firstFactor, final double[] secondFactor) {
        return firstFactor[0] * secondFactor[0] + firstFactor[1] * secondFactor[1];
    }

    private Circle currentCircle;
    private double currentSquareRadius;

    public final T computeValue(Circle circle, Shape shape) {
        initialize();
        processCircleShape(circle, shape);
        return getValue();
    }

    private void processCircleShape(Circle circle, final Shape cellBoundaryPolygon) {
        initializeForNewCirclePrivate(circle);
        if (cellBoundaryPolygon == null) {
            return;
        }
        PathIterator boundaryPathIterator = cellBoundaryPolygon.getPathIterator(null);
        double[] firstVertex = new double[2];
        double[] oldVertex = new double[2];
        double[] newVertex = new double[2];
        int segmentType = boundaryPathIterator.currentSegment(firstVertex);
        if (segmentType != PathIterator.SEG_MOVETO) {
            throw new AssertionError();
        }
        System.arraycopy(firstVertex, 0, newVertex, 0, 2);
        boundaryPathIterator.next();
        System.arraycopy(newVertex, 0, oldVertex, 0, 2);
        segmentType = boundaryPathIterator.currentSegment(newVertex);
        while (segmentType != PathIterator.SEG_CLOSE) {
            processSegment(oldVertex, newVertex);
            boundaryPathIterator.next();
            System.arraycopy(newVertex, 0, oldVertex, 0, 2);
            segmentType = boundaryPathIterator.currentSegment(newVertex);
        }
        processSegment(newVertex, firstVertex);
    }

    private void initializeForNewCirclePrivate(Circle circle) {
        currentCircle = circle;
        currentSquareRadius = currentCircle.getRadius() * currentCircle.getRadius();
        initializeForNewCircle(circle);
    }

    private void processSegment(double[] initialVertex, double[] finalVertex) {
        double[] segmentDisplacement = displacment2D(initialVertex, finalVertex);
        if (segmentDisplacement[0] == 0 && segmentDisplacement[1] == 0) {
            return;
        }
        double segmentLength = Math.sqrt(dotProduct2D(segmentDisplacement, segmentDisplacement));
        double[] centerToInitialDisplacement = new double[]{initialVertex[0] - getCurrentCircle().getCenterX(), initialVertex[1] - getCurrentCircle().getCenterY()};
        final double leftX = dotProduct2D(centerToInitialDisplacement, segmentDisplacement) / segmentLength;
        final double rightX = leftX + segmentLength;
        final double y = wedgeProduct2D(segmentDisplacement, centerToInitialDisplacement) / segmentLength;
        processSegmentStandardGeometry(leftX, rightX, y);
    }

    private void processSegmentStandardGeometry(double leftX, double rightX, double y) {
        if (y * y > getCurrentSquareRadius()) {
            processNonIntersectingRegion(leftX, rightX, y);
        } else {
            final double intersectionX = Math.sqrt(getCurrentSquareRadius() - y * y);
            if (leftX < -intersectionX) {
                final double leftRegionRightEndpoint = Math.min(-intersectionX, rightX);
                processNonIntersectingRegion(leftX, leftRegionRightEndpoint, y);
            }
            if (intersectionX < rightX) {
                final double rightRegionLeftEndpoint = Math.max(intersectionX, leftX);
                processNonIntersectingRegion(rightRegionLeftEndpoint, rightX, y);
            }
            final double middleRegionLeftEndpoint = Math.max(-intersectionX, leftX);
            final double middleRegionRightEndpoint = Math.min(intersectionX, rightX);
            final double middleRegionLength = Math.max(middleRegionRightEndpoint - middleRegionLeftEndpoint, 0);
            processIntersectingRegion(middleRegionLength, y);
        }
    }

    private void processNonIntersectingRegion(double leftX, double rightX, double y) {
        final double initialTheta = Math.atan2(y, leftX);
        final double finalTheta = Math.atan2(y, rightX);
        double deltaTheta = finalTheta - initialTheta;
        if (deltaTheta < -Math.PI) {
            deltaTheta += 2 * Math.PI;
        } else if (deltaTheta > Math.PI) {
            deltaTheta -= 2 * Math.PI;
        }
        processNonIntersectingRegion(deltaTheta);
    }

    protected abstract void initialize();

    protected abstract void initializeForNewCircle(Circle circle);

    protected abstract void processNonIntersectingRegion(double deltaTheta);

    protected abstract void processIntersectingRegion(double length, double y);

    protected abstract T getValue();

    protected final Circle getCurrentCircle() {
        return currentCircle;
    }

    protected final double getCurrentSquareRadius() {
        return currentSquareRadius;
    }

Wie auch immer, das ist meine Beschreibung des Algorithmus. Ich denke, es ist schön, weil esgenau, und es gibt nicht wirklich, dass viele Fälle zu überprüfen.

Computational Geometry

Hinweis: Dies ist kein triviales Problem, ich hoffe, es ist nicht Hausaufgaben; -)

Wenn Sie eine GPU zur Verfügung haben, können Sie diese Technik für eine Pixelanzahl der Kreuzung zu erhalten ..

Ich denke, man solle nicht angenäherten Kreis als eine Reihe von Dreiecken, statt dass Sie nähern es ist Form mit einem Polygon. Der naive Algorithmus kann wie folgt aussehen:

  1. Konvertieren Sie mit einer gewünschten Anzahl von Eckpunkten Kreis Polygon.
  2. Berechne den Schnittpunkt zweier Polygone (konvertierte Kreis und ein Dreieck).
  3. Berechnen Quadrat dieser Kreuzung.

Sie können diesen Algorithmus optimieren, indem die Schritte 2 und 3 in einzelne Funktion kombiniert werden.

Lesen Sie diese Links:
Fläche von konvexen Polygon
Überschneidung von konvexen Polygonen

Da Ihre Formen konvex sind, können Sie Monte Carlo Bereich Schätzung verwendet werden.

Zeichnen Sie ein Feld um den Kreis und Dreieck.

Wählen Sie beliebige Punkte in der Box und halten eine zu zählen, wie viele fallen in den Kreis, und wie viele fallen sowohl im Kreis und Dreieck.

Schnittbereich ≅ Fläche des Kreises * # Punkte in Kreis und Dreieck / # Punkte im Kreis

Haltepunkte wählen, wenn die geschätzte Fläche von mehr ändert sich nicht als einen bestimmten Betrag über eine bestimmte Anzahl von Runden, oder einfach nur eine feste Anzahl von Punkten basierend auf dem Bereich der Box wählen. Der Bereich Schätzung sollte ziemlich schnell konvergieren, wenn man nicht von Ihren Formen sehr wenig Fläche hat.

Hinweis: Hier ist, wie Sie bestimmen, ob ein Punkt in einem Dreieck ist: Baryzentrische Koordinaten

Wie genau müssen Sie sein? Wenn Sie den Kreis mit einfacheren Formen annähern können, können Sie das Problem vereinfachen. Es wäre nicht schwer, einen Kreis als eine Reihe von sehr schmalen Dreiecken in der Mitte treffen zu modellieren, zum Beispiel.

Wenn nur einer der Liniensegmente des Dreiecks den Kreis schneidet, ist die reine mathematische Lösung nicht zu hart. Sobald Sie wissen, wenn die beiden Schnittpunkte sind, können Sie die Abstandsformel verwenden, um die Sehnenlänge zu finden.

Nach diese Gleichungen :

ϑ = 2 sin⁻¹(0.5 c / r)
A = 0.5 r² (ϑ - sin(ϑ))

, wobei C die Sehnenlänge ist, r der Radius, θ der Winkel wird durch das Zentrum, und A die Fläche. Beachten Sie, dass diese Lösung bricht, wenn mehr als die Hälfte der Kreis abgeschnitten wird.

Es ist wahrscheinlich nicht der Mühe wert, wenn Sie nur eine Annäherung benötigen, da es mehrere Annahmen über macht, was die eigentliche Kreuzung aussieht.

Mein erster Instinkt wäre alles zu transformieren, so dass der Kreis auf Herkunft, trans das Dreieck in Polarkoordinaten zentriert ist, und für den Schnittpunkt (oder encompassment) des Dreiecks mit dem Kreis lösen. Ich habe nicht funktioniert es eigentlich noch auf dem Papier durch, obwohl, so dass nur eine Vermutung.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top