CreateIcoSphere

From Unify Community Wiki
Revision as of 16:48, 26 October 2014 by Jānis Lukss (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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;
	}
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox