Thuật toán tạo hình cầu?


27

Có ai có một thuật toán để tạo một thủ tục hình cầu với lasố lượng các đường vĩ độ, losố lượng các đường kinh độ và bán kính là r? Tôi cần nó để làm việc với Unity, vì vậy các vị trí đỉnh cần được xác định và sau đó, các tam giác được xác định thông qua các chỉ mục ( thông tin thêm ).


CHỈNH SỬA

nhập mô tả hình ảnh ở đây

Tôi quản lý để có được mã làm việc trong sự thống nhất. Nhưng tôi nghĩ rằng tôi có thể đã làm điều gì đó sai. Khi tôi bật lên detailLevel, tất cả những gì nó làm là thêm nhiều đỉnh và đa giác mà không di chuyển chúng xung quanh. Tôi đã quên một cái gì đó?


CHỈNH SỬA 2

nhập mô tả hình ảnh ở đây

Tôi đã thử mở rộng lưới dọc theo quy tắc của nó. Đây là những gì tôi nhận được. Tôi nghĩ rằng tôi đang thiếu một cái gì đó. Tôi có nên chỉ quy mô nhất định?


1
Tại sao bạn không nhìn vào cách các triển khai nguồn mở hiện có làm điều đó? ví dụ, hãy xem cách Three.js sử dụng các mắt lưới.
brice

3
Một lưu ý nhỏ: trừ khi bạn phải thực hiện vĩ độ / kinh độ, bạn gần như chắc chắn không muốn , bởi vì các hình tam giác bạn nhận được sẽ khác xa so với đồng phục so với các phương pháp khác. (So ​​sánh các tam giác gần cực bắc với các tam giác gần xích đạo: bạn đang sử dụng cùng một số tam giác để đi xung quanh một đường vĩ độ trong cả hai trường hợp, nhưng gần cực mà đường vĩ độ có chu vi rất nhỏ trong khi ở xích đạo đó là chu vi toàn cầu của bạn.) Các kỹ thuật như câu trả lời của David Lively thường tốt hơn nhiều.
Steven Stadnicki

1
Bạn không bình thường hóa các vị trí đỉnh sau khi chia nhỏ. Tôi đã không bao gồm phần đó trong ví dụ của tôi. Bình thường hóa làm cho tất cả chúng đều tương đương từ trung tâm, điều này tạo ra xấp xỉ đường cong mà bạn đang tìm kiếm.
3Dave

Hãy suy nghĩ thổi phồng một quả bóng ở trung tâm của khối u. Khi quả bóng đẩy lưới của chúng ta, nó khớp với hình dạng của quả bóng (hình cầu).
3Dave

4
"Bình thường hóa" có nghĩa là đặt độ dài của vectơ thành 1. Bạn cần làm một cái gì đó như vertices[i] = normalize(vertices[i]). Ngẫu nhiên, điều này cũng cung cấp cho bạn các quy tắc mới, chính xác của bạn, vì vậy bạn nên làm normals[i] = vertices[i]sau đó.
sam hocevar

Câu trả lời:


31

Để có được một cái gì đó như thế này:

nhập mô tả hình ảnh ở đây

Tạo một khối hình chữ nhật (vật rắn thông thường 20 mặt) và chia nhỏ các mặt để có được một hình cầu (xem mã bên dưới).

Ý tưởng về cơ bản là:

  • Tạo một n-hedron thông thường (một vật rắn trong đó mọi mặt đều có cùng kích thước). Tôi sử dụng một icosahedron bởi vì nó là vật rắn có số lượng khuôn mặt lớn nhất trong đó mọi khuôn mặt đều có cùng kích thước. (Có bằng chứng cho điều đó ở đâu đó ngoài kia. Hãy thoải mái với Google nếu bạn thực sự tò mò.) Điều này sẽ cung cấp cho bạn một hình cầu trong đó gần như mọi khuôn mặt đều có cùng kích thước, giúp việc tạo họa tiết dễ dàng hơn một chút.

nhập mô tả hình ảnh ở đây

  • Chia mỗi khuôn mặt thành bốn khuôn mặt có kích thước bằng nhau. Mỗi khi bạn làm điều này, nó sẽ tăng gấp bốn lần số lượng khuôn mặt trong mô hình.

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

i0, i1i2là các đỉnh của tam giác ban đầu. (Trên thực tế, các chỉ số vào bộ đệm đỉnh, nhưng đó là một chủ đề khác). m01là trung điểm của cạnh (i0,i1), m12 là trung điểm của cạnh (i1,12)m02rõ ràng là trung điểm của cạnh (i0,i2).

Bất cứ khi nào bạn chia nhỏ một khuôn mặt, hãy đảm bảo rằng bạn không tạo các đỉnh trùng lặp. Mỗi điểm giữa sẽ được chia sẻ bởi một mặt nguồn khác (vì các cạnh được chia sẻ giữa các mặt). Mã dưới đây giải thích điều đó bằng cách duy trì một từ điển các điểm giữa được đặt tên đã được tạo và trả về chỉ mục của điểm giữa được tạo trước đó khi có sẵn thay vì tạo một điểm mới.

  • Lặp lại cho đến khi bạn đạt được số lượng khuôn mặt mong muốn cho khối lập phương của mình.

  • Khi bạn đã hoàn tất, bình thường hóa tất cả các đỉnh để làm phẳng bề mặt. Nếu bạn không làm điều này, bạn sẽ chỉ nhận được một hình chữ nhật có độ phân giải cao hơn thay vì hình cầu.

  • Voila! Bạn đã hoàn tất. Chuyển đổi kết quả các bộ đệm vector và chỉ số thành một VertexBufferIndexBuffer, và vẽ với Device.DrawIndexedPrimitives().

Đây là những gì bạn sử dụng trong lớp "Sphere" của mình để tạo mô hình (kiểu dữ liệu XNA và C #, nhưng nó sẽ khá rõ ràng):

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

GeometryProviderlớp học

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


    }



}

Câu trả lời chính xác. Cảm ơn. Tôi không thể nói nhưng đây là mã thống nhất? Ồ, và lat / long không thành vấn đề, miễn là tôi có thể đặt độ phân giải.
Daniel Pendergast

Đó không phải là Unity (XNA) nhưng nó sẽ cung cấp cho bạn tọa độ đỉnh và danh sách chỉ mục. Thay thế Vector3 bằng bất cứ thứ gì tương đương với Unity. Bạn đặt độ phân giải bằng cách điều chỉnh số lần lặp Subdivide. Mỗi vòng lặp nhân số lượng khuôn mặt với 4. 2 hoặc 3 lần lặp sẽ cho ra một hình cầu đẹp.
3Dave

Ah tôi thấy. Nó gần giống với Unity C #. Chỉ cần một vài câu hỏi ... Tại sao khi các chỉ số được xác định, bạn đặt chúng bên trong một intmảng? Và .Select(i => i + vertices.Count)làm gì?
Daniel Pendergast

.Select(i => i + vertices.Count)không làm việc cho tôi cả. Đây có phải là tính năng duy nhất của XNA không?
Daniel Pendergast

1
Đảm bảo bạn đang bao gồm 'sử dụng System.Linq' như được định nghĩa. Chọn, v.v.
3Dave

5

Chúng ta hãy xem xét định nghĩa tham số của một hình cầu:

định nghĩa tham số của một hình cầu

trong đó theta và phi là hai góc tăng, mà chúng ta sẽ gọi là var tvar uvà Rx, Ry và Rz là bán kính độc lập (bán kính) trong cả ba hướng Descartes, trong đó, trong trường hợp của một quả cầu, sẽ được định nghĩa là một đơn bán kính var rad.

Bây giờ chúng ta hãy xem xét thực tế rằng ... biểu tượng chỉ ra một phép lặp gợi ý việc sử dụng một vòng lặp. Khái niệm stacksrowslà "bạn sẽ lặp đi lặp lại bao nhiêu lần". Vì mỗi lần lặp thêm giá trị của t hoặc u, số lần lặp càng nhiều, giá trị càng nhỏ, do đó độ cong của hình cầu càng chính xác.

Điều kiện tiên quyết của chức năng 'vẽ hình cầu' là có các tham số đã cho sau: int latitudes, int longitudes, float radius . Các điều kiện bài (đầu ra) là trả về, hoặc áp dụng các đỉnh được tính toán. Tùy thuộc vào cách bạn định sử dụng cái này, hàm có thể trả về một mảng vector3(vectơ ba chiều) hoặc, nếu bạn đang sử dụng một số loại OpenGL đơn giản, trước phiên bản 2.0, bạn có thể muốn trực tiếp áp dụng các đỉnh cho bối cảnh.

