Un algorithme pour gonfler / dégonfler (compenser, tamponner) les polygones

202

Comment "gonfler" un polygone? Autrement dit, je veux faire quelque chose de similaire à ceci:

texte alternatif

La condition est que les bords / points du nouveau polygone (gonflé) soient tous à la même distance constante de l'ancien polygone (d'origine) (sur l'image d'exemple, ils ne le sont pas, car il faudrait alors utiliser des arcs pour les sommets gonflés, mais disons oublie ça pour l'instant;)).

Le terme mathématique pour ce que je recherche est en fait un décalage de polygone vers l'intérieur / vers l'extérieur . +1 à balint pour l'avoir signalé. L'autre dénomination est la mise en mémoire tampon des polygones .

Résultats de ma recherche:

Voici quelques liens:

Igor Brejc
la source
17
Ce n'est pas du tout une question banale: si la déflation / inflation est faible, rien de grave ne se produit, mais à un moment donné, les sommets disparaîtront. Cela a probablement déjà été fait auparavant, alors je dirais: utilisez l'algorithme de quelqu'un d'autre, ne construisez pas le vôtre.
Martijn
1
En effet, si votre polygone est concave pour commencer (comme dans l'exemple ci-dessus), vous devez décider de ce qui devrait arriver au point où l'algorithme naïf veut faire un `` polygone '' auto-intersecté ...
AakashM
Oui, le problème principal réside dans les parties concaves du polygone, c'est là que réside la complexité. Je pense toujours que ce ne devrait pas être un problème de calculer quand un certain sommet doit être éliminé. La question principale est de savoir quel type de complexité asymptotique cela nécessiterait.
Igor Brejc
Bonjour, c'est aussi mon problème, sauf que je dois le faire en 3D. Existe-t-il une alternative à l'approche des squelettes droits des polyèdres tridimensionnels décrite dans l'article arxiv.org/pdf/0805.0022.pdf ?
stephanmg

Réponses:

138

J'ai pensé que je pourrais mentionner brièvement ma propre bibliothèque de découpage et de compensation de polygones - Clipper .

Alors que Clipper est principalement conçu pour les opérations de découpage de polygone, il effectue également la compensation de polygone. La bibliothèque est un logiciel gratuit open source écrit en Delphi, C ++ et C # . Il possède une licence Boost très libre qui lui permet d'être utilisé à la fois gratuitement et dans des applications commerciales.

Le décalage de polygone peut être effectué en utilisant l'un des trois styles de décalage - carré, rond et à onglet.

Styles de décalage de polygone

Angus Johnson
la source
2
Très cool! Où étiez-vous il y a 2 ans? :) Au final, j'ai dû implémenter ma propre logique de compensation (et y ai perdu beaucoup de temps). Quel algorithme utilisez-vous pour la compensation de polygones, BTW? J'ai utilisé du feu de gazon. Manipulez-vous des trous dans des polygones?
Igor Brejc
2
Il y a 2 ans, je cherchais une solution décente pour l'écrêtage de polygones qui n'était pas encombrée de problèmes de licence délicats :). Le décalage des bords est obtenu en générant des normales d'unité pour tous les bords. Les jointures de bord sont rangées par ma tondeuse à polygones car les orientations de ces intersections se chevauchant sont opposées à l'orientation des polygones. Les trous sont très certainement traités comme le sont les polygones auto-entrecroisés, etc. Il n'y a aucune restriction quant à leur type ou leur nombre. Voir aussi «Décalage de polygone par calcul des nombres d'enroulement» ici: me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf
Angus Johnson
Whoa! Ne pensez pas une seconde que cette question est "oubliée"! J'ai regardé ici la semaine dernière - je ne m'attendais pas à y revenir! Merci beaucoup!
Chris Burt-Brown
Les documents de Clipper sur la mise en mémoire tampon poly sont ici: angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/…
Drew Noakes
5
Pour quiconque souhaite le faire, une autre alternative consiste à utiliser GEOS, et si vous utilisez python, le wrapper de GEOS, Shapely. Un très joli exemple: toblerity.github.com/shapely/manual.html#object.buffer
pelson
40

Le polygone que vous recherchez est appelé polygone de décalage vers l'intérieur / vers l'extérieur dans la géométrie de calcul et il est étroitement lié au squelette droit .

Ce sont plusieurs polygones décalés pour un polygone compliqué:

Et voici le squelette droit d'un autre polygone:

Comme indiqué dans d'autres commentaires, également, selon la mesure dans laquelle vous prévoyez de «gonfler / dégonfler» votre polygone, vous pouvez vous retrouver avec une connectivité différente pour la sortie.

Du point de vue du calcul: une fois que vous avez le squelette droit, vous devriez pouvoir construire les polygones décalés relativement facilement. La bibliothèque CGAL open source et (gratuite pour les non-commerciaux) dispose d'un package implémentant ces structures. Voir cet exemple de code pour calculer des polygones décalés à l'aide de CGAL.

Le manuel du paquet devrait vous donner un bon point de départ sur la façon de construire ces structures même si vous n'allez pas utiliser CGAL, et contient des références aux articles avec les définitions et propriétés mathématiques:

Manuel CGAL: Squelette droit 2D et décalage de polygone

balint.miklos
la source
12

Pour ces types de choses, j'utilise habituellement JTS . À des fins de démonstration, j'ai créé ce jsFiddle qui utilise JSTS (port JavaScript de JTS). Il vous suffit de convertir les coordonnées que vous avez en coordonnées JSTS:

function vectorCoordinates2JTS (polygon) {
  var coordinates = [];
  for (var i = 0; i < polygon.length; i++) {
    coordinates.push(new jsts.geom.Coordinate(polygon[i].x, polygon[i].y));
  }
  return coordinates;
}

Le résultat est quelque chose comme ceci:

entrez la description de l'image ici

Informations supplémentaires : J'utilise généralement ce type de gonflage / dégonflage (un peu modifié à mes fins) pour définir des limites de rayon sur des polygones dessinés sur une carte (avec Leaflet ou Google maps). Vous convertissez simplement les paires (lat, lng) en coordonnées JSTS et tout le reste est le même. Exemple:

entrez la description de l'image ici

Marko Letic
la source
9

Cela me semble être ce que vous voulez:

  • En partant d'un sommet, faites face dans le sens inverse des aiguilles d'une montre le long d'un bord adjacent.
  • Remplacez le bord par un nouveau bord parallèle placé à distance dà la "gauche" de l'ancien.
  • Répétez pour tous les bords.
  • Trouvez les intersections des nouvelles arêtes pour obtenir les nouveaux sommets.
  • Détectez si vous êtes devenu un polygone croisé et décidez quoi faire à ce sujet. Ajoutez probablement un nouveau sommet au point de croisement et supprimez les anciens. Je ne sais pas s'il existe un meilleur moyen de détecter cela que de comparer chaque paire d'arêtes non adjacentes pour voir si leur intersection se situe entre les deux paires de sommets.

Le polygone résultant se trouve à la distance requise de l'ancien polygone "assez loin" des sommets. Près d'un sommet, l'ensemble des points à distance dde l'ancien polygone n'est pas, comme vous le dites, un polygone, de sorte que l'exigence telle qu'énoncée ne peut pas être remplie.

Je ne sais pas si cet algorithme a un nom, un exemple de code sur le web ou une optimisation diabolique, mais je pense qu'il décrit ce que vous voulez.

Steve Jessop
la source
5

Chaque ligne doit diviser l'avion en "intérieur" et "contour"; vous pouvez le découvrir en utilisant la méthode habituelle du produit interne.

Déplacez toutes les lignes vers l'extérieur d'une certaine distance.

Considérez toutes les paires de lignes voisines (lignes, pas segment de ligne), trouvez l'intersection. Ce sont le nouveau sommet.

Nettoyez le nouveau sommet en supprimant toutes les parties qui se croisent. - nous avons quelques cas ici

a) Cas 1:

 0--7  4--3
 |  |  |  |
 |  6--5  |
 |        |
 1--------2

si vous le dépensez par un, vous obtenez ceci:

0----a----3
|    |    |
|    |    |
|    b    |
|         |
|         |
1---------2

7 et 4 se chevauchent .. si vous voyez cela, vous supprimez ce point et tous les points entre les deux.

b) cas 2

 0--7  4--3
 |  |  |  |
 |  6--5  |
 |        |
 1--------2

si vous le dépensez par deux, vous obtenez ceci:

0----47----3
|    ||    |
|    ||    |
|    ||    |
|    56    |
|          |
|          |
|          |
1----------2

pour résoudre ce problème, pour chaque segment de ligne, vous devez vérifier s'il chevauche avec ces derniers segments.

c) cas 3

       4--3
 0--X9 |  |
 |  78 |  |
 |  6--5  |
 |        |
 1--------2

dépenser par 1. c'est un cas plus général pour le cas 1.

d) cas 4

comme cas 3, mais dépenser par deux.

En fait, si vous pouvez gérer le cas 4. Tous les autres cas ne sont que des cas spéciaux avec un chevauchement de ligne ou de sommet.

Pour faire le cas 4, vous gardez une pile de vertex. - tout comme ce que vous faites en coque convexe.

J-16 SDiZ
la source
connaissez-vous un algorithme psedo pour cela.
EmptyData
5

Voici une solution alternative, voyez si vous préférez cela.

  1. Faites une triangulation , cela n'a pas besoin d'être retardé - n'importe quelle triangulation ferait l'affaire.

  2. Gonflez chaque triangle - cela devrait être trivial. si vous stockez le triangle dans le sens inverse des aiguilles d'une montre, déplacez simplement les lignes vers la droite et faites l'intersection.

  3. Fusionnez-les en utilisant un algorithme de découpage Weiler-Atherton modifié

J-16 SDiZ
la source
comment gonflez-vous les triangles exactement? Votre sortie dépend-elle de la triangulation? Avec cette approche, pouvez-vous gérer le cas lorsque vous réduisez le polygone?
balint.miklos
Êtes-vous sûr que cette approche fonctionne vraiment pour l'inflation polygonale? Que se passe-t-il lorsque les parties concaves du polygone sont gonflées à un point tel que certains sommets doivent être éliminés. La chose est: quand vous regardez ce qui arrive aux triangles après poly. l'inflation, les triangles ne sont pas gonflés, ils sont au contraire déformés.
Igor Brejc
1
Igor: L'algorithme de découpage Weiler-Atherton peut gérer correctement le cas «certains sommets doivent être éliminés»;
J-16 SDiZ
@balint: gonfler un triangle est trivial: si vous stockez le vertrex dans l'ordre normal, le côté droit est toujours "vers l'extérieur". Il suffit de traiter ces segments de ligne comme des lignes, de les déplacer vers l'extérieur et de trouver l'interaction - ils sont le nouveau sommet. Pour la triangulation elle-même, dans un second temps, la triangulation delaunay peut donner un meilleur résultat.
J-16 SDiZ
4
Je pense que cette approche peut facilement donner de mauvais résultats. Même pour un exemple simple comme quad triangulé en utilisant une diagonale. Pour les deux triangles agrandis, vous obtenez: img200.imageshack.us/img200/2640/counterm.png et leur union n'est tout simplement pas ce que vous recherchez. Je ne vois pas en quoi cette méthode est utile.
balint.miklos
3

Un grand merci à Angus Johnson pour sa bibliothèque de tondeuses. Il existe de bons exemples de code pour effectuer les coupures sur la page d'accueil de clipper à http://www.angusj.com/delphi/clipper.php#code mais je n'ai pas vu d'exemple de décalage de polygone. J'ai donc pensé que cela pourrait être utile à quelqu'un si je poste mon code:

    public static List<Point> GetOffsetPolygon(List<Point> originalPath, double offset)
    {
        List<Point> resultOffsetPath = new List<Point>();

        List<ClipperLib.IntPoint> polygon = new List<ClipperLib.IntPoint>();
        foreach (var point in originalPath)
        {
            polygon.Add(new ClipperLib.IntPoint(point.X, point.Y));
        }

        ClipperLib.ClipperOffset co = new ClipperLib.ClipperOffset();
        co.AddPath(polygon, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon);

        List<List<ClipperLib.IntPoint>> solution = new List<List<ClipperLib.IntPoint>>();
        co.Execute(ref solution, offset);

        foreach (var offsetPath in solution)
        {
            foreach (var offsetPathPoint in offsetPath)
            {
                resultOffsetPath.Add(new Point(Convert.ToInt32(offsetPathPoint.X), Convert.ToInt32(offsetPathPoint.Y)));
            }
        }

        return resultOffsetPath;
    }
