Algorytm tworzenia sfer?

27

Czy ktoś ma algorytm do tworzenia kuli proceduralnie z lailością linii szerokości, loilością linii długości i promieniem r? Potrzebuję go do pracy z Unity, więc pozycje wierzchołków muszą zostać zdefiniowane, a następnie trójkąty zdefiniowane za pomocą indeksów ( więcej informacji ).


EDYTOWAĆ

wprowadź opis zdjęcia tutaj

Udało mi się sprawić, że kod działa w jedności. Ale myślę, że mogłem zrobić coś złego. Kiedy podkręcam detailLevel, jedyne co robi to dodawanie kolejnych wierzchołków i wielokątów bez ich przesuwania. Zapomniałem czegoś?


EDYCJA 2

wprowadź opis zdjęcia tutaj

Próbowałem przeskalować siatkę wzdłuż jej normalnych. Oto co mam. Chyba coś mi umknęło. Czy mam skalować tylko niektóre normalne?

Daniel Pendergast
źródło
1
Dlaczego nie spojrzysz na to, jak robią to istniejące implementacje open source? zobacz na przykład, jak Three.js robi to za pomocą siatek.
Brice
3
Mała uwaga: chyba że musisz wykonać szerokość / długość geograficzną, prawie na pewno nie chcesz , ponieważ otrzymane trójkąty będą znacznie dalej od jednolitych niż te, które otrzymujesz innymi metodami. (Porównaj trójkąty w pobliżu bieguna północnego z trójkątami w pobliżu równika: używasz tej samej liczby trójkątów, aby ominąć jedną linię szerokości w obu przypadkach, ale w pobliżu bieguna ta linia szerokości ma bardzo mały obwód, podczas gdy na równiku to cały obwód twojego globu.) Techniki takie jak ta w odpowiedzi Davida Lively są na ogół znacznie lepsze.
Steven Stadnicki
1
Nie normalizujesz pozycji wierzchołków po podzieleniu. Nie uwzględniłem tej części w moim przykładzie. Normalizacja powoduje, że wszystkie są w równej odległości od środka, co tworzy przybliżenie krzywej, którego szukasz.
3Dave
Pomyśl o nadmuchaniu balonu na środku dwudziestościanu. Gdy balon przesuwa naszą siatkę, dopasowuje się do kształtu balonu (kuli).
3Dave
4
„Normalizacja” oznacza ustawienie długości wektora na 1. Musisz zrobić coś takiego vertices[i] = normalize(vertices[i]). Nawiasem mówiąc, daje to również nowe, prawidłowe wartości normalne, więc powinieneś to zrobić normals[i] = vertices[i]później.
sam hocevar

Odpowiedzi:

31

Aby uzyskać coś takiego:

wprowadź opis zdjęcia tutaj

Utwórz dwudziestościan (20-stronna regularna bryła) i podziel ściany, aby uzyskać kulę (patrz kod poniżej).

Pomysł jest w zasadzie:

  • Utwórz zwykły n-hedron (bryłę, w której każda twarz ma ten sam rozmiar). Używam dwudziestościanu, ponieważ jest to bryła o największej liczbie twarzy, przy czym każda twarz ma ten sam rozmiar. (Gdzieś tam jest na to dowód. Jeśli jesteś naprawdę ciekawy, nie krępuj się w Google.) To da ci sferę, w której prawie każda twarz jest tego samego rozmiaru, co ułatwi teksturowanie.

wprowadź opis zdjęcia tutaj

  • Podziel każdą twarz na cztery równe twarze. Za każdym razem, gdy to zrobisz, czterokrotnie zwiększy liczbę twarzy w modelu.

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

i0, i1i i2są wierzchołkami oryginalnego trójkąta. (Właściwie indeksy w buforze wierzchołków, ale to już inny temat). m01jest punktem środkowym krawędzi (i0,i1), m12 jest punktem środkowym krawędzi (i1,12)i m02jest oczywiście punktem środkowym krawędzi (i0,i2).

Za każdym razem, gdy dzielisz twarz, upewnij się, że nie tworzysz duplikatów wierzchołków. Każdy punkt środkowy będzie współdzielony przez jedną inną ścianę źródłową (ponieważ krawędzie są wspólne między ścianami). Poniższy kod bierze to pod uwagę, utrzymując słownik nazwanych punktów środkowych, które zostały utworzone, i zwraca indeks wcześniej utworzonego punktu środkowego, gdy jest on dostępny, zamiast tworzenia nowego.

  • Powtarzaj, aż osiągniesz żądaną liczbę ścian dla swojej kostki.

  • Po zakończeniu normalizuj wszystkie wierzchołki, aby wygładzić powierzchnię. Jeśli tego nie zrobisz, zamiast kuli dostaniesz dwudziestościan o wyższej rozdzielczości.

  • Voila! Jesteś skończony. Konwertuj powstałe bufory wektorowe i indeksowe na VertexBuffera IndexBufferi rysuj za pomocą Device.DrawIndexedPrimitives().

