Algorithme pour créer des sphères?

27

Quelqu'un at-il un algorithme pour créer une sphère procédurale avec une laquantité de lignes de latitude, une loquantité de lignes de longitude et un rayon de r? J'en ai besoin pour travailler avec Unity, donc les positions des sommets doivent être définies puis les triangles définis via les index ( plus d'infos ).


MODIFIER

entrez la description de l'image ici

J'ai réussi à faire fonctionner le code dans l'unité. Mais je pense que j'aurais pu faire quelque chose de mal. Quand je monte le detailLevel, tout ce qu'il fait est d'ajouter plus de sommets et de polygones sans les déplacer. Ai-je oublié quelque chose?


EDIT 2

entrez la description de l'image ici

J'ai essayé de redimensionner le maillage le long de ses normales. Voilà ce que j'ai. Je pense que je manque quelque chose. Suis-je censé mettre à l'échelle uniquement certaines normales?

Daniel Pendergast
la source
1
Pourquoi ne regardez-vous pas comment les implémentations open source existantes le font? regardez comment Three.js le fait en utilisant des maillages, par exemple.
brice
3
Comme une petite note: à moins que vous ne deviez faire de la latitude / longitude, vous ne voulez certainement pas le faire, car les triangles que vous obtenez seront beaucoup plus éloignés de l'uniforme que ceux que vous obtenez avec d'autres méthodes. (Comparez les triangles près du pôle nord avec ceux près de l'équateur: vous utilisez le même nombre de triangles pour contourner une ligne de latitude dans les deux cas, mais près du pôle cette ligne de latitude a une très petite circonférence alors qu'à l'équateur c'est toute la circonférence de votre globe.) Les techniques comme celle de David Lively sont généralement bien meilleures.
Steven Stadnicki
1
Vous ne normalisez pas la position des sommets après la subdivision. Je n'ai pas inclus cette partie dans mon exemple. La normalisation les rend tous équidistants du centre, ce qui crée l'approximation de courbe que vous recherchez.
3Dave
Pensez à gonfler un ballon au centre de l'icosaèdre. Lorsque le ballon repousse le maillage, il épouse la forme du ballon (sphère).
3Dave
4
"Normaliser" signifie définir la longueur d'un vecteur à 1. Vous devez faire quelque chose comme vertices[i] = normalize(vertices[i]). Soit dit en passant, cela vous donne également vos nouvelles normales correctes, vous devriez donc le faire normals[i] = vertices[i]par la suite.
sam hocevar

Réponses:

31

Pour obtenir quelque chose comme ça:

entrez la description de l'image ici

Créez un icosaèdre (solide régulier à 20 faces) et subdivisez les faces pour obtenir une sphère (voir le code ci-dessous).

L'idée est essentiellement:

  • Créez un n-hèdre régulier (un solide où chaque visage est de la même taille). J'utilise un icosaèdre parce que c'est le solide avec le plus grand nombre de visages où chaque visage est de la même taille. (Il existe une preuve pour cela quelque part. N'hésitez pas à Google si vous êtes vraiment curieux.) Cela vous donnera une sphère où presque tous les visages sont de la même taille, ce qui rend la texturation un peu plus facile.

entrez la description de l'image ici

  • Subdivisez chaque face en quatre faces de même taille. Chaque fois que vous faites cela, cela quadruplera le nombre de faces dans le modèle.

    ///      i0
    ///     /  \
    ///    m02-m01
    ///   /  \ /  \
    /// i2---m12---i1

i0,, i1et i2sont les sommets du triangle d'origine. (En fait, les index dans le tampon de vertex, mais c'est un autre sujet). m01est le milieu du bord (i0,i1), m12 est le milieu du bord (i1,12), et m02est, évidemment, le milieu du bord (i0,i2).

Chaque fois que vous subdivisez une face, assurez-vous de ne pas créer de sommets en double. Chaque point médian sera partagé par une autre face source (car les bords sont partagés entre les faces). Le code ci-dessous en tient compte en conservant un dictionnaire des points médians nommés qui ont été créés et en renvoyant l'index d'un point médian créé précédemment lorsqu'il est disponible plutôt que d'en créer un nouveau.

  • Répétez jusqu'à ce que vous ayez atteint le nombre de faces souhaité pour votre cube.

  • Lorsque vous avez terminé, normalisez tous les sommets pour lisser la surface. Si vous ne le faites pas, vous obtiendrez simplement un icosaèdre de plus haute résolution au lieu d'une sphère.

  • Voila! Vous avez terminé. Convertissez le vecteur résultant et les tampons d'index en a VertexBufferet IndexBuffer, et dessinez avec Device.DrawIndexedPrimitives().

Voici ce que vous utiliseriez dans votre classe "Sphère" pour créer le modèle (types de données XNA et C #, mais cela devrait être assez clair):

        var vectors = new List<Vector3>();
        var indices = new List<int>();

        GeometryProvider.Icosahedron(vectors, indices);

        for (var i = 0; i < _detailLevel; i++)
            GeometryProvider.Subdivide(vectors, indices, true);

        /// normalize vectors to "inflate" the icosahedron into a sphere.
        for (var i = 0; i < vectors.Count; i++)
            vectors[i]=Vector3.Normalize(vectors[i]);

Et la GeometryProviderclasse

public static class GeometryProvider
{

    private static int GetMidpointIndex(Dictionary<string, int> midpointIndices, List<Vector3> vertices, int i0, int i1)
    {

        var edgeKey = string.Format("{0}_{1}", Math.Min(i0, i1), Math.Max(i0, i1));

        var midpointIndex = -1;

        if (!midpointIndices.TryGetValue(edgeKey, out midpointIndex))
        {
            var v0 = vertices[i0];
            var v1 = vertices[i1];

            var midpoint = (v0 + v1) / 2f;

            if (vertices.Contains(midpoint))
                midpointIndex = vertices.IndexOf(midpoint);
            else
            {
                midpointIndex = vertices.Count;
                vertices.Add(midpoint);
                midpointIndices.Add(edgeKey, midpointIndex);
            }
        }


        return midpointIndex;

    }

    /// <remarks>
    ///      i0
    ///     /  \
    ///    m02-m01
    ///   /  \ /  \
    /// i2---m12---i1
    /// </remarks>
    /// <param name="vectors"></param>
    /// <param name="indices"></param>
    public static void Subdivide(List<Vector3> vectors, List<int> indices, bool removeSourceTriangles)
    {
        var midpointIndices = new Dictionary<string, int>();

        var newIndices = new List<int>(indices.Count * 4);

        if (!removeSourceTriangles)
            newIndices.AddRange(indices);

        for (var i = 0; i < indices.Count - 2; i += 3)
        {
            var i0 = indices[i];
            var i1 = indices[i + 1];
            var i2 = indices[i + 2];

            var m01 = GetMidpointIndex(midpointIndices, vectors, i0, i1);
            var m12 = GetMidpointIndex(midpointIndices, vectors, i1, i2);
            var m02 = GetMidpointIndex(midpointIndices, vectors, i2, i0);

            newIndices.AddRange(
                new[] {
                    i0,m01,m02
                    ,
                    i1,m12,m01
                    ,
                    i2,m02,m12
                    ,
                    m02,m01,m12
                }
                );

        }

        indices.Clear();
        indices.AddRange(newIndices);
    }

    /// <summary>
    /// create a regular icosahedron (20-sided polyhedron)
    /// </summary>
    /// <param name="primitiveType"></param>
    /// <param name="size"></param>
    /// <param name="vertices"></param>
    /// <param name="indices"></param>
    /// <remarks>
    /// You can create this programmatically instead of using the given vertex 
    /// and index list, but it's kind of a pain and rather pointless beyond a 
    /// learning exercise.
    /// </remarks>

    /// note: icosahedron definition may have come from the OpenGL red book. I don't recall where I found it. 
    public static void Icosahedron(List<Vector3> vertices, List<int> indices)
    {

        indices.AddRange(
            new int[]
            {
                0,4,1,
                0,9,4,
                9,5,4,
                4,5,8,
                4,8,1,
                8,10,1,
                8,3,10,
                5,3,8,
                5,2,3,
                2,7,3,
                7,10,3,
                7,6,10,
                7,11,6,
                11,0,6,
                0,1,6,
                6,1,10,
                9,0,11,
                9,11,2,
                9,2,5,
                7,2,11 
            }
            .Select(i => i + vertices.Count)
        );

        var X = 0.525731112119133606f;
        var Z = 0.850650808352039932f;

        vertices.AddRange(
            new[] 
            {
                new Vector3(-X, 0f, Z),
                new Vector3(X, 0f, Z),
                new Vector3(-X, 0f, -Z),
                new Vector3(X, 0f, -Z),
                new Vector3(0f, Z, X),
                new Vector3(0f, Z, -X),
                new Vector3(0f, -Z, X),
                new Vector3(0f, -Z, -X),
                new Vector3(Z, X, 0f),
                new Vector3(-Z, X, 0f),
                new Vector3(Z, -X, 0f),
                new Vector3(-Z, -X, 0f) 
            }
        );


    }



}
David Lively
la source
Très bonne réponse. Merci. Je ne peux pas le dire mais est-ce un code d'unité? Oh, et le lat / long n'a pas d'importance, tant que je peux régler la résolution.
Daniel Pendergast
Ce n'est pas Unity (XNA) mais cela vous donnera les coordonnées des sommets et la liste des index. Remplacez Vector3 par l'équivalent Unity. Vous définissez la résolution en ajustant le nombre d'itérations Subdiviser. Chaque boucle multiplie le nombre de faces par 4. 2 ou 3 itérations donneront une jolie sphère.
3Dave
Ah, je vois. Il est presque identique à Unity C #. Juste quelques questions ... Pourquoi quand les indices sont définis, vous les mettez à l'intérieur d'un inttableau? Et que fait- .Select(i => i + vertices.Count)il?
Daniel Pendergast
Le .Select(i => i + vertices.Count)ne fonctionne pas pour moi du tout. Est-ce une fonctionnalité XNA uniquement?
Daniel Pendergast
1
Assurez-vous que vous incluez 'using System.Linq' tel qu'il définit.Select, etc.
3Dave
5

Considérons la définition paramétrique d'une sphère:

définition paramétrique d'une sphère

où thêta et phi sont deux angles d'incrémentation, que nous désignerons par var tet var uet Rx, Ry et Rz sont les rayons (rayons) indépendants dans les trois directions cartésiennes, qui, dans le cas d'une sphère, seront définis comme un seul rayon var rad.

Considérons maintenant le fait que le ...symbole indique une itération qui suggère l'utilisation d'une boucle. Le concept de stacksetrows est "combien de fois allez-vous répéter". Puisque chaque itération ajoute la valeur de t ou u, plus il y a d'itérations, plus la valeur est petite, donc plus la courbure de la sphère est précise.

La condition sine qua non de la fonction « dessin de la sphère » est d'avoir les paramètres donnés suivants: int latitudes, int longitudes, float radius. Les conditions de post (sortie) sont de retourner ou d'appliquer les sommets calculés. Selon la façon dont vous comptez l'utiliser, la fonction peut renvoyer un tableau devector3 (vecteurs tridimensionnels) ou, si vous utilisez une sorte d'OpenGL simple, avant la version 2.0, vous souhaiterez peut-être appliquer directement les sommets au contexte.

NB L'application d'un sommet en openGL appelle la fonction suivante glVertex3f(x, y, z). Dans le cas où nous stockerions les sommets, nous ajouterions un nouveauvector3(x, y, z) pour un stockage facile.

De plus, la façon dont vous avez demandé le fonctionnement du système de latitude et de longitude nécessitait un ajustement de la définition de la sphère (essentiellement en changeant z et y), mais cela montre simplement que la définition est très malléable et que vous êtes libre de changer de Paramètres x, y et z pour modifier la direction dans laquelle la sphère est dessinée (où se trouvent les latitudes et les longitudes).

Voyons maintenant comment nous allons faire les latitudes et les longitudes. Les latitudes sont représentées par la variable u, elles itèrent de 0 à 2π radians (360 degrés). On peut donc coder son itération comme ceci:

float latitude_increment = 360.0f / latitudes;

for (float u = 0; u < 360.0f; u += latitude_increment) {
    // further code ...
}

Maintenant, les longitudes sont représentées par la variable tet itèrent de 0 à π (180 degrés). par conséquent, le code suivant ressemble au précédent:

float latitude_increment = 360.0f / latitudes;
float longitude_increment = 180.0f / longitudes;

for (float u = 0; u <= 360.0f; u += latitude_increment) {
    for (float t = 0; t <= 180.0f; t += longitude_increment) {
        // further code ...
    }
}

(Notez que les boucles incluent la condition terminale, car l'intervalle d'intégration paramétrique est compris entre 0 et 2π inclus . Vous obtiendrez une sphère partielle si vos conditions ne sont pas inclusives.)

Maintenant, en suivant la définition simple de la sphère, nous pouvons dériver la définition de variable comme suit (supposons float rad = radius;):

float x = (float) (rad * Math.sin(Math.toRadians(t)) * Math.sin(Math.toRadians(u)));
float y = (float) (rad * Math.cos(Math.toRadians(t)));
float z = (float) (rad * Math.sin(Math.toRadians(t)) * Math.cos(Math.toRadians(u)));

Encore un avertissement important! Dans la plupart des cas, vous utiliserez une forme d'OpenGL, et même si ce n'est pas le cas, vous devrez peut-être le faire. Un objet en trois dimensions a besoin de plusieurs sommets pour être défini. Ceci est généralement réalisé en fournissant le sommet suivant qui est calculable.

comment plusieurs sommets sont utilisés pour définir une forme (primitive)

À quel point dans la figure ci-dessus les différentes coordonnées sont x+∂et y+∂, nous pouvons facilement générer trois autres sommets pour toute utilisation souhaitée. Les autres sommets sont (supposons float rad = radius;):

float x = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.sin(Math.toRadians(u)));
float y = (float) (rad * Math.cos(Math.toRadians(t + longitude_increment)));
float z = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.cos(Math.toRadians(u)));

float x = (float) (rad * Math.sin(Math.toRadians(t)) * Math.sin(Math.toRadians(u + latitude_increment)));
float y = (float) (rad * Math.cos(Math.toRadians(t)));
float z = (float) (rad * Math.sin(Math.toRadians(t)) * Math.cos(Math.toRadians(u + latitude_increment)));

float x = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.sin(Math.toRadians(u + latitude_increment)));
float y = (float) (rad * Math.cos(Math.toRadians(t + longitude_increment)));
float z = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.cos(Math.toRadians(u + latitude_increment)));

Enfin, voici une fonction complète qui retournerait tous les sommets d'une sphère, et la seconde montre une implémentation OpenGL fonctionnelle du code (c'est la syntaxe de style C et non JavaScript, cela devrait fonctionner avec tous les langages de style C, y compris C # lors de l'utilisation de Unity).

static Vector3[] generateSphere(float radius, int latitudes, int longitudes) {

    float latitude_increment = 360.0f / latitudes;
    float longitude_increment = 180.0f / longitudes;

    // if this causes an error, consider changing the size to [(latitude + 1)*(longitudes + 1)], but this should work.
    Vector3[] vertices = new Vector3[latitude*longitudes];

    int counter = 0;

    for (float u = 0; u < 360.0f; u += latitude_increment) {
        for (float t = 0; t < 180.0f; t += longitude_increment) {

            float rad = radius;

            float x = (float) (rad * Math.sin(Math.toRadians(t)) * Math.sin(Math.toRadians(u)));
            float y = (float) (rad * Math.cos(Math.toRadians(t)));
            float z = (float) (rad * Math.sin(Math.toRadians(t)) * Math.cos(Math.toRadians(u)));

            vertices[counter++] = new Vector3(x, y, z);

        }
    }

    return vertices;

}

Code OpenGL:

static int createSphereBuffer(float radius, int latitudes, int longitudes) {

    int lst;

    lst = glGenLists(1);

    glNewList(lst, GL_COMPILE);
    {

        float latitude_increment = 360.0f / latitudes;
        float longitude_increment = 180.0f / longitudes;

        for (float u = 0; u < 360.0f; u += latitude_increment) {

            glBegin(GL_TRIANGLE_STRIP);

            for (float t = 0; t < 180.0f; t += longitude_increment) {

                float rad = radius;

                float x = (float) (rad * Math.sin(Math.toRadians(t)) * Math.sin(Math.toRadians(u)));
                float y = (float) (rad * Math.cos(Math.toRadians(t)));
                float z = (float) (rad * Math.sin(Math.toRadians(t)) * Math.cos(Math.toRadians(u)));

                vertex3f(x, y, z);

                float x1 = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.sin(Math.toRadians(u + latitude_increment)));
                float y1 = (float) (rad * Math.cos(Math.toRadians(t + longitude_increment)));
                float z1 = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.cos(Math.toRadians(u + latitude_increment)));

                vertex3f(x1, y1, z1);

            }

            glEnd();

        }

    }
    glEndList()

    return lst;

}

// to render VVVVVVVVV

// external variable in main file
static int sphereList = createSphereBuffer(desired parameters)

// called by the main program
void render() {

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glCallList(sphereList);

    // any additional rendering and buffer swapping if not handled already.

}

PS Vous avez peut-être remarqué cette déclaration rad = radius;. Cela permet de modifier le rayon dans la boucle, en fonction de l'emplacement ou de l'angle. Cela signifie que vous pouvez appliquer du bruit à la sphère pour la rendre rugueuse, la rendant plus naturelle si l'effet souhaité est de type planète. Par exemplefloat rad = radius * noise[x][y][z];

Claude-Henry.

claudehenry
la source
La ligne `float z = (float) (rad * Math.sin (Math.toRadians (t)) * Math.cos (Math.toRadians (u)));` est incorrecte. Vous avez déjà calculé un X, Y avec une hypoténuse de rad. Maintenant, vous faites cette jambe d'un triangle, et sous-entend que l'hypoténuse dudit triangle l'est également rad. Cela vous donne effectivement un rayon de rad * sqrt(2).
3Dave
@DavidLively merci de l'avoir souligné, j'ai écrit cela il y a un certain temps, donc je ne suis pas surpris si c'est mauvais ou même carrément faux.
claudehenry
c'est toujours amusant quand je trouve une erreur dans l'un de mes messages d'il y a des années. Ça arrive. :)
3Dave
4

J'ai créé quelque chose comme ça il y a quelque temps pour créer une sphère de cubes, pour le plaisir et la science. Ce n'est pas trop dur. Fondamentalement, vous prenez une fonction qui crée un cercle de sommets, puis parcourez les incréments de hauteur que vous souhaitez créer des cercles à chaque hauteur au rayon requis pour créer une sphère. Ici, j'ai modifié le code pour ne pas être pour les cubes:

public static void makeSphere(float sphereRadius, Vector3f center, float heightStep, float degreeStep) {
    for (float y = center.y - sphereRadius; y <= center.y + sphereRadius; y+=heightStep) {
        double radius = SphereRadiusAtHeight(sphereRadius, y - center.y); //get the radius of the sphere at this height
        if (radius == 0) {//for the top and bottom points of the sphere add a single point
            addNewPoint((Math.sin(0) * radius) + center.x, y, (Math.cos(0) * radius) + center.z));
        } else { //otherwise step around the circle and add points at the specified degrees
            for (float d = 0; d <= 360; d += degreeStep) {
                addNewPoint((Math.sin(d) * radius) + center.x, y, (Math.cos(d) * radius) + center.z));
            }
        }
    }
}

public static double SphereRadiusAtHeight(double SphereRadius, double Height) {
    return Math.sqrt((SphereRadius * SphereRadius) - (Height * Height));
}

Maintenant, ce code ne ferait que créer des points pour la latitude. Cependant, vous pouvez presque utiliser le même code pour créer les lignes de longitude. Sauf que vous devrez tourner entre chaque itération et faire un cercle complet à chaquedegreeStep .

Désolé, ce n'est pas une réponse complète ou spécifique à Unity, mais j'espère que cela vous aidera à démarrer.

MichaelHouse
la source
C'est assez bien si vous avez besoin d'une sphère lat / longue, mais vous pouvez la simplifier un peu en travaillant en coordonnées sphériques jusqu'à la dernière étape.
3Dave
1
Merci @David. Je suis d'accord, si j'arrive à écrire une version en utilisant des cordes sphériques, je la posterai ici.
MichaelHouse
3

Ne pourriez-vous pas simplement commencer avec une forme simple, pourrait être une boîte avec une distance r du centre au coin. Pour créer une sphère plus détaillée, subdivisez tous les polygones, puis déplacez les sommets à une distance r du centre, le vecteur passant par sa position actuelle.

Continuez à répéter jusqu'à ce que suffisamment sphérique pour vos goûts.

Eric Johansson
la source
C'est essentiellement la même que l'approche icosaédrique, mais avec une forme de départ différente. Un avantage de commencer avec un cube que je ne pense pas a été mentionné: il est beaucoup plus facile de créer des cartes UV décentes car vous pouvez utiliser un cubemap et savoir que vos coutures de texture s'aligneront parfaitement avec les bords de votre maillage de sphère.
Steven Stadnicki
@StevenStadnicki le seul problème que j'ai avec les cubes est que les visages ont tendance à être de tailles très différentes après quelques subdivisions.
3Dave
@DavidLively Cela dépend beaucoup de la façon dont vous subdivisez - si vous découpez les faces carrées de votre cube en une grille régulière, puis projetez vers l'extérieur / normalisez, c'est vrai, mais si vous grillez vos faces de manière non uniforme, vous pouvez réellement faire le la projection doit être régulièrement espacée le long des arcs des bords; cela fonctionne plutôt bien.
Steven Stadnicki
@StevenStadnicki nifty!
3Dave
@EricJohansson btw, en tant qu'enseignant, je me sens obligé de mentionner qu'il s'agit d'un aperçu assez important pour quelqu'un qui n'a apparemment pas vu la méthode de subdivision auparavant. Vous avez renouvelé ma foi en l'humanité pour les 12 prochaines heures.
3Dave
2

Avez-vous réellement besoin de la géométrie 3D ou simplement de la forme?

Vous pouvez créer une "fausse" sphère en utilisant un seul quad. Il suffit de mettre un cercle dessus et de l'ombrer correctement. Cela a l'avantage d'avoir exactement la résolution requise quelle que soit la distance à la caméra ou la résolution.

Il y a un tutoriel ici .

David C. Bishop
la source
1
Bon hack, mais échoue si vous avez besoin de le texturer.
3Dave
@DavidLively Il devrait être possible de calculer les coordonnées de texture par pixel en fonction de sa rotation, sauf si vous devez texturer des polygones individuellement.
David C. Bishop
@DavidCBishop Vous devez tenir compte de la "lentille" de la surface - les cordons texels sont serrés près de la bordure du cercle en raison de la perspective - à quel point vous simulez la rotation. En outre, cela implique de déplacer beaucoup plus de travail dans le pixel shader qui pourrait être effectué dans le vertex shader (et nous savons tous que les VS sont beaucoup moins chers!).
3Dave
0

voici un code pour n'importe quel nombre de sommets également espacés d'une sphère, c'est comme une peau d'orange qui enroule une ligne de points autour d'une sphère en spirale. ensuite, la façon dont vous joignez les sommets dépend de vous. vous pouvez utiliser des points voisins dans la boucle en tant que 2 de chaque triangle, puis trouver le troisième serait une torsion proportionnelle autour de la sphère plus haut ou plus bas ... vous pouvez également faire des triangles par boucle et le plus proche voisin, fait quelqu'un connaissez une meilleure façon?

var spherevertices = vector3 generic list...

public var numvertices= 1234;
var size = .03;  

function sphere ( N:float){//<--- N is the number of vertices i.e 123

var inc =  Mathf.PI  * (3 - Mathf.Sqrt(5));
var off = 2 / N;
for (var k = 0; k < (N); k++)
{
    var y = k * off - 1 + (off / 2);
    var r = Mathf.Sqrt(1 - y*y);
    var phi = k * inc;
    var pos = Vector3((Mathf.Cos(phi)*r*size), y*size, Mathf.Sin(phi)*r*size); 

    spherevertices   add pos...

}

};

compréhensible
la source
-1

Bien que David ait tout à fait raison dans sa réponse, je veux offrir une perspective différente.

Pour ma mission de génération de contenu procédural, j'ai examiné (entre autres choses) l'icosaèdre par rapport à des sphères subdivisées plus traditionnelles. Regardez ces sphères générées de manière procédurale:

Des sphères impressionnantes

Les deux ressemblent à des sphères parfaitement valides, non? Eh bien, regardons leurs wireframes:

Wow c'est dense

Wow, que s'est-il passé là-bas? La version filaire de la deuxième sphère est si dense qu'elle a l'air texturée! Je vais vous dévoiler un secret: la deuxième version est un icosaèdre. C'est une sphère presque parfaite, mais elle a un prix élevé.

La sphère 1 utilise 31 subdivisions sur l'axe des x et 31 subdivisions sur l'axe des z, pour un total de 3 844 faces.

Sphère 2 utilise 5 subdivisions récursives, pour un total de 109 220 faces.

Mais d'accord, ce n'est pas vraiment juste. Réduisons considérablement la qualité:

Grumeleux

La sphère 1 utilise 5 subdivisions sur l'axe des x et 5 subdivisions sur l'axe des z, pour un total de 100 faces.

Sphère 2 utilise 0 subdivisions récursives, pour un total de 100 faces.

Ils utilisent la même quantité de visages, mais à mon avis, la sphère de gauche semble meilleure. Il semble moins bosselé et beaucoup plus rond. Jetons un coup d'œil au nombre de visages que nous générons avec les deux méthodes.

Icosaèdre:

  • Niveau 0 - 100 faces
  • Niveau 1 - 420 faces
  • Niveau 2 - 1700 faces
  • Niveau 3 - 6 820 faces
  • Niveau 4 - 27 300 faces
  • Niveau 5 - 109 220 faces

Sphère subdivisée:

  • YZ: 5 à 100 faces
  • YZ: 10 - 400 faces
  • YZ: 15 à 900 faces
  • YZ: 20-1 600 faces
  • YZ: 25-2 500 faces
  • YZ: 30-3 600 faces

Comme vous pouvez le voir, l'icosaèdre augmente de visages à un rythme exponentiel, à une troisième puissance! En effet, pour chaque triangle, nous devons les subdiviser en trois nouveaux triangles.

La vérité est: vous n'avez pas besoin de la précision qu'un icosaèdre vous donnera. Parce qu'ils cachent tous les deux un problème beaucoup plus difficile: texturer un plan 2D sur une sphère 3D. Voici à quoi ressemble le haut:

Top suce

En haut à gauche, vous pouvez voir la texture utilisée. Par coïncidence, il est également généré de manière procédurale. (Hé, c'était un cours sur la génération procédurale, non?)

Ça a l'air terrible, non? Eh bien, c'est aussi bon que ça va arriver. J'ai obtenu les meilleures notes pour mon mappage de texture, car la plupart des gens ne comprennent même pas cela correctement.

Alors s'il vous plaît, pensez à utiliser le cosinus et le sinus pour générer une sphère. Il génère beaucoup moins de visages pour la même quantité de détails.

chevalier666
la source
6
J'ai bien peur de ne pouvoir voter que pour cela. L'icosphère évolue de façon exponentielle? C'est uniquement parce que vous avez décidé que le vôtre devrait évoluer de façon exponentielle. Une sphère UV génère moins de visages qu'une icosphère pour la même quantité de détails? C'est faux, absolument faux, totalement à l'envers.
sam hocevar
4
La subdivision n'a pas besoin d'être récursive. Vous pouvez diviser le bord d'un triangle en autant de parties égales que vous le souhaitez. L'utilisation de Npièces vous donnera de N*Nnouveaux triangles, qui sont quadratiques, exactement comme ce que vous faites avec la sphère UV.
sam hocevar
6
Je dois également ajouter que la sphère que vous dites semble "moins grumeleuse et beaucoup plus ronde" est considérée sous le meilleur angle, ce qui rend cette affirmation malhonnête également. Faites juste la même capture d'écran avec les sphères vues d'en haut pour voir ce que je veux dire.
sam hocevar
4
De plus, vos numéros d'icosaèdre ne semblent pas corrects. Le niveau 0 est de 20 faces (par définition), puis 80, 320, 1280, etc. Vous pouvez subdiviser en n'importe quel nombre et n'importe quel motif que vous voulez. La régularité du modèle va finalement être déterminée par le nombre et la distribution des faces dans le résultat final (quelle que soit la méthode utilisée pour les générer), et nous voulons garder la taille de chaque face aussi uniforme que possible (pas de polaire compression) pour maintenir un profil uniforme quel que soit l'angle de vue. Ajoutez à cela le fait que le code de subdivision est beaucoup plus simple (à mon humble avis) ...
3Dave
2
Un certain travail a été mis dans cette réponse, ce qui me fait un peu mal à l'idée de le voter. Mais c'est complètement et complètement faux, donc je dois le faire. Une Icosphère parfaitement ronde qui remplit tout l'écran en FullHD a besoin de 5 subdivisions, avec un icosaèdre de base sans subdivisions. Un icosaèdre sans subdivisions n'a pas 100 faces, il en a 20. Icosa = 20. C'est le nom! Chaque subdivision multiplie le nombre de faces par 4, donc 1-> 80, 2-> 320, 3-> 1280, 4-> 5120, 5-> 20 480. Avec une géosphère, nous avons besoin d'au moins 40'000 faces pour obtenir une sphère également ronde.
Peter - Unban Robert Harvey
-1

Le script ci-dessous créera un Icosaèdre avec n Polygones ... base 12. Il subdivisera également les polygones en maillages séparés et calculera le total des verts-doublons et des polygones.

Je n'ai rien trouvé de similaire alors j'ai créé ça. Attachez simplement le script à un GameObject et définissez les subdivisions dans l'éditeur. Travail sur la modification du bruit ensuite.


/* Creates an initial Icosahedron with 12 vertices...
 * ...Adapted from https://medium.com/@peter_winslow/creating-procedural-icosahedrons-in-unity-part-1-df83ecb12e91
 * ...And a couple other Icosahedron C# for Unity scripts
 * 
 * Allows an Icosahedron to be created with multiple separate polygon meshes
 * I used a dictionary of Dictionary<int, List<Vector3>> to represent the 
 * Polygon index and the vertice index
 * polygon[0] corresponds to vertice[0]
 * so that all vertices in dictionary vertice[0] will correspond to the polygons in polygon[0]
 * 
 * If you need help understanding Dictionaries
 * https://msdn.microsoft.com/en-us/library/xfhwa508(v=vs.110).aspx
 * 
 * --I used dictionaries because I didn't know what programming instrument to use, so there may be more
 * elegant or efficient ways to go about this.
 * 
 * Essentially int represents the index, and 
 * List<Vector3> represents the actual Vector3 Transforms of the triangle
 * OR List<Vector3> in the polygon dictionary will act as a reference to the indice/index number of the vertices
 * 
 * For example the polygon dictionary at key[0] will contain a list of Vector3's representing polygons
 * ... Vector3.x , Vector3.y, Vector3.z in the polygon list would represent the 3 indexes of the vertice[0] list
 * AKA the three Vector3 transforms that make up the triangle
 *    .
 *  ./_\.
 * 
 * Create a new GameObject and attach this script
 *  -The folders for the material and saving of the mesh data will be created automatically 
 *    -Line 374/448
 * 
 * numOfMainTriangles will represent the individual meshes created
 * numOfSubdivisionsWithinEachTriangle represents the number of subdivisions within each mesh
 * 
 * Before running with Save Icosahedron checked be aware that it can take several minutes to 
 *   generate and save all the meshes depending on the level of divisions
 * 
 * There may be a faster way to save assets - Line 430 - AssetDatabase.CreateAsset(asset,path);
 * */

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class UnityIcosahedronGenerator : MonoBehaviour {
    IcosahedronGenerator icosahedron;
    public const int possibleSubDivisions = 7;
    public static readonly int[] supportedChunkSizes = { 20, 80, 320, 1280, 5120, 20480, 81920};

    [Range(0, possibleSubDivisions - 1)]
    public int numOfMainTriangles = 0;
    [Range(0,possibleSubDivisions - 1)]
    public int numOfSubdivisionsWithinEachTriangle = 0;
    public bool saveIcosahedron = false;

    // Use this for initialization
    void Start() {
        icosahedron = ScriptableObject.CreateInstance<IcosahedronGenerator>();

        // 0 = 12 verts, 20 tris
        icosahedron.GenBaseIcosahedron();
        icosahedron.SeparateAllPolygons();

        // 0 = 12 verts, 20 tris - Already Generated with GenBaseIcosahedron()
        // 1 = 42 verts, 80 tris
        // 2 = 162 verts, 320 tris
        // 3 = 642 verts, 1280 tris
        // 5 = 2562 verts, 5120 tris
        // 5 = 10242 verts, 20480 tris
        // 6 = 40962verts, 81920 tris
        if (numOfMainTriangles > 0) {
            icosahedron.Subdivide(numOfMainTriangles);
        }
        icosahedron.SeparateAllPolygons();

        if (numOfSubdivisionsWithinEachTriangle > 0) {
            icosahedron.Subdivide(numOfSubdivisionsWithinEachTriangle);
        }

        icosahedron.CalculateMesh(this.gameObject, numOfMainTriangles,numOfSubdivisionsWithinEachTriangle, saveIcosahedron);
        icosahedron.DisplayVertAndPolygonCount();
    }
}

public class Vector3Dictionary {
    public List<Vector3> vector3List;
    public Dictionary<int, List<Vector3>> vector3Dictionary;

    public Vector3Dictionary() {
        vector3Dictionary = new Dictionary<int, List<Vector3>>();
        return;
    }

    public void Vector3DictionaryList(int x, int y, int z) {
        vector3List = new List<Vector3>();

        vector3List.Add(new Vector3(x, y, z));
        vector3Dictionary.Add(vector3Dictionary.Count, vector3List);

        return;
    }

    public void Vector3DictionaryList(int index, Vector3 vertice) {
        vector3List = new List<Vector3>();

        if (vector3Dictionary.ContainsKey(index)) {
            vector3List = vector3Dictionary[index];
            vector3List.Add(vertice);
            vector3Dictionary[index] = vector3List;
        } else {
            vector3List.Add(vertice);
            vector3Dictionary.Add(index, vector3List);
        }

        return;
    }

    public void Vector3DictionaryList(int index, List<Vector3> vertice, bool list) {
        vector3List = new List<Vector3>();

        if (vector3Dictionary.ContainsKey(index)) {
            vector3List = vector3Dictionary[index];
            for (int a = 0; a < vertice.Count; a++) {
                vector3List.Add(vertice[a]);
            }
            vector3Dictionary[index] = vector3List;
        } else {
            for (int a = 0; a < vertice.Count; a++) {
                vector3List.Add(vertice[a]);
            }
            vector3Dictionary.Add(index, vector3List);
        }

        return;
    }

    public void Vector3DictionaryList(int index, int x, int y, int z) {
        vector3List = new List<Vector3>();

        if (vector3Dictionary.ContainsKey(index)) {
            vector3List = vector3Dictionary[index];
            vector3List.Add(new Vector3(x, y, z));
            vector3Dictionary[index] = vector3List;
        } else {
            vector3List.Add(new Vector3(x, y, z));
            vector3Dictionary.Add(index, vector3List);
        }

        return;
    }

    public void Vector3DictionaryList(int index, float x, float y, float z, bool replace) {
        if (replace) {
            vector3List = new List<Vector3>();

            vector3List.Add(new Vector3(x, y, z));
            vector3Dictionary[index] = vector3List;
        }

        return;
    }
}

public class IcosahedronGenerator : ScriptableObject {
    public Vector3Dictionary icosahedronPolygonDict;
    public Vector3Dictionary icosahedronVerticeDict;
    public bool firstRun = true;

    public void GenBaseIcosahedron() {
        icosahedronPolygonDict = new Vector3Dictionary();
        icosahedronVerticeDict = new Vector3Dictionary();

        // An icosahedron has 12 vertices, and
        // since it's completely symmetrical the
        // formula for calculating them is kind of
        // symmetrical too:

        float t = (1.0f + Mathf.Sqrt(5.0f)) / 2.0f;

        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(-1, t, 0).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(1, t, 0).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(-1, -t, 0).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(1, -t, 0).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(0, -1, t).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(0, 1, t).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(0, -1, -t).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(0, 1, -t).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(t, 0, -1).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(t, 0, 1).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(-t, 0, -1).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(-t, 0, 1).normalized);

        // And here's the formula for the 20 sides,
        // referencing the 12 vertices we just created.
        // Each side will be placed in it's own dictionary key.
        // The first number is the key/index, and the next 3 numbers reference the vertice index
        icosahedronPolygonDict.Vector3DictionaryList(0, 0, 11, 5);
        icosahedronPolygonDict.Vector3DictionaryList(1, 0, 5, 1);
        icosahedronPolygonDict.Vector3DictionaryList(2, 0, 1, 7);
        icosahedronPolygonDict.Vector3DictionaryList(3, 0, 7, 10);
        icosahedronPolygonDict.Vector3DictionaryList(4, 0, 10, 11);
        icosahedronPolygonDict.Vector3DictionaryList(5, 1, 5, 9);
        icosahedronPolygonDict.Vector3DictionaryList(6, 5, 11, 4);
        icosahedronPolygonDict.Vector3DictionaryList(7, 11, 10, 2);
        icosahedronPolygonDict.Vector3DictionaryList(8, 10, 7, 6);
        icosahedronPolygonDict.Vector3DictionaryList(9, 7, 1, 8);
        icosahedronPolygonDict.Vector3DictionaryList(10, 3, 9, 4);
        icosahedronPolygonDict.Vector3DictionaryList(11, 3, 4, 2);
        icosahedronPolygonDict.Vector3DictionaryList(12, 3, 2, 6);
        icosahedronPolygonDict.Vector3DictionaryList(13, 3, 6, 8);
        icosahedronPolygonDict.Vector3DictionaryList(14, 3, 8, 9);
        icosahedronPolygonDict.Vector3DictionaryList(15, 4, 9, 5);
        icosahedronPolygonDict.Vector3DictionaryList(16, 2, 4, 11);
        icosahedronPolygonDict.Vector3DictionaryList(17, 6, 2, 10);
        icosahedronPolygonDict.Vector3DictionaryList(18, 8, 6, 7);
        icosahedronPolygonDict.Vector3DictionaryList(19, 9, 8, 1);

        return;
    }

    public void SeparateAllPolygons(){
        // Separates all polygons and vertex keys/indicies into their own key/index
        // For example if the numOfMainTriangles is set to 2,
        // This function will separate each polygon/triangle into it's own index
        // By looping through all polygons in each dictionary key/index

        List<Vector3> originalPolygons = new List<Vector3>();
        List<Vector3> originalVertices = new List<Vector3>();
        List<Vector3> newVertices = new List<Vector3>();
        Vector3Dictionary tempIcosahedronPolygonDict = new Vector3Dictionary();
        Vector3Dictionary tempIcosahedronVerticeDict = new Vector3Dictionary();

        // Cycles through the polygon list
        for (int i = 0; i < icosahedronPolygonDict.vector3Dictionary.Count; i++) {
            originalPolygons = new List<Vector3>();
            originalVertices = new List<Vector3>();

            // Loads all the polygons in a certain index/key
            originalPolygons = icosahedronPolygonDict.vector3Dictionary[i];

            // Since the original script was set up without a dictionary index
            // It was easier to loop all the original triangle vertices into index 0
            // Thus the first time this function runs, all initial vertices will be 
            // redistributed to the correct indicies/index/key

            if (firstRun) {
                originalVertices = icosahedronVerticeDict.vector3Dictionary[0];
            } else {
                // i - 1 to account for the first iteration of pre-set vertices
                originalVertices = icosahedronVerticeDict.vector3Dictionary[i];
            }

            // Loops through all the polygons in a specific Dictionary key/index
            for (int a = 0; a < originalPolygons.Count; a++){
                newVertices = new List<Vector3>();

                int x = (int)originalPolygons[a].x;
                int y = (int)originalPolygons[a].y;
                int z = (int)originalPolygons[a].z;

                // Adds three vertices/transforms for each polygon in the list
                newVertices.Add(originalVertices[x]);
                newVertices.Add(originalVertices[y]);
                newVertices.Add(originalVertices[z]);

                // Overwrites the Polygon indices from their original locations
                // index (20,11,5) for example would become (0,1,2) to correspond to the
                // three new Vector3's added to the list.
                // In the case of this function there will only be 3 Vector3's associated to each dictionary key
                tempIcosahedronPolygonDict.Vector3DictionaryList(0, 1, 2);

                // sets the index to the size of the temp dictionary list
                int tempIndex = tempIcosahedronPolygonDict.vector3Dictionary.Count;
                // adds the new vertices to the corresponding same key in the vertice index
                // which corresponds to the same key/index as the polygon dictionary
                tempIcosahedronVerticeDict.Vector3DictionaryList(tempIndex - 1, newVertices, true);
            }
        }
        firstRun = !firstRun;

        // Sets the temp dictionarys as the main dictionaries
        icosahedronVerticeDict = tempIcosahedronVerticeDict;
        icosahedronPolygonDict = tempIcosahedronPolygonDict;
    }

    public void Subdivide(int recursions) {
        // Divides each triangle into 4 triangles, and replaces the Dictionary entry

        var midPointCache = new Dictionary<int, int>();
        int polyDictIndex = 0;
        List<Vector3> originalPolygons = new List<Vector3>();
        List<Vector3> newPolygons;

        for (int x = 0; x < recursions; x++) {
            polyDictIndex = icosahedronPolygonDict.vector3Dictionary.Count;
            for (int i = 0; i < polyDictIndex; i++) {
                newPolygons = new List<Vector3>();
                midPointCache = new Dictionary<int, int>();
                originalPolygons = icosahedronPolygonDict.vector3Dictionary[i];

                for (int z = 0; z < originalPolygons.Count; z++) {
                    int a = (int)originalPolygons[z].x;
                    int b = (int)originalPolygons[z].y;
                    int c = (int)originalPolygons[z].z;

                    // Use GetMidPointIndex to either create a
                    // new vertex between two old vertices, or
                    // find the one that was already created.
                    int ab = GetMidPointIndex(i,midPointCache, a, b);
                    int bc = GetMidPointIndex(i,midPointCache, b, c);
                    int ca = GetMidPointIndex(i,midPointCache, c, a);

                    // Create the four new polygons using our original
                    // three vertices, and the three new midpoints.
                    newPolygons.Add(new Vector3(a, ab, ca));
                    newPolygons.Add(new Vector3(b, bc, ab));
                    newPolygons.Add(new Vector3(c, ca, bc));
                    newPolygons.Add(new Vector3(ab, bc, ca));
                }
                // Replace all our old polygons with the new set of
                // subdivided ones.
                icosahedronPolygonDict.vector3Dictionary[i] = newPolygons;
            }
        }
        return;
    }

    int GetMidPointIndex(int polyIndex, Dictionary<int, int> cache, int indexA, int indexB) {
        // We create a key out of the two original indices
        // by storing the smaller index in the upper two bytes
        // of an integer, and the larger index in the lower two
        // bytes. By sorting them according to whichever is smaller
        // we ensure that this function returns the same result
        // whether you call
        // GetMidPointIndex(cache, 5, 9)
        // or...
        // GetMidPointIndex(cache, 9, 5)

        int smallerIndex = Mathf.Min(indexA, indexB);
        int greaterIndex = Mathf.Max(indexA, indexB);
        int key = (smallerIndex << 16) + greaterIndex;

        // If a midpoint is already defined, just return it.
        int ret;
        if (cache.TryGetValue(key, out ret))
            return ret;

        // If we're here, it's because a midpoint for these two
        // vertices hasn't been created yet. Let's do that now!
        List<Vector3> tempVertList = icosahedronVerticeDict.vector3Dictionary[polyIndex];

        Vector3 p1 = tempVertList[indexA];
        Vector3 p2 = tempVertList[indexB];
        Vector3 middle = Vector3.Lerp(p1, p2, 0.5f).normalized;

        ret = tempVertList.Count;
        tempVertList.Add(middle);
        icosahedronVerticeDict.vector3Dictionary[polyIndex] = tempVertList;

        cache.Add(key, ret);
        return ret;
    }

    public void CalculateMesh(GameObject icosahedron, int numOfMainTriangles, int numOfSubdivisionsWithinEachTriangle, bool saveIcosahedron) {
        GameObject meshChunk;
        List<Vector3> meshPolyList;
        List<Vector3> meshVertList;
        List<int> triList;

        CreateFolders(numOfMainTriangles, numOfSubdivisionsWithinEachTriangle);
        CreateMaterial();

        // Loads a material from the Assets/Resources/ folder so that it can be saved with the prefab later
        Material material = Resources.Load("BlankSphere", typeof(Material)) as Material;

        int polyDictIndex = icosahedronPolygonDict.vector3Dictionary.Count;

        // Used to assign the child objects as well as to be saved as the .prefab
        // Sets the name
        icosahedron.gameObject.name = "Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle;

        for (int i = 0; i < polyDictIndex; i++) {
            meshPolyList = new List<Vector3>();
            meshVertList = new List<Vector3>();
            triList = new List<int>();
            // Assigns the polygon and vertex indices
            meshPolyList = icosahedronPolygonDict.vector3Dictionary[i];
            meshVertList = icosahedronVerticeDict.vector3Dictionary[i];

            // Sets the child gameobject parameters
            meshChunk = new GameObject("MeshChunk");
            meshChunk.transform.parent = icosahedron.gameObject.transform;
            meshChunk.transform.localPosition = new Vector3(0, 0, 0);
            meshChunk.AddComponent<MeshFilter>();
            meshChunk.AddComponent<MeshRenderer>();
            meshChunk.GetComponent<MeshRenderer>().material = material;
            meshChunk.AddComponent<MeshCollider>();
            Mesh mesh = meshChunk.GetComponent<MeshFilter>().mesh;

            // Adds the triangles to the list
            for (int z = 0; z < meshPolyList.Count; z++) {
                triList.Add((int)meshPolyList[z].x);
                triList.Add((int)meshPolyList[z].y);
                triList.Add((int)meshPolyList[z].z);
            }

            mesh.vertices = meshVertList.ToArray();
            mesh.triangles = triList.ToArray();
            mesh.uv = new Vector2[meshVertList.Count];

            /*
            //Not Needed because all normals have been calculated already
            Vector3[] _normals = new Vector3[meshVertList.Count];
            for (int d = 0; d < _normals.Length; d++){
                _normals[d] = meshVertList[d].normalized;
            }
            mesh.normals = _normals;
            */

            mesh.normals = meshVertList.ToArray();

            mesh.RecalculateBounds();

            // Saves each chunk mesh to a specified folder
            // The folder must exist
            if (saveIcosahedron) {
                string sphereAssetName = "icosahedronChunk" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle + "_" + i + ".asset";
                AssetDatabase.CreateAsset(mesh, "Assets/Icosahedrons/Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle + "/" + sphereAssetName);
                AssetDatabase.SaveAssets();
            }
        }

        // Removes the script for the prefab save
        // Saves the prefab to a specified folder
        // The folder must exist
        if (saveIcosahedron) {
            DestroyImmediate(icosahedron.GetComponent<UnityIcosahedronGenerator>());
            PrefabUtility.CreatePrefab("Assets/Icosahedrons/Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle + "/Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle + ".prefab", icosahedron);
        }

        return;
    }

    void CreateFolders(int numOfMainTriangles, int numOfSubdivisionsWithinEachTriangle){
        // Creates the folders if they don't exist
        if (!AssetDatabase.IsValidFolder("Assets/Icosahedrons")) {
            AssetDatabase.CreateFolder("Assets", "Icosahedrons");
        }
        if (!AssetDatabase.IsValidFolder("Assets/Icosahedrons/Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle)) {
            AssetDatabase.CreateFolder("Assets/Icosahedrons", "Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle);
        }
        if (!AssetDatabase.IsValidFolder("Assets/Resources")) {
            AssetDatabase.CreateFolder("Assets", "Resources");
        }

        return;
    }

    static void CreateMaterial() {
        if (Resources.Load("BlankSphere", typeof(Material)) == null) {
            // Create a simple material asset if one does not exist
            Material material = new Material(Shader.Find("Standard"));
            material.color = Color.blue;
            AssetDatabase.CreateAsset(material, "Assets/Resources/BlankSphere.mat");
        }

        return;
    }

    // Displays the Total Polygon/Triangle and Vertice Count
    public void DisplayVertAndPolygonCount(){
        List<Vector3> tempVertices;
        HashSet<Vector3> verticeHash = new HashSet<Vector3>();

        int polygonCount = 0;
        List<Vector3> tempPolygons;

        // Saves Vertices to a hashset to ensure no duplicate vertices are counted
        for (int a = 0; a < icosahedronVerticeDict.vector3Dictionary.Count; a++) {
            tempVertices = new List<Vector3>();
            tempVertices = icosahedronVerticeDict.vector3Dictionary[a];
            for (int b = 0; b < tempVertices.Count; b++) {
                verticeHash.Add(tempVertices[b]);
            }
        }

        for (int a = 0; a < icosahedronPolygonDict.vector3Dictionary.Count; a++) {
            tempPolygons = new List<Vector3>();
            tempPolygons = icosahedronPolygonDict.vector3Dictionary[a];
            for (int b = 0; b < tempPolygons.Count; b++) {
                polygonCount++;
            }
        }

        Debug.Log("Vertice Count: " + verticeHash.Count);
        Debug.Log("Polygon Count: " + polygonCount);

        return;
    }
}
Ryan Shackelford
la source