อัลกอริทึมสำหรับการสร้างทรงกลม?


27

ไม่มีใครมีอัลกอริทึมสำหรับการสร้างโพรซีเดอร์ทรงกลมที่มีlaจำนวนเส้นรุ้งloจำนวนเส้นแวงและรัศมีrหรือไม่? ฉันต้องการมันเพื่อทำงานกับ Unity ดังนั้นตำแหน่งจุดสุดยอดจำเป็นต้องกำหนดและจากนั้นสามเหลี่ยมที่กำหนดผ่านดัชนี ( ข้อมูลเพิ่มเติม )


แก้ไข

ป้อนคำอธิบายรูปภาพที่นี่

ฉันจัดการเพื่อให้รหัสทำงานเป็นเอกภาพ แต่ฉันคิดว่าฉันอาจทำอะไรผิดไป เมื่อฉันเปิดใช้detailLevelงานทั้งหมดจะเพิ่มจุดยอดและรูปหลายเหลี่ยมเพิ่มเติมโดยไม่ย้ายไปรอบ ๆ ฉันลืมอะไรไปหรือเปล่า


แก้ไข 2

ป้อนคำอธิบายรูปภาพที่นี่

ฉันลองไต่ตาข่ายตามบรรทัดฐาน นี่คือสิ่งที่ฉันได้รับ ฉันคิดว่าฉันขาดอะไรไป ฉันควรจะปรับมาตรฐานบางอย่างเท่านั้นหรือไม่


1
ทำไมคุณไม่ลองดูว่าการใช้งานโอเพ่นซอร์สนั้นมีอยู่อย่างไร มาดูกันว่า Three.jsใช้งาน mesh ได้อย่างไร
ไบรส์

3
ข้อควรทราบเล็ก ๆ น้อย ๆ : ยกเว้นว่าคุณต้องทำละติจูด / ลองจิจูดคุณแทบจะไม่ต้องการเพราะสามเหลี่ยมที่คุณได้รับจะยิ่งไกลจากเครื่องแบบมากกว่าที่คุณได้รับด้วยวิธีอื่น ๆ (เปรียบเทียบสามเหลี่ยมที่อยู่ใกล้ขั้วโลกเหนือกับใกล้กับเส้นศูนย์สูตร: คุณใช้สามเหลี่ยมจำนวนเท่ากันเพื่อให้ได้เส้นละติจูดหนึ่งเส้นในกรณีใดกรณีหนึ่ง แต่ใกล้กับขั้วโลกนั้นเส้นละติจูดนั้นมีเส้นรอบวงเล็กมากขณะที่เส้นศูนย์สูตร มันเป็นวงรอบโลกของคุณ) เทคนิคเหมือนคำตอบของ David Lively โดยทั่วไปนั้นดีกว่ามาก
Steven Stadnicki

1
คุณไม่ได้ทำให้ตำแหน่งจุดสุดยอดปกติหลังจากการแบ่งย่อย ฉันไม่ได้รวมส่วนนั้นไว้ในตัวอย่าง การทำให้เป็นมาตรฐานทำให้พวกมันมีความยาวเท่ากันทั้งหมดจากศูนย์กลาง
3Dave

คิดว่าพองบอลลูนที่กึ่งกลางของไอโซโทป ในขณะที่บอลลูนผลักตาข่ายของเรามันตรงกับรูปร่างของบอลลูน (ทรงกลม)
3Dave

4
"Normalizing" การตั้งค่าความยาวของเวกเตอร์เพื่อ 1. vertices[i] = normalize(vertices[i])หมายความว่าคุณต้องทำสิ่งที่ชอบ อนึ่งนี่จะให้บรรทัดฐานใหม่ที่ถูกต้องแก่คุณดังนั้นคุณควรทำในnormals[i] = vertices[i]ภายหลัง
sam hocevar

คำตอบ:


31

เพื่อให้ได้สิ่งนี้:

ป้อนคำอธิบายรูปภาพที่นี่

สร้าง icosahedron (ของแข็ง 20 หน้าธรรมดา) แล้วแบ่งใบหน้าออกเป็นทรงกลม (ดูรหัสด้านล่าง)

ความคิดนั้นเป็นพื้น:

  • สร้าง n-hedron ปกติ (ของแข็งที่ทุกใบหน้ามีขนาดเท่ากัน) ฉันใช้ icosahedron เพราะมันเป็นของแข็งที่มีจำนวนหน้ามากที่สุดโดยที่ทุกหน้ามีขนาดเท่ากัน (มีข้อพิสูจน์ว่ามีที่ไหนสักแห่งที่นั่นรู้สึกฟรีกับ Google ถ้าคุณอยากรู้อยากเห็นจริง ๆ ) สิ่งนี้จะทำให้คุณมีทรงกลมที่เกือบทุกใบหน้ามีขนาดเท่ากันทำให้พื้นผิวง่ายขึ้นเล็กน้อย

ป้อนคำอธิบายรูปภาพที่นี่

  • แบ่งแต่ละหน้าออกเป็นสี่ขนาดเท่ากัน ทุกครั้งที่คุณทำเช่นนี้มันจะเพิ่มจำนวนใบหน้าเป็นสี่เท่าในโมเดล

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

i0,, i1และi2จุดยอดของสามเหลี่ยมดั้งเดิม (ที่จริงแล้วดัชนีลงในบัฟเฟอร์จุดสุดยอด แต่นั่นเป็นหัวข้ออื่น) m01เป็นจุดกึ่งกลางของขอบ(i0,i1), m12 เป็นจุดกึ่งกลางของขอบ(i1,12)และเป็นเห็นได้ชัดว่าจุดกึ่งกลางของขอบm02(i0,i2)

เมื่อใดก็ตามที่คุณแบ่งใบหน้าให้แน่ใจว่าคุณไม่ได้สร้างจุดยอดที่ซ้ำกัน แต่ละจุดกึ่งกลางจะถูกแบ่งปันโดยใบหน้าต้นทางอื่น ๆ (เนื่องจากการแบ่งปันขอบระหว่างใบหน้า) รหัสด้านล่างบัญชีสำหรับสิ่งนั้นโดยการรักษาพจนานุกรมของจุดกึ่งกลางที่ตั้งชื่อซึ่งถูกสร้างขึ้นและส่งคืนดัชนีของจุดกึ่งกลางที่สร้างไว้ก่อนหน้านี้เมื่อมันพร้อมใช้งานแทนที่จะสร้างขึ้นใหม่

  • ทำซ้ำจนกว่าจะถึงจำนวนใบหน้าที่ต้องการสำหรับคิวบ์ของคุณ

  • เมื่อเสร็จแล้วให้ปรับจุดยอดทั้งหมดให้เป็นปกติเพื่อทำให้พื้นผิวเรียบ หากคุณไม่ทำเช่นนี้คุณจะได้รับ icosahedron ความละเอียดสูงกว่าแทนที่จะเป็นทรงกลม

  • Voila! คุณทำเสร็จแล้ว แปลงที่เกิดเวกเตอร์และดัชนีบัฟเฟอร์เป็นVertexBufferและและวาดด้วยIndexBufferDevice.DrawIndexedPrimitives()

นี่คือสิ่งที่คุณจะใช้ในคลาส "Sphere" เพื่อสร้างแบบจำลอง (ประเภทข้อมูล 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 นั้นไม่สำคัญตราบใดที่ฉันสามารถตั้งค่าความละเอียดได้
Daniel Pendergast

ไม่ใช่ Unity (XNA) แต่จะให้พิกัดจุดยอดและรายการดัชนี แทนที่ Vector3 ด้วยค่า Unity ที่เทียบเท่ากัน คุณตั้งค่าความละเอียดโดยการปรับจำนวนของการแบ่งย่อยการวนซ้ำ การวนซ้ำแต่ละครั้งจะคูณจำนวนใบหน้าด้วยการวนซ้ำ 4 หรือ 2 ซ้ำ 3 รอบจะให้รูปทรงกลมสวยงาม
3Dave

ฉันเข้าใจแล้ว มันเกือบจะเหมือนกับ Unity C # คำถามสองสามข้อ ... ทำไมเมื่อกำหนดดัชนีคุณวางมันไว้ในintอาร์เรย์? และ.Select(i => i + vertices.Count)ทำอะไร
Daniel Pendergast

.Select(i => i + vertices.Count)ไม่ทำงานสำหรับฉันเลย มันเป็นคุณสมบัติของ XNA หรือไม่?
Daniel Pendergast

1
ตรวจสอบให้แน่ใจว่าคุณกำลังรวม 'ใช้ System.Linq' ตามที่กำหนดไว้เลือก ฯลฯ
3 ต.ค.

5

ให้เราพิจารณาคำจำกัดความพารามิเตอร์ของทรงกลม:

นิยามเชิงพารามิเตอร์ของทรงกลม

โดยทีและพีเป็นมุมที่เพิ่มขึ้นสองมุมซึ่งเราจะอ้างถึงvar tและvar uและ Rx, Ry และ Rz เป็นรัศมีอิสระ (รัศมี) ในทั้งสามทิศทางคาร์ทีเซียนซึ่งในกรณีของทรงกลมจะถูกกำหนดให้เป็นหนึ่งเดียว var radรัศมี

ให้เราพิจารณาความจริงที่ว่า...สัญลักษณ์บ่งชี้การวนซ้ำซึ่งบอกใบ้ให้ใช้การวนซ้ำ แนวคิดของstacksและrowsคือ "คุณจะย้ำกี่ครั้ง" เนื่องจากการวนซ้ำแต่ละครั้งจะเพิ่มค่าของ t หรือ u ยิ่งการวนซ้ำยิ่งมีค่าน้อยลงดังนั้นยิ่งความโค้งของทรงกลมมีความแม่นยำมากขึ้น

เงื่อนไขฟังก์ชั่น 'ทรงกลมวาดภาพ' int latitudes, int longitudes, float radiusคือการมีพารามิเตอร์ที่กำหนดต่อไปนี้: เงื่อนไขการโพสต์ (เอาท์พุท) คือการส่งคืนหรือใช้จุดยอดที่คำนวณได้ ฟังก์ชั่นอาจคืนค่าอาร์เรย์vector3(เวกเตอร์สามมิติ) หรือถ้าคุณใช้ OpenGL แบบง่ายบางประเภทก่อนรุ่น 2.0 คุณอาจต้องการใช้จุดยอดกับบริบทโดยตรง

NB การใช้จุดสุดยอดใน 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 ...
    }
}

(โปรดทราบว่าลูปนั้นเป็นแบบรวมของเงื่อนไขเทอร์มินัลเนื่องจากช่วงเวลาสำหรับการรวมพารามาติกมีค่าตั้งแต่ 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 และไม่ใช่จาวาสคริปต์ รวมถึง 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.

}

ป.ล. คุณอาจสังเกตเห็นคำสั่ง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

@ Davididively ขอบคุณที่ชี้ให้เห็นว่าฉันเขียนมันกลับมาแล้วดังนั้นฉันไม่แปลกใจถ้ามันไม่ดีหรือผิดเลย
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ครั้ง

ขออภัยนี่ไม่ใช่คำตอบที่สมบูรณ์หรือเป็นเอกภาพโดยเฉพาะ แต่หวังว่ามันจะช่วยให้คุณเริ่มต้นได้


นี่ค่อนข้างดีถ้าคุณต้องการทรงกลมละติจูด / ลองจิจูด แต่คุณสามารถทำให้มันเรียบง่ายขึ้นเล็กน้อยโดยการทำงานในพิกัดทรงกลมจนถึงขั้นตอนสุดท้าย
3 ต.ค.

1
ขอบคุณ @David ฉันเห็นด้วยถ้าฉันได้เขียนบทความโดยใช้ coords ทรงกลมฉันจะโพสต์ที่นี่
MichaelHouse

3

คุณไม่สามารถเริ่มด้วยรูปร่างที่เรียบง่ายอาจเป็นกล่องที่มีระยะห่าง r จากกึ่งกลางไปจนถึงมุม หากต้องการสร้างทรงกลมที่มีรายละเอียดมากขึ้นให้แบ่งรูปหลายเหลี่ยมทั้งหมดแล้วเลื่อนจุดยอดออกไปเป็นระยะทาง r จากจุดศูนย์กลางโดยให้เวกเตอร์ผ่านตำแหน่งปัจจุบัน

