//@script ExecuteInEditMode


var width = 32;
var height = 32;
var s=2;
var scale = 0.4;
var size = Vector3 (150.0, 1.0, 150.0);
var tiles_x = 20;
var tiles_y = 20;
var windx = 10.0;
var normal_scale = 16;
var normalStrength = 1.0;
var choppy_scale = 4.0;
var uv_speed = 0.01;
var uvScaling = 10.0;

var material_ocean : Material;
var material_plane : Material;
var material_underwater : Material;
var delayx=10;
var delayxtemp=0;

private var max_LOD = 4;
private var h0 : ComplexF[];
public var caustics : Texture[];
private var framesPerSecond=10;

private var t_x : ComplexF[];
private var t_y : ComplexF[];

private var n0 : ComplexF[];
private var n_x : ComplexF[];
private var n_y : ComplexF[];

private var data : ComplexF[];
private var data_x : ComplexF[];

private var pixelData : Color[];
private var textureA : Texture2D;
private var textureB : Texture2D;

private var baseHeight : Vector3[];
private var baseUV : Vector2[];

private var baseMesh : Mesh = null;

private var child : GameObject;

private var tiles_LOD : Array;

private var g_height : int;
private var g_width : int;

private var n_width : int;
private var n_height : int;

private var drawFrame = true;

private var normalDone = false;

private var reflectionRefractionEnabled : boolean = false;
private var offscreenCam : Camera = null;
private var reflectionTexture : RenderTexture = null;
private var refractionTexture : RenderTexture = null;

// public variables
public var foamTime = 1.3;
public var mycam : Camera;
public var foamTexture : Texture2D;
public var fresnelTexture : Texture2D;
public var bumpTexture : Texture2D;
public var maintexTexture : Texture2D;
public var causticsTexture : Texture2D;
public var surfaceColor : Color;
public var waterColor : Color;
public var mainColor : Color;
public var specularColor : Color;
public var transparency  : Color;
public var sunColor : Color;
public var sunposition : Light;
public var refractionlevel : float = 0.05;
public var sunpower : float = 0.05;
private var forceOriginalShader : boolean = false;

private var shader : Shader = null;
private var shader_plane : Shader = null;
private var shader_underwater : Shader = null;

private var waterDirtyness : float = 0.5;

private var uvs : Vector2[];
private var vertices : Vector3[];
private var normals : Vector3[];
private var tangents : Vector4[];
private var foamData : float[];

private var oceansize = width * s * scale * (max_LOD * 2 + 1);


function GetWaterHeightAtLocation (x : float, y : float) {
	x = x / size.x;
	x = (x-Mathf.Floor (x)) * width;
	y = y / size.z;
	y = (y-Mathf.Floor (y)) * height;

	// TODO: Interpolate
	return data[width * Mathf.FloorToInt(y) + Mathf.FloorToInt(x)].GetModulus() * scale / (width * height);
}
function GaussianRnd (x,y) {
	var x1 = Random.value;
	var x2 = Random.value;
	
	if (x1 == 0.0)
		x1 = 0.01;
	
	return Mathf.Sqrt (-2.0 * Mathf.Log (x1)) * Mathf.Cos (2.0 * Mathf.PI * x2);

	
	//return Mathf.Cos(x/73+y*81);
}

// Phillips spectrum
function P_spectrum (vec_k : Vector2, wind : Vector2) {
	var A = vec_k.x > 0.0 ? 1.0 : 0.25; // Set wind to blow only in one direction - otherwise we get turmoiling water
	
	var L = wind.sqrMagnitude / 9.81;
	var k2 = vec_k.sqrMagnitude;
	// Avoid division by zero
	if (vec_k.magnitude == 0.0) {
		return 0.0;
	}
	return A * Mathf.Exp (-1.0 / (k2*L*L) - Mathf.Pow (vec_k.magnitude * 0.1, 2.0)) / (k2*k2) * Mathf.Pow (Vector2.Dot (vec_k/vec_k.magnitude, wind/wind.magnitude), 2.0);// * wind_x * wind_y;
}

function Start() 
{
	// normal map size
	n_width = 256;
	n_height = 256;

	textureA = new Texture2D(n_width, n_height);
	textureB = new Texture2D(n_width, n_height);
	textureA.filterMode = FilterMode.Bilinear;
	textureB.filterMode = FilterMode.Bilinear;

	if (!SetupOffscreenRendering())
	{
		material_ocean.SetTexture("_BumpMap", textureA);
		material_ocean.SetTextureScale("_BumpMap", Vector2(normal_scale, normal_scale));

		material_ocean.SetTexture("_BumpMap2", textureB);
		material_ocean.SetTextureScale("_BumpMap2", Vector2(normal_scale, normal_scale));
	}
	
	pixelData = new Color[n_width * n_height];

	// Init the water height matrix
	data = new ComplexF[width * height];
	// lateral offset matrix to get the choppy waves
	data_x = new ComplexF[width * height];

	foamData = new float[height * width];
	for(i = 0; i < height * width; i++) {
	    foamData[i] = 1.0;
	}
	
	// tangent
	t_x = new ComplexF[width * height];
	t_y = new ComplexF[width * height];

	n_x = new ComplexF[n_width * n_height];
	n_y = new ComplexF[n_width * n_height];

	// Geometry size
	g_height = height + 1;	
	g_width = width + 1;

	tiles_LOD = new Array();
	for (LOD=0;LOD<max_LOD*max_LOD;LOD++) {
		tiles_LOD.Add (new Array());
 	}
 	
	var tile;
	var chDist :int; // Chebychev distance	
	for (y=0;y<tiles_y;y++) {
		for (x=0;x<tiles_x;x++) {
			chDist = Mathf.Max(Mathf.Abs(tiles_y / 2 - y), Mathf.Abs(tiles_x / 2 - x));
			chDist = chDist > 0 ? chDist-1 : 0;
			cy = y - tiles_y / 2;
			cx = x - tiles_x / 2;
			tile = new GameObject("Tile" + chDist);
			tile.transform.position.x = Mathf.RoundToInt(cx * size.x * s);
			tile.transform.position.y = -40.0 * chDist;
			tile.transform.position.z = Mathf.RoundToInt(cy * size.z * s);
			tile.transform.localScale = Vector3.one * s;
			tile.AddComponent(MeshFilter);
			tile.AddComponent("MeshRenderer");
			tile.renderer.material = material_ocean;

			//Make child of this object, so we don't clutter up the
			//scene hierarchy more than necessary.
			tile.transform.parent = transform;
			
			//Also we don't want these to be drawn while doing refraction/reflection passes,
			//so we'll add the to the water layer for easy filtering.
			tile.layer = LayerMask.NameToLayer("Water");
			
			tiles_LOD[chDist].Add(tile.GetComponent(MeshFilter).mesh);
		}
	}
	
	// Init wave spectra. One for vertex offset and another for normal map
	h0 = new ComplexF[width * height];
	n0 = new ComplexF[n_width * n_height];

	// Wind restricted to one direction, reduces calculations
	// To change wind direction the whole ocean should be rotated.
	wind = Vector2(windx, 0.0);

	// Initialize wave generator	
	for (y = 0; y < height; y++) {
		for (x = 0; x < width; x++) {
			yc = y < height / 2 ? y : -height + y;
			xc = x < width / 2 ? x : -width + x;
			vec_k = Vector2(2.0 * Mathf.PI * xc / size.x, 2.0 * Mathf.PI * yc / size.z);
			h0[width * y + x] = ComplexF(GaussianRnd(x, y), GaussianRnd(x, y)) *
			    0.707 * Mathf.Sqrt(P_spectrum(vec_k, wind));
		}
	}

	for (y = 0; y < n_height; y++) {
		for (x = 0; x < n_width; x++) {	
			yc = y < n_height / 2 ? y : -n_height + y;
			xc = x < n_width / 2 ? x : -n_width + x;
			vec_k = Vector2(2.0 * Mathf.PI * xc / (size.x / normal_scale),
			    2.0 * Mathf.PI * yc / (size.z / normal_scale));
			n0[n_width * y + x] = ComplexF(GaussianRnd(x, y), GaussianRnd(x, y)) *
			    0.707 * Mathf.Sqrt(P_spectrum (vec_k, wind));
		}
	}
	GenerateHeightmap();
	GenerateBumpmaps();
}

function GenerateBumpmaps() {
	if (!normalDone) { 
		for (idx = 0; idx < 2; idx++) {
			for (y = 0; y < n_height; y++) {
				for (x = 0; x < n_width; x++) {	
					yc = y < n_height / 2 ? y : -n_height + y;
					xc = x < n_width / 2 ? x : -n_width + x;
					vec_k = Vector2 (2.0 * Mathf.PI * xc / (size.x / normal_scale),
					    2.0 * Mathf.PI * yc / (size.z / normal_scale));

					iwkt = idx == 0 ? 0.0 : Mathf.PI / 2;
					coeffA = ComplexF(Mathf.Cos(iwkt), Mathf.Sin(iwkt));
					coeffB = coeffA.GetConjugate();

					ny = y > 0 ? n_height - y : 0;
					nx = x > 0 ? n_width - x : 0;

					n_x[n_width * y + x] = (n0[n_width * y + x] * coeffA +
					    n0[n_width * ny + nx].GetConjugate() * coeffB) *
					    ComplexF(0.0, -vec_k.x);				
					n_y[n_width * y + x] = (n0[n_width * y + x] * coeffA +
					    n0[n_width * ny + nx].GetConjugate() * coeffB) *
					    ComplexF(0.0, -vec_k.y);				
				}
			}
			Fourier.FFT2(n_x, n_width, n_height, FourierDirection.Backward);
			Fourier.FFT2(n_y, n_width, n_height, FourierDirection.Backward);

			for (i = 0; i < n_width * n_height; i++) {
				bump = Vector3(n_x[i].Re * Mathf.Abs(n_x[i].Re),
				    n_y[i].Re * Mathf.Abs(n_y[i].Re),
				    n_width * n_height / scale / normal_scale * normalStrength).normalized * 0.5;
				pixelData[i] = Color (bump.x + 0.5, bump.y + 0.5, bump.z + 0.8);
			}
			if (idx == 0) {
				textureA.SetPixels(pixelData, 0);
				textureA.Apply();
			}
			else {
				textureB.SetPixels(pixelData, 0);
				textureB.Apply();
			}
		}
		normalDone = true;
	}
}

function GenerateHeightmap() {
	var mesh : Mesh = new Mesh();
	var y = 0;
	var x = 0;

	// Build vertices and UVs
	var vertices = new Vector3[g_height * g_width];
	var tangents = new Vector4[g_height * g_width];
	var uv = new Vector2[g_height * g_width];

	var uvScale = Vector2(uvScaling / (g_width - 1), uvScaling / (g_height - 1));
	var sizeScale = Vector3(size.x / (g_width - 1), size.y, size.z / (g_height - 1));

	for (y = 0; y < g_height; y++) {
		for (x = 0; x < g_width; x++) {
			var vertex = Vector3(x, 0.0, y);
			vertices[y * g_width + x] = Vector3.Scale(sizeScale, vertex);
			uv[y * g_width + x] = Vector2.Scale(Vector2(x, y), uvScale);
		}
	}
	mesh.vertices = vertices;
	mesh.uv = uv;

	for (y = 0; y < g_height; y++) {
		for (x = 0; x < g_width; x++) {
			tangents[y * g_width + x] = Vector4(1.0, 0.0, 0.0, -1.0);
		}
	}
	mesh.tangents = tangents;	
	
	for (LOD = 0; LOD < max_LOD; LOD++) {
	    number_of_items = (height / Mathf.Pow(2, LOD) + 1) * (width / Mathf.Pow(2, LOD) + 1);
		verticesLOD = new Vector3[number_of_items];
		uvLOD = new Vector2[number_of_items];
		
		idx = 0;
		for (y = 0; y < g_height; y += Mathf.Pow(2, LOD)) {
			for (x = 0; x < g_width; x += Mathf.Pow(2, LOD)) {
				verticesLOD[idx] = vertices[g_width * y + x];
				uvLOD[idx++] = uv[g_width * y + x];
			}			
		}
		for (k = 0; k < tiles_LOD[LOD].length; k++) {
			meshLOD = tiles_LOD[LOD][k];
			meshLOD.vertices = verticesLOD;
			meshLOD.uv = uvLOD;
		}		
	}

	LOD = 0;
	// Build triangle indices: 3 indices into vertex array for each triangle
	for (LOD = 0; LOD < max_LOD; LOD++) {
		index = 0;
		width_LOD = width / Mathf.Pow(2, LOD) + 1;
		triangles = new int[(height / Mathf.Pow(2, LOD) * width / Mathf.Pow(2, LOD)) * 6];
		for (y = 0; y < height / Mathf.Pow(2, LOD); y++) {
			for (x = 0; x < width / Mathf.Pow(2, LOD); x++) {
				// For each grid cell output two triangles
				triangles[index++] = (y * width_LOD) + x;
				triangles[index++] = ((y + 1) * width_LOD) + x ;
				triangles[index++] = (y * width_LOD) + x + 1;

				triangles[index++] = ((y + 1) * width_LOD) + x;
				triangles[index++] = ((y + 1) * width_LOD) + x + 1;
				triangles[index++] = (y * width_LOD) + x + 1;
			}
		}
		for (k = 0; k < tiles_LOD[LOD].length;k++) {
			meshLOD = tiles_LOD[LOD][k];
			meshLOD.triangles = triangles;
		}
	}
	baseMesh = mesh;
}

/*
Prepares the scene for offscreen rendering; spawns a camera we'll use for for
temporary renderbuffers as well as the offscreen renderbuffers (one for
reflection and one for refraction).
*/
function SetupOffscreenRendering()
{
	//Check for rendertexture support and return false if not supported
	if( !SystemInfo.supportsRenderTextures)
		return false;
		
	
	shader = Shader.Find("Ocean/OceanMain_v1");
	
	//Bail out if the shader could not be compiled
	if (shader == null)
		return false;
	
	if (!shader.isSupported)
		return false;
		
	//TODO: More fail-tests?
	
		
	refractionTexture = new RenderTexture(128, 128, 16);
	refractionTexture.wrapMode = TextureWrapMode.Clamp;
	refractionTexture.isPowerOfTwo = true;
	
	reflectionTexture = new RenderTexture(128,128, 16);
	reflectionTexture.wrapMode = TextureWrapMode.Clamp;
	reflectionTexture.isPowerOfTwo = true;
		
		
	//Spawn the camera we'll use for offscreen rendering (refraction/reflection)
	var cam : GameObject = new GameObject();
	cam.name = "DeepWaterOffscreenCam";
	cam.transform.parent = transform;
	offscreenCam = cam.AddComponent(typeof(Camera)) as Camera;
	offscreenCam.enabled = false;
	offscreenCam.fieldOfView = 60;
	offscreenCam.farClipPlane = 10000;
	
	
	
	//Hack to make this object considered by the renderer - first make a plane
	//covering the watertiles so we get a decent bounding box, then
	//scale all the vertices to 0 to make it invisible.
	gameObject.AddComponent(MeshRenderer);
		
	renderer.material.renderQueue = 1;
	renderer.receiveShadows = true;
	renderer.castShadows = true;
	
	var m : Mesh = new Mesh();
		
	var verts : Vector3[] = new Vector3[4];
	var uv : Vector2[] = new Vector2[4];
	var n : Vector3[] = new Vector3[4];
	var tris : int[] =  new int[6];
		
	var minSizeX : float = -1024;
	var maxSizeX : float = 1024;
	
	var minSizeY : float = -1024;
	var maxSizeY : float = 1024;
		
	verts[0] = new Vector3(minSizeX, 0.0, maxSizeY);
	verts[1] = new Vector3(maxSizeX, 0.0, maxSizeY);
	verts[2] = new Vector3(maxSizeX, 0.0, minSizeY);
	verts[3] = new Vector3(minSizeX, 0.0, minSizeY);
	
	tris[0] = 0;
	tris[1] = 1;
	tris[2] = 2;
		
	tris[3] = 2;
	tris[4] = 3;
	tris[5] = 0;
		
	m.vertices = verts;
	m.uv = uv;
	m.normals = n;
	m.triangles = tris;
		
		
	var mfilter : MeshFilter = gameObject.GetComponent(MeshFilter);
		
	if (mfilter == null)
		mfilter = gameObject.AddComponent(MeshFilter);
		
	mfilter.mesh = m;
		
	m.RecalculateBounds();
		
	//Hopefully the bounds will not be recalculated automatically
	verts[0] = Vector3.zero;
	verts[1] = Vector3.zero;
	verts[2] = Vector3.zero;
	verts[3] = Vector3.zero;
		
	m.vertices = verts;
	
	//Create the material and set up the texture references.
	material_ocean = new Material(shader);
	
	material_ocean.SetTexture("_Reflection", reflectionTexture);
	material_ocean.SetTexture("_Refraction", refractionTexture);
	material_ocean.SetTexture ("_Bump", bumpTexture);
	material_ocean.SetTexture("_Fresnel", fresnelTexture);
	material_ocean.SetTexture("_Foam", foamTexture);
	material_ocean.SetTexture("_MainTex", maintexTexture);
	//material.SetTexture ("_Caustics", caustics);
	material_ocean.SetTexture("_Caustics", causticsTexture);
	material_ocean.SetVector("_Size", new Vector4(size.x, size.y, size.z, 0.0));
	material_ocean.SetColor("_SurfaceColor", surfaceColor);
	material_ocean.SetColor("_WaterColor", waterColor);
	material_ocean.SetColor("_Color", mainColor);
	material_ocean.SetColor("_SpecColor", specularColor);
	material_ocean.SetColor("_SunColor", sunColor);
	material_ocean.SetVector("_SunPosition", sunposition.transform.position);
//	material_ocean.SetVector("_SunPosition", sunposition.transform.position);
//	material_ocean.SetFloat("_SunPower", sunpower);
	material_ocean.SetColor("_Transparency", transparency);
	material_ocean.SetFloat("_RefractionLevel", refractionlevel);

	reflectionRefractionEnabled = true;

	return true;
}

/*
Delete the offscreen rendertextures on script shutdown.
*/
function OnDisable()
{
	if (reflectionTexture != null)
		DestroyImmediate(reflectionTexture);
			
	if (refractionTexture != null)
		DestroyImmediate(refractionTexture);
			
	reflectionTexture = null;
	refractionTexture = null;
}

// Wave dispersion
function disp (vec_k : Vector2) {
	return Mathf.Sqrt (9.81 * vec_k.magnitude);
}

function OnGUI()
{

/*	if (reflectionTexture != null)
		GUI.DrawTexture (new Rect(0, 0, 128, 128), reflectionTexture, ScaleMode.StretchToFill, false);
	
	if (refractionTexture != null)
		GUI.DrawTexture (new Rect(0, 128, 128, 128), refractionTexture, ScaleMode.StretchToFill, false);
	
	GUI.DrawTexture (new Rect(128, 0, 128, 128), textureA, ScaleMode.StretchToFill, false);
	GUI.DrawTexture (new Rect(128, 128, 128, 128), textureB, ScaleMode.StretchToFill, false);


	GUI.Label(new Rect(10, 10, 100, 20), "Waveheight");
	scale = GUI.HorizontalSlider(new Rect(120, 10, 200, 20), scale, 0.05, 10.0);
	// scale=3;
	
	GUI.Label(new Rect(10, 30, 100, 20), "Wave sharpness");
	choppy_scale = GUI.HorizontalSlider(new Rect(120, 30, 200, 20), choppy_scale, 0.00,100.0);
	//choppy_scale=5;
	GUI.Label(new Rect(10, 50, 100, 20), "Water dirtyness");
	waterDirtyness = GUI.HorizontalSlider(new Rect(120, 50, 200, 20), waterDirtyness, 0.0, 1);
*/	
		
}

function Update() {
    // Center ocean under the camera, snapped to vertex accuracy.
    dx = mycam.transform.position.x;
    dz = mycam.transform.position.z;

    LOD_size = 32; // Ocean vertex camera follow resolution
    
	tile_offset_x = Mathf.FloorToInt(dx / (s * size.x / LOD_size));
	tile_offset_y = Mathf.FloorToInt(dz / (s * size.z / LOD_size));

    dx = tile_offset_x * (s * size.x / LOD_size);
    dz = tile_offset_y * (s * size.z / LOD_size);
	
	tile_offset_x *= width / LOD_size;
	tile_offset_y *= height / LOD_size;
	

	transform.position.x = dx - s * size.x / 2;
	transform.position.z = dz - s * size.z / 2;	
	
	if (Input.GetKeyDown(KeyCode.P))
		Application.CaptureScreenshot("Screenshot.png");

    // Calculate the Fourier coefficients for this frame
	for (var y = 0; y < height; y++) {
		for (var x = 0; x < width; x++) {
			idx = width * y + x;
			yc = y < height / 2 ? y : -height + y;
			xc = x < width / 2 ? x : -width + x;
			vec_k = Vector2(2.0 * Mathf.PI * xc / size.x, 2.0 * Mathf.PI * yc / size.z);

			var iwkt = disp(vec_k) * Time.time * 3.0; // TODO : Expose t-scale as variable
			var coeffA = ComplexF(Mathf.Cos(iwkt), Mathf.Sin(iwkt));
			var coeffB = coeffA.GetConjugate();

			ny = y > 0 ? height - y : 0;
			nx = x > 0 ? width - x : 0;

			data[idx] = h0[idx] * coeffA + h0[width * ny + nx].GetConjugate() * coeffB;				
			t_x[idx] = data[idx] * ComplexF (-vec_k.y, vec_k.x);				

			// Choppy wave calcuations
			if (x + y > 0) {
				data[idx] += data[idx] * vec_k.x / vec_k.magnitude;
			}
		}
	}

// These were already provided by the Unity engine...	    	
//	material_ocean.SetFloat( "_BlendA",Mathf.Cos(Time.time)*0.1); 
//	material_ocean.SetFloat( "_BlendB",Mathf.Sin(Time.time)*0.5); 

	Fourier.FFT2(data, width, height, FourierDirection.Backward);
	Fourier.FFT2(t_x, width, height, FourierDirection.Backward);

	var scaleA = choppy_scale / (width * height);
	var scaleB = scale / (width * height);
	var scaleBinv = 1.0 / scaleB;

    LUT = new int[width * height];
    
	for (y = 0; y < height; y++) {
		for (x = 0; x < width; x++) {
		    idx_a = width * ((y + tile_offset_y) % height) + ((x + tile_offset_x) % width);
			idx = width * y + x;
			LUT[idx] = idx_a;
			
			// Foam calculations in unshifted coordinates
			var idx_d = (x == width - 1) ? -(width - 1) : 1;
			var oceanStretch : float = 1.0 + scaleA * (data[idx + idx_d].Im - data[idx].Im) / (size.x / g_width);
			if (oceanStretch < 0.0)
				foamData[idx] = 1.0;
			else 
			    foamData[idx] += (oceanStretch < 0.5 ? 2.0 : -0.5) * Time.deltaTime;
			
			foamData[idx] = Mathf.Clamp(foamData[idx] / foamTime, 0.0, 2.0);			
        }
    }
    // Foamdata contains the unshifted ocean stretching 
    
    
	// Get base values for vertices and uv coordinates.
	if (baseHeight == null) {
		var mesh : Mesh = baseMesh;
		baseHeight = mesh.vertices;
		baseUV = mesh.uv;
	
		var itemCount = baseHeight.Length;
		uvs = new Vector2[itemCount];
		vertices = new Vector3[itemCount];
		normals = new Vector3[itemCount];
		tangents = new Vector4[itemCount];
	}

	var vertex;
	var uv;
	var normal;
	var n_scale = size.x / width / scale;	
	
	for (i = 0; i < width * height; i++) {
		iw = i + i / width; // Correct index to skip over right edge
		vertices[iw] = baseHeight[iw];
		
		vertices[iw].x += data[LUT[i]].Im * scaleA;
		vertices[iw].y = data[LUT[i]].Re * scaleB;

		normals[iw] = Vector3(t_x[LUT[i]].Re, scaleBinv, t_x[LUT[i]].Im).normalized;
		if (reflectionRefractionEnabled)
		    tangents[iw].w = foamData[LUT[i]];
        
		uv = baseUV[iw];
//		uv.x += Time.time * uv_speed + 1.0 * tile_offset_x / width;
		uv += Vector2(1.0 * tile_offset_x / width, 1.0 * tile_offset_y / height) * uvScaling;
		uvs[iw] = uv;

		if (!((i + 1) % width)) {
			vertices[iw + 1] = baseHeight[iw + 1];
			vertices[iw + 1].x += data[LUT[i + 1 - width]].Im * scaleA;
			vertices[iw + 1].y = data[LUT[i + 1 - width]].Re * scaleB;
			normals[iw + 1] = Vector3(t_x[LUT[i + 1 - width]].Re,
			    scaleBinv, t_x[LUT[i + 1 - width]].Im).normalized;
    		if (reflectionRefractionEnabled)
    		    tangents[iw + 1].w = foamData[LUT[i + 1 - width]];
			uv = baseUV[iw + 1];
//			uv.x = uv.x + Time.time * uv_speed;
			uv += Vector2(1.0 * tile_offset_x / width, 1.0 * tile_offset_y / height) * uvScaling; 
			uvs[iw + 1] = uv;				
		}
	}

	var offset = g_width * (g_height-1);

	for (i = 0; i < g_width; i++) {
		vertices[i + offset] = baseHeight[i + offset];
		vertices[i + offset].x += data[LUT[i % width]].Im * scaleA;
		vertices[i + offset].y = data[LUT[i % width]].Re * scaleB;

		normals[i + offset] = Vector3(t_x[LUT[i % width]].Re,
		    scaleBinv, t_x[LUT[i % width]].Im).normalized;

		if (reflectionRefractionEnabled)
		    tangents[i + offset].w = foamData[LUT[i % width]];

		uv = baseUV[i + offset];
//		uv.x = uv.x + Time.time * uv_speed;
		uv += Vector2(1.0 * tile_offset_x / width, 1.0 * tile_offset_y / height) * uvScaling;
		uvs[i + offset] = uv;
	}

	var tmp;
	
    for (i = 0; i < g_width * g_height - 1; i++) {	    
        if (((i + 1) % g_width) == 0) {
            tmp = (vertices[i - width + 1] + Vector3(size.x, 0.0, 0.0) - vertices[i]).normalized;
        }
        else {
            tmp = (vertices[i + 1] - vertices[i]).normalized;
        }
        if (!reflectionRefractionEnabled) {
            tangents[i] = new Vector4(tmp.x, tmp.y, tmp.z, 1.0);
        }
        else {
            tangents[i] = new Vector4(tmp.x, tmp.y, tmp.z, tangents[i].w);            
        }
    }
	
	tangents[g_width * g_height - 1] = (vertices[g_width * g_height - 1] + 
	    Vector3(size.x, 0.0, 0.0) - vertices[1]).normalized;

	LOD = 0;
	for (LOD = 0; LOD < max_LOD; LOD++) {
		var den = Mathf.Pow(2, LOD);
		var itemcount = (height / den + 1) * (width / den + 1);
		
		tangentsLOD = new Vector4[itemcount];
		verticesLOD = new Vector3[itemcount];
		normalsLOD = new Vector3[itemcount];
		uvLOD = new Vector2[(height / Mathf.Pow(2, LOD) + 1) *
		    (width / Mathf.Pow(2, LOD) + 1)];
		idx = 0;

		for (y = 0; y < g_height; y += den) {
			for (x = 0; x < g_width; x += den) {
				idx2 = g_width * y + x;
				verticesLOD[idx] = vertices[idx2];
				
				verticesLOD[idx].y /= (LOD * 1.5 + 1);
				
				uvLOD[idx] = uvs[g_width * y + x];
				tangentsLOD[idx] = tangents[idx2];
				normalsLOD[idx++] = normals[idx2];
			}			
		}
		for (k = 0; k < tiles_LOD[LOD].length; k++) {
			meshLOD = tiles_LOD[LOD][k];
			meshLOD.vertices = verticesLOD;
			meshLOD.normals = normalsLOD;
			meshLOD.uv = uvLOD;
			meshLOD.tangents = tangentsLOD;
		}		
	}
}