NB Áp dụng một đỉnh trong openGL là gọi hàm sau glVertex3f(x, y, z) . Trong trường hợp chúng tôi sẽ lưu trữ các đỉnh, chúng tôi sẽ thêm một điểm mới vector3(x, y, z)để lưu trữ dễ dàng.

Ngoài ra, cách bạn yêu cầu hệ thống vĩ độ và kinh độ hoạt động cần một sự điều chỉnh cho định nghĩa của hình cầu (về cơ bản là chuyển đổi z và y), nhưng điều này chỉ cho thấy định nghĩa này rất dễ uốn, và bạn có thể tự do chuyển đổi tham số x, y và z để thay đổi hướng trong đó hình cầu được vẽ (nơi có vĩ độ và kinh độ).

Bây giờ chúng ta hãy xem làm thế nào chúng ta sẽ làm các vĩ độ và kinh độ. Các vĩ độ được biểu thị bằng biến u, chúng lặp từ 0 đến 2π radian (360 độ). Do đó chúng ta có thể mã hóa lần lặp của nó như vậy:

float latitude_increment = 360.0f / latitudes;

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

Bây giờ các kinh độ được biểu diễn bằng biến tvà lặp lại từ 0 đến π (180 độ). do đó đoạn mã sau trông tương tự như đoạn trước:

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

(Lưu ý rằng các vòng lặp đã bao gồm điều kiện đầu cuối, bởi vì khoảng thời gian tích hợp tham số là từ 0 đến 2π Bao gồm . Bạn sẽ nhận được một phần hình cầu nếu điều kiện của bạn không bao gồm.)

Bây giờ, theo định nghĩa đơn giản của hình cầu, chúng ta có thể rút ra định nghĩa biến như sau (giả sử 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)));

Một cảnh báo quan trọng hơn! Trong hầu hết các trường hợp, bạn sẽ sử dụng một số dạng OpenGL và ngay cả khi không phải vậy, bạn vẫn có thể cần phải làm điều này. Một đối tượng trong ba chiều cần xác định một số đỉnh. Điều này thường đạt được bằng cách cung cấp đỉnh tiếp theo có thể tính toán được.

làm thế nào nhiều đỉnh được sử dụng để xác định hình dạng (nguyên thủy)

Làm thế nào trong hình trên các tọa độ khác nhau x+∂y+∂, chúng ta có thể dễ dàng tạo ra ba đỉnh khác cho bất kỳ mục đích sử dụng nào. Các đỉnh khác là (giả sử 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)));

Cuối cùng, đây là một hàm đầy đủ hoạt động sẽ trả về tất cả các đỉnh của một hình cầu và cái thứ hai cho thấy việc triển khai mã OpenGL đang hoạt động (đây là cú pháp kiểu C và không phải JavaScript, nên hoạt động với tất cả các ngôn ngữ kiểu C, bao gồm C # khi sử dụng 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;

}

Mã 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 Bạn có thể đã nhận thấy tuyên bố này rad = radius;. Điều này cho phép bán kính được sửa đổi trong vòng lặp, dựa trên vị trí hoặc góc. Điều này có nghĩa là bạn có thể áp dụng tiếng ồn cho quả cầu để làm nhám nó, làm cho nó trông tự nhiên hơn nếu hiệu ứng mong muốn là giống như hành tinh. Ví dụfloat rad = radius * noise[x][y][z];

Claude-Henry.


Dòng `float z = (float) (rad * Math.sin (Math.toRadians (t)) * Math.cos (Math.toRadians (u)));` không chính xác. Bạn đã tính X, Y với cạnh huyền là rad. Bây giờ bạn đang tạo ra một chân của một hình tam giác, và ngụ ý rằng cạnh huyền của tam giác nói trên cũng vậy rad. Điều này có hiệu quả cung cấp cho bạn một bán kính rad * sqrt(2).
3Dave

@DavidLively cảm ơn vì đã chỉ ra nó, tôi đã viết nó một thời gian trước để tôi không ngạc nhiên nếu nó xấu hoặc thậm chí hoàn toàn sai.
claudehenry

thật vui khi tôi tìm thấy một lỗi trong một trong những bài viết của tôi từ nhiều năm trước. Nó xảy ra. :)
3Dave

4

Tôi đã tạo ra một cái gì đó như thế này một thời gian trước để tạo ra một khối hình khối, cho vui và khoa học. Nó không quá khó. Về cơ bản, bạn lấy một hàm tạo ra một vòng tròn các đỉnh, sau đó bước qua các bước tăng chiều cao mà bạn muốn tạo các vòng tròn ở mỗi độ cao ở bán kính cần thiết để tạo một hình cầu. Ở đây tôi đã sửa đổi mã không dành cho hình khối:

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

Bây giờ mã này sẽ chỉ tạo điểm cho vĩ độ. Tuy nhiên, bạn gần như có thể sử dụng cùng một mã để tạo các đường kinh độ. Ngoại trừ bạn sẽ cần xoay giữa mỗi lần lặp và tạo một vòng tròn đầy đủ ở mỗi lần lặpdegreeStep .

Xin lỗi, đây không phải là câu trả lời hoàn chỉnh hoặc Unity cụ thể, nhưng hy vọng nó sẽ giúp bạn bắt đầu.


Điều này là khá tốt nếu bạn cần một hình cầu lat / long, nhưng bạn có thể đơn giản hóa nó một chút bằng cách làm việc trong các tọa độ hình cầu cho đến bước cuối cùng.
3Dave

1
Cảm ơn @David. Tôi đồng ý, nếu tôi có ý định viết một phiên bản bằng cách sử dụng các hình cầu, tôi sẽ đăng nó ở đây.
MichaelHouse

3

Bạn không thể chỉ bắt đầu với một hình dạng đơn giản, có thể là một hộp với khoảng cách r từ trung tâm đến góc. Để tạo một hình cầu chi tiết hơn, chia nhỏ tất cả các đa giác và sau đó di chuyển các đỉnh ra khoảng cách r từ tâm, để vectơ đi qua vị trí hiện tại của chúng.

Tiếp tục lặp lại cho đến khi đủ hình cầu cho thị hiếu của bạn.


Điều này về cơ bản giống như cách tiếp cận icosah thờ, chỉ với một hình dạng bắt đầu khác nhau. Một lợi thế của việc bắt đầu với một khối lập phương mà tôi không nghĩ rằng đã được đề cập: việc xây dựng bản đồ UV tốt hơn vì bạn có thể sử dụng sơ đồ khối và biết rằng các đường nối kết cấu của bạn sẽ thẳng hàng với các cạnh trong lưới hình cầu của bạn.
Steven Stadnicki

@StevenStadnicki vấn đề duy nhất tôi có với các hình khối là khuôn mặt có xu hướng cuộn lên có kích thước rất khác nhau sau một vài phân nhóm.
3Dave

@DavidLively Điều đó phụ thuộc rất nhiều vào cách bạn chia nhỏ - nếu bạn cắt các mặt vuông của khối lập phương thành một lưới chẵn và sau đó chiếu ra ngoài / bình thường hóa thì điều đó là đúng, nhưng nếu bạn tạo khuôn mặt không đồng đều thì bạn thực sự có thể làm cho hình chiếu được đặt cách đều nhau dọc theo các cung của các cạnh; Hóa ra hoạt động khá tốt.
Steven Stadnicki

@StevenStadnicki tiện lợi!
3Dave

@EricJohansson btw, với tư cách là một giáo viên, tôi cảm thấy buộc phải đề cập rằng đây là một cái nhìn sâu sắc khá quan trọng đối với một người mà dường như chưa thấy phương pháp phân chia trước đây. Bạn đã đổi mới niềm tin của tôi vào nhân loại trong 12 giờ tới.
3Dave

2

Bạn có thực sự cần hình học 3D hay chỉ là hình dạng?

Bạn có thể tạo một hình cầu 'giả' bằng cách sử dụng một hình tứ giác. Chỉ cần đặt một vòng tròn trên nó và tô màu chính xác. Điều này có lợi thế là nó sẽ có độ phân giải chính xác cần thiết bất kể khoảng cách đến camera hay độ phân giải.

Có một hướng dẫn ở đây .


1
Hack đẹp, nhưng thất bại nếu bạn cần kết cấu nó.
3Dave

@DavidLively Có thể tính toán tọa độ kết cấu trên mỗi pixel dựa trên xoay vòng của nó trừ khi bạn cần kết cấu các đa giác riêng lẻ.
David C. Giám mục

@DavidCBishop Bạn sẽ phải tính đến "thấu kính" của bề mặt - các dây texel được ép sát với đường viền vòng tròn do phối cảnh - tại thời điểm đó bạn đang giả mạo xoay. Ngoài ra, điều đó liên quan đến việc di chuyển nhiều công việc hơn vào trình tạo bóng pixel có thể được thực hiện trong trình tạo bóng đỉnh (và tất cả chúng ta đều biết rằng VS rẻ hơn rất nhiều!).
3Dave

0

Đây là một số mã cho bất kỳ số đỉnh cách đều nhau của một hình cầu, giống như một vỏ cam, nó uốn lượn một đường các chấm xung quanh một hình cầu theo hình xoắn ốc. Sau đó, cách bạn tham gia các đỉnh là tùy thuộc vào bạn. bạn có thể sử dụng các chấm lân cận trong vòng lặp là 2 của mỗi tam giác và sau đó tìm điểm thứ ba sẽ là một vòng xoắn tỷ lệ xung quanh quả cầu cao hơn hoặc thấp hơn ... bạn cũng có thể thực hiện các hình tam giác theo vòng lặp và hàng xóm gần nhất trên đó, có ai đó không biết một cách tốt hơn?

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

Mặc dù David hoàn toàn chính xác trong câu trả lời của mình, tôi muốn đưa ra một quan điểm khác.

Đối với nhiệm vụ của tôi để tạo nội dung theo thủ tục, tôi đã xem xét (trong số những thứ khác) icosahedron so với các lĩnh vực được chia nhỏ truyền thống hơn. Nhìn vào các lĩnh vực được tạo theo thủ tục:

Quả cầu tuyệt vời

Cả hai trông giống như những quả cầu hoàn toàn hợp lệ, phải không? Chà, hãy nhìn vào khung dây của họ:

Wow đó là dày đặc

Wow, chuyện gì đã xảy ra ở đó? Phiên bản khung dây của quả cầu thứ hai rất dày đặc đến nỗi trông nó có kết cấu! Tôi sẽ cho bạn biết một bí mật: phiên bản thứ hai là một hình chữ nhật. Đó là một hình cầu gần như hoàn hảo, nhưng nó có giá cao.

Hình cầu 1 sử dụng 31 phân khu trên trục x và 31 phân khu trên trục z, với tổng số 3,844 mặt.

Sphere 2 sử dụng 5 phân khu đệ quy, với tổng số 109.220 khuôn mặt.

Nhưng không sao, điều đó không thực sự công bằng. Hãy giảm chất lượng xuống đáng kể:

Hàng loạt

Hình cầu 1 sử dụng 5 phân vùng trên trục x và 5 phân vùng trên trục z, với tổng số 100 mặt.

Sphere 2 sử dụng 0 phân vùng đệ quy, với tổng số 100 mặt.

Họ sử dụng cùng một số lượng khuôn mặt, nhưng theo tôi, hình cầu bên trái trông đẹp hơn. Nó trông bớt sần và tròn hơn rất nhiều. Chúng ta hãy xem có bao nhiêu khuôn mặt chúng ta tạo ra bằng cả hai phương thức.

Vật liệu nhựa:

  • Cấp 0 - 100 khuôn mặt
  • Cấp 1 - 420 khuôn mặt
  • Cấp 2 - 1.700 khuôn mặt
  • Cấp 3 - 6.820 khuôn mặt
  • Cấp 4 - 27.300 khuôn mặt
  • Cấp 5 - 109.220 khuôn mặt

Hình cầu chia nhỏ:

  • YZ: 5 - 100 khuôn mặt
  • YZ: 10 - 400 khuôn mặt
  • YZ: 15 - 900 khuôn mặt
  • YZ: 20 - 1.600 khuôn mặt
  • YZ: 25 - 2.500 khuôn mặt
  • YZ: 30 - 3.600 mặt

Như bạn có thể thấy, icosahedron tăng ở các mặt với tốc độ theo cấp số nhân, đến một sức mạnh thứ ba! Đó là bởi vì với mỗi tam giác, chúng ta phải chia chúng thành ba tam giác mới.

