CreateIcoSphere
From Unify Community Wiki
Revision as of 16:48, 26 October 2014 by Jānis Lukss (Talk | contribs)
Author: Michael Garforth.
Based on IcoSphere script in ProceduralPrimitives and CreatePlane, UV textures are bugged though, there is a reason a normal sphere is also called "UV sphere".
Usage
Place this script as "CreateIcoSphere.cs" in YourProject/Assets/Editor and a menu item will automatically appear in the "GameObject/Create Other" menu after it is compiled.
Add Collider creates a box collider for the plane if checked.
Create At Origin creates the plane at the origin (0,0,0) if checked; otherwise it's created a little ways in front of the editor camera.
It will create a mesh asset named either "IcoSphere" or "OptionalName" in the "YourProject/Assets/Editor" folder. Appended to the name is the number of segments and orientation.
C# - CreateIcoSphere.cs
using UnityEngine; using UnityEditor; using System.Collections; using System.Collections.Generic; public class CreateIcoSphere : ScriptableWizard { // Not implemented public enum AnchorPoint { TopLeft, TopHalf, TopRight, RightHalf, BottomRight, BottomHalf, BottomLeft, LeftHalf, Center } public int recursionLevel = 1; public float radius = 0.5f; private AnchorPoint anchor = AnchorPoint.Center; public bool addCollider = false; public bool createAtOrigin = true; public string optionalName; static Camera cam; static Camera lastUsedCam; [MenuItem("GameObject/Create Other/IcoSphere...")] static void CreateWizard() { cam = Camera.current; // Hack because camera.current doesn't return editor camera if scene view doesn't have focus if (!cam) cam = lastUsedCam; else lastUsedCam = cam; ScriptableWizard.DisplayWizard("Create IcoSphere",typeof(CreateIcoSphere)); } void OnWizardUpdate() { recursionLevel = Mathf.Clamp(recursionLevel, 1, 3); } private struct TriangleIndices { public int v1; public int v2; public int v3; public TriangleIndices(int v1, int v2, int v3) { this.v1 = v1; this.v2 = v2; this.v3 = v3; } } // return index of point in the middle of p1 and p2 private static int getMiddlePoint(int p1, int p2, ref List<Vector3> vertices, ref Dictionary<long, int> cache, float radius) { // first check if we have it already bool firstIsSmaller = p1 < p2; long smallerIndex = firstIsSmaller ? p1 : p2; long greaterIndex = firstIsSmaller ? p2 : p1; long key = (smallerIndex << 32) + greaterIndex; int ret; if (cache.TryGetValue(key, out ret)) { return ret; } // not in cache, calculate it Vector3 point1 = vertices[p1]; Vector3 point2 = vertices[p2]; Vector3 middle = new Vector3 ( (point1.x + point2.x) / 2f, (point1.y + point2.y) / 2f, (point1.z + point2.z) / 2f ); // add vertex makes sure point is on unit sphere int i = vertices.Count; vertices.Add( middle.normalized * radius ); // store it, return index cache.Add(key, i); return i; } void OnWizardCreate() { GameObject sphere = new GameObject(); if (!string.IsNullOrEmpty(optionalName)) sphere.name = optionalName; else sphere.name = "IcoSphere"; if (!createAtOrigin && cam) sphere.transform.position = cam.transform.position + cam.transform.forward*5.0f; else sphere.transform.position = Vector3.zero; MeshFilter filter = (MeshFilter)sphere.AddComponent(typeof(MeshFilter)); sphere.AddComponent(typeof(MeshRenderer)); string anchorId; switch (anchor) { case AnchorPoint.Center: default: anchorId = "C"; break; } string sphereAssetName = sphere.name + recursionLevel + anchorId + ".asset"; Mesh mesh = (Mesh)AssetDatabase.LoadAssetAtPath("Assets/Editor/" + sphereAssetName,typeof(Mesh)); if (mesh == null) { mesh = new Mesh(); mesh.name = sphere.name; List<Vector3> vertList = new List<Vector3>(); Dictionary<long, int> middlePointIndexCache = new Dictionary<long, int>(); // create 12 vertices of a icosahedron float t = (1f + Mathf.Sqrt(5f)) / 2f; vertList.Add(new Vector3(-1f, t, 0f).normalized * radius); vertList.Add(new Vector3( 1f, t, 0f).normalized * radius); vertList.Add(new Vector3(-1f, -t, 0f).normalized * radius); vertList.Add(new Vector3( 1f, -t, 0f).normalized * radius); vertList.Add(new Vector3( 0f, -1f, t).normalized * radius); vertList.Add(new Vector3( 0f, 1f, t).normalized * radius); vertList.Add(new Vector3( 0f, -1f, -t).normalized * radius); vertList.Add(new Vector3( 0f, 1f, -t).normalized * radius); vertList.Add(new Vector3( t, 0f, -1f).normalized * radius); vertList.Add(new Vector3( t, 0f, 1f).normalized * radius); vertList.Add(new Vector3(-t, 0f, -1f).normalized * radius); vertList.Add(new Vector3(-t, 0f, 1f).normalized * radius); // create 20 triangles of the icosahedron List<TriangleIndices> faces = new List<TriangleIndices>(); // 5 faces around point 0 faces.Add(new TriangleIndices(0, 11, 5)); faces.Add(new TriangleIndices(0, 5, 1)); faces.Add(new TriangleIndices(0, 1, 7)); faces.Add(new TriangleIndices(0, 7, 10)); faces.Add(new TriangleIndices(0, 10, 11)); // 5 adjacent faces faces.Add(new TriangleIndices(1, 5, 9)); faces.Add(new TriangleIndices(5, 11, 4)); faces.Add(new TriangleIndices(11, 10, 2)); faces.Add(new TriangleIndices(10, 7, 6)); faces.Add(new TriangleIndices(7, 1, 8)); // 5 faces around point 3 faces.Add(new TriangleIndices(3, 9, 4)); faces.Add(new TriangleIndices(3, 4, 2)); faces.Add(new TriangleIndices(3, 2, 6)); faces.Add(new TriangleIndices(3, 6, 8)); faces.Add(new TriangleIndices(3, 8, 9)); // 5 adjacent faces faces.Add(new TriangleIndices(4, 9, 5)); faces.Add(new TriangleIndices(2, 4, 11)); faces.Add(new TriangleIndices(6, 2, 10)); faces.Add(new TriangleIndices(8, 6, 7)); faces.Add(new TriangleIndices(9, 8, 1)); // refine triangles for (int i = 0; i < recursionLevel; i++) { List<TriangleIndices> faces2 = new List<TriangleIndices>(); foreach (var tri in faces) { // replace triangle by 4 triangles int a = getMiddlePoint(tri.v1, tri.v2, ref vertList, ref middlePointIndexCache, radius); int b = getMiddlePoint(tri.v2, tri.v3, ref vertList, ref middlePointIndexCache, radius); int c = getMiddlePoint(tri.v3, tri.v1, ref vertList, ref middlePointIndexCache, radius); faces2.Add(new TriangleIndices(tri.v1, a, c)); faces2.Add(new TriangleIndices(tri.v2, b, a)); faces2.Add(new TriangleIndices(tri.v3, c, b)); faces2.Add(new TriangleIndices(a, b, c)); } faces = faces2; } mesh.vertices = vertList.ToArray(); List<int> triList = new List<int>(); for( int i = 0; i < faces.Count; i++ ) { triList.Add( faces[i].v1 ); triList.Add( faces[i].v2 ); triList.Add( faces[i].v3 ); } mesh.triangles = triList.ToArray(); var nVertices = mesh.vertices; Vector2[] UVs = new Vector2[nVertices.Length]; for(var i= 0; i < nVertices.Length; i++){ var unitVector = nVertices[i].normalized; Vector2 ICOuv = new Vector2(0, 0); ICOuv.x = (Mathf.Atan2(unitVector.x, unitVector.z) + Mathf.PI) / Mathf.PI / 2; ICOuv.y = (Mathf.Acos(unitVector.y) + Mathf.PI) / Mathf.PI - 1; UVs[i] = new Vector2(ICOuv.x, ICOuv.y); } mesh.uv = UVs; Vector3[] normales = new Vector3[ vertList.Count]; for( int i = 0; i < normales.Length; i++ ) normales[i] = vertList[i].normalized; mesh.normals = normales; mesh.RecalculateBounds(); mesh.Optimize(); AssetDatabase.CreateAsset(mesh, "Assets/Editor/" + sphereAssetName); AssetDatabase.SaveAssets(); } filter.sharedMesh = mesh; mesh.RecalculateBounds(); if (addCollider) sphere.AddComponent(typeof(BoxCollider)); Selection.activeObject = sphere; } }