Douleur élémentaire
la source
2

Une autre option consiste à utiliser boost :: polygon - la documentation fait quelque peu défaut, mais vous devriez trouver que les méthodes resizeet bloat, et également l' +=opérateur surchargé , qui implémentent en fait la mise en mémoire tampon. Ainsi, par exemple, augmenter la taille d'un polygone (ou d'un ensemble de polygones) d'une certaine valeur peut être aussi simple que:

poly += 2; // buffer polygon by 2
Paul R
la source
Je ne comprends pas comment vous êtes censé faire quoi que ce soit avec boost :: polygon car il ne prend en charge que les coordonnées entières? Disons que j'ai un polygone général (coordonnées en virgule flottante) et que je veux l'agrandir - que dois-je faire?
David Doria
@DavidDoria: cela dépend de la résolution / précision et de la plage dynamique dont vous avez besoin pour vos coordonnées, mais vous pouvez utiliser un int 32 bits ou 64 bits avec un facteur d'échelle approprié. Par ailleurs, j'ai (accidentellement) utilisé boost :: polygon avec des coordonnées flottantes dans le passé et cela semble fonctionner correctement, mais il n'est peut-être pas 100% robuste (les docs mettent en garde contre cela!).
Paul R
Je serais d'accord avec "ça va marcher la plupart du temps" :). J'ai essayé ceci: ideone.com/XbZeBf mais il ne compile pas - des pensées?
David Doria
Je ne vois rien de mal à l'évidence, mais dans mon cas, j'utilisais les spécialisations rectilignes (polygone_90), donc je ne sais pas si cela fait une différence. Cela fait quelques années que je ne joue pas avec ça.
Paul R
OK - ça me revient maintenant - vous ne pouvez l'utiliser +=qu'avec un ensemble de polygones , pas avec des polygones individuels. Essayez-le avec un vecteur std :: de polygones. (Bien sûr, le vecteur ne doit contenir qu'un seul polygone).
Paul R
1

Sur la base des conseils de @ JoshO'Brian, il semble que le rGeospackage dans le Rlangage implémente cet algorithme. Tu vois rGeos::gBuffer.

Carl Witthoft
la source
0

Il existe quelques bibliothèques que l'on peut utiliser (également utilisable pour les ensembles de données 3D).

  1. https://github.com/otherlab/openmesh
  2. https://github.com/alecjacobson/nested_cages
  3. http://homepage.tudelft.nl/h05k3/Projects/MeshThickeningProj.htm

On peut également trouver des publications correspondantes pour ces bibliothèques pour comprendre les algorithmes plus en détail.

Le dernier a le moins de dépendances et est autonome et peut lire dans des fichiers .obj.

Meilleurs voeux, Stephan

stephanmg
la source
0

J'utilise une géométrie simple: vecteurs et / ou trigonométrie

  1. À chaque coin, trouvez le vecteur médian et l'angle médian. Le vecteur médian est la moyenne arithmétique des deux vecteurs unitaires définis par les bords du coin. L'angle moyen est la moitié de l'angle défini par les bords.

  2. Si vous avez besoin d'agrandir (ou de contracter) votre polygone de la quantité de d de chaque bord; vous devez sortir (in) du montant d / sin (midAngle) pour obtenir le nouveau point de coin.

  3. Répétez cette opération pour tous les coins

*** Faites attention à votre direction. Faites le test CounterClockWise en utilisant les trois points définissant le coin; pour savoir quelle voie est sortie ou vers l'intérieur.

user2800464
la source