Oto, czego użyłbyś w swojej klasie „Sphere” do stworzenia modelu (typy danych XNA i C #, ale powinno być całkiem jasne):

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

I GeometryProviderklasa

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
źródło
Świetna odpowiedź. Dzięki. Nie mogę powiedzieć, ale czy to kod jedności? Aha, a długość / długość nie ma znaczenia, o ile mogę ustawić rozdzielczość.
Daniel Pendergast
To nie jest jedność (XNA), ale da ci współrzędne wierzchołków i listę indeksów. Zamień Vector3 na dowolny odpowiednik Unity. Rozdzielczość ustawia się, dostosowując liczbę iteracji podzielonych. Każda pętla mnoży liczbę twarzy przez 4. 2 lub 3 iteracje dają miłą kulę.
3Dave
O, rozumiem. Jest prawie identyczny z Unity C #. Tylko kilka pytań ... Dlaczego po zdefiniowaniu wskaźników umieszczasz je w inttablicy? A co robi .Select(i => i + vertices.Count)?
Daniel Pendergast
To w .Select(i => i + vertices.Count)ogóle nie działa dla mnie. Czy jest to funkcja tylko XNA?
Daniel Pendergast
1
Upewnij się, że dołączasz „korzystanie z System.Linq”, jak to definiuje. Wybierz itp.
3Dave
5

Rozważmy parametryczną definicję kuli:

parametryczna definicja kuli

gdzie theta i phi to dwa narastające kąty, które będziemy określać jako var ti, var ua Rx, Ry i Rz są niezależnymi promieniami (promieniami) we wszystkich trzech kierunkach kartezjańskich, które w przypadku kuli będą zdefiniowane jako jeden pojedynczy promień var rad.

Rozważmy teraz fakt, że ...symbol wskazuje iterację wskazującą na użycie pętli. Pojęcie stacksi rowsbrzmi „ile razy będziesz iterować”. Ponieważ każda iteracja dodaje wartość t lub u, im więcej iteracji, tym mniejsza wartość, a zatem dokładniejsza jest krzywizna kuli.

„Sfera rysunek” Warunkiem funkcja jest mieć następujące parametry podane: int latitudes, int longitudes, float radius. Warunki końcowe (wynik) mają zwrócić lub zastosować obliczone wierzchołki. W zależności od tego, jak zamierzasz tego użyć, funkcja może zwrócić tablicę vector3(wektory trójwymiarowe) lub, jeśli używasz jakiegoś prostego OpenGL, przed wersją 2.0, możesz chcieć zastosować wierzchołki bezpośrednio do kontekstu.

NB: Zastosowanie wierzchołka w openGL wywołuje następującą funkcję glVertex3f(x, y, z). W przypadku przechowywania wierzchołków dodalibyśmy nowy vector3(x, y, z)dla łatwiejszego przechowywania.

Również sposób, w jaki poprosiłeś o działanie systemu szerokości i długości geograficznej, wymagał dostosowania definicji kuli (w zasadzie przełączania z i y), ale to po prostu pokazuje, że definicja jest bardzo plastyczna i że możesz swobodnie przełączać się między parametry x, y i z, aby zmienić kierunek, w którym rysowana jest kula (gdzie znajdują się szerokości i długości geograficzne).

Teraz spójrzmy na to, jak będziemy robić szerokości i długości geograficzne. Szerokości są reprezentowane przez zmienną u, iterują od 0 do 2π radianów (360 stopni). Możemy zatem zakodować jego iterację w następujący sposób:

float latitude_increment = 360.0f / latitudes;

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

Teraz długości geograficzne są reprezentowane przez zmienną ti iterują się od 0 do π (180 stopni). dlatego poniższy kod wygląda podobnie do poprzedniego:

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

(Należy pamiętać, że pętle są Inclusive od tam stan terminala, ponieważ przedział dla parametrycznego integracji wynosi od 0 do 2 n Inclusive . Dostaniesz częściową sferę jeśli warunki są non-inclusive).

Teraz, zgodnie z prostą definicją sfery, możemy uzyskać definicję zmiennej w następujący sposób (załóżmy 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)));

Jeszcze jedno ważne ostrzeżenie! W większości przypadków będziesz używać jakiejś formy OpenGL, a nawet jeśli nie, nadal będziesz musiał to zrobić. Obiekt trójwymiarowy wymaga zdefiniowania kilku wierzchołków. Zazwyczaj osiąga się to przez zapewnienie następnego obliczalnego wierzchołka.

jak wiele wierzchołków jest używanych do zdefiniowania (prymitywnego) kształtu

Tak jak na powyższym rysunku są różne współrzędne x+∂i y+∂możemy z łatwością wygenerować trzy inne wierzchołki dla dowolnego pożądanego zastosowania. Pozostałe wierzchołki to (załóżmy 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)));

Na koniec, oto działająca pełna funkcja, która zwróciłaby wszystkie wierzchołki kuli, a druga pokazuje działającą implementację kodu OpenGL (jest to składnia w stylu C, a nie JavaScript, to powinno działać ze wszystkimi językami w stylu C, w tym C # podczas korzystania z 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;

}

Kod 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 Być może zauważyłeś to oświadczenie rad = radius;. Umożliwia to modyfikację promienia w pętli na podstawie położenia lub kąta. Oznacza to, że możesz zastosować hałas do kuli, aby ją zszorstkować, dzięki czemu będzie wyglądać bardziej naturalnie, jeśli pożądany efekt będzie podobny do planety. Na przykładfloat rad = radius * noise[x][y][z];

Claude-Henry.

claudehenry
źródło
Wiersz `float z = (float) (rad * Math.sin (Math.toRadians (t)) * Math.cos (Math.toRadians (u)));` jest niepoprawny. Już obliczyłeś X, Y z przeciwprostokątną rad. Teraz tworzysz tę jedną nogę z trójkąta i sugerujesz, że jest również przeciwprostokątna tego trójkąta rad. To skutecznie daje promień rad * sqrt(2).
3Dave
@DavidLively dziękuję za zwrócenie na to uwagi, napisałem to jakiś czas temu, więc nie jestem zaskoczony, czy to źle, czy wręcz wprost źle.
claudehenry
zawsze jest fajnie, gdy znajdę błąd w jednym z MOICH postów sprzed lat. Zdarza się. :)
3Dave
4

Jakiś czas temu stworzyłem coś takiego, aby stworzyć kulę z kostek dla zabawy i nauki. To nie jest zbyt trudne. Zasadniczo bierzesz funkcję, która tworzy okrąg wierzchołków, a następnie przechodzisz przez przyrosty wysokości, które chcesz tworzyć koła na każdej wysokości w promieniu wymaganym do utworzenia kuli. Tutaj zmodyfikowałem kod, aby nie był dla kostek:

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

Teraz ten kod po prostu stworzy punkty dla szerokości geograficznej. Możesz jednak prawie użyć tego samego kodu, aby utworzyć linie długości geograficznej. Tyle, że będziesz musiał obracać się między kolejnymi iteracjami i tworzyć wokół siebie pełne koło degreeStep.

Niestety, nie jest to pełna odpowiedź ani odpowiedź na temat Jedności, ale mam nadzieję, że to pomoże.

MichaelHouse
źródło
Jest to całkiem dobre, jeśli potrzebujesz kuli lat / long, ale możesz to trochę uprościć, pracując w sferycznych współrzędnych do ostatniego kroku.
3Dave
1
Dzięki @David. Zgadzam się, jeśli zacznę pisać wersję przy użyciu sferycznych współrzędnych, opublikuję ją tutaj.
MichaelHouse
3

Czy nie możesz po prostu zacząć od prostego kształtu, może to być pudełko o odległości od środka do rogu. Aby stworzyć bardziej szczegółową kulę, podziel wszystkie wielokąty, a następnie przesuń wierzchołki na odległość r od środka, tak aby wektor przechodził przez ich bieżącą pozycję.

Powtarzaj, aż będzie wystarczająco sferyczny dla swoich upodobań.

Eric Johansson
źródło
Jest to zasadniczo to samo co podejście dwudziestościenne, tylko z innym kształtem początkowym. Jedną z zalet rozpoczynania od sześcianu, o którym nie sądzę, zostało wspomniane: znacznie łatwiej jest budować przyzwoite mapy UV, ponieważ można użyć mapy sześciennej i wiedzieć, że szwy tekstury będą idealnie wyrównywać się z krawędziami w siatce sfery.
Steven Stadnicki
@ StevenStadnicki Jedyny problem, jaki mam z sześcianami, to to, że po kilku podrozdziałach twarze mają zwykle bardzo różne rozmiary.
3Dave
@DavidLively To zależy w dużej mierze od tego, jak podzielisz - jeśli pokroisz kwadratowe ściany sześcianu w równą siatkę, a następnie rzutujesz na zewnątrz / normalizujesz, to prawda, ale jeśli ustawisz twarze nierównomiernie, możesz faktycznie zrobić rzut jest równomiernie rozmieszczony wzdłuż łuków krawędzi; okazuje się, że działa całkiem dobrze.
Steven Stadnicki
@StevenStadnicki fajne!
3Dave
@EricJohansson btw, jako nauczyciel czuję się zmuszony wspomnieć, że jest to dość znaczący wgląd dla kogoś, kto najwyraźniej nie widział wcześniej metody podziału. Odnowiłeś moją wiarę w ludzkość na następne 12 godzin.
3Dave
2

Czy potrzebujesz geometrii 3D, czy tylko kształtu?

Możesz stworzyć „fałszywą” kulę za pomocą pojedynczego kwadratu. Po prostu nałóż na nią kółko i cieniuj je poprawnie. Ma to tę zaletę, że będzie miała dokładnie wymaganą rozdzielczość niezależnie od odległości od kamery lub rozdzielczości.

Tutaj jest tutorial .

David C. Bishop
źródło
1
Niezły hack, ale kończy się niepowodzeniem, jeśli trzeba go teksturować.
3Dave
@DavidLively Powinno być możliwe obliczenie współrzędnych tekstury na piksel na podstawie jego obrotu, chyba że trzeba osobno teksturować wielokąty.
David C. Bishop
@DavidCBishop Musisz wziąć pod uwagę „soczewkowanie” powierzchni - teksle są ściskane blisko granicy okręgu ze względu na perspektywę - w tym momencie udajesz obrót. Obejmuje to także przeniesienie o wiele więcej pracy do modułu cieniującego piksele, który można by wykonać w module cieniującym wierzchołki (i wszyscy wiemy, że VS są znacznie tańsze!).
3Dave
0

oto kod dla dowolnej liczby równomiernie rozmieszczonych wierzchołków kuli, jej jak pomarańczowa skórka nawija linię kropek wokół kuli w spiralę. potem, jak dołączyć do wierzchołków, zależy od ciebie. możesz użyć sąsiednich kropek w pętli jako 2 każdego trójkąta, a następnie znajdź trzeci byłby proporcjonalny jeden obrót wokół kuli wyżej lub niżej ... możesz również wykonywać trójkąty po pętli i na najbliższym sąsiadu, robi to ktoś znasz lepszy sposób?

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

}

};

zrozumiały
źródło
-1

Chociaż David ma absolutną rację w swojej odpowiedzi, chcę zaoferować inną perspektywę.

W ramach mojego zadania generowania treści proceduralnych spojrzałem (między innymi) na dwudziestościan w porównaniu z bardziej tradycyjnymi podzielonymi sferami. Spójrz na te generowane proceduralnie sfery:

Niesamowite kule

Oba wyglądają jak idealnie poprawne kule, prawda? Spójrzmy na ich szkielety:

Wow, to gęste

Wow, co się tam stało? Druga kula w wersji szkieletowej jest tak gęsta , że wygląda na teksturowaną! Powiem ci o tajemnicy: druga wersja to dwudziestościan. To prawie idealna kula, ale ma wysoką cenę.

Sfera 1 wykorzystuje 31 podpodziałów na osi x i 31 podpodziałów na osi z, co daje łącznie 3 844 ścianki.

Sfera 2 wykorzystuje 5 rekurencyjnych podziałów, co daje w sumie 109 220 twarzy.

Ale dobrze, to nie do końca uczciwe. Zmniejszmy znacznie jakość:

Grudkowaty

Sfera 1 wykorzystuje 5 podziałów na osi X i 5 podziałów na osi Z, co daje łącznie 100 ścian.

Sfera 2 wykorzystuje 0 rekurencyjnych podziałów, co daje łącznie 100 twarzy.

Używają tej samej liczby twarzy, ale moim zdaniem kula po lewej wygląda lepiej. Wygląda mniej nierówno i jest bardziej okrągły. Zobaczmy, ile twarzy generujemy za pomocą obu metod.

