J'essaye de créer un jeûne point 2D l'intérieur d'un algorithme de polygone, pour une utilisation dans les tests de hit (par exemple Polygon.contains(p:Point)
). Des suggestions de techniques efficaces seraient appréciées.
performance
graphics
collision-detection
polygon
point-in-polygon
Scott Evernden
la source
la source
Réponses:
Pour les graphiques, je préfère ne pas préférer les entiers. De nombreux systèmes utilisent des entiers pour la peinture de l'interface utilisateur (les pixels sont des entiers après tout), mais macOS utilise par exemple float pour tout. macOS ne connaît que les points et un point peut se traduire par un pixel, mais selon la résolution du moniteur, il peut se traduire par autre chose. Sur les écrans rétiniens, un demi-point (0,5 / 0,5) correspond au pixel. Pourtant, je n'ai jamais remarqué que les interfaces utilisateur macOS sont beaucoup plus lentes que les autres interfaces utilisateur. Après tout, les API 3D (OpenGL ou Direct3D) fonctionnent également avec les flotteurs et les bibliothèques graphiques modernes profitent très souvent de l'accélération GPU.
Maintenant, vous avez dit que la vitesse est votre principale préoccupation, d'accord, allons-y pour la vitesse. Avant d'exécuter un algorithme sophistiqué, effectuez d'abord un test simple. Créer un cadre de délimitation aligné sur l'axe autour de votre polygone. C'est très facile, rapide et peut déjà vous permettre de faire de nombreux calculs. Comment ça marche? Itérer sur tous les points du polygone et trouver les valeurs min / max de X et Y.
Par exemple, vous avez les points
(9/1), (4/3), (2/7), (8/2), (3/6)
. Cela signifie que Xmin est 2, Xmax est 9, Ymin est 1 et Ymax est 7. Un point à l'extérieur du rectangle avec les deux bords (2/1) et (9/7) ne peut pas être dans le polygone.Il s'agit du premier test à exécuter pour n'importe quel point. Comme vous pouvez le voir, ce test est ultra rapide mais il est aussi très grossier. Pour gérer les points qui se trouvent dans le rectangle englobant, nous avons besoin d'un algorithme plus sophistiqué. Il y a deux façons de calculer cela. La méthode qui fonctionne dépend également du fait que le polygone peut avoir des trous ou sera toujours solide. Voici des exemples de solides (un convexe, un concave):
Et en voici un avec un trou:
Le vert a un trou au milieu!
L'algorithme le plus simple, qui peut gérer les trois cas ci-dessus et qui est encore assez rapide, est nommé ray casting . L'idée de l'algorithme est assez simple: dessinez un rayon virtuel de n'importe où en dehors du polygone jusqu'à votre point et comptez la fréquence à laquelle il frappe un côté du polygone. Si le nombre de coups est pair, il est en dehors du polygone, s'il est impair, il est à l'intérieur.
L' algorithme du nombre d'enroulement serait une alternative, il est plus précis pour les points très proches d'une ligne polygonale mais il est également beaucoup plus lent. Le lancer de rayons peut échouer pour des points trop proches d'un côté de polygone en raison de la précision limitée en virgule flottante et des problèmes d'arrondi, mais en réalité ce n'est guère un problème, comme si un point se trouve aussi près d'un côté, il est souvent visuellement impossible même pour un spectateur pour reconnaître s'il est déjà à l'intérieur ou encore à l'extérieur.
Vous avez toujours la boîte englobante ci-dessus, vous vous souvenez? Choisissez simplement un point en dehors du cadre de sélection et utilisez-le comme point de départ pour votre rayon. Par exemple, le point
(Xmin - e/p.y)
est en dehors du polygone, c'est sûr.Mais c'est quoi
e
? Eh bien,e
(en fait epsilon) donne à la boîte englobante un peu de rembourrage . Comme je l'ai dit, le lancer de rayons échoue si nous commençons trop près d'une ligne de polygone. Étant donné que le cadre de délimitation peut être égal au polygone (si le polygone est un rectangle aligné sur l'axe, le cadre de délimitation est égal au polygone lui-même!), Nous avons besoin d'un rembourrage pour le rendre sûr, c'est tout. Quelle taille devriez-vous choisire
? Pas si gros. Cela dépend de l'échelle du système de coordonnées que vous utilisez pour le dessin. Si la largeur de votre pixel est de 1,0, choisissez simplement 1,0 (mais 0,1 aurait également fonctionné)Maintenant que nous avons le rayon avec ses coordonnées de début et de fin, le problème passe de " est le point dans le polygone " à "à quelle fréquence le rayon intersecte-t-il un côté de polygone ". Par conséquent, nous ne pouvons pas simplement travailler avec les points du polygone comme auparavant, nous avons maintenant besoin des côtés réels. Un côté est toujours défini par deux points.
Vous devez tester le rayon contre tous les côtés. Considérez le rayon comme un vecteur et chaque côté comme un vecteur. Le rayon doit frapper chaque côté exactement une fois ou jamais du tout. Il ne peut pas toucher le même côté deux fois. Deux lignes dans l'espace 2D se coupent toujours exactement une fois, sauf si elles sont parallèles, auquel cas elles ne se coupent jamais. Cependant, comme les vecteurs ont une longueur limitée, deux vecteurs peuvent ne pas être parallèles et ne jamais se croiser car ils sont trop courts pour se rencontrer.
Jusqu'ici tout va bien, mais comment testez-vous si deux vecteurs se croisent? Voici du code C (non testé), qui devrait faire l'affaire:
Les valeurs d'entrée sont les deux extrémités du vecteur 1 (
v1x1/v1y1
etv1x2/v1y2
) et du vecteur 2 (v2x1/v2y1
etv2x2/v2y2
). Vous avez donc 2 vecteurs, 4 points, 8 coordonnées.YES
etNO
sont clairs.YES
augmente les intersections,NO
ne fait rien.Et COLLINEAR? Cela signifie que les deux vecteurs se trouvent sur la même ligne infinie, selon la position et la longueur, ils ne se coupent pas du tout ou ils se coupent en un nombre infini de points. Je ne sais pas trop comment gérer ce cas, je ne le considérerais pas comme une intersection de toute façon. Eh bien, ce cas est assez rare dans la pratique de toute façon en raison d'erreurs d'arrondi à virgule flottante; un meilleur code ne testerait probablement pas,
== 0.0f
mais plutôt quelque chose comme< epsilon
, où epsilon est un nombre plutôt petit.Si vous devez tester un plus grand nombre de points, vous pouvez certainement accélérer un peu le tout en gardant en mémoire les formes standard d'équation linéaire des côtés du polygone, vous n'avez donc pas à les recalculer à chaque fois. Cela vous fera économiser deux multiplications en virgule flottante et trois soustractions en virgule flottante à chaque test en échange du stockage en mémoire de trois valeurs en virgule flottante par côté de polygone. C'est un compromis typique entre mémoire et temps de calcul.
Dernier point mais non le moindre: si vous pouvez utiliser du matériel 3D pour résoudre le problème, il existe une alternative intéressante. Laissez le GPU faire tout le travail pour vous. Créez une surface de peinture hors écran. Remplissez-le complètement avec la couleur noire. Maintenant, laissez OpenGL ou Direct3D peindre votre polygone (ou même tous vos polygones si vous voulez simplement tester si le point se trouve dans l'un d'eux, mais vous ne vous souciez pas de savoir lequel) et remplissez le ou les polygones avec un autre couleur, par exemple blanc. Pour vérifier si un point se trouve dans le polygone, obtenez la couleur de ce point à partir de la surface de dessin. Ceci est juste une extraction de mémoire O (1).
Bien sûr, cette méthode n'est utilisable que si votre surface de dessin n'a pas besoin d'être énorme. Si elle ne peut pas entrer dans la mémoire du GPU, cette méthode est plus lente que de le faire sur le CPU. Si cela devait être énorme et que votre GPU prend en charge les shaders modernes, vous pouvez toujours utiliser le GPU en implémentant le lancer de rayons illustré ci-dessus en tant que shader GPU, ce qui est absolument possible. Pour un plus grand nombre de polygones ou un grand nombre de points à tester, cela sera payant, sachez que certains GPU pourront tester 64 à 256 points en parallèle. Notez cependant que le transfert de données du CPU vers le GPU et vice-versa est toujours coûteux, donc pour simplement tester quelques points contre quelques polygones simples, où les points ou les polygones sont dynamiques et changent fréquemment, une approche GPU paiera rarement de.
la source
Je pense que le code suivant est la meilleure solution (prise à partir d' ici ):
Arguments
Il est à la fois court et efficace et fonctionne à la fois pour les polygones convexes et concaves. Comme suggéré précédemment, vous devez d'abord vérifier le rectangle englobant et traiter séparément les trous de polygone.
L'idée derrière cela est assez simple. L'auteur le décrit comme suit:
La variable c passe de 0 à 1 et de 1 à 0 chaque fois que le rayon horizontal traverse un bord. Donc, fondamentalement, il vérifie si le nombre d'arêtes traversées est pair ou impair. 0 signifie pair et 1 signifie impair.
la source
verty[i]
etverty[j]
est de chaque côté detesty
sorte qu'ils ne sont jamais égaux.Voici une version C # de la réponse donnée par nirg , qui vient de ce professeur RPI . Notez que l'utilisation du code de cette source RPI nécessite une attribution.
Une case à cocher englobante a été ajoutée en haut. Cependant, comme le souligne James Brown, le code principal est presque aussi rapide que la case à cocher elle-même, de sorte que la case à cocher peut en fait ralentir l'opération globale, dans le cas où la plupart des points que vous vérifiez se trouvent à l'intérieur de la zone de délimitation . Vous pouvez donc laisser la case de délimitation cochée, ou une alternative serait de précalculer les boîtes de délimitation de vos polygones s'ils ne changent pas trop souvent de forme.
la source
Voici une variante JavaScript de la réponse de M. Katz basée sur l'approche de Nirg:
la source
Calculez la somme orientée des angles entre le point p et chacun des sommets du polygone. Si l'angle orienté total est de 360 degrés, le point est à l'intérieur. Si le total est 0, le point est à l'extérieur.
J'aime mieux cette méthode car elle est plus robuste et moins dépendante de la précision numérique.
Les méthodes qui calculent la régularité du nombre d'intersections sont limitées car vous pouvez «frapper» un sommet pendant le calcul du nombre d'intersections.
EDIT: By The Way, cette méthode fonctionne avec des polygones concaves et convexes.
EDIT: J'ai récemment trouvé un article complet de Wikipedia sur le sujet.
la source
Cette question est tellement intéressante. J'ai une autre idée réalisable différente des autres réponses à ce post. L'idée est d'utiliser la somme des angles pour décider si la cible est à l'intérieur ou à l'extérieur. Mieux connu sous le nom de numéro d'enroulement .
Soit x le point cible. Soit array [0, 1, .... n] les tous les points de la zone. Connectez le point cible à chaque point frontière avec une ligne. Si le point cible se trouve à l'intérieur de cette zone. La somme de tous les angles sera de 360 degrés. Sinon, les angles seront inférieurs à 360.
Référez-vous à cette image pour avoir une compréhension de base de l'idée:
Mon algorithme suppose que le sens horaire est le sens positif. Voici une entrée potentielle:
Voici le code python qui implémente l'idée:
la source
L' article d'Eric Haines cité par bobobobo est vraiment excellent. Les tableaux comparant les performances des algorithmes sont particulièrement intéressants; la méthode de sommation d'angle est vraiment mauvaise par rapport aux autres. Il est également intéressant de noter que les optimisations telles que l'utilisation d'une grille de recherche pour subdiviser davantage le polygone en secteurs "entrant" et "sortant" peuvent rendre le test incroyablement rapide même sur des polygones avec> 1000 côtés.
Quoi qu'il en soit, ce n'est que le début, mais mon vote va à la méthode des «croisements», qui est à peu près ce que Mecki décrit, je pense. Cependant, je l'ai trouvé le plus succinctement décrit et codifié par David Bourke . J'aime qu'il n'y ait pas de véritable trigonométrie requise, et cela fonctionne pour les convexes et les concaves, et il fonctionne assez bien lorsque le nombre de côtés augmente.
Soit dit en passant, voici l'une des tables de performances de l'article d'Eric Haines pour l'intérêt, les tests sur les polygones aléatoires.
la source
Version rapide de la réponse de nirg :
la source
Vraiment comme la solution publiée par Nirg et éditée par bobobobo. Je viens de le rendre compatible javascript et un peu plus lisible pour mon utilisation:
la source
J'ai travaillé sur ce sujet lorsque j'étais chercheur sous Michael Stonebraker - vous savez, le professeur qui a proposé Ingres , PostgreSQL , etc.
Nous avons réalisé que le moyen le plus rapide était de faire d'abord une boîte englobante parce que c'est SUPER rapide. Si c'est en dehors de la zone de délimitation, c'est à l'extérieur. Sinon, vous faites le travail le plus difficile ...
Si vous voulez un excellent algorithme, regardez le code source du projet open source PostgreSQL pour le travail de géo ...
Je tiens à souligner que nous n'avons jamais eu un aperçu de la droitier vs gaucher (également exprimable comme un problème "à l'intérieur" vs "à l'extérieur" ...
MISE À JOUR
Le lien de BKB a fourni un bon nombre d'algorithmes raisonnables. Je travaillais sur des problèmes liés aux sciences de la Terre et j'avais donc besoin d'une solution qui fonctionne en latitude / longitude, et elle a le problème particulier de la neutralité - la zone est-elle à l'intérieur de la zone la plus petite ou la plus grande? La réponse est que la "direction" des sommets est importante - elle est soit pour gaucher soit pour droitier et de cette façon, vous pouvez indiquer l'une ou l'autre zone comme "à l'intérieur" d'un polygone donné. En tant que tel, mon travail a utilisé la solution trois énumérée sur cette page.
De plus, mon travail a utilisé des fonctions distinctes pour les tests "en ligne".
... Depuis que quelqu'un a demandé: nous avons compris que les tests de boîte englobante étaient meilleurs lorsque le nombre de sommets dépassait un certain nombre - faites un test très rapide avant de faire le test plus long si nécessaire ... Une boîte englobante est créée en prenant simplement le le plus grand x, le plus petit x, le plus grand y et le plus petit y et les assembler pour faire quatre points d'une boîte ...
Une autre astuce pour ceux qui suivent: nous avons fait tous nos calculs plus sophistiqués et "atténuant la lumière" dans un espace de grille tous en points positifs sur un plan, puis re-projeté en longitude / latitude "réelle", évitant ainsi d'éventuelles erreurs de s'enrouler quand on franchit la ligne 180 de longitude et lors de la manipulation des régions polaires. Fonctionne très bien!
la source
La réponse de David Segond est à peu près la réponse générale standard, et celle de Richard T est l'optimisation la plus courante, bien qu'il en existe d'autres. D'autres optimisations fortes reposent sur des solutions moins générales. Par exemple, si vous allez vérifier le même polygone avec beaucoup de points, la triangulation du polygone peut accélérer considérablement les choses car il existe un certain nombre d'algorithmes de recherche de TIN très rapides. Un autre est que si le polygone et les points sont sur un plan limité à basse résolution, par exemple un affichage à l'écran, vous pouvez peindre le polygone sur un tampon d'affichage mappé en mémoire dans une couleur donnée et vérifier la couleur d'un pixel donné pour voir s'il se trouve dans les polygones.
Comme de nombreuses optimisations, celles-ci sont basées sur des cas spécifiques plutôt que généraux et donnent des avantages basés sur le temps amorti plutôt que sur une seule utilisation.
Travaillant dans ce domaine, j'ai trouvé que la géométrie de calcul de Joeseph O'Rourkes en C 'ISBN 0-521-44034-3 était d'une grande aide.
la source
La solution triviale serait de diviser le polygone en triangles et de tester les triangles comme expliqué ici
Si votre polygone est CONVEX, il pourrait y avoir une meilleure approche. Regardez le polygone comme une collection de lignes infinies. Chaque ligne divisant l'espace en deux. pour chaque point, il est facile de dire si c'est d'un côté ou de l'autre de la ligne. Si un point se trouve du même côté de toutes les lignes, il se trouve à l'intérieur du polygone.
la source
Je me rends compte que c'est vieux, mais voici un algorithme de lancer de rayons implémenté dans Cocoa, au cas où quelqu'un serait intéressé. Je ne sais pas si c'est la façon la plus efficace de faire les choses, mais cela peut aider quelqu'un.
la source
Version Obj-C de la réponse de nirg avec exemple de méthode pour tester les points. La réponse de Nirg a bien fonctionné pour moi.
la source
CGPathContainsPoint()
c'est votre ami.CGPathContainsPoint()
Il n'y a rien de plus beau qu'une définition inductive d'un problème. Par souci d'exhaustivité ici, vous avez une version en prologue qui pourrait également clarifier les pensées derrière le casting de rayons :
Basé sur la simulation de l'algorithme de simplicité dans http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
Certains prédicats d'aide:
L'équation d'une ligne donnée 2 points A et B (ligne (A, B)) est:
Il est important que le sens de rotation de la ligne soit réglé dans le sens horaire pour les limites et dans le sens antihoraire pour les trous. Nous allons vérifier si le point (X, Y), c'est-à-dire le point testé est au demi-plan gauche de notre droite (c'est une question de goût, ça pourrait aussi être le côté droit, mais aussi la direction des frontières les lignes doivent être changées dans ce cas), c'est de projeter le rayon du point vers la droite (ou la gauche) et de reconnaître l'intersection avec la ligne. Nous avons choisi de projeter le rayon dans le sens horizontal (encore une fois c'est une question de goût, cela pourrait aussi se faire en vertical avec des restrictions similaires), nous avons donc:
Maintenant, nous devons savoir si le point se trouve uniquement sur le côté gauche (ou droit) du segment de ligne, pas sur tout le plan, nous devons donc restreindre la recherche uniquement à ce segment, mais cela est facile car être à l'intérieur du segment un seul point de la ligne peut être supérieur à Y sur l'axe vertical. Comme il s'agit d'une restriction plus forte, elle doit être la première à vérifier, nous prenons donc uniquement les lignes répondant à cette exigence, puis vérifions sa possession. Selon le théorème de la courbe de Jordan, tout rayon projeté sur un polygone doit se croiser sur un nombre pair de lignes. Donc, nous avons terminé, nous allons lancer le rayon vers la droite, puis chaque fois qu'il coupe une ligne, basculer son état. Cependant, dans notre mise en œuvre, nous allons vérifier la longueur du sachet de solutions répondant aux restrictions données et décider de leur intégration. cela doit être fait pour chaque ligne du polygone.
la source
La version C # de la réponse de nirg est ici: je vais juste partager le code. Cela peut faire gagner du temps à quelqu'un.
la source
Version Java:
la source
Port .Net:
la source
VERSION VBA:
Remarque: N'oubliez pas que si votre polygone est une zone d'une carte, la latitude / longitude sont des valeurs Y / X par opposition à X / Y (latitude = Y, longitude = X) en raison de ce que je comprends, ce sont des implications historiques remontant à quand La longitude n'était pas une mesure.
MODULE DE CLASSE: CPoint
MODULE:
la source
Je l' ai fait une implémentation Python de de nirg c ++ Code :
Contributions
bounding_box_positions: points candidats à filtrer. (Dans mon implémentation créée à partir de la boîte englobante.
(Les entrées sont des listes de tuples au format:
[(xcord, ycord), ...]
)Retour
Encore une fois, l'idée est tirée d' ici
la source
Surpris, personne n'a évoqué cela plus tôt, mais pour les pragmatiques qui ont besoin d'une base de données: MongoDB a un excellent support pour les requêtes Geo, y compris celle-ci.
Ce que vous recherchez, c'est:
Neighborhoods
est la collection qui stocke un ou plusieurs polygones au format GeoJson standard. Si la requête renvoie null, elle n'est pas intersectée, sinon elle l'est.Très bien documenté ici: https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/
Les performances de plus de 6 000 points classés dans une grille de polygones irréguliers de 330 étaient inférieures à une minute sans aucune optimisation et incluant le temps de mise à jour des documents avec leur polygone respectif.
la source
Voici un point dans le test de polygone en C qui n'utilise pas le lancer de rayons. Et cela peut fonctionner pour les zones qui se chevauchent (auto-intersections), voir l'
use_holes
argument.Remarque: c'est l'une des méthodes les moins optimales car elle inclut beaucoup d'appels à
atan2f
, mais elle peut intéresser les développeurs qui lisent ce fil (dans mes tests, son ~ 23x est plus lent que la méthode d'intersection de lignes).la source
Pour détecter un hit sur Polygon, nous devons tester deux choses:
la source
Pour traiter les cas spéciaux suivants dans l' algorithme de lancer de rayons :
Cochez Déterminer si un point se trouve à l'intérieur d'un polygone complexe . L'article fournit un moyen facile de les résoudre, de sorte qu'aucun traitement spécial ne sera requis pour les cas ci-dessus.
la source
Vous pouvez le faire en vérifiant si la zone formée en connectant le point souhaité aux sommets de votre polygone correspond à la zone du polygone lui-même.
Ou vous pouvez vérifier si la somme des angles intérieurs de votre point à chaque paire de deux sommets de polygone consécutifs à votre point de contrôle est de 360, mais j'ai le sentiment que la première option est plus rapide car elle n'implique pas de divisions ni de calculs de l'inverse des fonctions trigonométriques.
Je ne sais pas ce qui se passe si votre polygone a un trou à l'intérieur mais il me semble que l'idée principale peut être adaptée à cette situation
Vous pouvez également poster la question dans une communauté mathématique. Je parie qu'ils ont un million de façons de le faire
la source
Si vous recherchez une bibliothèque de scripts java, il existe une extension javascript google maps v3 pour la classe Polygon pour détecter si un point y réside ou non.
Google Extention Github
la source
Lors de l'utilisation qt(Qt 4.3 +), on peut utiliser la fonction de QPolygon de containsPoint
la source
La réponse dépend de si vous avez des polygones simples ou complexes. Les polygones simples ne doivent pas avoir d'intersections de segments de ligne. Ils peuvent donc avoir des trous mais les lignes ne peuvent pas se croiser. Les régions complexes peuvent avoir des intersections de lignes - elles peuvent donc avoir les régions qui se chevauchent, ou des régions qui se touchent juste par un seul point.
Pour les polygones simples, le meilleur algorithme est l'algorithme de lancer de rayons (nombre de croisements). Pour les polygones complexes, cet algorithme ne détecte pas les points qui se trouvent à l'intérieur des régions qui se chevauchent. Donc, pour les polygones complexes, vous devez utiliser l'algorithme de nombre de bobinage.
Voici un excellent article avec l'implémentation C des deux algorithmes. Je les ai essayés et ils fonctionnent bien.
http://geomalgorithms.com/a03-_inclusion.html
la source
Version Scala de la solution par nirg (suppose que la pré-vérification du rectangle englobant est effectuée séparément):
la source
Voici la version Golang de la réponse @nirg (inspirée du code C # par @@ m-katz)
la source