L'algorithme le plus simple du diagramme de Voronoi à implémenter? [fermé]


88

Quels sont les algorithmes simples pour implémenter le diagramme de Voronoi?

Je n'ai trouvé aucun algorithme spécialement sous forme de pseudo. Veuillez partager quelques liens d'algorithme de diagramme de Voronoi, tutoriel, etc.


Réponses:


32

Un algorithme simple pour calculer la triangulation de Delaunay d'un ensemble de points consiste à inverser les bords . Puisqu'une triangulation de Delaunay est le double graphe d'un diagramme de Voronoi, vous pouvez construire le diagramme à partir de la triangulation en temps linéaire.

Malheureusement, le pire des cas d'exécution de l'approche de retournement est O (n ^ 2). De meilleurs algorithmes tels que le balayage de ligne de Fortune existent, qui prennent du temps O (n log n). Ceci est cependant un peu délicat à mettre en œuvre. Si vous êtes paresseux (comme je le suis), je suggérerais de rechercher une implémentation existante d'une triangulation de Delaunay, de l'utiliser, puis de calculer le double graphe.

En général, un bon livre sur le sujet est Computational Geometry de de Berg et al.


19

Le plus simple? C'est l'approche de la force brute: pour chaque pixel de votre sortie, parcourez tous les points, calculez la distance, utilisez le plus proche. Aussi lent que possible, mais très simple. Si la performance n'est pas importante, elle fait le travail. J'ai moi-même travaillé sur un raffinement intéressant, mais je cherche toujours à voir si quelqu'un d'autre a eu la même idée (plutôt évidente).


14

L'algorithme de Bowyer-Watson est assez facile à comprendre. Voici une implémentation: http://paulbourke.net/papers/triangulate/ . C'est une triangulation de delaunay pour un ensemble de points mais vous pouvez l'utiliser pour obtenir le dual du delaunay, c'est-à-dire un diagramme de voronoi. BTW. l'arbre couvrant minimum est un sous-ensemble de la triangulation de delaunay.


12

L'algorithme le plus efficace pour construire un diagramme voronoï est l'algorithme de Fortune . Il s'exécute en O (n log n).

Voici un lien vers son implémentation de référence en C .

Personnellement, j'aime beaucoup l' implémentation python de Bill Simons et Carson Farmer, car je l'ai trouvé plus facile à étendre.


le lien vers l'implémentation c ne semble plus fonctionner :(
FutureCake

1
@FutureCake Internet Archive à la rescousse: web.archive.org/web/20181018224943/http://ect.bell-labs.com/who/…
polettix


9

Il existe une implémentation voronoi disponible gratuitement pour les graphes 2D en C et en C ++ de Stephan Fortune / Shane O'Sullivan:

VoronoiDiagramGenerator.cpp 

VoronoiDiagramGenerator.h 

Vous le trouverez à de nombreux endroits. Ie à http://www.skynet.ie/~sos/masters/