ทำซ้ำจนกว่าจะเป็นทรงกลมพอสำหรับรสนิยมของคุณ


นี่เป็นหลักการเดียวกับวิธีการสร้างวิหาร ข้อดีอย่างหนึ่งของการเริ่มต้นด้วยลูกบาศก์ที่ฉันไม่คิดว่าจะกล่าวถึง: มันง่ายกว่ามากในการสร้างแผนที่ UV ที่ดีเพราะคุณสามารถใช้ cubemap และรู้ว่าตะเข็บพื้นผิวของคุณจะเรียงกันอย่างสมบูรณ์กับขอบในตาข่ายทรงกลมของคุณ
Steven Stadnicki

@StevenStadnicki ปัญหาเดียวที่ฉันมีกับคิวบ์คือใบหน้ามีแนวโน้มที่จะจบลงด้วยขนาดที่แตกต่างกันมากหลังจากการย่อยไม่กี่ครั้ง
3Dave

@DavidLively นั้นขึ้นอยู่กับว่าคุณแบ่งย่อยอย่างไร - ถ้าคุณสับใบหน้าสี่เหลี่ยมของคิวบ์ของคุณให้เป็นกริดคู่แล้วฉายออกไปด้านนอก / ทำให้ปกติแล้วนั่นเป็นเรื่องจริง แต่ถ้าคุณกริดใบหน้าของคุณไม่เหมือนกัน การฉายภาพจะเว้นระยะเท่ากันตามส่วนโค้งของขอบ ที่เปิดใช้งานได้ค่อนข้างดี
Steven Stadnicki

@StevenStadnicki ดี!
3 ต.ค.

@EricJohansson btw ในฐานะที่เป็นครูฉันรู้สึกว่าต้องพูดถึงว่านี่เป็นข้อมูลเชิงลึกที่สำคัญมากสำหรับคนที่เห็นได้ชัดว่าไม่เคยเห็นวิธีการจัดสรรมาก่อน คุณได้สร้างความเชื่อมั่นให้กับมนุษยชาติในอีก 12 ชั่วโมงข้างหน้า
3 ต.ค.

2

คุณต้องการเรขาคณิต 3 มิติหรือเพียงแค่รูปร่างจริง ๆ หรือไม่?

คุณสามารถสร้างทรงกลม 'ปลอม' โดยใช้รูปสี่เหลี่ยมเดียว เพียงแค่ใส่วงกลมบนมันและแรเงาอย่างถูกต้อง นี่เป็นข้อดีที่จะต้องมีความละเอียดที่แน่นอนโดยไม่คำนึงถึงระยะทางจากกล้องหรือความละเอียด

มีความเป็นกวดวิชาที่นี่


1
แฮ็คที่ดี แต่ล้มเหลวหากคุณต้องการพื้นผิว
3 ต.ค.

@DavidLively มันเป็นไปได้ที่จะคำนวณพิกัดพื้นผิวต่อพิกเซลโดยขึ้นอยู่กับการหมุนของมันยกเว้นว่าคุณต้องการพื้นผิวรูปหลายเหลี่ยมแบบเอกเทศ
เดวิดซี. บิชอป

@DavidCBishop คุณจะต้องคำนึงถึง "lensing" ของพื้นผิว - coords texel ถูกบีบใกล้กับขอบวงกลมเนื่องจากมุมมอง - ณ จุดที่คุณแกล้งหมุน นอกจากนี้ยังเกี่ยวข้องกับการเคลื่อนย้ายงานจำนวนมากไปยังตัวแบ่งพิกเซลซึ่งสามารถทำได้ในจุดสุดยอด (และเราทุกคนรู้ว่า VS มีราคาถูกกว่ามาก!)
3

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

แม้ว่าเดวิดตอบถูกต้องอย่างแน่นอน แต่ฉันต้องการเสนอมุมมองที่ต่างออกไป

สำหรับงานมอบหมายของฉันสำหรับการสร้างเนื้อหาขั้นตอนฉันดูที่ icosahedron เทียบกับทรงกลมแบ่งย่อยแบบดั้งเดิมมากขึ้น ดูทรงกลมที่สร้างขึ้นตามขั้นตอนเหล่านี้:

ทรงกลมที่น่ากลัว

ทั้งสองดูเหมือนทรงกลมที่ใช้ได้จริงใช่มั้ย เรามาดู wireframes กันดีกว่า:

ว้าวหนาแน่น

เกิดอะไรขึ้นที่นั่น รุ่น wireframe ของทรงกลมที่สองนั้นมีความหนาแน่นสูงจนดูเหมือนพื้นผิว! ฉันจะให้คุณเป็นความลับ: รุ่นที่สองเป็นไอโซโทป มันเป็นทรงกลมเกือบสมบูรณ์แบบ แต่มันมาในราคาสูง

Sphere 1 ใช้ 31 ส่วนย่อยในแกน x และ 31 ส่วนย่อยบนแกน z รวมเป็น3,844ใบหน้า

Sphere 2 ใช้การแบ่งซ้ำ 5 ครั้งรวมเป็น109,220ใบหน้า

แต่ไม่เป็นไรมันไม่ยุติธรรมเลย มาลดคุณภาพลงอย่างมาก:

เป็นก้อน

Sphere 1 ใช้การแบ่ง 5 ส่วนบนแกน x และ 5 ส่วนย่อยบนแกน z รวมเป็น 100 ใบหน้า

Sphere 2 ใช้การแบ่งย่อยแบบเรียกซ้ำ 0 ครั้งรวมเป็น 100 ใบหน้า

พวกเขาใช้ใบหน้าจำนวนเท่ากัน แต่ในความคิดของฉันทรงกลมด้านซ้ายดูดีกว่า มันดูเป็นก้อนน้อยลงและมีรอบมากขึ้น เรามาดูกันว่าเราสร้างใบหน้าด้วยวิธีใดบ้าง

ฮอว์คิง:

  • ระดับ 0 - 100 ใบหน้า
  • ระดับ 1 - 420 ใบหน้า
  • ระดับ 2 - 1,700 ใบหน้า
  • ระดับ 3 - 6,820 หน้า
  • ระดับ 4 - 27,300 ใบหน้า
  • ระดับ 5 - 109,220 ใบหน้า

ทรงกลมแบ่ง:

  • YZ: 5 - 100 ใบหน้า
  • YZ: 10 - 400 ใบหน้า
  • YZ: 15 - 900 ใบหน้า
  • YZ: 20 - 1,600 ใบหน้า
  • YZ: 25 - 2,500 ใบหน้า
  • YZ: 30 - 3,600 ใบหน้า

อย่างที่คุณเห็นไอโซเฮดตรอนจะเพิ่มจำนวนใบหน้าในอัตราเลขยกกำลังเป็นพลังงานดวงที่สาม! นั่นเป็นเพราะสำหรับสามเหลี่ยมทุกรูปเราต้องแบ่งมันออกเป็นสามสามเหลี่ยมใหม่

ความจริงก็คือ: คุณไม่ต้องการความแม่นยำเท่าที่ icosahedron จะให้คุณ เพราะทั้งคู่ซ่อนปัญหาที่หนักกว่ามากนั่นคือการสร้างพื้นผิวระนาบ 2 มิติบนทรงกลม 3 มิติ นี่คือลักษณะที่ปรากฏบน:

สุดยอดครับ

ที่ด้านบนซ้ายคุณสามารถเห็นพื้นผิวที่ใช้ บังเอิญมันก็ถูกสร้างขึ้นตามขั้นตอน (เฮ้มันเป็นหลักสูตรเกี่ยวกับการสร้างขั้นตอนใช่ไหม?)

มันดูแย่ใช่มั้ย นี่มันดีเท่าที่จะได้รับ ฉันได้รับคะแนนสูงสุดสำหรับการทำแผนที่พื้นผิวของฉันเพราะคนส่วนใหญ่ไม่ได้ทำให้ถูกต้อง

ดังนั้นโปรดพิจารณาการใช้โคไซน์และไซน์เพื่อสร้างทรงกลม มันสร้างใบหน้าน้อยลงมากสำหรับรายละเอียดในจำนวนเดียวกัน