Sự thật là: bạn không cần độ chính xác mà một icosahedron sẽ cung cấp cho bạn. Bởi vì cả hai đều ẩn một vấn đề khó khăn hơn nhiều: kết cấu một mặt phẳng 2D trên quả cầu 3D. Đây là những gì hàng đầu trông giống như:

Hút hàng đầu

Ở phía trên bên trái, bạn có thể thấy kết cấu đang được sử dụng. Thật trùng hợp, nó cũng được tạo ra theo thủ tục. (Này, đó là một khóa học về tạo thủ tục, phải không?)

Trông thật kinh khủng phải không? Vâng, điều này là tốt như nó sẽ nhận được. Tôi đã đạt điểm cao cho bản đồ kết cấu của mình, bởi vì hầu hết mọi người thậm chí không làm cho nó đúng.

Vì vậy, xin vui lòng, xem xét sử dụng cosine và sin để tạo ra một hình cầu. Nó tạo ra rất ít khuôn mặt cho cùng một lượng chi tiết.


6
Tôi sợ rằng tôi chỉ có thể downvote này. Các tầng điện ly quy mô theo cấp số nhân? Điều đó chỉ bởi vì bạn quyết định của bạn nên mở rộng theo cấp số nhân. Một quả cầu UV tạo ra ít mặt hơn một icosphere cho cùng một lượng chi tiết? Đó là sai, hoàn toàn sai, hoàn toàn ngược.
sam hocevar

4
Phân ngành không cần phải đệ quy. Bạn có thể chia cạnh của một hình tam giác thành nhiều phần bằng nhau như bạn muốn. Sử dụng Ncác bộ phận sẽ cung cấp cho bạn N*Ncác hình tam giác mới, là bậc hai, giống hệt như những gì bạn làm với quả cầu UV.
sam hocevar

6
Tôi cũng phải nói thêm rằng hình cầu mà bạn nói trông "ít sần và tròn hơn nhiều" được nhìn từ góc độ tốt nhất, khiến cho tuyên bố đó không trung thực. Chỉ cần làm cùng một ảnh chụp màn hình với các hình cầu nhìn từ trên xuống để xem ý tôi là gì.
sam hocevar

4
Ngoài ra, số icosahedron của bạn không chính xác. Cấp 0 là 20 khuôn mặt (theo định nghĩa), sau đó là 80, 320, 1280, v.v. Bạn có thể chia nhỏ thành bất kỳ số nào và bất kỳ mẫu nào bạn muốn. Độ mịn của mô hình cuối cùng sẽ được xác định bởi số lượng và phân bố các mặt trong kết quả cuối cùng (bất kể phương pháp nào được sử dụng để tạo ra chúng) và chúng tôi muốn giữ kích thước của mỗi mặt đồng nhất nhất có thể (không phân cực ép) để duy trì một cấu hình nhất quán bất kể góc nhìn. Thêm vào đó là thực tế rằng mã phân chia đơn giản hơn rất nhiều (
imho

2
Một số công việc đã được đưa vào câu trả lời này, điều này khiến tôi cảm thấy hơi tệ về việc hạ thấp nó. Nhưng nó hoàn toàn và hoàn toàn sai, vì vậy tôi phải làm. Một Icosphere có hình tròn hoàn hảo, lấp đầy toàn bộ màn hình trong FullHD cần 5 phân khu, với một icosahedron cơ bản không có phân khu. Một khối nhựa không có phân khu không có 100 mặt, nó có 20. Icosa = 20. Đó là tên! Mỗi phân khu nhân số lượng khuôn mặt với 4, do đó 1-> 80, 2-> 320, 3-> 1280, 4-> 5120, 5-> 20.480. Với một không gian địa lý, chúng ta cần ít nhất 40'000 khuôn mặt để có được một quả cầu tròn bằng nhau.
Peter

-1

Kịch bản dưới đây sẽ tạo ra một Icosahedron với n Đa giác ... cơ sở 12. Nó cũng sẽ chia các đa giác thành các lưới riêng biệt và tính toán tổng số lần lặp và đa giác.

Tôi không thể tìm thấy bất cứ điều gì tương tự vì vậy tôi đã tạo ra điều này. Chỉ cần đính kèm tập lệnh vào GameObject và đặt các phân mục trong Trình chỉnh sửa. Làm việc trên sửa đổi tiếng ồn tiếp theo.


/* 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;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.