IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Développement 2D, 3D et Jeux Discussion :

Génération Aléatoire d'Univers


Sujet :

Développement 2D, 3D et Jeux

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Rédacteur/Modérateur

    Avatar de yahiko
    Homme Profil pro
    Développeur
    Inscrit en
    Juillet 2013
    Messages
    1 424
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 1 424
    Billets dans le blog
    43
    Par défaut Génération Aléatoire d'Univers
    Génération Aléatoire d'Univers


    1. Introduction

    J'ai tout récemment eu l'envie de m'atteler à la génération aléatoire d'univers, et cet article a pour but de vous faire partager mon expérience et mes résultats. Loin d'être un expert dans le domaine, toute contribution complémentaire sera appréciée. Le langage utilisé dans cet exposé sera TypeScript, dont vous pourrez trouver une présentation ici.

    La génération procédurale pour créer des univers est une pratique très courante dans le domaine des jeux vidéos se déroulant dans le vide intersidéral. Le mythique Elite fut l'un des précurseurs et pour ne citer que quelques titres, on peut relever la série des Master of Orion, Galactic Civilization, Faster than Light, Star Wars: Empire at War, etc, etc, etc.


    Master of Orion 3


    Faster than Light

    Je vais ici présenter les différentes approches que j'ai essayé pour générer des univers aléatoires ayant une topologie à peu près acceptable. Dans un plan en deux dimensions, il s'agira donc de générer un ensemble de positions (des étoiles ou des planètes dans le cas d'un contexte spatial, ou de villes s'il s'agit d'un contexte plus réaliste).

    Ces positions devront être reliées par des liens (des chemins si on considère des villes, ou bien des routes spatiales si on est dans le vide intersidéral) de façon à respecter une certaine règle de proximité. On privilégiera de relier deux positions proches plutôt que deux positions éloignées.


    2. Génération des positions

    2.1. Approche naïve

    Une position sera définie comme un point à deux dimensions ayant d'autres attributs que nous ajouterons par la suite. En TypeScript cela pourrait donner ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Position extends Point {
      // autres attributs...
     
      constructor(x: number, y: number) {
        super(x, y);
      }
    }
     
    class Point {
      x: number;
      y: number;
     
      constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
      }
    }
    Pour le moment, la distinction entre Point et Position peut sembler assez artificielle. Elle n'est pas indispensable mais permet de bien séparer ce qui relève de l'outillage (la classe Point) et ce qui relève de la sémantique (la classe Position). Les fonctions utilitaires à usage généraliste seront associées à la classe Point, tandis que ce qui relèvera des spécificités de l'application sera associé à la classe Position.

    Pour peupler notre univers, une façon naïve pourrait consister tout d'abord à générer un ensemble aléatoire de couples (x, y), où la valeur de l'abscisse (resp. de l'ordonnée) représente un pourcentage de la longueur (resp. de la largeur) de l'univers, puisque cette valeur est comprise entre 0 et 1. En retardant le plus possible l'utilisation des dimensions concrètes de l'Univers, cela permet de rendre l'implémentation plus générique.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    var nbPositions = 400; // Nombre de positions
    univers: Position[] = [];
     
    for (var i = 0; i < nbPositions; i++) {
      univers.push(new Position(Math.random(), Math.random()));
    }
    Ceci donne les résultats qu'on peut voir ci-dessous.


    On se rend compte que cette approche naïve pose deux problèmes assez évidents. Le premier est que certaines positions sont trop proches du bord du domaine. Le second est que certaines positions sont trop proches les unes des autres ou se confondent.

    La résolution de ces deux problèmes est assez simple. Pour le premier problème, nous réduirons l'intervalle des nombres aléatoires qui est pour le moment compris entre 0 et 1 avec la méthode de base Math.random(). Pour le second problème, nous empêcherons la génération d'une nouvelle position, si elle est trop proche de positions déjà générées.

    2.2. Correction du phénomène de bord

    Donnons-nous une "marge" autour du bord dans laquelle aucune position ne pourra être créé, et fixons sa valeur à 5% en longueur et à la même chose en largeur. Avec une simple règle de mise à l'échelle, cela donnerait la formule suivante applicable à la fois aux abscisses et aux ordonnées générées aléatoirement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    0.05 + 0.90 * Math.random()
    2.3. Correction des points trop rapprochés

    Pour éviter d'avoir des positions trop proches les unes des autres, il suffit de définir une distance minimale en deçà de laquelle générer une nouvelle position n'est pas permis. En utilisant l'approche simple, celle qui consiste à balayer linéairement l'ensemble des points existants, on pourrait avoir le code suivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    var nbPositions = 400; // Nombre de positions
    var univers: Position[] = [];
     
    var i = 0;
    var point = new Point(0, 0);
    while (i < nbPositions) {
      point.x = 0.05 + 0.90 * Math.random()
      point.y = 0.05 + 0.90 * Math.random();
     
      if (isValidLocation(point, univers)) {
        univers.push(new Position(point.x, point.y));
        i++;
      }
    }
     
    function isValidLocation(point: Point, univers: Position[]): boolean {
      for (var i = 0; i < univers.length; i++) {
        var pos = univers[i];
     
        if (distance(point.x, point.y, pos.point.x, pos.point.y) < 0.02) {
          return false;
        }
      }
     
      return true;
    }
     
    function distance(x1: number, y1: number, x2: number, y2: number): number {
      return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    }
    Il va sans dire au final, en terme d'efficacité algorithmique, que cette méthode n'est pas très efficiente puisque l'ordre de grandeur est une complexité en O(n²) ce qui peut vite se révéler rédhibitoire pour un grand ensemble de positions. Pour améliorer les performances de ce processus, il serait sans doute utile d'implémenter une variante de l'algorithme des plus proches voisins (All Nearest Neighbors)

    Cependant, pour le moment, nous nous contenterons de l'approche "naïve" compte-tenu d'un nombre de positions à priori limité. Le résultat de ces modifications, l'ajout d'une "marge" et l'application d'une distance minimale entre les positions, donne ce qui suit.


    Nous pouvons estimer en première approche cette distribution des positions comme satisfaisante ce qui nous amène à la seconde partie de cet exposé, à savoir la liaison entre les positions.

    >> suite
    Tutoriels et FAQ TypeScript

  2. #2
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    Bonjour,

    C'est un beau travail

    Juste une petite optimisation ici:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if (distance(point.x, point.y, pos.point.x, pos.point.y) < 0.02)
    Tu peux économiser les racines carrées qui ont un cout énorme pour les processeurs en comparant la distance au carré comme ceci donc:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if (distanceSquare(point.x, point.y, pos.point.x, pos.point.y) < 0.0004)
    Normalement, dans le calcul des distances tu auras très rarement besoin de la racine carré sauf si tu veux la distance réel à afficher quelque part.

  3. #3
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    Autre chose

    Je ne connais pas TypeScript mais est-ce que ceci:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var i = 0;
    while (i < nbPositions) {
      var point = new Point(0.05 + 0.90 * Math.random(), 0.05 + 0.90 * Math.random());
     
      if (isValidLocation(point, univers)) {
        univers.push(new Position(point));
        i++;
      }
    }
    Ne serait pas plus lent que ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var i = 0;
    var point : new Point;
    while (i < nbPositions) {
       var rand  = 0.05 + 0.90 * Math.random();
      point.x = rand ;
      point.y = rand ;
      if (isValidLocation(point, univers)) {
        univers.push(new Position(point));
        i++;
      }
    }
    Si tu y gagne après avoir mesurer la différence, alors tu devrais également sortir le new Position de la boucle

  4. #4
    Rédacteur/Modérateur

    Avatar de yahiko
    Homme Profil pro
    Développeur
    Inscrit en
    Juillet 2013
    Messages
    1 424
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 1 424
    Billets dans le blog
    43
    Par défaut
    Citation Envoyé par Astraya Voir le message
    Juste une petite optimisation ici:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if (distance(point.x, point.y, pos.point.x, pos.point.y) < 0.02)
    Tu peux économiser les racines carrées qui ont un coup énorme pour les processeurs en comparant la distance au carré comme ceci donc:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if (distanceSquare(point.x, point.y, pos.point.x, pos.point.y) < 0.0004)
    Normalement, dans le calcul des distances tu aura très rarement besoin de la racine carré sauf si tu veux la distance réel à afficher quelque part.
    C'est en effet une optimisation tout à fait pertinente. Merci de l'avoir signalée

    Citation Envoyé par Astraya Voir le message
    Autre chose

    Je ne connais pas TypeScript mais est-ce que ceci:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var i = 0;
    while (i < nbPositions) {
      var point = new Point(0.05 + 0.90 * Math.random(), 0.05 + 0.90 * Math.random());
     
      if (isValidLocation(point, univers)) {
        univers.push(new Position(point));
        i++;
      }
    }
    Ne serait pas plus lent que ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var i = 0;
    var point : new Point;
    while (i < nbPositions) {
       var rand  = 0.05 + 0.90 * Math.random();
      point.x = rand ;
      point.y = rand ;
      if (isValidLocation(point, univers)) {
        univers.push(new Position(point));
        i++;
      }
    }
    Si tu y gagne après avoir mesurer la différence, alors tu devrais également sortir le new Position de la boucle
    J'aime bien les idées présentes dans ta proposition. Mais elles ne sont pas applicables en TypeScript.

    Tout d'abord, un objet Position tel qu'il a été défini dans l'article contient une référence sur un objet Point, et non les valeurs x et y en elles-même. Ce qui fait que je ne peux pas créer une instance de Point en dehors de la boucle sous peine d'avoir toutes mes instances de Position avoir les mêmes coordonnées x, y. Ce raisonnement s'applique également à la création d'un objet Position dans le tableau univers. Si je ne crée pas à chaque pas de boucle un nouvel objet, alors je me retrouverai avec un univers ayant une multitude de copies d'une seule et même position.

    Pour ce qui est de l'optimisation sur la génération aléatoire, ta proposition a l'inconvénient d'affecter à point.x et à point.y la même valeur aléatoire ce qui au final engendrera un univers avec des positions uniquement présente sur une diagonale.
    Tutoriels et FAQ TypeScript

  5. #5
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    J'aime bien les idées présentes dans ta proposition. Mais elles ne sont pas applicables en TypeScript.

    Tout d'abord, un objet Position tel qu'il a été défini dans l'article contient une référence sur un objet Point, et non les valeurs x et y en elles-même. Ce qui fait que je ne peux pas créer une instance de Point en dehors de la boucle sous peine d'avoir toutes mes instances de Position avoir les mêmes coordonnées x, y. Ce raisonnement s'applique également à la création d'un objet Position dans le tableau univers. Si je ne crée pas à chaque pas de boucle un nouvel objet, alors je me retrouverai avec un univers ayant une multitude de copies d'une seule et même position.
    Ok je vois, je ne connais pas du tout ce langage, je me demandais si cela ne faisait pas de copie. J'ai proposé ça afin d'éviter un maximum le nombre d'allocation faite, car peut importe le langage, instancier 1 fois X objets est moins couteux que instancier X objet fois 1. (HS: On peut tromper 1 personnes 1000 fois mais pas 1 fois 1000... euh non... On peut tromper 1000 .... )

    Pour ce qui est de l'optimisation sur la génération aléatoire, ta proposition a l'inconvénient d'affecter à point.x et à point.y la même valeur aléatoire ce qui au final engendrera un univers avec des positions uniquement présente sur une diagonale.
    Je suis allez un peu vite sur ce coup la :p

    J'attends la suite

  6. #6
    Rédacteur/Modérateur

    Avatar de yahiko
    Homme Profil pro
    Développeur
    Inscrit en
    Juillet 2013
    Messages
    1 424
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 1 424
    Billets dans le blog
    43
    Par défaut
    J'ai modifié la classe Position qui désormais dérive de la classe Point, au lieu d'inclure un attribut point.
    Ceci permet de rendre le code un peu plus compact et permet une petite optimisation.

    Je n'ai pas touché par contre aux calculs des distances pour des raisons de clarté explicative même si pour une implémentation, rester au niveau des carrés de distances sans extraire la racine serait plus optimal.
    Tutoriels et FAQ TypeScript

Discussions similaires

  1. Encore génération aléatoire
    Par sebdu94 dans le forum C
    Réponses: 22
    Dernier message: 21/05/2007, 09h58
  2. Problème de génération aléatoire
    Par sebdu94 dans le forum C
    Réponses: 13
    Dernier message: 19/05/2007, 18h04
  3. [VBA-E] memmory génération aléatoire d'images
    Par jhonnybegood dans le forum Macros et VBA Excel
    Réponses: 4
    Dernier message: 04/03/2007, 21h09
  4. génération aléatoire
    Par acewb00 dans le forum MFC
    Réponses: 1
    Dernier message: 02/12/2005, 09h46
  5. génération aléatoire de couleur claire
    Par jiraiya dans le forum Algorithmes et structures de données
    Réponses: 5
    Dernier message: 25/02/2004, 19h52

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo