using UnityEngine;
using System.Collections;

public class WorldField : MonoBehaviour
{
	public const float FIELD_UV_MULT = 0.5f;
	public const float FIELD_HOLE_RADIUS = 0.75f;
	public const float FIELD_HOLE_OFFSET = 10.0f;
	public const float FIELD_HOLE_DEPTH = 0.25f*WorldFrame.FRAME_HEIGHT;
	public const float FIELD_HEIGHT = 0.98f*FIELD_HOLE_DEPTH;
	const int FIELD_HOLE_DETAIL_H = 32;
	const int THREE_QUARTERS = 3*FIELD_HOLE_DETAIL_H/4;
	const int FIELD_HOLE_DETAIL_V = 5;
	const int FIELD_QUADRANT_DETAIL = 9;
	const int FIELD_HOLE_NUM_VERTS = FIELD_HOLE_DETAIL_H*FIELD_HOLE_DETAIL_V;
	const int FIELD_QUADRANT_NUM_VERTS = FIELD_HOLE_DETAIL_H*FIELD_QUADRANT_DETAIL - (FIELD_HOLE_DETAIL_H/4+1);
	const int FIELD_HOLE_NUM_TRIS = 2*FIELD_HOLE_DETAIL_H*(FIELD_HOLE_DETAIL_V-1);
	const int FIELD_QUADRANT_NUM_TRIS = 2*FIELD_HOLE_DETAIL_H*(FIELD_QUADRANT_DETAIL-1);
	const int FIELD_QUADRANT_NUM_VERTS_TOTAL = FIELD_HOLE_NUM_VERTS + FIELD_QUADRANT_NUM_VERTS;
	const int FIELD_QUADRANT_NUM_TRIS_TOTAL = FIELD_HOLE_NUM_TRIS + FIELD_QUADRANT_NUM_TRIS;
	const float SQRT2 = 1.4142135623730950488016887242097f;

	public int index = 0;

	private static float Brightness(Vector3 RGB)
	{
		return Mathf.Max(1.0e-8f, 0.3f*RGB.x + 0.59f*RGB.y + 0.11f*RGB.z);
	}

	private static Color HSB_2_Color(Vector3 HSB)
	{
		Vector3 RGB = Vector3.zero;
		
		HSB.x -= Mathf.Floor(HSB.x);
		float f = 6*HSB.x - Mathf.Floor(6*HSB.x);
		switch (Mathf.FloorToInt(6*HSB.x))
		{
		case 0:
			RGB.x = 1;
			RGB.y = f;
			break;
		case 1:
			RGB.y = 1;
			RGB.x = 1-f;
			break;
		case 2:
			RGB.y = 1;
			RGB.z = f;
			break;
		case 3:
			RGB.z = 1;
			RGB.y = 1-f;
			break;
		case 4:
			RGB.z = 1;
			RGB.x = f;
			break;
		case 5:
			RGB.x = 1;
			RGB.z = 1-f;
			break;
		}
		
		RGB = Vector3.Lerp (RGB, Vector3.one, 1-HSB.y);

		return (new Color(RGB.x, RGB.y, RGB.z)) * (HSB.z/Brightness(RGB));
	}

	void Start ()
	{
		int num_verts = 4*FIELD_QUADRANT_NUM_VERTS_TOTAL+1;
		int num_tris = 4*FIELD_QUADRANT_NUM_TRIS_TOTAL;

		//Debug.Log("Field: " + num_verts + " vertices, " + num_tris + " tris");

		float index_scale = 1 - Mathf.Pow(2,-0.1f*index);

		Vector3[] p = new Vector3[num_verts];
		Vector3[] n = new Vector3[num_verts];
		Vector2[] uv = new Vector2[num_verts];
		Color[] col = new Color[num_verts];
		bool[] f = new bool[num_verts];
		bool[] f2 = new bool[num_verts];
		int[] nn = new int[num_verts];
		int[] t = new int[3*num_tris];

		Color ref_color = HSB_2_Color(new Vector3(Random.value, 0.5f + 0.4f*Random.value, 0.4f + 0.4f*Random.value));

		for (int j=0; j<num_verts; j++)
		{
			col[j] = ref_color;
		}

		p[num_verts-1].Set(0,FIELD_HOLE_DEPTH,0);
		//n[num_verts-1].Set(0,1,0);
		n[num_verts-1].Set(0,0,0);

		for (int i=0; i<4; i++)
		{
			int vs = i*FIELD_QUADRANT_NUM_VERTS_TOTAL;
			int ts = i*FIELD_QUADRANT_NUM_TRIS_TOTAL;
			int next_vs = ((i+3)&3)*FIELD_QUADRANT_NUM_VERTS_TOTAL;

			for (int b=0; b<FIELD_HOLE_DETAIL_H; b++) // hole shaft vertices
			{
				float phi = ((float)b)/FIELD_HOLE_DETAIL_H*2*Mathf.PI;
				float cs = Mathf.Cos(phi);
				float sn = Mathf.Sin(phi);

				for (int a=0; a<FIELD_HOLE_DETAIL_V; a++)
				{
					float y = ((float)a)/(FIELD_HOLE_DETAIL_V-1);
					float g = Mathf.Clamp (2*y*y*y - 0.25f,0,1);
					y = FIELD_HOLE_DEPTH*y*y;
					col[vs + b*FIELD_HOLE_DETAIL_V + a] *= g;
					p[vs + b*FIELD_HOLE_DETAIL_V + a].Set(FIELD_HOLE_RADIUS*cs, y, FIELD_HOLE_OFFSET + FIELD_HOLE_RADIUS*sn);
					n[vs + b*FIELD_HOLE_DETAIL_V + a].Set(-cs,0,-sn);
				}
			}

			for (int j=0; j<FIELD_HOLE_NUM_VERTS; j++) f[vs+j] = true;

			for (int b=0; b<FIELD_HOLE_DETAIL_H; b++) // hole shaft tris
			{
				int next_b = (b+1)%FIELD_HOLE_DETAIL_H;
				
				for (int a=0; a<FIELD_HOLE_DETAIL_V-1; a++)
				{
					t[3*(ts + 2*b*(FIELD_HOLE_DETAIL_V-1)+2*a+0) + 0] = vs + b*FIELD_HOLE_DETAIL_V + a+1;
					t[3*(ts + 2*b*(FIELD_HOLE_DETAIL_V-1)+2*a+0) + 1] = vs + b*FIELD_HOLE_DETAIL_V + a;
					t[3*(ts + 2*b*(FIELD_HOLE_DETAIL_V-1)+2*a+0) + 2] = vs + next_b*FIELD_HOLE_DETAIL_V + a;
					
					t[3*(ts + 2*b*(FIELD_HOLE_DETAIL_V-1)+2*a+1) + 0] = vs + next_b*FIELD_HOLE_DETAIL_V + a;
					t[3*(ts + 2*b*(FIELD_HOLE_DETAIL_V-1)+2*a+1) + 1] = vs + next_b*FIELD_HOLE_DETAIL_V + a+1;
					t[3*(ts + 2*b*(FIELD_HOLE_DETAIL_V-1)+2*a+1) + 2] = vs + b*FIELD_HOLE_DETAIL_V + a+1;
				}
			}

			for (int b=0; b<FIELD_HOLE_DETAIL_H; b++) // quadrant field vertices
			{
				float tarc = ((float)b)/FIELD_HOLE_DETAIL_H;
				float phi = tarc*2*Mathf.PI;
				float cs = Mathf.Cos(phi);
				float sn = Mathf.Sin(phi);

				float cs2 = Mathf.Cos((0.25f + tarc)*Mathf.PI);
				float sn2 = Mathf.Sin((0.25f + tarc)*Mathf.PI);

				int dv = ((b>0)&&(b<THREE_QUARTERS))?FIELD_QUADRANT_DETAIL:(FIELD_QUADRANT_DETAIL-1);
				int bu = (b+FIELD_HOLE_DETAIL_H-1)%FIELD_HOLE_DETAIL_H;
				for (int a=0; a<dv; a++)
				{
					float s = ((float)a)/(FIELD_QUADRANT_DETAIL-1);
					s += s*(Mathf.Sqrt(s)-s);

					//if ((i==0)&&(b==1)) Debug.Log(a + ": " + s);

					float bx=cs2;
					float bz=sn2;

					if (tarc>0.75f)
					{
						float tl = 2*(tarc-0.75f);
						bz = tl*SQRT2;
						bx = bz;
					}
					else if (tarc>0.5f)
					{
						float tl = 0.5f-2*(tarc-0.5f);
						bz = tl*SQRT2;
						bx = -bz;
					}

					bx *= WorldFrame.FRAME_RADIUS;
					bz *= WorldFrame.FRAME_RADIUS;

					//if ((a==0)&&(i==0)) Debug.Log(b + ": " + bx + " " + bz);

					p[vs + FIELD_HOLE_NUM_VERTS + a*FIELD_HOLE_DETAIL_H + bu].Set((1-s)*FIELD_HOLE_RADIUS*cs + s*bx, FIELD_HOLE_DEPTH, (1-s)*(FIELD_HOLE_OFFSET + FIELD_HOLE_RADIUS*sn) + s*bz);
					//n[vs + FIELD_HOLE_NUM_VERTS + a*FIELD_HOLE_DETAIL_H + bu].Set(0,1,0);
					n[vs + FIELD_HOLE_NUM_VERTS + a*FIELD_HOLE_DETAIL_H + bu].Set(0,0,0);
					if (a==0) f[vs + FIELD_HOLE_NUM_VERTS + a*FIELD_HOLE_DETAIL_H + bu] = true;
					if ((a==FIELD_QUADRANT_DETAIL-1) && (b<=FIELD_HOLE_DETAIL_H/2)) f2[vs + FIELD_HOLE_NUM_VERTS + a*FIELD_HOLE_DETAIL_H + bu] = true;
				}
			}

			for (int b=0; b<FIELD_HOLE_DETAIL_H; b++) // quadrant field tris
			{
				int bu = (b+FIELD_HOLE_DETAIL_H-1)%FIELD_HOLE_DETAIL_H;
				int next_b = (b+1)%FIELD_HOLE_DETAIL_H;
				int next_bu = (next_b+FIELD_HOLE_DETAIL_H-1)%FIELD_HOLE_DETAIL_H;
				
				for (int a=0; a<FIELD_QUADRANT_DETAIL-1; a++)
				{
					int p00 = vs + FIELD_HOLE_NUM_VERTS + a*FIELD_HOLE_DETAIL_H + bu;
					int p01 = vs + FIELD_HOLE_NUM_VERTS + (a+1)*FIELD_HOLE_DETAIL_H + bu;
					int p10 = vs + FIELD_HOLE_NUM_VERTS + a*FIELD_HOLE_DETAIL_H + next_bu;
					int p11 = vs + FIELD_HOLE_NUM_VERTS + (a+1)*FIELD_HOLE_DETAIL_H + next_bu;

					if (a == FIELD_QUADRANT_DETAIL-2)
					{
						if (b==0)
						{
							p01 = next_vs + FIELD_HOLE_NUM_VERTS + (a+1)*FIELD_HOLE_DETAIL_H + ((FIELD_HOLE_DETAIL_H/2+FIELD_HOLE_DETAIL_H-1)%FIELD_HOLE_DETAIL_H);
						}
						else if (b>THREE_QUARTERS)
						{
							p01 = next_vs + FIELD_HOLE_NUM_VERTS + (a+1)*FIELD_HOLE_DETAIL_H + ((THREE_QUARTERS-(b-THREE_QUARTERS)+FIELD_HOLE_DETAIL_H-1)%FIELD_HOLE_DETAIL_H);
						}
						else if (b==THREE_QUARTERS) p01 = num_verts-1;

						if (next_b==0)
						{
							p11 = next_vs + FIELD_HOLE_NUM_VERTS + (a+1)*FIELD_HOLE_DETAIL_H + ((FIELD_HOLE_DETAIL_H/2+FIELD_HOLE_DETAIL_H-1)%FIELD_HOLE_DETAIL_H);
						}
						else if (next_b>THREE_QUARTERS)
						{
							p11 = next_vs + FIELD_HOLE_NUM_VERTS + (a+1)*FIELD_HOLE_DETAIL_H + ((THREE_QUARTERS-(next_b-THREE_QUARTERS)+FIELD_HOLE_DETAIL_H-1)%FIELD_HOLE_DETAIL_H);
						}
						else if (next_b==THREE_QUARTERS) p11 = num_verts-1;
					}

					t[3*(ts + FIELD_HOLE_NUM_TRIS + a*2*FIELD_HOLE_DETAIL_H+2*b+0)+0] = p01;
					t[3*(ts + FIELD_HOLE_NUM_TRIS + a*2*FIELD_HOLE_DETAIL_H+2*b+0)+1] = p00;
					t[3*(ts + FIELD_HOLE_NUM_TRIS + a*2*FIELD_HOLE_DETAIL_H+2*b+0)+2] = p10;

					t[3*(ts + FIELD_HOLE_NUM_TRIS + a*2*FIELD_HOLE_DETAIL_H+2*b+1)+0] = p10;
					t[3*(ts + FIELD_HOLE_NUM_TRIS + a*2*FIELD_HOLE_DETAIL_H+2*b+1)+1] = p11;
					t[3*(ts + FIELD_HOLE_NUM_TRIS + a*2*FIELD_HOLE_DETAIL_H+2*b+1)+2] = p01;
				}
			}

			float gq_phi = 0.25f*i*2*Mathf.PI;
			float gq_cs = Mathf.Cos(gq_phi);
			float gq_sn = Mathf.Sin(gq_phi);

			for (int j=0; j<FIELD_QUADRANT_NUM_VERTS_TOTAL; j++)
			{
				p[vs+j].Set(gq_cs*p[vs+j].x - gq_sn*p[vs+j].z, p[vs+j].y, gq_cs*p[vs+j].z + gq_sn*p[vs+j].x);
				n[vs+j].Set(gq_cs*n[vs+j].x - gq_sn*n[vs+j].z, n[vs+j].y, gq_cs*n[vs+j].z + gq_sn*n[vs+j].x);
				if (!f[vs+j])
				{
					float rand_height = FIELD_HEIGHT*(2*Random.value-1);
					float fixed_height = (p[vs+j].x*p[vs+j].x + p[vs+j].z*p[vs+j].z)/(WorldFrame.FRAME_RADIUS*WorldFrame.FRAME_RADIUS);
					fixed_height = Mathf.Max(0,6*fixed_height-5);
					fixed_height *= 0.85f*FIELD_HEIGHT*fixed_height;
					p[vs+j].y += (1-index_scale)*fixed_height + index_scale*rand_height;
				}
			}
		}

		for (int j=0; j<num_tris; j++)
		{
			nn[t[3*j+0]] += 2;
			nn[t[3*j+1]] += 2;
			nn[t[3*j+2]] += 2;
		}

		Vector3[] diff = new Vector3[num_verts];
		float lambda = 0.5f;
		for (int k=0; k<20; k++)
		{
			for (int i=0; i<4; i++)
			{
				int ts = i*FIELD_QUADRANT_NUM_TRIS_TOTAL;

				for(int j=FIELD_HOLE_NUM_TRIS; j<FIELD_QUADRANT_NUM_TRIS_TOTAL; j++)
				{
					int a = t[3*(ts+j)+0];
					int b = t[3*(ts+j)+1];
					int c = t[3*(ts+j)+2];

					Vector3 A = p[a];
					Vector3 B = p[b];
					Vector3 C = p[c];

					diff[a] += B+C - 2*A;
					diff[b] += A+B - 2*B;
					diff[c] += A+C - 2*C;
				}
			}

			p[num_verts-1] += lambda*diff[num_verts-1]/nn[num_verts-1];
			diff[num_verts-1].Set(0,0,0);

			for (int i=0; i<4; i++)
			{
				int vs = i*FIELD_QUADRANT_NUM_VERTS_TOTAL;
				
				for(int j=FIELD_HOLE_NUM_VERTS; j<FIELD_QUADRANT_NUM_VERTS_TOTAL; j++)
				{
					if (f[vs+j]) continue;
					if (f2[vs+j])
					{
						diff[vs+j].x=0;
						diff[vs+j].y=Mathf.Max(0,diff[vs+j].y);
						diff[vs+j].z=0;
					}

					p[vs+j] += lambda*diff[vs+j]/nn[vs+j];
					diff[vs+j].Set(0,0,0);
				}
			}
		}

		for (int i=0; i<4; i++)
		{
			int ts = i*FIELD_QUADRANT_NUM_TRIS_TOTAL;
			
			for(int j=FIELD_HOLE_NUM_TRIS; j<FIELD_QUADRANT_NUM_TRIS_TOTAL; j++)
			{
				int a = t[3*(ts+j)+0];
				int b = t[3*(ts+j)+1];
				int c = t[3*(ts+j)+2];
				
				Vector3 A = p[a];
				Vector3 B = p[b];
				Vector3 C = p[c];
				
				n[a] += Vector3.Cross(B-A,C-A).normalized * Vector3.Angle(B-A,C-A);
				n[b] += Vector3.Cross(C-B,A-B).normalized * Vector3.Angle(C-B,A-B);
				n[c] += Vector3.Cross(A-C,B-C).normalized * Vector3.Angle(A-C,B-C);
			}
		}

		n[num_verts-1].Normalize();
		for (int i=0; i<4; i++)
		{
			int vs = i*FIELD_QUADRANT_NUM_VERTS_TOTAL;
			
			for(int j=FIELD_HOLE_NUM_VERTS; j<FIELD_QUADRANT_NUM_VERTS_TOTAL; j++)
			{
				if (f[vs+j]) n[vs+j].y *= 0.25f;
				n[vs+j].Normalize();
			}
		}

		for (int i=0; i<4; i++)
		{
			float gq_phi = 0.25f*i*2*Mathf.PI;
			float gq_cs = Mathf.Cos(gq_phi);
			float gq_sn = Mathf.Sin(gq_phi);

			float hx = -gq_sn*FIELD_HOLE_OFFSET;
			float hz =  gq_cs*FIELD_HOLE_OFFSET;

			int vs = i*FIELD_QUADRANT_NUM_VERTS_TOTAL;
			for (int j=0; j<FIELD_QUADRANT_NUM_VERTS_TOTAL; j++)
			{
				if (j < FIELD_HOLE_NUM_VERTS)
				{
					float tx = p[vs+j].x;
					float tz = p[vs+j].z;
					float h = p[vs+j].y/FIELD_HOLE_DEPTH;

					tx += (1-h)*(hx-tx);
					tz += (1-h)*(hz-tz);
						
					uv[vs+j].Set(FIELD_UV_MULT*tx,FIELD_UV_MULT*tz);
				}
				else uv[vs+j].Set(FIELD_UV_MULT*p[vs+j].x,FIELD_UV_MULT*p[vs+j].z);
			}
		}

		MeshFilter mf = gameObject.AddComponent<MeshFilter>();
		MeshRenderer mr = gameObject.AddComponent<MeshRenderer>();
		MeshCollider mc = gameObject.AddComponent<MeshCollider>();

		Mesh mesh = new Mesh();
		mesh.vertices = p;
		mesh.normals = n;
		mesh.uv = uv;
		mesh.colors = col;
		mesh.triangles = t;
		mesh.Optimize();
		
		mf.sharedMesh = mesh;
		mc.sharedMesh = mesh;

		mc.material = Resources.Load<PhysicMaterial>("phys_materials/field_physmat");
		
		Material mat = Resources.Load<Material>("materials/field_mat");
		
		mr.sharedMaterial = mat;
		mr.castShadows = true;
		mr.receiveShadows = true;
	}

	void Update ()
	{
	}
}
