Punkt-in-Polygon-Test nach Jordan funktioniert nicht

  • C#
  • .NET (FX) 4.0

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von Trade.

    Punkt-in-Polygon-Test nach Jordan funktioniert nicht

    Moin,

    zusammen mit @Gonger96 habe ich gestern in SharpMath noch einen kleinen Bug gefunden, der bei mir beim Testen nicht vorkam.
    Dazu haben wir eine Testanwendung erstellt und einfach eine Form hinzugefügt:

    C#-Quellcode

    1. using System.Drawing;
    2. using System.Windows.Forms;
    3. using SharpMath.Geometry;
    4. namespace PolygonTest
    5. {
    6. public partial class Form1 : Form
    7. {
    8. private readonly Polygon _p;
    9. private readonly PointF[] _pts =
    10. {
    11. new PointF(0, 0), new PointF(200, 80), new PointF(400, 200),
    12. new PointF(100, 60)
    13. };
    14. private bool _b;
    15. public Form1()
    16. {
    17. InitializeComponent();
    18. _p =
    19. new Polygon(new Point2D(0, 0), new Point2D(200, 80), new Point2D(400, 200), new Point2D(100, 60));
    20. }
    21. private void Form1_Paint(object sender, PaintEventArgs e)
    22. {
    23. e.Graphics.FillRectangle(Brushes.Black, ClientRectangle);
    24. e.Graphics.FillPolygon(_b ? Brushes.AliceBlue : Brushes.Red, _pts);
    25. }
    26. private void Form1_MouseMove(object sender, MouseEventArgs e)
    27. {
    28. bool bOld = _b;
    29. _b = _p.ContainsPoint(new Point2D(e.X, e.Y));
    30. if (_b != bOld)
    31. Invalidate();
    32. Text = e.Location.ToString();
    33. }
    34. }
    35. }


    Simpel, aber erledigt seine Arbeit. Es wird einfach ein entsprechendes Polygon gezeichnet und wenn sich die Maus innerhalb des Polygons befindet, dann soll dieses weiß gefüllt werden (normal ist es rot).
    Die Methode dazu habe ich geschrieben und dazu den Punkt-in-Polygon-Test nach Jordan implementiert. Das Ganze sieht so aus:

    C#-Quellcode

    1. /// <summary>
    2. /// Determines whether this <see cref="Polygon" /> contains the specified <see cref="Point" />.
    3. /// </summary>
    4. /// <param name="point">The <see cref="Point" /> to check.</param>
    5. /// <returns>
    6. /// <c>true</c> if this <see cref="Polygon" /> contains the specified <see cref="Point" />; otherwise <c>false</c>
    7. /// .
    8. /// </returns>
    9. public bool ContainsPoint(Point2D point)
    10. {
    11. double t = -1;
    12. var points = Points;
    13. //points[0] = points[points.Count - 1];
    14. int j = 1;
    15. for (int i = 0; i < points.Count; ++i)
    16. {
    17. t = t*ContainsPointInternal(point, points[i], points[j]);
    18. j++;
    19. if (j == points.Count)
    20. j = 0;
    21. }
    22. return t >= 0;
    23. }
    24. private int ContainsPointInternal(Point2D q, Point2D p1, Point2D p2)
    25. {
    26. Debug.Print($"From: {p1} to {p2}");
    27. if (FloatingNumber.AreApproximatelyEqual(q.Y, p1.Y) && FloatingNumber.AreApproximatelyEqual(p1.Y, p2.Y))
    28. {
    29. if ((p1.X <= q.X && q.X <= p2.X) || (p2.X <= q.X && q.X <= p1.X))
    30. return 0;
    31. return 1;
    32. }
    33. if (p1.Y > p2.Y)
    34. {
    35. var currentP1 = p1;
    36. p1 = p2;
    37. p2 = currentP1;
    38. }
    39. else if (FloatingNumber.AreApproximatelyEqual(q.Y, p1.Y) && FloatingNumber.AreApproximatelyEqual(q.X, p1.X))
    40. return 0;
    41. else if (q.Y <= p1.Y || q.Y > p2.Y)
    42. return 1;
    43. var delta = (p1.X - q.X)*(p2.Y - q.Y) - (p1.Y - q.Y)*(p2.X - q.X);
    44. if (delta > 0)
    45. return -1;
    46. return delta < 0 ? 1 : 0;
    47. }


    Das mit dem j ist im Pseudocode von Wikipedia nicht so gemacht, allerdings habe ich das so implementiert und so oder so funktioniert es nicht. Die auskommentierte Zeile verursachte außerdem, dass die Verbindung des letzten Punkts von ​100, 60 aus nicht mit ​0, 0, sondern eben ​200, 80 stattfand, sodass über dieser Strecke sowieso keine Punkte entdeckt wurden.
    Dennoch klappt das nicht und ich glaube, es ist am Einfachsten, das Problem visuell zu zeigen:



    Man sieht, es klappt auch mit der Erkennung, wenn ich im Polygon bin, allerdings macht er das tw. auch, wenn ich weiter unten bin. Ich tippe darauf, dass das mit einer entsprechenden Linie durch den einen Punkt ​100, 60 dort zusammenhängt, denn das wird ja auch geprüft, aber ich komme einfach nicht drauf, warum das so ist.
    Könnt Ihr mir da helfen?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    @Trade SharpMath.Geometry und Polygon ist dabei was?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Wie gesagt, meine Bibliothek.
    github.com/ProgTrade/SharpMath…pMath/Geometry/Polygon.cs

    Die zwei Methoden von oben sind aber auf GitHub noch anders. Es geht nun um das oben.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Naja, aber das wäre zu umständlich, denn das sollte alles durch diesen Algorithmus bereits getan sein. Auf diese Weise würde ja das Grundproblem nicht behoben werden. ^^

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    @Trade Kannst Du mal einen compilierbaren Code / Projekt posten?
    =======
    Ich hab mal leere Hülsen für alle nicht vorhandenen Klassen / Methoden / Properties generiert und kann den Effekt reproduzieren.
    Ohne in den Code gesehen zu haben würde ich meinen, dass da ein Richtungsvektor eine übergroße / unendliche Länge hat, an der sich der Zustand scheidet.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „RodFromGermany“ ()

    @Gonger96 Habe Deinen Edit jetzt gesehen. Danke, wenn alles nicht klappt, dann werde ich das so implementieren. Das lässt sich ja relativ angenehm übersetzen.
    @RodFromGermany Im Anhang. Die Anwendung ist "PolygonTest".

    Grüße
    Dateien
    • SharpMath.zip

      (4,07 MB, 266 mal heruntergeladen, zuletzt: )
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    @Trade Ich hab mal alle Pixel entsprechend ihrer Antwort eingefärbt. Erst mal ohne Kommentar.

    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @Trade Gelöst. Deine Methoden ContainsPoint() und ContainsPointInternal() waren nicht ganz korrekt, ich hab sie noch mal neu implementiert:
    Spoiler anzeigen

    C#-Quellcode

    1. public bool ContainsPoint(Point2D point)
    2. {
    3. double t = -1;
    4. List<Point2D> points = new List<Point2D>();
    5. points.AddRange(Points);
    6. points.Add(points[0]); // Dies insbesondere
    7. for (int i = 0; i < points.Count - 1; i++)
    8. {
    9. t = t * this.ContainsPointInternal(point, points[i], points[i + 1]);
    10. }
    11. return t >= 0;
    12. }
    13. /// <summary>
    14. /// −1, wenn der Strahl von A nach rechts die Kante [BC] schneidet (außer im unteren Endpunkt);
    15. /// 0, wenn A auf [BC] liegt;
    16. /// sonst +1
    17. /// </summary>
    18. /// <param name="A"></param>
    19. /// <param name="B"></param>
    20. /// <param name="C"></param>
    21. /// <returns></returns>
    22. private int ContainsPointInternal(Point2D A, Point2D B, Point2D C)
    23. {
    24. // Wenn y_A = y_B = y_C
    25. if (FloatingNumber.AreApproximatelyEqual(A.Y, B.Y) &&
    26. FloatingNumber.AreApproximatelyEqual(A.Y, C.Y))
    27. {
    28. // Wenn x_B ≤ x_A ≤ x_C oder x_C ≤ x_A ≤ x_B
    29. if ((B.X <= A.X && A.X <= C.X) ||
    30. (C.X <= A.X && A.X <= B.X))
    31. {
    32. return 0;
    33. }
    34. return 1;
    35. }
    36. // Wenn y_B > y_C
    37. if (B.Y > C.Y)
    38. {
    39. // Vertausche B und C
    40. var currentB = B;
    41. B = C;
    42. C = currentB;
    43. }
    44. // Wenn y_A = y_B und x_A = x_B
    45. if (FloatingNumber.AreApproximatelyEqual(A.Y, B.Y) &&
    46. FloatingNumber.AreApproximatelyEqual(A.X, B.X))
    47. {
    48. return 0;
    49. }
    50. // Wenn y_A ≤ y_B oder y_A > y_C
    51. if (A.Y <= B.Y || A.Y > C.Y)
    52. {
    53. return 1;
    54. }
    55. // Delta = (x_B−x_A) * (y_C−y_A) − (y_B−y_A) * (x_C−x_A)
    56. double Delta = (B.X - A.X) * (C.Y - A.Y) - (B.Y - A.Y) * (C.X - A.X);
    57. if (Delta > 0)
    58. {
    59. // Wenn Delta > 0
    60. return -1;
    61. }
    62. else if (Delta < 0)
    63. {
    64. // wenn Delta < 0
    65. return 1;
    66. }
    67. // sonst
    68. return 0;
    69. }

    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Danke, das klappt. :)
    Jetzt sehe ich den Fehler in Zeile 6, da hatte ich den Algorithmus falsch verstanden. n ist ja der Count, folglich hätte ich noch ein weiteres Item hinzufügen müssen, um auf diesen Index zu kommen. Was genau war bei ContainsPointInternal falsch?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Dein ContainsPointInternal() mit dem richtigen ContainsPoint() bringt dieses Bild, algorithmisch hab ich das nicht untersucht:

    =====
    @Trade Ich merke, Du hast noch nix von Fortran & Co hehört.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „RodFromGermany“ ()

    Musste das erstmal googlen. Was ist damit im Kontext bzw. was willst Du mir damit sagen?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    @Trade Der Pseudo-Code bei Wiki ist mit Kenntnis der alten Programmiersprache Fortran ganz easy zu lesen.
    Indices fangen dort z.B. bei 1 an.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ah okay, das ist gut zu wissen. Kannte ich wirklich nicht.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!: