Algoritmo para criar esferas?


27

Alguém tem um algoritmo para criar uma esfera processualmente com laquantidade de linhas de latitude, loquantidade de linhas de longitude e raio de r? Eu preciso que ele trabalhe com o Unity, para que as posições dos vértices precisem ser definidas e, em seguida, os triângulos definidos por meio de índices ( mais informações ).


EDITAR

insira a descrição da imagem aqui

Consegui fazer o código funcionar em unidade. Mas acho que posso ter feito algo errado. Quando eu ligo o detailLevel, Tudo o que faz é adicionar mais vértices e polígonos sem movê-los. Eu esqueci alguma coisa?


EDIT 2

insira a descrição da imagem aqui

Tentei escalar a malha ao longo de suas normais. Isto é o que eu tenho. Eu acho que estou perdendo alguma coisa. Devo escalar apenas certos normais?


11
Por que você não vê como as implementações de código aberto existentes fazem isso? veja como o Three.js faz isso usando malhas, por exemplo.
28412 brice

3
Como uma pequena nota: a menos que você tenha que fazer latitude / longitude, você quase certamente não quer , porque os triângulos obtidos serão muito mais uniformes do que os obtidos com outros métodos. (Compare os triângulos próximos ao pólo norte com os próximos ao equador: você está usando o mesmo número de triângulos para contornar uma linha de latitude em ambos os casos, mas perto do polo essa linha de latitude tem circunferência muito pequena, enquanto no equador é a circunferência total do seu globo.) Técnicas como a da resposta de David Lively geralmente são muito melhores.
Steven Stadnicki

11
Você não está normalizando as posições dos vértices após subdividir. Não incluí essa parte no meu exemplo. A normalização os torna todos eqüidistantes do centro, o que cria a aproximação da curva que você está procurando.
3Dave

Pense em encher um balão no centro do icosaedro. Quando o balão empurra a nossa malha, ele combina com a forma do balão (esfera).
3Dave

4
"Normalizar" significa definir o comprimento de um vetor como 1. Você precisa fazer algo como vertices[i] = normalize(vertices[i]). Aliás, isso também fornece suas novas e corretas normais, então você deve fazer normals[i] = vertices[i]depois.
Sam Hocevar

Respostas:


31

Para obter algo assim:

insira a descrição da imagem aqui

Crie um icosaedro (sólido regular de 20 lados) e subdividir as faces para obter uma esfera (consulte o código abaixo).

A ideia é basicamente:

  • Crie um n-hedron regular (um sólido em que cada rosto tenha o mesmo tamanho). Eu uso um icosaedro porque é o sólido com o maior número de faces em que cada face é do mesmo tamanho. (Existe uma prova disso em algum lugar. Sinta-se à vontade para o Google, se você estiver realmente curioso.) Isso fornecerá uma esfera em que quase todos os rostos são do mesmo tamanho, facilitando um pouco a textura.

insira a descrição da imagem aqui

  • Subdividir cada face em quatro faces do mesmo tamanho. Cada vez que você fizer isso, ele quadruplicará o número de faces no modelo.

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

i0, i1E i2são os vértices do triângulo original. (Na verdade, índices no buffer de vértice, mas esse é outro tópico). m01é o ponto médio da aresta (i0,i1), m12 é o ponto médio da aresta (i1,12)e m02é, obviamente, o ponto médio da aresta (i0,i2).

Sempre que você subdividir uma face, não crie vértices duplicados. Cada ponto médio será compartilhado por uma outra face de origem (já que as arestas são compartilhadas entre as faces). O código abaixo explica isso mantendo um dicionário de pontos médios nomeados que foram criados e retornando o índice de um ponto médio criado anteriormente quando estiver disponível, em vez de criar um novo.

  • Repita até atingir o número desejado de faces para o seu cubo.

  • Quando terminar, normalize todos os vértices para suavizar a superfície. Se você não fizer isso, obterá um icosaedro de maior resolução em vez de uma esfera.

  • Voila! Você está feito. Converta o vetor resultante e os buffers de índice em VertexBuffere IndexBuffer, e desenhe com Device.DrawIndexedPrimitives().

Aqui está o que você usaria na classe "Sphere" para criar o modelo (tipos de dados XNA e C #, mas deve ficar bem claro):

        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]);

E a 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) 
            }
        );


    }



}

Ótima resposta. Obrigado. Não sei dizer, mas esse código de unidade é? Ah, e o lat / long não importa, desde que eu possa definir a resolução.
precisa

Não é o Unity (XNA), mas fornecerá as coordenadas dos vértices e a lista de índices. Substitua Vector3 por qualquer que seja o equivalente do Unity. Você define a resolução ajustando o número de iterações Subdivide. Cada loop multiplica o número de faces por 4. 2 ou 3 iterações fornecerão uma esfera agradável.
3Dave

Ah entendo. É quase idêntico ao Unity C #. Apenas algumas perguntas ... Por que, quando os índices são definidos, você os coloca dentro de uma intmatriz? E o que .Select(i => i + vertices.Count)faz?
precisa

O .Select(i => i + vertices.Count)não funciona para mim. É um recurso exclusivo do XNA?
Daniel Pendergast

11
Certifique-se de que você está incluindo 'usando System.Linq' como defines.Select, etc.
3Dave

5

Vamos considerar a definição paramétrica de uma esfera:

definição paramétrica de uma esfera

onde theta e phi são dois ângulos incrementais, aos quais iremos nos referir como var t e var ue Rx, Ry e Rz são os raios independentes (raios) nas três direções cartesianas, que, no caso de uma esfera, serão definidos como um único raio var rad.

Vamos agora considerar o fato de que o ... símbolo indica uma iteração que sugere o uso de um loop. O conceito de stackse rowsé "quantas vezes você iterará". Como cada iteração adiciona o valor de t ou u, quanto mais iterações, menor o valor, portanto, mais precisa é a curvatura da esfera.

A pré-condição da função 'desenho da esfera' é ter os seguintes parâmetros fornecidos: int latitudes, int longitudes, float radius . As condições de postagem (saída) são para retornar ou aplicar os vértices calculados. Dependendo de como você pretende usar isso, a função pode retornar uma matriz de vector3(vetores tridimensionais) ou, se você estiver usando algum tipo de OpenGL simples, anterior à versão 2.0, poderá aplicar os vértices ao contexto diretamente.

NB A aplicação de um vértice no openGL está chamando a seguinte função glVertex3f(x, y, z) . No caso em que armazenaríamos os vértices, adicionaríamos um novo vector3(x, y, z)para facilitar o armazenamento.

Além disso, a maneira como você solicitou o funcionamento do sistema de latitude e longitude precisava de um ajuste na definição da esfera (basicamente alternando z e y), mas isso apenas mostra que a definição é muito maleável e que você é livre para alternar entre as esferas. parâmetros x, ye z para alterar a direção em que a esfera é desenhada (onde estão as latitudes e longitudes).

Agora vamos ver como vamos fazer as latitudes e longitudes. As latitudes são representadas pela variável u, elas iteram de 0 a 2π radianos (360 graus). Portanto, podemos codificar sua iteração da seguinte maneira:

float latitude_increment = 360.0f / latitudes;

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

Agora as longitudes são representadas pela variável te iteram de 0 a π (180 graus). portanto, o código a seguir é semelhante ao anterior:

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 ...
    }
}

(Note-se que loops são Inclusive de lá condição terminal, porque o intervalo para a integração paramétrico é de 0 a 2p Inclusive . Você terá uma esfera parcial se suas condições são não-inclusive.)

Agora, seguindo a definição simples da esfera, podemos derivar a definição da variável da seguinte forma (suponha 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)));

Mais um aviso importante! Na maioria dos casos, você usará algum tipo de OpenGL, e mesmo se não estiver, talvez ainda precise fazer isso. Um objeto tridimensional precisa de vários vértices para serem definidos. Isso geralmente é conseguido fornecendo o próximo vértice que é computável.

como vários vértices são usados ​​para definir uma forma (primitiva)

Exatamente como na figura acima estão as diferentes coordenadas x+∂e y+∂, podemos facilmente gerar três outros vértices para qualquer uso desejado. Os outros vértices são (assuma 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)));

Finalmente, aqui está uma função completa que retornaria todos os vértices de uma esfera, e a segunda mostra uma implementação de código OpenGL funcional (essa é a sintaxe no estilo C e não o JavaScript, isso deve funcionar com todas as linguagens no estilo C, incluindo C # ao usar o 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;

}

Código 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 Você pode ter notado esta afirmação rad = radius;. Isso permite que o raio seja modificado no loop, com base na localização ou no ângulo. Isso significa que você pode aplicar ruído à esfera para torná-la áspera, tornando-a mais natural se o efeito desejado for semelhante ao de um planeta. Por exemplofloat rad = radius * noise[x][y][z];

Claude-Henry.


A linha `float z = (float) (rad * Math.sin (Math.toRadians (t)) * Math.cos (Math.toRadians (u)));` está incorreta. Você já calculou um X, Y com uma hipotenusa de rad. Agora você está criando essa perna de um triângulo e implicando que a hipotenusa do referido triângulo também é rad. Isso efetivamente fornece um raio de rad * sqrt(2).
3Dave

@DavidLively obrigado por apontar isso, eu escrevi isso há um tempo, então não estou surpreso se for ruim ou mesmo totalmente errado.
claudehenry

