<commit_before><!DOCTYPE html>
<html>
<head>
	<title>IFC - Two-Way Coupling Test</title>
	<meta charset="UTF-8"/>
	
	<link rel="stylesheet" href="/css/style.css">
	<style type="text/css">
		#stats { position: absolute; top:0; left: 0 }
		#stats #fps { background: transparent !important }
		#stats #fps #fpsText { color: #aaa !important }
		#stats #fps #fpsGraph { display: block }
	</style>
	
	<!--load helper libraries-->
	<script type="text/javascript" src="/js/lib/jquery-2.0.2.min.js"></script>
	
	<!--load AR and 3D libraries-->
	<script type="text/javascript" src="/js/lib/three.min.js"></script>
	<script type="text/javascript" src="/js/lib/Detector.js"></script>
	<script type="text/javascript" src="/js/lib/stats.min.js"></script>
	<script type="text/javascript" src="/js/lib/dat.gui.min.js"></script>
	<script type="text/javascript" src="/js/lib/OrbitControls.js"></script>

	<script type="text/javascript" src="/js/lib/seedrandom.js"></script>

	<script type="text/javascript" src="/js/heightfieldwatersim.js"></script>
	<script type="text/javascript" src="/js/skvoxelizer.js"></script>
	<script type="text/javascript" src="/js/lib/physics/physi.js"></script>
	
	<script>

	//config Physijs
	Physijs.scripts.worker = '/js/lib/physics/physijs_worker.js';
	Physijs.scripts.ammo = '/js/lib/physics/ammo.js';

	var WATER_SIZE = 10;
	var WATER_RES = 80;
	var WATER_GRID_SIZE = WATER_SIZE / WATER_RES;
	var WATER_SIZE_HALF = WATER_SIZE / 2.0;
	var WATER_VOXEL_VOL = WATER_GRID_SIZE * WATER_GRID_SIZE * WATER_GRID_SIZE;
	var DAMPING_FACTOR = 0.8;
	var DISTURB_AMOUNT = 0.015;

	var SHADOW_MAP_RES = 1024;

	var EPSILON = 0.001;

	var clock = new THREE.Clock();

	function checkWebGl()
	{
		//check for WebGL
		if (!Detector.webgl)
		{
			$('div').remove();
			Detector.addGetWebGLMessage();
			throw new Error('Your browser does not seem to support WebGL');
		}
	}

	var renderer, scene, camera, controls;
	var materials = [];
	var localAxis;
	function setupThreejsScene()
	{
		//create renderer
		renderer = new THREE.WebGLRenderer({
			antialias : true
		});
		renderer.setSize(window.innerWidth, window.innerHeight-5);
		renderer.setClearColor('#111111', 1);
		renderer.shadowMapEnabled = true;
		renderer.shadowMapType = THREE.PCFSoftShadowMap;
		renderer.shadowMapSoft = true;
		var $container = $('#threejs-container');
		$container.append(renderer.domElement);

		//create scene
		scene = new Physijs.Scene();
		scene.addEventListener(
			'ready',
			function() {
				console.log('Physijs scene is ready');
				setupDynamicAttributes();
			}
		);
		// scene.setGravity(new THREE.Vector3(0, -5, 0));

		//create camera
		camera = new THREE.PerspectiveCamera(25, renderer.domElement.width/renderer.domElement.height, 0.1, 1000);
		camera.position.set(8, 8, 15);
		camera.lookAt(new THREE.Vector3(0,0,0));

		//create controls for camera
		controls = new THREE.OrbitControls(camera);
		controls.userPanSpeed = 0.2;
		//controls.autoRotate = true;
		controls.modifierKey = 'alt';

		//create axis
		localAxis = new THREE.AxisHelper();
		scene.add(localAxis);

		//setup lights
		scene.add(new THREE.AmbientLight(0x111111));

		keyLight = new THREE.SpotLight(0xffff44, 0.6);
		keyLight.position.set(5, 15, -15);
		keyLight.target.position.set(0, 0, 0);
		keyLight.castShadow = true;
		keyLight.shadowCameraNear = 10;
		keyLight.shadowCameraFar = 40;
		keyLight.shadowCameraFov = 40;
		// keyLight.shadowCameraVisible = true;
		keyLight.shadowBias = 0.00001;
		keyLight.shadowDarkness = 0.4;
		keyLight.shadowMapWidth = SHADOW_MAP_RES;
		keyLight.shadowMapHeight = SHADOW_MAP_RES;
		scene.add(keyLight);

		fillLight = new THREE.SpotLight(0xff0044, 0.4);
		fillLight.position.set(5, 2, 15);
		fillLight.target.position.set(0, 0, 0);
		scene.add(fillLight);

		//create plane for intersection test
		var groundPlaneGeom = new THREE.PlaneGeometry(WATER_SIZE, WATER_SIZE, 1, 1);
		groundPlaneGeom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI/2));
		var groundPlaneMaterial = new THREE.MeshPhongMaterial();
		materials.push(groundPlaneMaterial);
		groundPlaneMesh = new THREE.Mesh(groundPlaneGeom, groundPlaneMaterial);
		groundPlaneMesh.castShadow = false;
		groundPlaneMesh.receiveShadow = false;
		groundPlaneMesh.visible = false;
		scene.add(groundPlaneMesh);
	}

	var RANDOM_SEED = 1;
	Math.seedrandom(RANDOM_SEED);
	var waterMesh;
	var heightFieldWaterSim, heightFieldWaterSim_iWave, heightFieldWaterSim_mullerGdc2008, heightFieldWaterSim_mullerGdc2008_helloWorld, heightFieldWaterSim_xWater;
	function setupWaterScene()
	{
		//create a height-field water sim from the plane
		var waterGeom = new THREE.PlaneGeometry(WATER_SIZE, WATER_SIZE, WATER_RES-1, WATER_RES-1);
		waterGeom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI/2));
		var waterMaterial = new THREE.MeshPhongMaterial({color:'#3366FF'});
		materials.push(waterMaterial);
		waterMesh = new THREE.Mesh(waterGeom, waterMaterial);
		waterMesh.castShadow = true;
		waterMesh.receiveShadow = true;
		scene.add(waterMesh);

		//create the different water sims
		heightFieldWaterSim_iWave = new HeightFieldWaterSim_Tessendorf_iWave(waterMesh, WATER_SIZE, WATER_RES, DAMPING_FACTOR, 4, 3);
		heightFieldWaterSim_mullerGdc2008 = new HeightFieldWaterSim_Muller_GDC2008(waterMesh, WATER_SIZE, WATER_RES, DAMPING_FACTOR, 5.0);
		heightFieldWaterSim_mullerGdc2008_helloWorld = new HeightFieldWaterSim_Muller_GDC2008_HelloWorld(waterMesh, WATER_SIZE, WATER_RES, DAMPING_FACTOR);
		heightFieldWaterSim_xWater = new HeightFieldWaterSim_xWater(waterMesh, WATER_SIZE, WATER_RES, DAMPING_FACTOR);
		heightFieldWaterSim = heightFieldWaterSim_iWave;
	}

	var iSegments = 3;
	var jSegments = 3;
	var iSegmentSize = WATER_SIZE / iSegments;
	var jSegmentSize = WATER_SIZE / jSegments;
	var dynMeshes = [];
	var obstacles = [];
	function setupPhysicsScene()
	{
		//create a dynamic box
		var boxGeom = new THREE.CubeGeometry(1, 1, 1);
		var boxMaterial = new THREE.MeshPhongMaterial({color:'#00ff00'});
		materials.push(boxMaterial);
		var boxMass = 700;  //kg
		var boxDynMesh = new Physijs.BoxMesh(boxGeom, boxMaterial, boxMass);
		boxDynMesh.position.set(0, 2, 0);
		boxDynMesh.castShadow = true;
		boxDynMesh.receiveShadow = true;
		scene.add(boxDynMesh);
		dynMeshes.push(boxDynMesh);
		boxDynMesh.voxelizer = new SkVoxelizer(boxDynMesh, WATER_GRID_SIZE, WATER_GRID_SIZE, WATER_GRID_SIZE);

		//create a dynamic sphere
		var sphereGeom = new THREE.SphereGeometry(0.75, 8, 8);
		var sphereMaterial = new THREE.MeshPhongMaterial({color:'#00ff00'});
		materials.push(sphereMaterial);
		var sphereMass = 930;  //kg
		var sphereDynMesh = new Physijs.SphereMesh(sphereGeom, sphereMaterial, sphereMass);
		sphereDynMesh.position.set(3, 3, 3);
		sphereDynMesh.castShadow = true;
		sphereDynMesh.receiveShadow = true;
		scene.add(sphereDynMesh);
		dynMeshes.push(sphereDynMesh);
		sphereDynMesh.voxelizer = new SkVoxelizer(sphereDynMesh, WATER_GRID_SIZE, WATER_GRID_SIZE, WATER_GRID_SIZE);
		
		//create 4 walls to prevent dynamic object from flying off
		var thickness = 1;
		var halfThickness = thickness / 2.0;
		var wallGeom = new THREE.CubeGeometry(WATER_SIZE, WATER_SIZE * 2, thickness);
		var wallRotatedGeom = new THREE.CubeGeometry(WATER_SIZE, WATER_SIZE * 2, thickness);
		wallRotatedGeom.applyMatrix(new THREE.Matrix4().makeRotationY(-Math.PI/2));
		var wallMaterial = new THREE.MeshPhongMaterial();
		//+z wall
		var wallMesh = new Physijs.BoxMesh(wallGeom, wallMaterial, 0);
		wallMesh.position.z = WATER_SIZE_HALF + halfThickness;
		wallMesh.visible = false;
		scene.add(wallMesh);
		//-z wall
		wallMesh = new Physijs.BoxMesh(wallGeom, wallMaterial, 0);
		wallMesh.position.z = - (WATER_SIZE_HALF + halfThickness);
		wallMesh.visible = false;
		scene.add(wallMesh);
		//+x wall
		wallMesh = new Physijs.BoxMesh(wallRotatedGeom, wallMaterial, 0);
		wallMesh.position.x = WATER_SIZE_HALF + halfThickness;
		wallMesh.visible = false;
		scene.add(wallMesh);
		//-x wall
		wallMesh = new Physijs.BoxMesh(wallRotatedGeom, wallMaterial, 0);
		wallMesh.position.x = -(WATER_SIZE_HALF + halfThickness);
		wallMesh.visible = false;
		scene.add(wallMesh);

		//create some obstacles for the water sim
		var i, j;
		for (i = 0; i < iSegments; i++)
		{
			for (j = 0; j < jSegments; j++)
			{
				var length = WATER_SIZE / 5.0;
				var obstacleGeom = new THREE.CubeGeometry(length*(Math.random()*0.5+0.5), 1, length*(Math.random()*0.5+0.5));
				var obstacleMaterial = new THREE.MeshPhongMaterial();
				materials.push(obstacleMaterial);
				obstacleMesh = new Physijs.BoxMesh(obstacleGeom, obstacleMaterial, 0);
				obstacleMesh.castShadow = true;
				obstacleMesh.receiveShadow = true;
				obstacleMesh.position.x = (iSegmentSize * i) - WATER_SIZE_HALF + ((Math.random()) * iSegmentSize);
				obstacleMesh.position.z = (jSegmentSize * j) - WATER_SIZE_HALF + ((Math.random()) * jSegmentSize);
				obstacleMesh.rotation.y = (Math.random() * 360);
				scene.add(obstacleMesh);
				obstacles.push(obstacleMesh);
				heightFieldWaterSim.addObstacle(obstacleMesh);
			}
		}

		//temp, just to make the demo nicer
		obstacles[1].position.x += -1;
		obstacles[1].__dirtyPosition = true;
		obstacles[2].position.x = 10000;
		obstacles[2].position.x = 10000;
		obstacles[2].__dirtyPosition = true;
	}

	function setupDynamicAttributes()
	{
		var dynMesh;
		for (dynMeshId in dynMeshes)
		{
			dynMesh = dynMeshes[dynMeshId];
			dynMesh.setDamping(0.75, 0.5);
		}
	}

	function randomizeObstaclePositions()
	{
		var i, j;
		for (i = 0; i < iSegments; i++)
		{
			for (j = 0; j < jSegments; j++)
			{
				idx = i * iSegments + j;
				obstacles[idx].position.x = (iSegmentSize * i) - WATER_SIZE_HALF + ((Math.random()) * iSegmentSize * 0.8);
				obstacles[idx].position.y = Math.random() * 4 - 2;
				obstacles[idx].position.z = (jSegmentSize * j) - WATER_SIZE_HALF + ((Math.random()) * jSegmentSize * 0.8);
				obstacles[idx].rotation.y = (Math.random() * 360);
			}
		}
	}

	//TODO: this should go into the height field water class
	displacedVoxelCountPrev = {};
	displacedVoxelCount = {};
	function handleTwoWayCoupling(dt)
	{
		//find out how much water the objects are displacing
		//for each object, project AABB down to water, check all affected grid points to see if they are inside or outside water
		var dynMesh, bbox, h, voxelizer;
		for (dynMeshId in dynMeshes)
		{
			dynMesh = dynMeshes[dynMeshId];

			//TODO: check if dynMesh is totally not touching the water, using bounding box checks

			//get intersection data from voxelizer
			voxelizer = dynMesh.voxelizer;
			voxelizer.updateIntersections();  //this will also update AABB voxelizer.__min and voxelizer.__max
			//visualize if necessary, for debugging purposes:
			// dynMesh.voxelizer.voxelize();
			// dynMesh.voxelizer.visualize(scene);

			//project AABB down and get (x,z) of all affected grid points
			var startX = Math.ceil(voxelizer.__min.x / WATER_GRID_SIZE) * WATER_GRID_SIZE;
			var endX = Math.ceil(voxelizer.__max.x / WATER_GRID_SIZE) * WATER_GRID_SIZE;
			var startZ = Math.ceil(voxelizer.__min.z / WATER_GRID_SIZE) * WATER_GRID_SIZE;
			var endZ = Math.ceil(voxelizer.__max.z / WATER_GRID_SIZE) * WATER_GRID_SIZE;
			var x, z;
			var vertices = heightFieldWaterSim.geometry.vertices;
			var minIntersectHeight, maxIntersectHeight, waterHeight;
			for (x = startX; x <= endX + EPSILON; x += WATER_GRID_SIZE)
			{
				for (z = startZ; z <= endZ + EPSILON; z += WATER_GRID_SIZE)
				{
					// console.log(x +', ' + z);
					var vertexId = calcVertexId(x, z);
					if (vertices[vertexId])
					{
						//get current water height and intersection heights
						waterHeight = vertices[vertexId].y;
						if (voxelizer.intersectionFirstAndLastHeights[x] && voxelizer.intersectionFirstAndLastHeights[x][z])
						{
							minIntersectHeight = voxelizer.intersectionFirstAndLastHeights[x][z][0];
							maxIntersectHeight = voxelizer.intersectionFirstAndLastHeights[x][z][1];

							//compare current water height with the voxelizer intersection heights to figure out how much water has been displaced
							if (minIntersectHeight > waterHeight)
							{
								//no intersection
								displacedVoxelCount[vertexId] = 0;
							}
							else if (maxIntersectHeight < waterHeight)
							{
								//totally submerged
								displacedVoxelCount[vertexId] = (maxIntersectHeight - minIntersectHeight) / WATER_GRID_SIZE;
							}
							else
							{
								//partially submerged
								displacedVoxelCount[vertexId] = (waterHeight - minIntersectHeight) / WATER_GRID_SIZE;
							}
						}
					}
				}
			}

			//sum up all displaced amounts for the object and apply buoyancy force
			var totalDisplacedVoxels = 0;
			for (x = startX; x <= endX + EPSILON; x += WATER_GRID_SIZE)
			{
				for (z = startZ; z <= endZ + EPSILON; z += WATER_GRID_SIZE)
				{
					var vertexId = calcVertexId(x, z);
					if (displacedVoxelCount[vertexId])
					{
						totalDisplacedVoxels += displacedVoxelCount[vertexId];
					}
				}
			}
			var totalDisplacedVol = totalDisplacedVoxels * WATER_VOXEL_VOL;  //cubic metres
			// if (totalDisplacedVol !== 0) console.log(totalDisplacedVol);
			var gravitationalAcc = -9.81;  //m per squared second
			var waterDensity = 1000;  //1000 kg per cubic metres
			var buoyancyForce = - totalDisplacedVol * waterDensity * gravitationalAcc;
			// if (totalDisplacedVol !== 0) console.log(force);
			//TODO: this force has to be applied per column
			dynMesh.applyCentralForce(new THREE.Vector3(0, buoyancyForce, 0));

			//spread out displaced volume
			for (x = startX; x <= endX + EPSILON; x += WATER_GRID_SIZE)
			{
				for (z = startZ; z <= endZ + EPSILON; z += WATER_GRID_SIZE)
				{
					var vertexId = calcVertexId(x, z);
					if (displacedVoxelCount[vertexId])
					{
						var diff = displacedVoxelCount[vertexId];
						if (displacedVoxelCountPrev[vertexId])
						{
							diff -= displacedVoxelCountPrev[vertexId];
						}
						var spreadHeight = diff / 4.0 * WATER_GRID_SIZE;
						// if (x===0 && z===0 && spreadHeight!==0) console.log(spreadHeight);

						//neighbour (x+1,z)
						var neighbourId = vertexId + WATER_RES;
						if (vertices[neighbourId])
						{
							heightFieldWaterSim.disturb(neighbourId, spreadHeight);
						}

						//neighbour (x-1,z)
						neighbourId = vertexId - WATER_RES;
						if (vertices[neighbourId])
						{
							heightFieldWaterSim.disturb(neighbourId, spreadHeight);
						}

						//neighbour (x,z+1)
						neighbourId = vertexId + 1;
						if (vertices[neighbourId])
						{
							heightFieldWaterSim.disturb(neighbourId, spreadHeight);
						}

						//neighbour (x,z-1)
						neighbourId = vertexId - 1;
						if (vertices[neighbourId])
						{
							heightFieldWaterSim.disturb(neighbourId, spreadHeight);
						}

						//store this value as prev
						displacedVoxelCountPrev[vertexId] = displacedVoxelCount[vertexId];
					}
				}
			}

			//remove any spread in volume for inside the object which helps in stability and makes clearer ripples
			for (x = startX; x <= endX + EPSILON; x += WATER_GRID_SIZE)
			{
				for (z = startZ; z <= endZ + EPSILON; z += WATER_GRID_SIZE)
				{
					var vertexId = calcVertexId(x, z);
					if (displacedVoxelCount[vertexId] > 0)
					{
						heightFieldWaterSim.disturb(vertexId, 0);
					}
				}
			}

		}
	}

	function setupEvents()
	{
		window.addEventListener('resize', window_onResize, false);
		window.addEventListener('mousedown', window_onMouseDown, false);
		window.addEventListener('mousemove', window_onMouseMove, false);
		window.addEventListener('mouseup', window_onMouseUp, false);
	}

	function window_onResize(event)
	{
		//update camera projection
		camera.aspect = window.innerWidth / (window.innerHeight-5);
		camera.updateProjectionMatrix();

		//update renderer size
		renderer.setSize(window.innerWidth, window.innerHeight-5);
	}

	var isDisturbing = false;
	function window_onMouseDown(event)
	{
		if (event.ctrlKey)
		{
			if (event.button === 0)  //LMB
			{
				intersectPoint = detectIntersection();
				if (intersectPoint)
				{
					isDisturbing = true;
					heightFieldWaterSim.disturb(calcVertexId(intersectPoint.x, intersectPoint.z), DISTURB_AMOUNT);
				}
			}
		}
	}

	function window_onMouseMove(event)
	{
		event.preventDefault();

		if (event.ctrlKey && isDisturbing)
		{
			intersectPoint = detectIntersection();
			if (intersectPoint)
			{
				heightFieldWaterSim.disturb(calcVertexId(intersectPoint.x, intersectPoint.z), DISTURB_AMOUNT);
			}
		}
	}

	function window_onMouseUp(event)
	{
		isDisturbing = false;
	}

	var intersectPoint = new THREE.Vector3();
	var mousePosNorm = new THREE.Vector2();
	var projector = new THREE.Projector();
	var raycaster = new THREE.Raycaster();
	function detectIntersection()
	{
		mousePosNorm.x = (event.clientX / window.innerWidth) * 2 - 1;
		mousePosNorm.y = - (event.clientY / window.innerHeight) * 2 + 1;

		//cast a ray from camera into screen
		var worldPos = new THREE.Vector3(mousePosNorm.x, mousePosNorm.y, 1);
		projector.unprojectVector(worldPos, camera);
		raycaster.set(camera.position, worldPos.sub(camera.position).normalize());
		var intersectInfo = raycaster.intersectObject(groundPlaneMesh);
		
		//get intersection point and vertexId
		if (intersectInfo && intersectInfo[0])
		{
			return intersectInfo[0].point;
		}
	}

	function calcVertexId(x, z, roundDown)
	{
		roundDown = (typeof(roundDown)==='undefined') ? true : roundDown;

		//calculate vertex id using x and z
		var row, col;
		if (roundDown)
		{
			row = Math.floor((z + WATER_SIZE_HALF) / WATER_SIZE * WATER_RES);
			col = Math.floor((x + WATER_SIZE_HALF) / WATER_SIZE * WATER_RES);
		}
		else
		{
			row = Math.ceil((z + WATER_SIZE_HALF) / WATER_SIZE * WATER_RES);
			col = Math.ceil((x + WATER_SIZE_HALF) / WATER_SIZE * WATER_RES);
		}
		return (row * WATER_RES) + col;
	}

	var stats;
	function setupStats()
	{
		//create a stats monitor
		stats = new Stats();
		$('body').append(stats.domElement);
	}

	var gui;
	var waterSimFolder, obstaclesFolder, renderingFolder, displayFolder;
	var waterSimTypes = {
		'Tessendorf iWave': 'heightFieldWaterSim_iWave',
		'Muller GDC2008': 'heightFieldWaterSim_mullerGdc2008',
		'Muller GDC2008 HelloWorld': 'heightFieldWaterSim_mullerGdc2008_helloWorld',
		'X Water': 'heightFieldWaterSim_xWater'
	};
	var waterSimTypeSettings = {
		'heightFieldWaterSim_iWave': { 'disturbAmount':0.01, 'dampingFactor':0.7 },
		'heightFieldWaterSim_mullerGdc2008': { 'disturbAmount':0.25, 'dampingFactor':0.95 },
		'heightFieldWaterSim_mullerGdc2008_helloWorld': { 'disturbAmount':0.25, 'dampingFactor':0.95 },
		'heightFieldWaterSim_xWater': { 'disturbAmount':0.25, 'dampingFactor':0.95 }
	};
	var options = {
		waterSimType: waterSimTypes[Object.keys(waterSimTypes)[3]],
		obstaclesActive: true,
		obstaclesSeed: 1,
		obstaclesShowObstacles: true,
		obstaclesShowDepthMap: true,
		renderingShadows: true,  //FIXME: this has to be init to true
		displayWireframe: false,
		displayLocalAxis: false,
	};
	function setupGui() {

		gui = new dat.GUI({width: 350});
		//gui.close();  //close GUI by default

		//Water Sim folder
		waterSimFolder = gui.addFolder('Water Sim');
		// waterSimFolder.open();

		var control = waterSimFolder.add(options, 'waterSimType', waterSimTypes).name('Type');
		function changeWaterSimType(value)
		{
			//reset current sim first
			heightFieldWaterSim.reset();

			//swap the sim
			heightFieldWaterSim = eval(value);

			//load some presets so that that sim works well
			DISTURB_AMOUNT = waterSimTypeSettings[value].disturbAmount;
			DAMPING_FACTOR = waterSimTypeSettings[value].dampingFactor;
			heightFieldWaterSim.dampingFactor = DAMPING_FACTOR;

			//make sure to inherit the active value from the GUI too
			heightFieldWaterSim.setObstaclesActive(options.obstaclesActive);
		}
		control.onChange(changeWaterSimType);
		changeWaterSimType(options.waterSimType);

		//Obstacles folder
		obstaclesFolder = gui.addFolder('Obstacles');
		// obstaclesFolder.open();

		control = obstaclesFolder.add(options, 'obstaclesActive').name('Active');
		function changeObstaclesActive(value)
		{
			heightFieldWaterSim.setObstaclesActive(value);
		}
		control.onChange(changeObstaclesActive);
		changeObstaclesActive(options.obstaclesActive);

		control = obstaclesFolder.add(options, 'obstaclesSeed', 0, 50).step(1).name('Seed');
		function changeObstaclesSeed(value)
		{
			RANDOM_SEED = Math.floor(value);
			Math.seedrandom(RANDOM_SEED);
			randomizeObstaclePositions();
		}
		control.onChange(changeObstaclesSeed);

		control = obstaclesFolder.add(options, 'obstaclesShowObstacles').name('Show Obstacles');
		function changeObstaclesShowObstacles(value)
		{
			for (obstacleId in obstacles)
			{
				obstacles[obstacleId].visible = value;
			}
		}
		control.onChange(changeObstaclesShowObstacles);
		changeObstaclesShowObstacles(options.obstaclesShowObstacles);

		control = obstaclesFolder.add(options, 'obstaclesShowDepthMap').name('Show Depth Map');
		function changeObstaclesShowDepthMap(value)
		{
			if (value)
			{
				ObstacleManager.$depthMapImageObj.css({display:'block'});
			}
			else
			{
				ObstacleManager.$depthMapImageObj.css({display:'none'});
			}
		}
		control.onChange(changeObstaclesShowDepthMap);
		changeObstaclesShowDepthMap(options.obstaclesShowDepthMap);

		//Rendering folder
		renderingFolder = gui.addFolder('Rendering');

		control = renderingFolder.add(options, 'renderingShadows').name('Shadows');
		function changeRenderingShadows(value)
		{
			renderer.shadowMapAutoUpdate = value;
			if (!value)
			{
				renderer.clearTarget(keyLight.shadowMap);
			}
		}
		control.onChange(changeRenderingShadows);
		changeRenderingShadows(options.renderingShadows);

		//Display folder
		displayFolder = gui.addFolder('Display');
		
		control = displayFolder.add(options, 'displayWireframe').name('Wireframe');
		function toggleWireframe(value)
		{
			for (materialId in materials)
			{
				materials[materialId].wireframe = value;
			}
		}
		control.onChange(toggleWireframe);
		toggleWireframe(options.displayWireframe);
		
		control = displayFolder.add(options, 'displayLocalAxis').name('Local Axis');
		function toggleLocalAxis(value)
		{
			localAxis.visible = value;
		}
		control.onChange(toggleLocalAxis);
		toggleLocalAxis(options.displayLocalAxis);
	}

	$(document).ready(function()
	{
		//check for WebGL
		checkWebGl();

		console.log('Document is ready.');
		
		//setup scene
		setupThreejsScene();

		//setup water
		setupWaterScene();

		//setup physics scene
		setupPhysicsScene();

		//setup events
		setupEvents();

		//setup stats monitor
		setupStats();
		
		//setup GUI
		setupGui();
		
		//main loop
		function loop()
		{
			var dt = clock.getDelta();  //have to call this before getElapsedTime()
			var time = clock.getElapsedTime();

			controls.update();

			scene.simulate();

			renderer.autoClear = false;
			renderer.clear();
			renderer.render(scene, camera);

			//FIXME: this should appear before renderer.render(), but it seems like the transformation matrix hasn't been applied on the first frame before calling render(), so have to do it after.
			//handle two-way coupling
			handleTwoWayCoupling(dt);

			heightFieldWaterSim.update(dt);

			stats.update();

			requestAnimationFrame(loop);
		}
		loop();
		
	});
	</script>
