1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
public class GeneratePlanet : GenerateIcosphere {
    /*
     * Inheritates GenerateIcosphere to create a planet and
     * also to create a reference position pivot object for each piece.
     * The shader provided by specifying color information for each Vertex allows the planet to be monochrome,
     * and the texture problems that appear in icosphere have also been corrected to enable material application.
     * 
     * 지오데식 구 생성 기능을 담당하는 GenerateIcosphere 클래스를 상속받아 행성을 생성합니다. (2창 참고)
     * 또한 각 행성 조각들의 중심 담당 오브젝트 또한 여기서 생성합니다.
     * 기본적으로 버테스 안에 있는 색상정보를 통해 단색으로 이루어진 행성 표면을 표면하며,
     * 메테리얼을 적용할때 발생하는 텍스처 문제 (지퍼, 극점공유) 또한 해결하였습니다.
     */ 
 
    [Header("GeneratePlanetSetting")]
    public int generateMapRange = 0;
    public int planetDetail = 1;
    public float planetScale = 5f;
 
    [Header("PivotObjectSetting")]
    public bool useCenterPivotObject = false;
    public bool useOutlinePivotObject = false;
    public bool OutlinePivotObjectOverlapCorrect = false;
 
    [Header("TextureSetting")]
    public Material planetMaterial;
    public Color32 color1 = new Color32(201480255);
    public Color32 color2 = new Color32(1481280255);
 
    private List<GameObject> planet;
    
    //Perform all functions of this script
    //1. Checks whether a planet is already created or has a vaild value
    //2. Create an icosphere-based mesh and store planetary fragment information
    //3, Create a map planet based on 2. It also modifies texture coordinates
    //4. Create a pivot object based on each piece based on user selection
    //5. Resizes and position the planets
 
    /*
     * 1. 행성이 중복 생성되었는지, 입력된 옵션 값이 문제가 없는지 확인합니다.
     * 2. 지오데식 기반 구체 메쉬 생성 및 이것을 구성하는 조각들의 정보를 저장합니다.
     * 3. 2번을 바탕으로 행성을 생성합니다. 또한 텍스처도 적용합니다.
     * 4. 유저 선택에 따라 추가로 각 행성 조각 중심 담당 오브젝트를 생성합니다.
     * 5. 행성의 크기를 조정합니다.
     */
    public void _GeneratePlanet()
    {
        if(generateMapRange >= planetDetail)
        {
            Debug.LogError("GenerateMapRange value must be less than the PlanetDetail value!");
            return;
        }
 
        if(planet != null)
        {
            foreach(GameObject piece in planet)
            {
                DestroyImmediate(piece);
            }
            planet.Clear();
        }
 
        /*
         * 정 20면체 생성 후 이것을 바탕으로 행성을 제작합니다.
         * 사용자가 원하는 디테일 만큼 구체를 매끈하게 다듬고, 원하는 조각 만큼 행성 조각을 생성합니다.
         * 해당 메소드 설명은 2장 참고해주세요.
         */
        base.InitIcosahedron();
        base.SubdividedTriangles(planetDetail, generateMapRange);
 
        CreatePlanet();
 
        if (useCenterPivotObject) GeneratePieceCenterPivotObject();
        if (useOutlinePivotObject) GeneratePieceOutlinePivotObject();
 
        gameObject.transform.localScale = Vector3.one * planetScale;
        gameObject.transform.position = Vector3.zero;
    }
 
    //Create a planet based on each piece information
    //_GeneratePlanet 메소드에서 생성된 행성 정보를 바탕으로 각 조각들에 메테리얼 및 컬라이더를 추가합니다.
    //또한 Hierachy에서 관리를 쉽게 하기 위한 정리 또한 수행합니다.
    private void CreatePlanet()
    {
        planet = new List<GameObject>();
 
        for(int i = 0; i < base.generateSurface.Count; i++)
        {
            GameObject piece = new GameObject();
            MeshRenderer meshRenderer = piece.AddComponent<MeshRenderer>();
            meshRenderer.material = planetMaterial;
 
            MeshFilter meshFilter = piece.AddComponent<MeshFilter>();
            meshFilter.mesh = GenerateMesh(base.generateSurface[i]);
 
            MeshCollider meshCollider = piece.AddComponent<MeshCollider>();
            meshCollider.convex = true;
 
            piece.transform.parent = gameObject.transform;
            piece.name = "PlanetSurface_" + i.ToString();
            piece.tag = gameObject.transform.tag;
            planet.Add(piece);
        }
    }
 
    //Generates a mesh based on stored vertex information
    //It also modifies each vertex color information and uv coordinates
    //저장된 정점 정보를 바탕으로 메쉬를 생성합니다.
    //또한 정점마다 색상 정보와 함께 텍스처 범위도 적용합니다.
    private Mesh GenerateMesh(List<TrianglePoly> piecePoly)
    {
        Mesh mesh = new Mesh();
        int vertexesCount = piecePoly.Count * 3;
        int[] indies = new int[vertexesCount];
        Vector3[] vertexes = new Vector3[vertexesCount];
        Vector3[] normals = new Vector3[vertexesCount];
        Color32[] colors = new Color32[vertexesCount];
        Color32 groundColor = Color32.Lerp(color1, color2, Random.Range(0f, 1f));
        
        for(int i = 0; i < piecePoly.Count; i++)
        {
            TrianglePoly tri = piecePoly[i];
            
            indies[i * 3 + 0= i * 3 + 0;
            indies[i * 3 + 1= i * 3 + 1;
            indies[i * 3 + 2= i * 3 + 2;
 
            vertexes[i * 3 + 0= base.vertexes[tri.triA];
            vertexes[i * 3 + 1= base.vertexes[tri.triB];
            vertexes[i * 3 + 2= base.vertexes[tri.triC];
 
            normals[i * 3 + 0= base.vertexes[tri.triA];
            normals[i * 3 + 1= base.vertexes[tri.triB];
            normals[i * 3 + 2= base.vertexes[tri.triC];
 
            colors[i * 3 + 0= groundColor;
            colors[i * 3 + 1= groundColor;
            colors[i * 3 + 2= groundColor;
        }
 
        mesh.vertices = vertexes;
        mesh.normals = normals;
        mesh.colors32 = colors;
 
        Vector2[] uv = CreateUV(vertexes);
        mesh.uv = uv;
 
        mesh.SetTriangles(indies, 0);
 
        return mesh;
    }
 
    //Spherical mapping calculation for point given on sphere
    //3차원 정점 좌표를 역삼각함수로 2차원 텍스처 좌표로 치환하여 반환합니다.
    private Vector2[] CreateUV(Vector3[] vertexes)
    {
        Vector2[] newUV = new Vector2[vertexes.Length];
 
        for(int i = 0; i < vertexes.Length; i++)
        {
            Vector2 textureCoord;
            textureCoord.x = 0.5f + Mathf.Atan2(vertexes[i].x, vertexes[i].z) / (2f * Mathf.PI);
            textureCoord.y = 0.5f - Mathf.Asin(vertexes[i].y) / Mathf.PI;
            newUV[i] = textureCoord;
        }
 
        FixWrappedUV(newUV);
 
        return newUV;
    }
 
    //Modify Flipping piece and Fixing Polarity Sharing
    //텍스처 지퍼 문제를 일으키는 뒤집힌 조각을 검출하여(벡터 외적 계산을 이용) FixZipperUV 메소드에 넘깁니다.
    private void FixWrappedUV(Vector2[] uv)
    {
        List<int> FixUVVerticesIndex = new List<int>();
 
        for (int i = 0; i < uv.Length / 3; i++)
        {
            Vector3 texA = new Vector3(uv[i * 3 + 0].x,
                                       uv[i * 3 + 0].y,
                                       0);
            Vector3 texB = new Vector3(uv[i * 3 + 1].x,
                                       uv[i * 3 + 1].y,
                                       0);
            Vector3 texC = new Vector3(uv[i * 3 + 2].x,
                                       uv[i * 3 + 2].y,
                                       0);
            //삼각형을 이루는 3개의 벡터 중 2개의 벡터의 외적을 계산하여 양수가 나온다면 뒤집혀 있습니다.
            Vector3 texNormal = Vector3.Cross(texB - texA, texC - texA);
 
            if (texNormal.z > 0)
            {
                FixUVVerticesIndex.Add(i);
            }
        }
 
        FixZipperUV(FixUVVerticesIndex, uv);
        FixPoleUV(uv);
    }
 
    //Fix the flipping piece to solve the zipper problem
    //뒤집한 행성에 대한 텍스처를 뒤집어 주어 지퍼 문제를 해결합니다.
    private void FixZipperUV(List<int> fixUVIndex, Vector2[] uv)
    {
        foreach (int index in fixUVIndex)
        {
            if (uv[index * 3 + 0].x < 0.25f)
            {
                Vector2 tempUVA = uv[index * 3 + 0];
                tempUVA.x += 1f;
                uv[index * 3 + 0= tempUVA;
            }
            if (uv[index * 3 + 1].x < 0.25f)
            {
                Vector2 tempUVB = uv[index * 3 + 1];
                tempUVB.x += 1f;
                uv[index * 3 + 1= tempUVB;
            }
            if (uv[index * 3 + 2].x < 0.25f)
            {
                Vector2 tempUVC = uv[index * 3 + 2];
                tempUVC.x += 1f;
                uv[index * 3 + 2= tempUVC;
            }
        }
    }
 
    //Troubleshoot the polar sharing
    //공유된 북극, 남극 점을 객체만큼 복사하여 해결합니다. 
    private void FixPoleUV(Vector2[] uv)
    {
        for (int i = 0; i < uv.Length / 3; i++)
        {
            List<Vector2> triVtx = new List<Vector2>();
 
            triVtx.Add(uv[i * 3 + 0]);
            triVtx.Add(uv[i * 3 + 1]);
            triVtx.Add(uv[i * 3 + 2]);
 
            for (int j = 0; j < triVtx.Count; j++)
            {
                if (triVtx[j].y == 1)
                {
                    List<Vector2> newTriVtx = new List<Vector2>(triVtx);
                    Vector2 newNorth = newTriVtx[j];
                    newTriVtx.RemoveAt(j);
                    newNorth.x = (newTriVtx[0].x + newTriVtx[1].x) / 2;
                    uv[i * 3 + j] = newNorth;
                }
                if (triVtx[j].y == 0)
                {
                    List<Vector2> newTriVtx = new List<Vector2>(triVtx);
                    Vector2 newSouth = newTriVtx[j];
                    newTriVtx.RemoveAt(j);
                    newSouth.x = (newTriVtx[0].x + newTriVtx[1].x) / 2;
                    uv[i * 3 + j] = newSouth;
                }
            }
        }
    }
 
    //Create OutlinePivot Object. Choose to prevent pivot objects from being created in the same position.
    //각 행성 조각의 정점 피봇 오브젝트를 생성합니다.
    private void GeneratePieceOutlinePivotObject()
    {
        Dictionary<float, Vector3> cache = new Dictionary<float, Vector3>();
 
        for(int i = 0; i < planet.Count; i++)
        {
            Quaternion rot;
            initObjectPosition(planet[i].transform, base.outlinePositions[i], out rot);
 
            //정점 마다 생성되는 기준 오브젝트의 중복 생성 여부를 확인하는 조건문
            if (OutlinePivotObjectOverlapCorrect)
            {
                float outlineObjectKey;
 
                //해당 규칙을 이용하여 포지션에 해당하는 고유 키를 생성합니다. 이 키는 중복된 위치인지 검출하는데 사용합니다.
                outlineObjectKey = (base.outlinePositions[i].triA.x + 16* (base.outlinePositions[i].triA.y + 15* (base.outlinePositions[i].triA.z + 14);
 
                if (!cache.ContainsKey(outlineObjectKey))
                {
                    GameObject outlineObjA = new GameObject("OutlinePivotA");
                    outlineObjA.transform.position = base.outlinePositions[i].triA;
                    outlineObjA.transform.rotation = rot;
                    outlineObjA.transform.parent = planet[i].transform;
                    cache.Add(outlineObjectKey, base.outlinePositions[i].triA);
                }
 
                //해당 규칙을 이용하여 포지션에 해당하는 고유 키를 생성합니다. 이 키는 중복된 위치인지 검출하는데 사용합니다.
                outlineObjectKey = (base.outlinePositions[i].triB.x + 16* (base.outlinePositions[i].triB.y + 15* (base.outlinePositions[i].triB.z + 14);
 
                if (!cache.ContainsKey(outlineObjectKey))
                {
                    GameObject outlineObjB = new GameObject("OutlinePivotB");
                    outlineObjB.transform.position = base.outlinePositions[i].triB;
                    outlineObjB.transform.rotation = rot;
                    outlineObjB.transform.parent = planet[i].transform;
                    cache.Add(outlineObjectKey, base.outlinePositions[i].triB);
                }
 
                //해당 규칙을 이용하여 포지션에 해당하는 고유 키를 생성합니다. 이 키는 중복된 위치인지 검출하는데 사용합니다.
                outlineObjectKey = (base.outlinePositions[i].triC.x + 16* (base.outlinePositions[i].triC.y + 15* (base.outlinePositions[i].triC.z + 14);
 
                if (!cache.ContainsKey(outlineObjectKey))
                {
                    GameObject outlineObjC = new GameObject("OutlinePivotC");
                    outlineObjC.transform.position = base.outlinePositions[i].triC;
                    outlineObjC.transform.rotation = rot;
                    outlineObjC.transform.parent = planet[i].transform;
                    cache.Add(outlineObjectKey, base.outlinePositions[i].triC);
                }
            }
            else
            {
                GameObject outlineObjA = new GameObject("OutlinePivotA");
                outlineObjA.transform.position = base.outlinePositions[i].triA;
                outlineObjA.transform.rotation = rot;
                outlineObjA.transform.parent = planet[i].transform;
 
                GameObject outlineObjB = new GameObject("OutlinePivotB");
                outlineObjB.transform.position = base.outlinePositions[i].triB;
                outlineObjB.transform.rotation = rot;
                outlineObjB.transform.parent = planet[i].transform;
 
                GameObject outlineObjC = new GameObject("OutlinePivotC");
                outlineObjC.transform.position = base.outlinePositions[i].triC;
                outlineObjC.transform.rotation = rot;
                outlineObjC.transform.parent = planet[i].transform;
            }
        }
    }
 
    //Create Center pivot object
    private void GeneratePieceCenterPivotObject()
    {
        for (int i = 0; i < planet.Count; i++)
        {
            Quaternion rot;
            Vector3 centerPos = initObjectPosition(planet[i].transform, base.outlinePositions[i], out rot);
            GameObject centerPivot = new GameObject("CenterPivot");
            centerPivot.transform.position = centerPos;
            centerPivot.transform.rotation = rot;
 
            centerPivot.transform.parent = planet[i].transform;
        }
    }
 
    //Return the center position and the correct angle based on the outline position
    //중심과 외곽 위치를 바탕으로 올바른 오브젝트의 각도를 반환
    private Vector3 initObjectPosition(Transform objTransform, OutlineVector outLine, out Quaternion rotation)
    {
        Vector3 v1 = outLine.triB - outLine.triA;
        Vector3 v2 = outLine.triC - outLine.triA;
 
        Vector3 centerNormal = Vector3.Cross(v1, v2).normalized;
 
        rotation = Quaternion.FromToRotation(objTransform.up, centerNormal) * objTransform.rotation;
 
        return new Vector3(
            (outLine.triA.x + outLine.triB.x + outLine.triC.x) / 3f,
            (outLine.triA.y + outLine.triB.y + outLine.triC.y) / 3f,
            (outLine.triA.z + outLine.triB.z + outLine.triC.z) / 3f);
    }
}
cs

2장에서 소개한 지오데식 구 생성 클래스를 상속받아 행성 오브젝트 생성 및 각 행성 조각에 필요한 기준 오브젝트를 생성합니다.

Posted by Haedo
,