6
ฉันเกรงว่าฉันสามารถลงคะแนนได้เท่านั้น เกล็ดน้ำแข็งมีการกระจายตัวแบบทวีคูณ? นั่นเป็นเพียงเพราะคุณตัดสินใจว่าคุณควรปรับขยายชี้แจง ทรงกลม UV สร้างใบหน้าน้อยกว่าไอโซสเฟียร์สำหรับรายละเอียดจำนวนเท่ากัน? นั่นเป็นสิ่งที่ผิดผิดอย่างสิ้นเชิงย้อนกลับไปโดยสิ้นเชิง
sam hocevar

4
การจัดสรรไม่จำเป็นต้องเรียกซ้ำ คุณสามารถแบ่งขอบของสามเหลี่ยมออกเป็นส่วนเท่า ๆ กันได้มากเท่าที่คุณต้องการ การใช้Nชิ้นส่วนจะให้N*Nสามเหลี่ยมใหม่ซึ่งเป็นสมการกำลังสองเหมือนกับที่คุณทำกับ UV-sphere
sam hocevar

6
ฉันต้องเพิ่มว่าทรงกลมที่คุณพูดจะดู "เป็นก้อนน้อยลงและมีรอบมากขึ้น" นั้นถูกมองจากมุมที่ดีที่สุด เพียงทำภาพหน้าจอเดียวกันกับทรงกลมที่ดูจากด้านบนเพื่อดูว่าฉันหมายถึงอะไร
sam hocevar

4
นอกจากนี้หมายเลข icosahedron ของคุณก็ดูไม่ถูกต้องเช่นกัน ระดับ 0 คือ 20 ใบหน้า (ตามคำจำกัดความ) จากนั้น 80, 320, 1280 เป็นต้นคุณสามารถแบ่งย่อยในจำนวนใดก็ได้และรูปแบบใดก็ได้ที่คุณต้องการ ความเรียบของแบบจำลองจะถูกกำหนดโดยจำนวนและการกระจายของใบหน้าในผลลัพธ์สุดท้าย (โดยไม่คำนึงถึงวิธีการสร้างใบหน้า) และเราต้องการรักษาขนาดของใบหน้าแต่ละหน้าให้เท่ากันมากที่สุด (ไม่มีขั้ว) บีบ) เพื่อรักษาโปรไฟล์ที่สอดคล้องโดยไม่คำนึงถึงมุมมอง เพิ่มไปที่ความจริงที่ว่ารหัสการแบ่งเป็นเรื่องง่ายกว่ามาก (imho) ...
3Dave

2
มีการใส่งานบางส่วนลงในคำตอบนี้ซึ่งทำให้ฉันรู้สึกแย่เกี่ยวกับการ downvoting แต่มันผิดอย่างสมบูรณ์และอย่างเต็มที่ดังนั้นฉันจึงต้อง ไอโซสเฟียร์ที่มีรูปทรงกลมที่สมบูรณ์แบบที่เติมเต็มทั้งหน้าจอใน FullHD ต้องการการแบ่ง 5 ส่วนโดยที่ไอโซโทปพื้นฐานนั้นไม่มีส่วนย่อย icosahedron ไม่มีส่วนย่อยไม่มี 100 ใบหน้ามี 20 ใบหน้า Icosa = 20 เป็นชื่อ! แต่ละส่วนย่อยจะคูณจำนวนใบหน้าด้วย 4 ดังนั้น 1-> 80, 2-> 320, 3-> 1280, 4-> 5120, 5-> 20,480 เราต้องการใบหน้าอย่างน้อย 40,000 ใบหน้าเพื่อให้ได้ทรงกลมที่เท่าเทียมกัน
ปีเตอร์

-1

สคริปต์ด้านล่างจะสร้าง Icosahedron ที่มี n รูปหลายเหลี่ยม ... ฐาน 12 นอกจากนี้ยังจะแบ่งรูปหลายเหลี่ยมออกเป็นตาข่ายแยกต่างหากและคำนวณรวม verts-ซ้ำและรูปหลายเหลี่ยม

ฉันไม่พบสิ่งที่คล้ายกันดังนั้นฉันจึงสร้างสิ่งนี้ เพียงแนบสคริปต์กับ 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;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.