Dwudziestościan:

  • Poziom 0 - 100 twarzy
  • Poziom 1 - 420 twarzy
  • Poziom 2 - 1700 twarzy
  • Poziom 3 - 6820 twarzy
  • Poziom 4 - 27 300 twarzy
  • Poziomy 5–109,220 twarzy

Podzielona kula:

  • YZ: 5-100 twarzy
  • YZ: 10–400 twarzy
  • YZ: 15–900 twarzy
  • YZ: 20-1 600 twarzy
  • YZ: 25 - 2500 twarzy
  • YZ: 30 - 3600 twarzy

Jak widać, dwudziestościan rośnie w tempie wykładniczym do trzeciej potęgi! Jest tak, ponieważ dla każdego trójkąta musimy podzielić je na trzy nowe trójkąty.

Prawda jest taka: nie potrzebujesz precyzji, którą da ci dwudziestościan. Ponieważ oba kryją znacznie trudniejszy problem: teksturowanie płaszczyzny 2D na kuli 3D. Oto jak wygląda góra:

Top jest do bani

W lewym górnym rogu widać używaną teksturę. Przypadkowo jest również generowany proceduralnie. (Hej, to był kurs generowania procedur, prawda?)

Wygląda okropnie, prawda? Cóż, to jest tak dobre, jak się da. Mam najlepsze oceny za mapowanie tekstur, ponieważ większość ludzi nawet tego nie rozumie.

Rozważ więc użycie cosinusa i sinusa do wygenerowania kuli. Generuje o wiele mniej twarzy dla tej samej ilości szczegółów.

knight666
źródło
6
Obawiam się, że mogę tylko głosować za tym. Ikosfera skaluje się wykładniczo? To tylko dlatego, że zdecydowałeś, że twój powinien skalować się wykładniczo. Kula UV generuje mniej twarzy niż icosfera dla tej samej ilości szczegółów? To źle, całkowicie źle, całkowicie wstecz.
sam hocevar
4
Podział nie musi być rekurencyjny. Możesz podzielić krawędź trójkąta na tyle równych części, ile chcesz. Używanie Nczęści da ci N*Nnowe trójkąty, które są kwadratowe, dokładnie tak, jak robisz z kulą UV.
sam hocevar
6
Muszę również dodać, że kula, którą mówisz, wygląda „mniej nierówna i dużo bardziej okrągła” jest oglądana pod najlepszym kątem, co czyni to twierdzenie nieuczciwym. Po prostu zrób ten sam zrzut ekranu z kulami oglądanymi z góry, aby zobaczyć, co mam na myśli.
sam hocevar
4
Ponadto liczby dwudziestościanu nie wyglądają poprawnie. Poziom 0 to 20 twarzy (z definicji), a następnie 80, 320, 1280 itd. Możesz podzielić na dowolną liczbę i dowolny wzór. Gładkość modelu zostanie ostatecznie określona przez liczbę i rozmieszczenie ścian w wyniku końcowym (niezależnie od metody użytej do ich wygenerowania), a my chcemy zachować rozmiar każdej ściany tak jednolity, jak to możliwe (bez biegunowości ściskanie), aby zachować spójny profil niezależnie od kąta widzenia. Dodaj do tego fakt, że kod podziału jest znacznie prostszy (imho) ...
3Dave
2
W tę odpowiedź włożono trochę pracy, co sprawia, że ​​czuję się trochę źle z powodu jej głosowania. Ale to całkowicie i całkowicie źle, więc muszę. Idealnie okrągła Icosphere, która wypełnia cały ekran w FullHD, potrzebuje 5 podpodziałów, a podstawowy dwudziestościan nie ma żadnych podpodziałów. Dwudziestościan bez podziału nie ma 100 ścian, ma 20. Icosa = 20. To nazwa! Każdy podział mnoży liczbę ścian przez 4, więc 1-> 80, 2-> 320, 3-> 1280, 4-> 5120, 5-> 20 480. W przypadku geosfery potrzebujemy co najmniej 40 000 twarzy, aby uzyskać równie okrągłą kulę.
Peter - Unban Robert Harvey,
-1

Poniższy skrypt utworzy Dwudziestościan z n Wielokątami ... podstawa 12. Podzielą również wieloboki na osobne siatki i obliczą całkowitą liczbę duplikatów i wielokątów.

Nie mogłem znaleźć niczego podobnego, więc to stworzyłem. Wystarczy dołączyć skrypt do GameObject i ustawić podziały w Edytorze. Następne prace nad modyfikacją hałasu.


/* 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
źródło