/*
Called when the object is about to be rendered. We render the refraction/reflection
passes from here, since we only need to do it once per frame, not once per tile.
*/
function OnWillRenderObject()
//function OnRenderImage()
{
	//Recursion guard, don't let the offscreen cam go into a never-ending loop.
	if (Camera.current == offscreenCam)
	    return;
			
	if (reflectionTexture == null
	|| refractionTexture == null)
		return;
		

	material_ocean.SetTexture("_Reflection", reflectionTexture);
	material_ocean.SetTexture("_Refraction", refractionTexture);
	material_ocean.SetTexture ("_Bump", bumpTexture);
	material_ocean.SetTexture("_Fresnel", fresnelTexture);
	material_ocean.SetTexture("_Foam", foamTexture);
	material_ocean.SetTexture("_MainTex", maintexTexture);
	material_ocean.SetVector("_Size", new Vector4(size.x, size.y, size.z, 0.0));
	material_ocean.SetColor("_SurfaceColor", surfaceColor);
	material_ocean.SetColor("_WaterColor", waterColor);
	material_ocean.SetColor("_Color", mainColor);
	material_ocean.SetColor("_SpecColor", specularColor);
	material_ocean.SetColor("_Transparency", transparency);
	var index : int = (Time.time * framesPerSecond) % caustics.Length;
	causticsTexture = caustics[index]; 
	material_ocean.SetTexture("_Caustics", causticsTexture);
	material_ocean.SetFloat("_RefractionLevel", refractionlevel);
	material_ocean.SetColor("_SunColor", sunColor);
	material_ocean.SetVector("_SunPosition", sunposition.transform.position);
	material_ocean.SetFloat("_SunPower", sunpower);

	RenderReflectionAndRefraction();
}


/*
Renders the reflection and refraction buffers using a second camera copying the current
camera settings.
*/
function RenderReflectionAndRefraction()
{
//return;
	//camera.ResetWorldToCameraMatrix();

	offscreenCam.enabled = true;
	var oldPixelLightCount : int = QualitySettings.pixelLightCount;
	QualitySettings.pixelLightCount = 0;
	
	
	var renderCamera : Camera = mycam;

	var originalWorldToCam : Matrix4x4 = renderCamera.worldToCameraMatrix;
		
	var cullingMask : int = renderCamera.cullingMask & ~(1 << LayerMask.NameToLayer("Water"));
		
	//Reflection pass
	var reflection : Matrix4x4 = Matrix4x4.zero;
	
	//TODO: Use local plane here, not global!
	CameraHelper.CalculateReflectionMatrix (reflection, new Vector4(0.0, 1.0, 0.0, 0.0));
		
	offscreenCam.transform.position = reflection.MultiplyPoint(renderCamera.transform.position);
	offscreenCam.transform.rotation = renderCamera.transform.rotation;
	offscreenCam.transform.Rotate(0,180,0); 
	offscreenCam.worldToCameraMatrix = originalWorldToCam * reflection;
	
	offscreenCam.cullingMask = cullingMask;
	offscreenCam.targetTexture = reflectionTexture;
	offscreenCam.clearFlags = renderCamera.clearFlags;
	
	//Need to reverse face culling for reflection pass, since the camera
	//is now flipped upside/down.
	GL.SetRevertBackfacing (false);
		
	var cameraSpaceClipPlane : Vector4 = CameraHelper.CameraSpacePlane(offscreenCam, Vector3.zero, Vector3.up, 1.0);
		
	var projection : Matrix4x4 = renderCamera.projectionMatrix;
	var obliqueProjection : Matrix4x4 = projection;
		
	CameraHelper.CalculateObliqueMatrix (obliqueProjection, cameraSpaceClipPlane);
	
	//Do the actual render, with the near plane set as the clipping plane. See the
	//pro water source for details.
	offscreenCam.projectionMatrix = obliqueProjection;
	offscreenCam.Render();

	
	GL.SetRevertBackfacing (false);
	
	//Refractionpass
	var fog : boolean = RenderSettings.fog;
	var fogColor : Color = RenderSettings.fogColor;
	var fogDensity : float = RenderSettings.fogDensity;
		
	RenderSettings.fog = true;
	RenderSettings.fogColor = Color.grey;
	RenderSettings.fogDensity = waterDirtyness/10;
		
	//TODO: If we want to use this as a refraction seen from under the seaplane,
	//      the cameraclear should be skybox.
	offscreenCam.clearFlags = CameraClearFlags.Color;
	offscreenCam.backgroundColor = Color.grey;
		
	offscreenCam.targetTexture = refractionTexture;
	obliqueProjection = projection;
	
	offscreenCam.transform.position = renderCamera.transform.position;
	offscreenCam.transform.rotation = renderCamera.transform.rotation;
	offscreenCam.worldToCameraMatrix = originalWorldToCam;
	
	cameraSpaceClipPlane = CameraHelper.CameraSpacePlane(offscreenCam, Vector3.zero, Vector3.up, -1.0);
	CameraHelper.CalculateObliqueMatrix (obliqueProjection, cameraSpaceClipPlane);
	offscreenCam.projectionMatrix = obliqueProjection;
	
	offscreenCam.Render();
		
	RenderSettings.fog = fog;
	RenderSettings.fogColor = fogColor;
	RenderSettings.fogDensity = fogDensity;
		
	offscreenCam.projectionMatrix = projection;
		
		
	offscreenCam.targetTexture = null;
		
	QualitySettings.pixelLightCount = oldPixelLightCount;

//	camera.ResetWorldToCameraMatrix();

//GL.SetRevertBackfacing (true);
}
