Алгоритм створення сфер?


27

Хтось має алгоритм для створення сфери процедурно з laкількістю ліній широти, loрозміром ліній довготи та радіусом r? Мені це потрібно для роботи з Unity, тому потрібно визначити позиції вершин, а потім трикутники, визначені за допомогою індексів ( детальніше ).


EDIT

введіть тут опис зображення

Мені вдалося змусити код працювати в єдності. Але я думаю, що, можливо, я зробив щось не так. Коли я з'являюсь detailLevel, все, що він робить, - це додавати більше вершин і багатокутників, не переміщуючи їх. Я щось забув?


EDIT 2

введіть тут опис зображення

Я спробував масштабувати сітку уздовж її норм. Це те, що я отримав. Я думаю, що мені чогось не вистачає. Чи повинен я масштабувати лише певні норми?


1
Чому ви не дивитесь, як це роблять існуючі реалізації з відкритим кодом? подивіться, як, наприклад, Three.js робить це за допомогою сіток.
брич

3
Як невелика примітка: якщо вам не доведеться виконувати широту / довготу, ви майже напевно не хочете , тому що отримані трикутники будуть набагато далі від рівномірних, ніж ті, які ви отримуєте за допомогою інших методів. (Порівняйте трикутники біля північного полюса з тими біля екватора: ви використовуєте однакову кількість трикутників, щоб обійти одну лінію широти в будь-якому випадку, але біля полюса ця лінія широти має дуже мале коло, тоді як на екваторі це повна окружність вашої земної кулі.) Такі методи, як відповідь Девіда Лівелі, як правило, набагато кращі.
Стівен Стадницький

1
Ви не нормалізуєте положення вершин після підрозділу. Я не включив цю частину в свій приклад. Нормалізація робить їх усі рівновіддалені від центру, що створює наближення кривої, яке ви шукаєте.
3Dave

Подумайте надути повітряну кулю в центрі ікосаедра. Оскільки повітряна куля штовхає сітку нашої, вона відповідає формі повітряної кулі (сфери).
3Dave

4
"Нормалізація" означає встановити довжину вектора до 1. Вам потрібно зробити щось на кшталт vertices[i] = normalize(vertices[i]). Між іншим, це також дає вам нові, правильні норми, так що ви повинні робити це normals[i] = vertices[i]згодом.
sam hocevar

Відповіді:


31

Щоб отримати щось подібне:

введіть тут опис зображення

Створіть ікосаедр (20-сторонній звичайний суцільний) і розділіть грані, щоб отримати сферу (див. Код нижче).

Ідея в основному:

  • Створіть звичайний n-hedron (суцільне, де кожне обличчя однакового розміру). Я використовую ікосаедр, тому що це тверда речовина з найбільшою кількістю граней, де кожне обличчя однакового розміру. (Там є доказ цього десь там. Не соромтеся Google, якщо ви справді цікаві.) Це дасть вам сферу, де майже кожне обличчя має однаковий розмір, зробивши текстурування трохи простішим.

введіть тут опис зображення

  • Розділіть кожне обличчя на чотири особи однакового розміру. Кожен раз, коли ви це робите, це вчетверо збільшуватиме кількість облич у моделі.

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

i0, i1І i2є вершинами вихідного трикутника. (Насправді, індекси в буфер вершин, але це вже інша тема). m01- середина ребра (i0,i1), m12 - середина ребра (i1,12), і m02, очевидно, середня точка краю(i0,i2) .

Щоразу, коли ви підрозділяєте обличчя, переконайтеся, що ви не створюєте повторюваних вершин. Кожну середню точку поділить одне інше джерело (оскільки краї розділені між гранями). Код нижче пояснює це, підтримуючи словник іменованих середніх точок, які були створені, і повертає індекс попередньо створеної середини, коли вона доступна, а не створює нову.

  • Повторіть, поки не досягнете потрібної кількості граней для куба.

  • Коли ви закінчите, нормалізуйте всі вершини, щоб згладити поверхню. Якщо цього не зробити, ви просто отримаєте ікосаедр з більшою роздільною здатністю замість сфери.

  • Вуаля! Ви закінчили. Перетворіть отримані векторні та індексні буфери в a VertexBufferі IndexBufferта намалюйте за допомогою Device.DrawIndexedPrimitives().

Ось що б ви використовували у своєму класі "Сфера" для створення моделі (типи даних XNA та C #, але це має бути досить зрозуміло):

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

І GeometryProviderклас

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


    }



}

Чудова відповідь. Спасибі. Я не можу сказати, але це код єдності? О, і lat / long не має значення, доки я можу встановити роздільну здатність.
Даніель Пендергаст

Це не Unity (XNA), але він надасть вам координати вершин та список індексів. Замініть Vector3 будь-яким еквівалентом Unity. Ви встановлюєте роздільну здатність, регулюючи кількість ітерацій Підрозділити. Кожна петля множить кількість граней на 4. 2 або 3 повторення дадуть приємну сферу.
3Dave

А, бачу. Це майже ідентично Unity C #. Лише кілька питань ... Чому, коли індекси визначені, ви ставите їх всередину intмасиву? І що робить .Select(i => i + vertices.Count)?
Даніель Пендергаст

Це .Select(i => i + vertices.Count)не працює для мене взагалі. Це лише функція XNA?
Даніель Пендергаст

1
Переконайтеся, що ви включаєте "використовуючи System.Linq", як це визначено. Виберіть і т.д.
3Dave

5

Розглянемо параметричне визначення сфери:

параметричне визначення сфери

де тета і фі є двома прирістними кутами, які ми будемо позначати як var tі, var uа Rx, Ry і Rz - незалежні радіуси (радіуси) у всіх трьох декартових напрямках, які у випадку сфери будуть визначені як один радіус var rad.

Розглянемо тепер факт, що ...символ позначає ітерацію, яка натякає на використання циклу. Поняття stacksіrows "скільки разів ви повторите". Оскільки кожна ітерація додає значення t або u, чим більше ітерацій, тим меншим є значення, тому тим точнішою є кривизна сфери.

«Сфери малювання» передумови функції є мати такі дані параметрів: int latitudes, int longitudes, float radius. Умови пост (вихід) - це повернення або застосування обчислених вершин. Залежно від того, як ви маєте намір використовувати це, функція може повернути масивvector3 (тривимірні вектори) або, якщо ви використовуєте якийсь простий OpenGL, до версії 2.0, ви, можливо, захочете застосувати вершини до контексту безпосередньо.

Примітка Застосування вершини у openGL викликає наступну функцію glVertex3f(x, y, z). У випадку, коли ми б зберігали вершини, ми додавали б новіvector3(x, y, z) для зручного зберігання.

Крім того, те, як ви запросили систему широти та довготи для роботи, потребувало коригування визначення сфери (в основному перемикання z і y), але це просто показує, що визначення дуже податливе і що ви можете перемикатися навколо Параметри x, y та z змінюють напрямок, у якому намальована сфера (де знаходяться широти та довготи).

Тепер давайте подивимось, як ми будемо робити широти і довготи. Широти представлені змінною u, вони ітерають від 0 до 2π радіанів (360 градусів). Тому ми можемо кодувати його ітерацію так:

float latitude_increment = 360.0f / latitudes;

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

Тепер довготи представлені змінною tі ітераціюють від 0 до π (180 градусів). тому наступний код схожий на попередній:

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

(Зверніть увагу, що петлі є Inclusive з там термінального стану, тому що інтервал для параметричного інтегрування від 0 до 2 л Inclusive . Ви отримаєте часткову сферу , якщо ваші умови не включені.)

Тепер, слідуючи простому визначенню сфери, ми можемо отримати змінне визначення наступним чином (припустимо 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)));

Ще одне важливе попередження! У більшості випадків ви будете використовувати якусь форму OpenGL, і навіть якщо це не так, вам це все одно знадобиться зробити. Об'єкт у тривимірному масштабі потребує визначення декількох вершин. Зазвичай це досягається шляхом надання наступної вершини, яка обчислюється.

як багато вершин використовуються для визначення (примітивної) форми

Так само , як на малюнку вище різних координат x+∂і y+∂, ми можемо легко генерувати три інші вершини для будь-якого бажаного використання. Інші вершини - це (припустимо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)));

Нарешті, тут працює повноцінна функція, яка б повертала всі вершини сфери, а друга показує працюючу OpenGL-реалізацію коду (це синтаксис стилю C, а не JavaScript. Це має працювати з усіма мовами стилю C, включаючи C # під час використання 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;

}

Код 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 Ви, можливо, помітили це твердження rad = radius; . Це дозволяє змінювати радіус в циклі, виходячи з місця розташування або кута. Це означає, що ви можете застосувати шум до сфери, щоб розробити її, зробивши це виглядати більш природним, якщо бажаний ефект є подібним до планети. Напрfloat rad = radius * noise[x][y][z];

Клод-Генрі.


Рядок `float z = (float) (rad * Math.sin (Math.toRadians (t)) * Math.cos (Math.toRadians (u)));` неправильний. Ви вже обчислили X, Y з гіпотенузою rad. Тепер ви складаєте цю одну ногу трикутника і маєте на увазі, що гіпотенуза цього трикутника також є rad. Це ефективно дає радіус rad * sqrt(2).
3Dave

@DavidLively дякую за вказівку на це, я написав це деякий час назад, так що я не здивований, якщо це погано або навіть відверто неправильно.
claudehenry

це завжди весело, коли я знаходжу помилку в одному з моїх публікацій років тому. Це буває. :)
3Dave

4

Я створив щось подібне назад, щоб зробити сферу кубів, для розваги та науки. Це не надто важко. В основному, ви берете функцію, яка створює коло вершин, потім переходите через приріст висоти, який потрібно створити кола на кожній висоті в радіусі, необхідному для створення кулі. Тут я змінив код не для кубів:

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

Тепер цей код просто створив би точки для широти. Однак ви можете майже використовувати один і той же код для створення ліній довготи. За винятком того, що вам потрібно буде обертатися між кожною ітерацією та робити повне коло на кожнійdegreeStep .

Вибачте, це не є повною відповіддю чи специфікою Unity, але, сподіваємось, це розпочнеться.


Це досить добре, якщо вам потрібна сфера з шириною / довгою, але ви можете трохи спростити її, працюючи в сферичних координатах до останнього кроку.
3Dave

1
Дякую @David. Я погоджуюся, якщо я змушусь написати версію за допомогою сферичних координат, опублікую її тут.
MichaelHouse

3

Не могли б ви просто почати з простої форми, це може бути ящик з відстані r від центру до кута. Щоб зробити більш детальну сферу, підрозділіть всі многокутники, а потім перемістіть вершини на r відстань від центру, вектор проходить через їх поточне положення.

Продовжуйте повторювати, поки не буде достатньо сферичного для ваших смаків.


Це по суті те саме, що ікосаедричний підхід, тільки з іншою початковою формою. Одна перевага починати з куба, який, на мою думку, не згадується: набагато простіше створити гідні УФ-карти, оскільки ви можете використовувати кубічну карту і знаєте, що ваші текстурні шви будуть ідеально вирівнюватись з краями вашої сітчастої сітки.
Стівен Стадницький

@StevenStadnicki Єдине питання, яке я маю з кубиками, - це те, що обличчя мають тенденцію до різних розмірів після кількох підрозділів.
3Dave

@DavidLively Це дуже залежить від того, як ви поділите - якщо ви подрібніть квадратні грані куба в рівну сітку, а потім проектуєте назовні / нормалізуєте, то це правда, але якщо ви з’єднаєте обличчя нерівномірно, то ви можете насправді зробити виступ повинен бути рівномірно розташований уздовж дуг ребер; це, як виявляється, працює досить добре.
Стівен Стадницький

@StevenStadnicki nifty!
3Dave

@EricJohansson btw, як викладач, я змушений зазначити, що це досить важливе розуміння для того, хто, мабуть, раніше не бачив методу підрозділу. Ти відновив мою віру в людство протягом наступних 12 годин.
3Dave

2

Вам справді потрібна 3D-геометрія чи просто форма?

Ви можете зробити «підроблену» сферу за допомогою одного квадратика. Просто покладіть на нього коло і правильно заштрихуйте. Це має перевагу в тому, що він буде мати саме потрібну роздільну здатність незалежно від відстані до камери чи роздільної здатності.

Тут є підручник .


1
Гарний злом, але не вдається, якщо вам потрібно його текстурувати.
3Dave

@DavidLively Повинно бути можливим обчислити координати текстур на піксель, виходячи з його обертання, якщо вам не потрібно текстурувати багатокутники окремо.
Давид К. Єпископ

@DavidCBishop Вам доведеться враховувати "лінзування" поверхні - текстові скори стискаються близько до межі кола через перспективу - в цей момент ви підробляєте обертання. Крім того, це передбачає переміщення набагато більше роботи в піксельний шейдер, який можна було б виконати у вершинній шейдері (і всі ми знаємо, що VS набагато дешевше!).
3Dave

0

ось деякий код для будь-якої кількості однаково розташованих вершин сфери, начебто апельсинової кірки він накручує по спіралі лінію крапок навколо сфери. згодом, як ви приєднаєтеся до вершин, залежить від вас. Ви можете використовувати сусідні точки в циклі, як 2 у кожному трикутнику, а потім знайти третій буде пропорційним поворотом навколо сфери вище вгору чи вниз ... Ви також можете робити трикутники за петлею та найближчим сусідом по ній, чи хтось знаєте кращий спосіб?

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

Хоча Девід абсолютно правильний у своїй відповіді, я хочу запропонувати іншу точку зору.

Для мого призначення породженню процедурного вмісту я розглядав (серед іншого) ікосаедр порівняно з більш традиційними поділеними сферами. Подивіться на ці процедурно сформовані сфери:

Дивовижні сфери

Обидва виглядають як ідеально сфери, правда? Що ж, давайте розглянемо їхні каркаси:

Нічого собі, це щільно

Вау, що там сталося? Варіант другої сфери каркаса такий щільний що виглядає фактурним! Я дозволю вам секретно: друга версія - ікосаедр. Це майже ідеальна сфера, але приходить за високу ціну.

Сфера 1 використовує 31 підрозділ на осі x і 31 підрозділ на осі z, в цілому 3 844 грані.

Сфера 2 використовує 5 рекурсивних підрозділів, загалом 109,220 осіб.

Але добре, це не дуже справедливо. Давайте значно зменшимо якість:

Грудочки

Сфера 1 використовує 5 підрозділів на осі x та 5 підрозділів на осі z, загалом 100 граней.

Сфера 2 використовує 0 рекурсивних підрозділів для загальної кількості 100 облич.

Вони використовують однакову кількість граней, але, на мою думку, сфера зліва виглядає краще. Він виглядає менш грудким і набагато більш круглим. Давайте розглянемо, скільки облич ми генеруємо обома методами.

Ікозаедр:

  • Рівень 0 - 100 облич
  • Рівень 1 - 420 граней
  • 2 рівень - 1700 облич
  • 3 рівень - 6 820 граней
  • 4 рівень - 27 300 осіб
  • 5 рівень - 109 220 осіб

Підрозділена сфера:

  • YZ: 5 - 100 граней
  • YZ: 10 - 400 граней
  • YZ: 15 - 900 граней
  • YZ: 20 - 1600 осіб
  • YZ: 25 - 2500 осіб
  • YZ: 30 - 3600 осіб

Як бачите, ікосаедр збільшується в обличчях з експоненціальною швидкістю, до третьої сили! Це тому, що для кожного трикутника ми повинні поділити їх на три нові трикутники.

Правда така: вам не потрібна точність, яку вам дасть ікосаедр. Оскільки вони обидва приховують набагато складнішу проблему: текстурування 2D площини на 3D-сфері. Ось як виглядає верх:

Верх смокче

У верхньому лівому куті ви бачите текстуру, яка використовується. Випадково він також генерується процедурно. (Гей, це був курс на процедурне покоління, правда?)

Це виглядає жахливо, правда? Ну, це так добре, як це дістанеться. Я отримав найкращі оцінки за своє відображення текстури, оскільки більшість людей навіть не розуміють це правильно.

Тому, будь ласка, подумайте про використання косинуса та синуса для створення сфери. Це створює набагато менше облич за однакову кількість деталей.


6
Я боюся, що можу лише спростувати це. Ікосферні шкали експоненціально? Це лише тому, що ти вирішив, що твій має масштабуватись експоненціально. УФ-сфера генерує менше облич, ніж ікосфера за однакову кількість деталей? Це неправильно, абсолютно неправильно, абсолютно назад.
sam hocevar

4
Підрозділ не має бути рекурсивним. Ви можете розділити край трикутника на стільки рівних частин, скільки хочете. Використання Nдеталей дасть вам N*Nнові трикутники, які є квадратичними, точно так само, як ви робите з УФ-сферою.
sam hocevar

6
Треба також додати, що сфера, про яку ви говорите, виглядає «менш грудкою і набагато більше круглої», розглядається з найкращого кута, що робить це твердження нечесним. Просто зробіть той самий знімок екрана зі сферами, які переглядаються зверху, щоб побачити, що я маю на увазі.
sam hocevar

4
Крім того, ваші номери ікосаедра не виглядають правильно. Рівень 0 - це 20 облич (за визначенням), потім 80, 320, 1280 тощо. Ви можете поділити будь-яку кількість та будь-який шаблон, який ви хочете. Плавність моделі в кінцевому підсумку визначається кількістю та розподілом облич у кінцевому результаті (незалежно від методу, який використовується для їх генерування), і ми хочемо зберегти розмір кожного обличчя максимально рівномірним (немає полярних стискання) для підтримки стійкого профілю незалежно від кута огляду. Додайте до цього той факт, що код підрозділу набагато простіше (imho) ...
3Dave

2
Деякі роботи були вкладені в цю відповідь, і це змушує мене трохи погано сприймати її. Але це абсолютно і зовсім неправильно, тому я повинен. Ікосфера ідеально круглої форми, яка заповнює весь екран у FullHD, потребує 5 підрозділів, причому в базовому ікосаедрі немає підрозділів. У ікосаедра без підрозділів немає 100 граней, він має 20. Ікоза = 20. Це ім'я! Кожен підрозділ множує кількість граней на 4, тому 1-> 80, 2-> 320, 3-> 1280, 4-> 5120, 5-> 20,480. З геосферою нам потрібно щонайменше 40 тис. Граней, щоб отримати однаково круглу сферу.
Пітер - Унбан Роберт Харві

-1

Наведений нижче сценарій створить ікосаедр із п полігонами ... основа 12. Він також підрозділить багатокутники на окремі сітки та обчислить загальну кількість вершин-дублікатів та багатокутників.

Я не зміг знайти нічого подібного, тому створив це. Просто приєднайте сценарій до GameObject і встановіть підрозділи в редакторі. Далі працює над модифікацією шуму.


/* 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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.