</head>
<body>
	<div id="threejs-container"></div>
	<div class="translucent" id="info-container">
		<div>Camera: [ Alt+LMB: rotate ] [ Alt+MMB: pan ] [ Alt+RMB: zoom ]</div>
		<div>Water Sim: [ Ctrl+LMB: disturb water ]</div>
	</div>
</body>
</html><commit_msg>Added code for user to push floating objects around<commit_after><!DOCTYPE html>
<html>
<head>
	<title>IFC - Two-Way Coupling Test</title>
	<meta charset="UTF-8"/>
	
	<link rel="stylesheet" href="/css/style.css">
	<style type="text/css">
		#stats { position: absolute; top:0; left: 0 }
		#stats #fps { background: transparent !important }
		#stats #fps #fpsText { color: #aaa !important }
		#stats #fps #fpsGraph { display: block }
	</style>
	
	<!--load helper libraries-->
	<script type="text/javascript" src="/js/lib/jquery-2.0.2.min.js"></script>
	
	<!--load AR and 3D libraries-->
	<script type="text/javascript" src="/js/lib/three.min.js"></script>
	<script type="text/javascript" src="/js/lib/Detector.js"></script>
	<script type="text/javascript" src="/js/lib/stats.min.js"></script>
	<script type="text/javascript" src="/js/lib/dat.gui.min.js"></script>
	<script type="text/javascript" src="/js/lib/OrbitControls.js"></script>

	<script type="text/javascript" src="/js/lib/seedrandom.js"></script>

	<script type="text/javascript" src="/js/heightfieldwatersim.js"></script>
	<script type="text/javascript" src="/js/skvoxelizer.js"></script>
	<script type="text/javascript" src="/js/lib/physics/physi.js"></script>
	
	<script>

	//config Physijs
	Physijs.scripts.worker = '/js/lib/physics/physijs_worker.js';
	Physijs.scripts.ammo = '/js/lib/physics/ammo.js';

	var WATER_SIZE = 10;
	var WATER_RES = 80;
	var WATER_GRID_SIZE = WATER_SIZE / WATER_RES;
	var WATER_SIZE_HALF = WATER_SIZE / 2.0;
	var WATER_VOXEL_VOL = WATER_GRID_SIZE * WATER_GRID_SIZE * WATER_GRID_SIZE;
	var DAMPING_FACTOR = 0.8;
	var DISTURB_AMOUNT = 0.015;

	var SHADOW_MAP_RES = 1024;

	var EPSILON = 0.001;

	var clock = new THREE.Clock();

	function checkWebGl()
	{
		//check for WebGL
		if (!Detector.webgl)
		{
			$('div').remove();
			Detector.addGetWebGLMessage();
			throw new Error('Your browser does not seem to support WebGL');
		}
	}

	var renderer, scene, camera, controls;
	var materials = [];
	var localAxis;
	function setupThreejsScene()
	{
		//create renderer
		renderer = new THREE.WebGLRenderer({
			antialias : true
		});
		renderer.setSize(window.innerWidth, window.innerHeight-5);
		renderer.setClearColor('#111111', 1);
		renderer.shadowMapEnabled = true;
		renderer.shadowMapType = THREE.PCFSoftShadowMap;
		renderer.shadowMapSoft = true;
		var $container = $('#threejs-container');
		$container.append(renderer.domElement);

		//create scene
		scene = new Physijs.Scene();
		scene.addEventListener(
			'ready',
			function() {
				console.log('Physijs scene is ready');
				setupDynamicAttributes();
			}
		);
		// scene.setGravity(new THREE.Vector3(0, -5, 0));

		//create camera
		camera = new THREE.PerspectiveCamera(25, renderer.domElement.width/renderer.domElement.height, 0.1, 1000);
		camera.position.set(8, 8, 15);
		camera.lookAt(new THREE.Vector3(0,0,0));

		//create controls for camera
		controls = new THREE.OrbitControls(camera);
		controls.userPanSpeed = 0.2;
		//controls.autoRotate = true;
		controls.modifierKey = 'alt';

		//create axis
		localAxis = new THREE.AxisHelper();
		scene.add(localAxis);

		//setup lights
		scene.add(new THREE.AmbientLight(0x111111));

		keyLight = new THREE.SpotLight(0xffff44, 0.6);
		keyLight.position.set(5, 15, -15);
		keyLight.target.position.set(0, 0, 0);
		keyLight.castShadow = true;
		keyLight.shadowCameraNear = 10;
		keyLight.shadowCameraFar = 40;
		keyLight.shadowCameraFov = 40;
		// keyLight.shadowCameraVisible = true;
		keyLight.shadowBias = 0.00001;
		keyLight.shadowDarkness = 0.4;
		keyLight.shadowMapWidth = SHADOW_MAP_RES;
		keyLight.shadowMapHeight = SHADOW_MAP_RES;
		scene.add(keyLight);

		fillLight = new THREE.SpotLight(0xff0044, 0.4);
		fillLight.position.set(5, 2, 15);
		fillLight.target.position.set(0, 0, 0);
		scene.add(fillLight);

		//create plane for intersection test
		var groundPlaneGeom = new THREE.PlaneGeometry(WATER_SIZE, WATER_SIZE, 1, 1);
		groundPlaneGeom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI/2));
		var groundPlaneMaterial = new THREE.MeshPhongMaterial();
		materials.push(groundPlaneMaterial);
		groundPlaneMesh = new THREE.Mesh(groundPlaneGeom, groundPlaneMaterial);
		groundPlaneMesh.castShadow = false;
		groundPlaneMesh.receiveShadow = false;
		groundPlaneMesh.visible = false;
		scene.add(groundPlaneMesh);
	}

	var RANDOM_SEED = 1;
	Math.seedrandom(RANDOM_SEED);
	var waterMesh;
	var heightFieldWaterSim, heightFieldWaterSim_iWave, heightFieldWaterSim_mullerGdc2008, heightFieldWaterSim_mullerGdc2008_helloWorld, heightFieldWaterSim_xWater;
	function setupWaterScene()
	{
		//create a height-field water sim from the plane
		var waterGeom = new THREE.PlaneGeometry(WATER_SIZE, WATER_SIZE, WATER_RES-1, WATER_RES-1);
		waterGeom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI/2));
		var waterMaterial = new THREE.MeshPhongMaterial({color:'#3366FF'});
		materials.push(waterMaterial);
		waterMesh = new THREE.Mesh(waterGeom, waterMaterial);
		waterMesh.castShadow = true;
		waterMesh.receiveShadow = true;
		scene.add(waterMesh);

		//create the different water sims
		heightFieldWaterSim_iWave = new HeightFieldWaterSim_Tessendorf_iWave(waterMesh, WATER_SIZE, WATER_RES, DAMPING_FACTOR, 4, 3);
		heightFieldWaterSim_mullerGdc2008 = new HeightFieldWaterSim_Muller_GDC2008(waterMesh, WATER_SIZE, WATER_RES, DAMPING_FACTOR, 5.0);
		heightFieldWaterSim_mullerGdc2008_helloWorld = new HeightFieldWaterSim_Muller_GDC2008_HelloWorld(waterMesh, WATER_SIZE, WATER_RES, DAMPING_FACTOR);
		heightFieldWaterSim_xWater = new HeightFieldWaterSim_xWater(waterMesh, WATER_SIZE, WATER_RES, DAMPING_FACTOR);
		heightFieldWaterSim = heightFieldWaterSim_iWave;
	}

	var iSegments = 3;
	var jSegments = 3;
	var iSegmentSize = WATER_SIZE / iSegments;
	var jSegmentSize = WATER_SIZE / jSegments;
	var dynMeshes = [];
	var obstacles = [];
	function setupPhysicsScene()
	{
		//create a dynamic box
		var boxGeom = new THREE.CubeGeometry(1, 1, 1);
		var boxMaterial = new THREE.MeshPhongMaterial({color:'#00ff00'});
		materials.push(boxMaterial);
		var boxMass = 700;  //kg
		var boxDynMesh = new Physijs.BoxMesh(boxGeom, boxMaterial, boxMass);
		boxDynMesh.position.set(0, 2, 0);
		boxDynMesh.castShadow = true;
		boxDynMesh.receiveShadow = true;
		scene.add(boxDynMesh);
		dynMeshes.push(boxDynMesh);
		boxDynMesh.voxelizer = new SkVoxelizer(boxDynMesh, WATER_GRID_SIZE, WATER_GRID_SIZE, WATER_GRID_SIZE);

		//create a dynamic sphere
		var sphereGeom = new THREE.SphereGeometry(0.75, 8, 8);
		var sphereMaterial = new THREE.MeshPhongMaterial({color:'#00ff00'});
		materials.push(sphereMaterial);
		var sphereMass = 930;  //kg
		var sphereDynMesh = new Physijs.SphereMesh(sphereGeom, sphereMaterial, sphereMass);
		sphereDynMesh.position.set(3, 3, 3);
		sphereDynMesh.castShadow = true;
		sphereDynMesh.receiveShadow = true;
		scene.add(sphereDynMesh);
		dynMeshes.push(sphereDynMesh);
		sphereDynMesh.voxelizer = new SkVoxelizer(sphereDynMesh, WATER_GRID_SIZE, WATER_GRID_SIZE, WATER_GRID_SIZE);
		
		//create 4 walls to prevent dynamic object from flying off
		var thickness = 1;
		var halfThickness = thickness / 2.0;
		var wallGeom = new THREE.CubeGeometry(WATER_SIZE, WATER_SIZE * 2, thickness);
		var wallRotatedGeom = new THREE.CubeGeometry(WATER_SIZE, WATER_SIZE * 2, thickness);
		wallRotatedGeom.applyMatrix(new THREE.Matrix4().makeRotationY(-Math.PI/2));
		var wallMaterial = new THREE.MeshPhongMaterial();
		//+z wall
		var wallMesh = new Physijs.BoxMesh(wallGeom, wallMaterial, 0);
		wallMesh.position.z = WATER_SIZE_HALF + halfThickness;
		wallMesh.visible = false;
		scene.add(wallMesh);
		//-z wall
		wallMesh = new Physijs.BoxMesh(wallGeom, wallMaterial, 0);
		wallMesh.position.z = - (WATER_SIZE_HALF + halfThickness);
		wallMesh.visible = false;
		scene.add(wallMesh);
		//+x wall
		wallMesh = new Physijs.BoxMesh(wallRotatedGeom, wallMaterial, 0);
		wallMesh.position.x = WATER_SIZE_HALF + halfThickness;
		wallMesh.visible = false;
		scene.add(wallMesh);
		//-x wall
		wallMesh = new Physijs.BoxMesh(wallRotatedGeom, wallMaterial, 0);
		wallMesh.position.x = -(WATER_SIZE_HALF + halfThickness);
		wallMesh.visible = false;
		scene.add(wallMesh);

		//create some obstacles for the water sim
		var i, j;
		for (i = 0; i < iSegments; i++)
		{
			for (j = 0; j < jSegments; j++)
			{
				var length = WATER_SIZE / 5.0;
				var obstacleGeom = new THREE.CubeGeometry(length*(Math.random()*0.5+0.5), 1, length*(Math.random()*0.5+0.5));
				var obstacleMaterial = new THREE.MeshPhongMaterial();
				materials.push(obstacleMaterial);
				obstacleMesh = new Physijs.BoxMesh(obstacleGeom, obstacleMaterial, 0);
				obstacleMesh.castShadow = true;
				obstacleMesh.receiveShadow = true;
				obstacleMesh.position.x = (iSegmentSize * i) - WATER_SIZE_HALF + ((Math.random()) * iSegmentSize);
				obstacleMesh.position.z = (jSegmentSize * j) - WATER_SIZE_HALF + ((Math.random()) * jSegmentSize);
				obstacleMesh.rotation.y = (Math.random() * 360);
				scene.add(obstacleMesh);
				obstacles.push(obstacleMesh);
				heightFieldWaterSim.addObstacle(obstacleMesh);
			}
		}

		//temp, just to make the demo nicer
		obstacles[1].position.x += -1;
		obstacles[1].__dirtyPosition = true;
		obstacles[2].position.x = 10000;
		obstacles[2].position.x = 10000;
		obstacles[2].__dirtyPosition = true;
	}

	function setupDynamicAttributes()
	{
		var dynMesh;
		for (dynMeshId in dynMeshes)
		{
			dynMesh = dynMeshes[dynMeshId];
			dynMesh.setDamping(0.75, 0.5);
		}
	}

	function randomizeObstaclePositions()
	{
		var i, j;
		for (i = 0; i < iSegments; i++)
		{
			for (j = 0; j < jSegments; j++)
			{
				idx = i * iSegments + j;
				obstacles[idx].position.x = (iSegmentSize * i) - WATER_SIZE_HALF + ((Math.random()) * iSegmentSize * 0.8);
				obstacles[idx].position.y = Math.random() * 4 - 2;
				obstacles[idx].position.z = (jSegmentSize * j) - WATER_SIZE_HALF + ((Math.random()) * jSegmentSize * 0.8);
				obstacles[idx].rotation.y = (Math.random() * 360);
			}
		}
	}

	//TODO: this should go into the height field water class
	displacedVoxelCountPrev = {};
	displacedVoxelCount = {};
	function handleTwoWayCoupling(dt)
	{
		//find out how much water the objects are displacing
		//for each object, project AABB down to water, check all affected grid points to see if they are inside or outside water
		var dynMesh, bbox, h, voxelizer;
		for (dynMeshId in dynMeshes)
		{
			dynMesh = dynMeshes[dynMeshId];

			//TODO: check if dynMesh is totally not touching the water, using bounding box checks

			//get intersection data from voxelizer
			voxelizer = dynMesh.voxelizer;
			voxelizer.updateIntersections();  //this will also update AABB voxelizer.__min and voxelizer.__max
			//visualize if necessary, for debugging purposes:
			// dynMesh.voxelizer.voxelize();
			// dynMesh.voxelizer.visualize(scene);

			//project AABB down and get (x,z) of all affected grid points
			var startX = Math.ceil(voxelizer.__min.x / WATER_GRID_SIZE) * WATER_GRID_SIZE;
			var endX = Math.ceil(voxelizer.__max.x / WATER_GRID_SIZE) * WATER_GRID_SIZE;
			var startZ = Math.ceil(voxelizer.__min.z / WATER_GRID_SIZE) * WATER_GRID_SIZE;
			var endZ = Math.ceil(voxelizer.__max.z / WATER_GRID_SIZE) * WATER_GRID_SIZE;
			var x, z;
			var vertices = heightFieldWaterSim.geometry.vertices;
			var minIntersectHeight, maxIntersectHeight, waterHeight;
			for (x = startX; x <= endX + EPSILON; x += WATER_GRID_SIZE)
			{
				for (z = startZ; z <= endZ + EPSILON; z += WATER_GRID_SIZE)
				{
					var vertexId = calcVertexId(x, z);
					if (vertices[vertexId])
					{
						//get current water height and intersection heights
						waterHeight = vertices[vertexId].y;
						if (voxelizer.intersectionFirstAndLastHeights[x] && voxelizer.intersectionFirstAndLastHeights[x][z])
						{
							minIntersectHeight = voxelizer.intersectionFirstAndLastHeights[x][z][0];
							maxIntersectHeight = voxelizer.intersectionFirstAndLastHeights[x][z][1];

							//compare current water height with the voxelizer intersection heights to figure out how much water has been displaced
							if (minIntersectHeight > waterHeight)
							{
								//no intersection
								displacedVoxelCount[vertexId] = 0;
							}
							else if (maxIntersectHeight < waterHeight)
							{
								//totally submerged
								displacedVoxelCount[vertexId] = (maxIntersectHeight - minIntersectHeight) / WATER_GRID_SIZE;
							}
							else
							{
								//partially submerged
								displacedVoxelCount[vertexId] = (waterHeight - minIntersectHeight) / WATER_GRID_SIZE;
							}
						}
					}
				}
			}

			//sum up all displaced amounts for the object and apply buoyancy force
			var totalDisplacedVoxels = 0;
			for (x = startX; x <= endX + EPSILON; x += WATER_GRID_SIZE)
			{
				for (z = startZ; z <= endZ + EPSILON; z += WATER_GRID_SIZE)
				{
					var vertexId = calcVertexId(x, z);
					if (displacedVoxelCount[vertexId])
					{
						totalDisplacedVoxels += displacedVoxelCount[vertexId];
					}
				}
			}
			var totalDisplacedVol = totalDisplacedVoxels * WATER_VOXEL_VOL;  //cubic metres
			var gravitationalAcc = -9.81;  //m per squared second
			var waterDensity = 1000;  //1000 kg per cubic metres
			var buoyancyForce = - totalDisplacedVol * waterDensity * gravitationalAcc;
			//TODO: this force has to be applied per column?
			dynMesh.applyCentralForce(new THREE.Vector3(0, buoyancyForce, 0));

			//spread out displaced volume
			for (x = startX; x <= endX + EPSILON; x += WATER_GRID_SIZE)
			{
				for (z = startZ; z <= endZ + EPSILON; z += WATER_GRID_SIZE)
				{
					var vertexId = calcVertexId(x, z);
					if (displacedVoxelCount[vertexId])
					{
						var diff = displacedVoxelCount[vertexId];
						if (displacedVoxelCountPrev[vertexId])
						{
							diff -= displacedVoxelCountPrev[vertexId];
						}
						var spreadHeight = diff / 4.0 * WATER_GRID_SIZE;

						//neighbour (x+1,z)
						var neighbourId = vertexId + WATER_RES;
						if (vertices[neighbourId])
						{
							heightFieldWaterSim.disturb(neighbourId, spreadHeight);
						}

						//neighbour (x-1,z)
						neighbourId = vertexId - WATER_RES;
						if (vertices[neighbourId])
						{
							heightFieldWaterSim.disturb(neighbourId, spreadHeight);
						}

						//neighbour (x,z+1)
						neighbourId = vertexId + 1;
						if (vertices[neighbourId])
						{
							heightFieldWaterSim.disturb(neighbourId, spreadHeight);
						}

						//neighbour (x,z-1)
						neighbourId = vertexId - 1;
						if (vertices[neighbourId])
						{
							heightFieldWaterSim.disturb(neighbourId, spreadHeight);
						}

						//store this value as prev
						displacedVoxelCountPrev[vertexId] = displacedVoxelCount[vertexId];
					}
				}
			}

			//remove any spread in volume for inside the object which helps in stability and makes clearer ripples
			for (x = startX; x <= endX + EPSILON; x += WATER_GRID_SIZE)
			{
				for (z = startZ; z <= endZ + EPSILON; z += WATER_GRID_SIZE)
				{
					var vertexId = calcVertexId(x, z);
					if (displacedVoxelCount[vertexId] > 0)
					{
						heightFieldWaterSim.disturb(vertexId, 0);
					}
				}
			}

		}
	}

	function setupEvents()
	{
		window.addEventListener('resize', window_onResize, false);
		window.addEventListener('mousedown', window_onMouseDown, false);
		window.addEventListener('mousemove', window_onMouseMove, false);
		window.addEventListener('mouseup', window_onMouseUp, false);
	}

	function window_onResize(event)
	{
		//update camera projection
		camera.aspect = window.innerWidth / (window.innerHeight-5);
		camera.updateProjectionMatrix();

		//update renderer size
		renderer.setSize(window.innerWidth, window.innerHeight-5);
	}

	var isDisturbing = false;
	function window_onMouseDown(event)
	{
		if (event.ctrlKey)
		{
			if (event.button === 0)  //LMB
			{
				intersectPoint = detectIntersection();
				if (intersectPoint)
				{
					isDisturbing = true;
					heightFieldWaterSim.disturb(calcVertexId(intersectPoint.x, intersectPoint.z), DISTURB_AMOUNT);
				}
			}
		}
		else if (event.shiftKey)
		{
			if (event.button === 0)  //LMB
			{
				intersectObj = detectIntersectionDynObjects();
				if (intersectObj)
				{
					intersectObj.applyCentralImpulse(new THREE.Vector3(Math.random(), Math.random(), Math.random()).sub(intersectObj.position).multiplyScalar(5*intersectObj.mass));
				}
			}
		}
	}

	function window_onMouseMove(event)
	{
		event.preventDefault();

		if (event.ctrlKey && isDisturbing)
		{
			intersectPoint = detectIntersection();
			if (intersectPoint)
			{
				heightFieldWaterSim.disturb(calcVertexId(intersectPoint.x, intersectPoint.z), DISTURB_AMOUNT);
			}
		}
	}

	function window_onMouseUp(event)
	{
		isDisturbing = false;
	}

	var intersectPoint = new THREE.Vector3();
	var mousePosNorm = new THREE.Vector2();
	var projector = new THREE.Projector();
	var raycaster = new THREE.Raycaster();
	function detectIntersection()
	{
		mousePosNorm.x = (event.clientX / window.innerWidth) * 2 - 1;
		mousePosNorm.y = - (event.clientY / window.innerHeight) * 2 + 1;

		//cast a ray from camera into screen
		var worldPos = new THREE.Vector3(mousePosNorm.x, mousePosNorm.y, 1);
		projector.unprojectVector(worldPos, camera);
		raycaster.set(camera.position, worldPos.sub(camera.position).normalize());
		var intersectInfo = raycaster.intersectObject(groundPlaneMesh);
		
		//get intersection point and vertexId
		if (intersectInfo && intersectInfo[0])
		{
			return intersectInfo[0].point;
		}
	}

	function detectIntersectionDynObjects()
	{
		mousePosNorm.x = (event.clientX / window.innerWidth) * 2 - 1;
		mousePosNorm.y = - (event.clientY / window.innerHeight) * 2 + 1;

		//cast a ray from camera into screen
		var worldPos = new THREE.Vector3(mousePosNorm.x, mousePosNorm.y, 1);
		projector.unprojectVector(worldPos, camera);
		raycaster.set(camera.position, worldPos.sub(camera.position).normalize());
		var intersectInfo = raycaster.intersectObjects(dynMeshes);
		
		//get intersection point and vertexId
		if (intersectInfo && intersectInfo[0])
		{
			return intersectInfo[0].object;
		}
	}

	function calcVertexId(x, z, roundDown)
	{
		roundDown = (typeof(roundDown)==='undefined') ? true : roundDown;

		//calculate vertex id using x and z
		var row, col;
		if (roundDown)
		{
			row = Math.floor((z + WATER_SIZE_HALF) / WATER_SIZE * WATER_RES);
			col = Math.floor((x + WATER_SIZE_HALF) / WATER_SIZE * WATER_RES);
		}
		else
		{
			row = Math.ceil((z + WATER_SIZE_HALF) / WATER_SIZE * WATER_RES);
			col = Math.ceil((x + WATER_SIZE_HALF) / WATER_SIZE * WATER_RES);
		}
		return (row * WATER_RES) + col;
	}

	var stats;
	function setupStats()
	{
		//create a stats monitor
		stats = new Stats();
		$('body').append(stats.domElement);
	}

	var gui;
	var waterSimFolder, obstaclesFolder, renderingFolder, displayFolder;
	var waterSimTypes = {
		'Tessendorf iWave': 'heightFieldWaterSim_iWave',
		'Muller GDC2008': 'heightFieldWaterSim_mullerGdc2008',
		'Muller GDC2008 HelloWorld': 'heightFieldWaterSim_mullerGdc2008_helloWorld',
		'X Water': 'heightFieldWaterSim_xWater'
	};
	var waterSimTypeSettings = {
		'heightFieldWaterSim_iWave': { 'disturbAmount':0.01, 'dampingFactor':0.7 },
		'heightFieldWaterSim_mullerGdc2008': { 'disturbAmount':0.25, 'dampingFactor':0.95 },
		'heightFieldWaterSim_mullerGdc2008_helloWorld': { 'disturbAmount':0.25, 'dampingFactor':0.95 },
		'heightFieldWaterSim_xWater': { 'disturbAmount':0.25, 'dampingFactor':0.95 }
	};
	var options = {
		waterSimType: waterSimTypes[Object.keys(waterSimTypes)[3]],
		obstaclesActive: true,
		obstaclesSeed: 1,
		obstaclesShowObstacles: true,
		obstaclesShowDepthMap: true,
		renderingShadows: true,  //FIXME: this has to be init to true
		displayWireframe: false,
		displayLocalAxis: false,
	};
	function setupGui() {

		gui = new dat.GUI({width: 350});
		//gui.close();  //close GUI by default

		//Water Sim folder
		waterSimFolder = gui.addFolder('Water Sim');
		// waterSimFolder.open();

		var control = waterSimFolder.add(options, 'waterSimType', waterSimTypes).name('Type');
		function changeWaterSimType(value)
		{
			//reset current sim first
			heightFieldWaterSim.reset();

			//swap the sim
			heightFieldWaterSim = eval(value);

			//load some presets so that that sim works well
			DISTURB_AMOUNT = waterSimTypeSettings[value].disturbAmount;
			DAMPING_FACTOR = waterSimTypeSettings[value].dampingFactor;
			heightFieldWaterSim.dampingFactor = DAMPING_FACTOR;

			//make sure to inherit the active value from the GUI too
			heightFieldWaterSim.setObstaclesActive(options.obstaclesActive);
		}
		control.onChange(changeWaterSimType);
		changeWaterSimType(options.waterSimType);

		//Obstacles folder
		obstaclesFolder = gui.addFolder('Obstacles');
		// obstaclesFolder.open();

		control = obstaclesFolder.add(options, 'obstaclesActive').name('Active');
		function changeObstaclesActive(value)
		{
			heightFieldWaterSim.setObstaclesActive(value);
		}
		control.onChange(changeObstaclesActive);
		changeObstaclesActive(options.obstaclesActive);

		control = obstaclesFolder.add(options, 'obstaclesSeed', 0, 50).step(1).name('Seed');
		function changeObstaclesSeed(value)
		{
			RANDOM_SEED = Math.floor(value);
			Math.seedrandom(RANDOM_SEED);
			randomizeObstaclePositions();
		}
		control.onChange(changeObstaclesSeed);

		control = obstaclesFolder.add(options, 'obstaclesShowObstacles').name('Show Obstacles');
		function changeObstaclesShowObstacles(value)
		{
			for (obstacleId in obstacles)
			{
				obstacles[obstacleId].visible = value;
			}
		}
		control.onChange(changeObstaclesShowObstacles);
		changeObstaclesShowObstacles(options.obstaclesShowObstacles);

		control = obstaclesFolder.add(options, 'obstaclesShowDepthMap').name('Show Depth Map');
		function changeObstaclesShowDepthMap(value)
		{
			if (value)
			{
				ObstacleManager.$depthMapImageObj.css({display:'block'});
			}
			else
			{
				ObstacleManager.$depthMapImageObj.css({display:'none'});
			}
		}
		control.onChange(changeObstaclesShowDepthMap);
		changeObstaclesShowDepthMap(options.obstaclesShowDepthMap);

		//Rendering folder
		renderingFolder = gui.addFolder('Rendering');

		control = renderingFolder.add(options, 'renderingShadows').name('Shadows');
		function changeRenderingShadows(value)
		{
			renderer.shadowMapAutoUpdate = value;
			if (!value)
			{
				renderer.clearTarget(keyLight.shadowMap);
			}
		}
		control.onChange(changeRenderingShadows);
		changeRenderingShadows(options.renderingShadows);

		//Display folder
		displayFolder = gui.addFolder('Display');
		
		control = displayFolder.add(options, 'displayWireframe').name('Wireframe');
		function toggleWireframe(value)
		{
			for (materialId in materials)
			{
				materials[materialId].wireframe = value;
			}
		}
		control.onChange(toggleWireframe);
		toggleWireframe(options.displayWireframe);
		
		control = displayFolder.add(options, 'displayLocalAxis').name('Local Axis');
		function toggleLocalAxis(value)
		{
			localAxis.visible = value;
		}
		control.onChange(toggleLocalAxis);
		toggleLocalAxis(options.displayLocalAxis);
	}

	$(document).ready(function()
	{
		//check for WebGL
		checkWebGl();

		console.log('Document is ready.');
		
		//setup scene
		setupThreejsScene();

		//setup water
		setupWaterScene();

		//setup physics scene
		setupPhysicsScene();

		//setup events
		setupEvents();

		//setup stats monitor
		setupStats();
		
		//setup GUI
		setupGui();
		
		//main loop
		function loop()
		{
			var dt = clock.getDelta();  //have to call this before getElapsedTime()
			var time = clock.getElapsedTime();

			controls.update();

			scene.simulate();

			renderer.autoClear = false;
			renderer.clear();
			renderer.render(scene, camera);

			//FIXME: this should appear before renderer.render(), but it seems like the transformation matrix hasn't been applied on the first frame before calling render(), so have to do it after.
			//handle two-way coupling
			handleTwoWayCoupling(dt);

			heightFieldWaterSim.update(dt);

			stats.update();

			requestAnimationFrame(loop);
		}
		loop();
		
	});
	</script>
</head>
<body>
	<div id="threejs-container"></div>
	<div class="translucent" id="info-container">
		<div>Camera: [ Alt+LMB: rotate ] [ Alt+MMB: pan ] [ Alt+RMB: zoom ]</div>
		<div>Sim: [ Ctrl+LMB: disturb water ] [ Shift+LMB: push objects ] </div>
	</div>
</body>
</html>
{'fraction_non_alphanumeric': 0.12322116189519504, 'fraction_numerical': 0.014314414866901055, 'mean_word_length': 4.050200803212851, 'pattern_counts': {'":': 0, '<': 119, '<?xml version=': 0, '>': 97, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2484709', 'n_tokens_mistral': 19661, 'n_tokens_neox': 16393, 'n_words': 3337}