4
Largement référencé, non documenté, et presque toutes les réimplémentations que j'ai vues basées sur ce code sont erronées (dans différentes langues, beaucoup de gens ont besoin de Voronoi, peu peuvent le comprendre assez bien pour porter correctement). Les seuls ports fonctionnels que j'ai vus proviennent de la communauté scientifique / académique et ont des signatures de fonction extrêmement compliquées - ou massivement optimisées (de sorte qu'elles ne peuvent pas être utilisées dans la plupart des cas) les rendant inutilisables par les programmeurs normaux.
Adam

VoronoiDiagramGenerator.cpp a des fonctionnalités limitées. Il produira un ensemble d'arêtes non ordonné. En extraire des polygones réels n'est pas trivial. Du côté positif, il comporte un clip contre un rectangle de délimitation, de sorte qu'aucun point d'infini n'est généré.
Bram le


6

Alors que la question initiale demande comment mettre en œuvre Voronoi, si j'avais trouvé un article qui disait ce qui suit lorsque je cherchais des informations sur ce sujet, cela m'aurait fait gagner beaucoup de temps:

Il y a beaucoup de code C ++ "presque correct" sur Internet pour implémenter les diagrammes de Voronoi. La plupart ont rarement déclenché des échecs lorsque les points d'amorçage deviennent très denses. Je recommanderais de tester de manière approfondie tout code que vous trouvez en ligne avec le nombre de points que vous prévoyez d'utiliser dans votre projet fini avant de perdre trop de temps dessus.

La meilleure des implémentations que j'ai trouvées en ligne faisait partie du programme MapManager lié à partir d'ici: http://www.skynet.ie/~sos/mapviewer/voronoi.php Cela fonctionne principalement mais une corruption de diagramme intermittente lorsque je traite avec commandez 10 ^ 6 points. Je n'ai pas été en mesure de comprendre exactement comment la corruption s'installe.

Hier soir, j'ai trouvé ceci: http://www.boost.org/doc/libs/1_53_0_beta1/libs/polygon/doc/voronoi_main.htm "La bibliothèque Boost.Polygon Voronoi". Cela semble très prometteur. Cela vient avec des tests de référence pour prouver sa précision et ses performances. La bibliothèque a une interface et une documentation appropriées. Je suis surpris de ne pas avoir trouvé cette bibliothèque avant maintenant, d'où mes écrits à ce sujet ici. (J'ai lu cet article au début de mes recherches.)


4

En fait, il existe des implémentations pour 25 langues différentes disponibles sur https://rosettacode.org/wiki/Voronoi_diagram

Par exemple pour Java:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.swing.JFrame;

public class Voronoi extends JFrame {
    static double p = 3;
    static BufferedImage I;
    static int px[], py[], color[], cells = 100, size = 1000;

    public Voronoi() {
        super("Voronoi Diagram");
        setBounds(0, 0, size, size);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        int n = 0;
        Random rand = new Random();
        I = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
        px = new int[cells];
        py = new int[cells];
        color = new int[cells];
        for (int i = 0; i < cells; i++) {
            px[i] = rand.nextInt(size);
            py[i] = rand.nextInt(size);
            color[i] = rand.nextInt(16777215);

        }
        for (int x = 0; x < size; x++) {
            for (int y = 0; y < size; y++) {
                n = 0;
                for (byte i = 0; i < cells; i++) {
                    if (distance(px[i], x, py[i], y) < distance(px[n], x, py[n], y)) {
                        n = i;

                    }
                }
                I.setRGB(x, y, color[n]);

            }
        }

        Graphics2D g = I.createGraphics();
        g.setColor(Color.BLACK);
        for (int i = 0; i < cells; i++) {
            g.fill(new Ellipse2D .Double(px[i] - 2.5, py[i] - 2.5, 5, 5));
        }

        try {
            ImageIO.write(I, "png", new File("voronoi.png"));
        } catch (IOException e) {

        }

    }

    public void paint(Graphics g) {
        g.drawImage(I, 0, 0, this);
    }

    static double distance(int x1, int x2, int y1, int y2) {
        double d;
        d = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); // Euclidian
    //  d = Math.abs(x1 - x2) + Math.abs(y1 - y2); // Manhattan
    //  d = Math.pow(Math.pow(Math.abs(x1 - x2), p) + Math.pow(Math.abs(y1 - y2), p), (1 / p)); // Minkovski
        return d;
    }

    public static void main(String[] args) {
        new Voronoi().setVisible(true);
    }
}

3

L'algorithme le plus simple provient de la définition d'un diagramme de voronoï: "Le partitionnement d'un plan à n points en polygones convexes de telle sorte que chaque polygone contient exactement un point générateur et chaque point d'un polygone donné est plus proche de son point générateur que de tout autre . "définition de wolfram.

La partie importante ici est que chaque point est plus proche du point générateur que tout autre, à partir de là, l'algorithme est très simple:

  1. Avoir un tableau de points générateurs.
  2. Parcourez chaque pixel de votre toile.
  3. Pour chaque pixel, recherchez le point de génération le plus proche.
  4. Selon quel diagramme vous souhaitez obtenir la couleur du pixel. Si vous voulez un diagramme séparé par une bordure, vérifiez le deuxième point le plus proche, puis vérifiez leur différence et leur couleur avec la couleur de la bordure si elle est inférieure à une valeur.

Si vous voulez un diagramme de couleurs, associez une couleur à chaque point de génération et colorez chaque pixel avec la couleur associée au point de génération le plus proche. Et c'est à peu près tout, ce n'est pas efficace mais très facile à mettre en œuvre.


3

C'est le plus rapide possible - c'est un simple voronoi mais il a fière allure. Il divise les espaces en une grille, place un point dans chaque cellule de la grille placée au hasard et se déplace le long de la grille en vérifiant les cellules 3x3 pour trouver comment il se rapporte aux cellules adjacentes.

C'est plus rapide sans le dégradé.

Vous pouvez vous demander quel serait le voronoi 3D le plus simple. Ce serait fascinant de savoir. Probablement 3x3x3 cellules et vérification du gradient.

http://www.iquilezles.org/www/articles/smoothvoronoi/smoothvoronoi.htm

float voronoi( in vec2 x )
{
    ivec2 p = floor( x );
    vec2  f = fract( x );

    float res = 8.0;
    for( int j=-1; j<=1; j++ )
    for( int i=-1; i<=1; i++ )
    {
        ivec2 b = ivec2( i, j );
        vec2  r = vec2( b ) - f + random2f( p + b );
        float d = dot( r, r );

        res = min( res, d );
    }
    return sqrt( res );
}

et voici la même chose avec la distance de Chebychev. vous pouvez utiliser un bruit flottant random2f 2d à partir d'ici:

https://www.shadertoy.com/view/Msl3DM

edit: j'ai converti ceci en code comme C

C'était il y a quelque temps, pour le bénéfice de ceux qui le disent, je pense que c'est cool:

 function rndng ( n: float ): float
 {//random number -1, 1
     var e = ( n *321.9)%1;
     return  (e*e*111.0)%2-1;
 }

 function voronoi(  vtx: Vector3  )
 {
     var px = Mathf.Floor( vtx.x );
     var pz = Mathf.Floor( vtx.z );
     var fx = Mathf.Abs(vtx.x%1);
     var fz = Mathf.Abs(vtx.z%1);

     var res = 8.0;
     for( var j=-1; j<=1; j++ )
     for( var i=-1; i<=1; i++ )
     {
         var rx = i - fx + nz2d(px+i ,pz + j ) ;
         var rz = j - fz + nz2d(px+i ,pz + j ) ;
         var d = Vector2.Dot(Vector2(rx,rz),Vector2(rx,rz));
         res = Mathf.Min( res, d );
     }
     return Mathf.Sqrt( res );
 }

Pourquoi utilisez-vous autant de variables à une lettre qui ne sont pas explicites? Et quoi ivec2? ou vec2? Ceci est illisible.
shinzou

Bon point, je pense que j'ai eu du mal toute la journée avec ça aussi: answers.unity3d.com/questions/638662/… mis à jour ce texte avec du code
aliential


0

J'ai trouvé cette excellente bibliothèque C # sur le code Google basée sur l'algorithme de Fortune / l'algorithme de ligne de balayage

https://code.google.com/p/fortune-voronoi/

Il vous suffit de créer une liste. Un vecteur peut être créé en passant deux nombres (coordonnées) sous forme de flottant. Passez ensuite la liste dans Fortune.ComputeVoronoiGraph ()

Vous pouvez comprendre un peu plus le concept de l'algorithme à partir de ces pages wikipedia:

http://en.wikipedia.org/wiki/Fortune%27s_algorithm

http://en.wikipedia.org/wiki/Sweep_line_algorithm

Bien qu'une chose que je n'ai pas pu comprendre, c'est comment créer une ligne pour les arêtes partiellement infinies (je ne sais pas grand-chose sur la géométrie des coordonnées :-)). Si quelqu'un sait, merci de me le faire savoir également.


2
Bien que ces liens puissent répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien pour référence. Les réponses aux liens uniquement peuvent devenir invalides si la page liée change.
Kmeixner

0

Si vous essayez de le dessiner sur une image, vous pouvez utiliser un algorithme de remplissage basé sur la file d'attente.

Voronoi::draw(){
    // define colors for each point in the diagram;
    // make a structure to hold {pixelCoords,sourcePoint} queue objects
    // initialize a struct of two closest points for each pixel on the map
    // initialize an empty queue;

    // for each point in diagram:
        // for the push object, first set the pixelCoords to pixel coordinates of point;
        // set the sourcePoint of the push object to the current point;
        // push the queue object;

    // while queue is not empty:
        // dequeue a queue object;
        // step through cardinal neighbors n,s,e,w:
            // if the current dequeued source point is closer to the neighboring pixel than either of the two closest:
                // set a boolean doSortAndPush to false;
                // if only one close neighbor is set:
                    // add sourcePoint to closestNeighbors for pixel;
                    // set doSortAndPush to true;
                // elif sourcePoint is closer to pixel than it's current close neighbor points:
                    // replace the furthest neighbor point with sourcePoint;
                    // set doSortAndPush to true;
                // if flag doSortAndPush is true:
                    // re-sort closest neighbors; 
                    // enqueue object made of neighbor pixel coordinates and sourcePoint;

    // for each pixel location:
        // if distance to closest point within a radius for point drawing:
            // color pixel the point color;
        // elif distances to the two closest neighbors are roughly equal:
            // color the pixel to your border color;
        // else 
            // color the pixel the color of the point's region; 

}

L'utilisation d'une file d'attente garantira que les régions se répartissent en parallèle, minimisant le nombre total de visites de pixels. Si vous utilisez une pile, le premier point remplira toute l'image, puis le second remplira tous les pixels plus proches de lui que le premier point. Cela continuera, augmentant considérablement le nombre de visites. L'utilisation d'une file d'attente FIFO traite les pixels dans l'ordre dans lequel ils sont poussés. Les images résultantes seront à peu près les mêmes que vous utilisiez pile ou file d'attente, mais le big-O pour la file d'attente est beaucoup plus proche du linéaire (en relation avec le nombre de pixels de l'image) que le big-O de l'algorithme de pile. L'idée générale est que les régions se propageront au même rythme et que les collisions se produiront généralement exactement aux points qui correspondent aux limites des régions.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.