é sempre divertido quando encontro um erro em uma das minhas postagens de anos atrás. Acontece. :)
3Dave

4

Eu criei algo assim um tempo atrás para fazer uma esfera de cubos, por diversão e ciência. Não é muito difícil. Basicamente, você utiliza uma função que cria um círculo de vértices e, em seguida, percorre os incrementos de altura que deseja, criando círculos a cada altura no raio necessário para formar uma esfera. Aqui eu modifiquei o código para não ser para cubos:

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));
}

Agora, esse código criaria apenas pontos para a latitude. No entanto, você quase pode usar o mesmo código para criar as linhas de longitude. Exceto que você precisará alternar entre cada iteração e fazer um círculo completo a cadadegreeStep .

Lamentamos, mas esta não é uma resposta completa ou específica do Unity, mas espero que o ajude a começar.


Isso é muito bom se você precisar de uma esfera latina / longa, mas você pode simplificá-la um pouco trabalhando em coordenadas esféricas até a última etapa.
3Dave

11
Obrigado @David. Eu concordo que, se eu escrever uma versão usando cordas esféricas, eu a publicarei aqui.
MichaelHouse

3

Você não poderia simplesmente começar com uma forma simples, poderia ser uma caixa com uma distância r do centro aos cantos. Para criar uma esfera mais detalhada, subdivide todos os polígonos e mova os vértices para a distância r do centro, fazendo com que o vetor passe pela posição atual.

Continue repetindo até esférico o suficiente para o seu gosto.


Isto é essencialmente o mesmo que a abordagem icosaédrica, apenas com uma forma inicial diferente. Uma vantagem de começar com um cubo que eu acho que não foi mencionado: é substancialmente mais fácil criar mapas UV decentes, porque você pode usar um mapa de cubos e saber que suas costuras de textura se alinharão perfeitamente com as bordas da malha da esfera.
Steven Stadnicki

@StevenStadnicki, o único problema que tenho com cubos é que os rostos tendem a ter tamanhos muito diferentes depois de algumas subdivisões.
3Dave

@DavidLively Isso depende muito de como você subdividir - se você cortar as faces quadradas do seu cubo em uma grade uniforme e depois projetar para fora / normalizar, isso é verdade, mas se você classificar suas faces de maneira não uniforme, poderá criar o a projeção seja espaçada uniformemente ao longo dos arcos das bordas; isso acaba funcionando muito bem.
Steven Stadnicki

@StevenStadnicki nifty!
3Dave

@EricJohansson btw, como professor, sinto-me compelido a mencionar que esse é um insight bastante significativo para alguém que aparentemente nunca viu o método de subdivisão antes. Você renovou minha fé na humanidade pelas próximas 12 horas.
3Dave

2

Você realmente precisa da geometria 3D ou apenas da forma?

Você pode fazer uma esfera 'falsa' usando um único quad. Basta colocar um círculo e sombrear corretamente. Isso tem a vantagem de ter exatamente a resolução necessária, independentemente da distância da câmera ou da resolução.

Há um tutorial aqui .


11
Bom hack, mas falha se você precisar texturizá-lo.
3Dave

@DavidLively Deverá ser possível calcular as coordenadas da textura por pixel com base na rotação, a menos que você precise texturizar polígonos individualmente.
David C. Bishop

@DavidCBishop Você precisaria considerar as "lentes" da superfície - as cordas texel são espremidas perto da borda do círculo devido à perspectiva - nesse ponto, você está fingindo a rotação. Além disso, isso envolve mover muito mais trabalho para o pixel shader que poderia ser executado no vertex shader (e todos sabemos que os VSs são muito mais baratos!).
precisa saber é o seguinte

0

aqui está um código para qualquer número de vértices igualmente espaçados, como uma casca de laranja, que enrola uma linha de pontos ao redor de uma esfera em espiral. depois, como você une os vértices é com você. você pode usar pontos vizinhos no loop como 2 de cada triângulo e, em seguida, descobrir que o terceiro seria uma torção proporcional ao redor da esfera, mais acima ou mais abaixo ... você também pode fazer triângulos por loop e o vizinho mais próximo, alguém conhece uma maneira melhor?

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...

}

};


-1

Embora David esteja absolutamente correto em sua resposta, quero oferecer uma perspectiva diferente.

Para minha tarefa de geração de conteúdo processual, observei (entre outras coisas) o icosaedro versus as esferas subdivididas mais tradicionais. Veja estas esferas geradas processualmente:

Esferas impressionantes

Ambos parecem esferas perfeitamente válidas, certo? Bem, vejamos seus wireframes:

Uau, isso é denso

Uau, o que aconteceu lá? A versão em estrutura de arame da segunda esfera é tão densa que parece texturizada! Vou contar um segredo: a segunda versão é um icosaedro. É uma esfera quase perfeita, mas tem um preço alto.

A esfera 1 usa 31 subdivisões no eixo xe 31 subdivisões no eixo z, para um total de 3.844 faces.

A esfera 2 usa 5 subdivisões recursivas, para um total de 109.220 faces.

Mas tudo bem, isso não é realmente justo. Vamos reduzir a qualidade consideravelmente:

Grumoso

A esfera 1 usa 5 subdivisões no eixo xe 5 subdivisões no eixo z, para um total de 100 faces.

A esfera 2 usa 0 subdivisões recursivas, para um total de 100 faces.

Eles usam a mesma quantidade de rostos, mas na minha opinião, a esfera da esquerda parece melhor. Parece menos irregular e muito mais redondo. Vamos dar uma olhada em quantas faces geramos com os dois métodos.

Icosaedro:

  • Nível 0 - 100 faces
  • Nível 1 - 420 faces
  • Nível 2 - 1.700 faces
  • Nível 3 - 6.820 faces
  • Nível 4 - 27.300 faces
  • Nível 5 - 109.220 faces

Esfera subdividida:

  • 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

Como você pode ver, o icosaedro aumenta em rostos a uma taxa exponencial, para uma terceira potência! Isso porque para cada triângulo, devemos subdividi-los em três novos triângulos.

A verdade é: você não precisa da precisão que um icosaedro lhe dará. Porque ambos escondem um problema muito mais difícil: texturizar um plano 2D em uma esfera 3D. Aqui está a aparência do topo:

Top é uma merda

No canto superior esquerdo, você pode ver a textura sendo usada. Coincidentemente, também está sendo gerado procedimentalmente. (Ei, foi um curso sobre geração processual, certo?)

Parece terrível, certo? Bem, isso é tão bom quanto será possível. Eu consegui as melhores notas para o meu mapeamento de texturas, porque a maioria das pessoas nem entende direito.

Então, por favor, considere usar cosseno e seno para gerar uma esfera. Ele gera muito menos faces para a mesma quantidade de detalhes.


6
Receio que só posso diminuir isso. A icosfera escala exponencialmente? Isso é apenas porque você decidiu que o seu deve escalar exponencialmente. Uma esfera UV gera menos faces do que uma icosfera para a mesma quantidade de detalhes? Isso está errado, absolutamente errado, totalmente ao contrário.
Sam Hocevar

4
A subdivisão não precisa ser recursiva. Você pode dividir a aresta de um triângulo em quantas partes iguais desejar. O uso de Npeças fornecerá N*Nnovos triângulos, que são quadráticos, exatamente como o que você faz com a esfera UV.
Sam Hocevar

6
Devo também acrescentar que a esfera que você diz parece "menos irregular e muito mais redonda" é vista do melhor ângulo, tornando essa afirmação desonesta também. Basta fazer a mesma captura de tela com as esferas vistas de cima para ver o que quero dizer.
Sam Hocevar

4
Além disso, seus números de icosaedro não parecem corretos. O nível 0 é de 20 faces (por definição) e, em seguida, 80, 320, 1280, etc. Você pode subdividir em qualquer número e padrão que desejar. A suavidade do modelo será finalmente determinada pelo número e distribuição de faces no resultado final (independentemente do método usado para gerá-las), e queremos manter o tamanho de cada face o mais uniforme possível (sem polaridade). apertar) para manter um perfil consistente, independentemente do ângulo de visão. Acrescente a isso o fato de que o código de subdivisão é muito mais simples (IMHO) ...
3Dave

2
Algum trabalho foi colocado nessa resposta, o que me faz sentir um pouco mal por votá-la. Mas é completamente e totalmente errado, então eu preciso. Uma icosfera de aparência perfeitamente redonda que preenche a tela inteira em FullHD precisa de 5 subdivisões, com um icosaedro básico sem subdivisões. Um icosaedro sem subdivisões não possui 100 faces, possui 20. Icosa = 20. É o nome! Cada subdivisão multiplica o número de faces por 4, então 1-> 80, 2-> 320, 3-> 1280, 4-> 5120, 5-> 20.480. Com uma geosfera, precisamos de pelo menos 40.000 faces para obter uma esfera igualmente redonda.
Peter

-1

O script abaixo criará um icosaedro com n polígonos ... base 12. Ele também subdividirá os polígonos em malhas separadas e calculará o total de verts-duplicados e polígonos.

Não encontrei nada parecido, então criei isso. Apenas anexe o script a um GameObject e defina as subdivisões no Editor. Trabalhando na modificação de ruído a seguir.


/* 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;
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.