<commit_before><commit_msg>Added first augmented reality terrain sculpting and water sim demo<commit_after><!DOCTYPE html>
<html>
<head>
	<title>IFC - Augmented Reality Flood</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 webcam streaming libraries-->
	<script type="text/javascript" src="/js/lib/webrtc_adapter.js"></script>
	<script type="text/javascript" src="/js/streamvideo.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/JSARToolKit.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/skarf.js"></script>

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

	<script type="text/javascript" src="/js/lib/skulpt.js"></script>
	<script type="text/javascript" src="/js/heightfieldwatersim.js"></script>
	<script type="text/javascript" src="/js/skvoxelizer.js"></script>
	
	<script>
	
	var GLOBAL_SCALE = 30;

	var TRACKING_THRESHOLD = 30;
	var JSAR_DEBUG = false;

	var SHADOW_MAP_RES = 1024;
	var TERRAIN_RES = 64;
	var TERRAIN_SIZE = 10;
	var USE_TERRAIN_IMAGE = true;
	var MEAN_WATER_HEIGHT = 0.1;

	var WATER_SIZE = 10;
	var WATER_RES = 64;
	var WATER_GRID_SIZE = WATER_SIZE / WATER_RES;
	var DAMPING_FACTOR = 0.8;
	var DISTURB_AMOUNT = 0.015;

	var clock = new THREE.Clock();
	var currWaterHeight = MEAN_WATER_HEIGHT;

	var terrainImages = {
		'IGMS1':'/resources/images/terrain/IGMS/igms_693432,4598934,694456,4599958_512.jpg',
		'IGMS2':'/resources/images/terrain/IGMS/igms_617851,4595749,620411,4598309_512.jpg',
		'IGMS3':'/resources/images/terrain/IGMS/igms_640934,4596481,643494,4599041_512.jpg',
		'IGMS4':'/resources/images/terrain/IGMS/igms_678336,4595147,680896,4597707_512.jpg',
		'IGMS5':'/resources/images/terrain/IGMS/igms_679104,4595950,680128,4596974_512.jpg',
		'Clouds':'/resources/images/terrain/clouds_256.png'
	};
	//TODO: clean up these settings into json file
	var terrainImageSettings = {
		'/resources/images/terrain/IGMS/igms_693432,4598934,694456,4599958_512.jpg': { 'preblur':5, 'height':1.5, 'midGreyIsLowest':true },
		'/resources/images/terrain/IGMS/igms_617851,4595749,620411,4598309_512.jpg': { 'preblur':1, 'height':0.3, 'midGreyIsLowest':true },
		'/resources/images/terrain/IGMS/igms_640934,4596481,643494,4599041_512.jpg': { 'preblur':3, 'height':0.3, 'midGreyIsLowest':true },
		'/resources/images/terrain/IGMS/igms_678336,4595147,680896,4597707_512.jpg': { 'preblur':2, 'height':0.3, 'midGreyIsLowest':true },
		'/resources/images/terrain/IGMS/igms_679104,4595950,680128,4596974_512.jpg': { 'preblur':3, 'height':0.3, 'midGreyIsLowest':true },
		'/resources/images/terrain/clouds_256.png': { 'preblur':0, 'height':2, 'midGreyIsLowest':false }
	};
	var waterSimTypes = {
		'X Water': 'heightFieldWaterSim_xWater',
		'Tessendorf iWave': 'heightFieldWaterSim_iWave',
		'Muller GDC2008': 'heightFieldWaterSim_mullerGdc2008',
		'Muller GDC2008 HelloWorld': 'heightFieldWaterSim_mullerGdc2008_helloWorld'
	};
	var waterSimTypeSettings = {
		'heightFieldWaterSim_xWater': { 'disturbAmount':0.25, 'dampingFactor':0.95 },
		'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 }
	};
	var options = {
		trackingThreshold: TRACKING_THRESHOLD,
		waterSimType: waterSimTypes[Object.keys(waterSimTypes)[0]],
		waterSimFloodRate: 0.01,
		terrainImage: terrainImages[Object.keys(terrainImages)[0]],
		terrainShowImage: false,
		terrainMidGreyIsLowest: true,
		terrainPreBlur: terrainImageSettings[Object.keys(terrainImageSettings)[0]].preblur,
		terrainHeight: terrainImageSettings[Object.keys(terrainImageSettings)[0]].height,
		sculptSize: 1.0,
		sculptAmount: 0.1,
		sculptClearSculpts: sculptClearSculpts,
		renderingShadows: true,
		renderingShadowCasters: false,
		displayWireframe: false,
		displayLocalAxis: false,
	};

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

	var video, source;
	function setupVideoStream()
	{
		//stream to video element
		video = $('#mainVideo')[0];
		streamVideo(video);
		source = video;	
	}

	var skarf, jsArToolKitArLib, threeJsRenderer, $threejsContainerElem;
	function setupSkarf()
	{
		//create an AR library
		jsArToolKitArLib = new JsArToolKitArLib({
			canvasElem: canvas,
			markerWidth: 120,
			threshold: TRACKING_THRESHOLD,
			debug: JSAR_DEBUG
		});
		
		//get the camera projection matrix from jsArToolKitArLib
		var camProjMatrixArray = new Float32Array(16);
		jsArToolKitArLib.flarParam.copyCameraMatrix(camProjMatrixArray, 10, 10000);
		
		//create a renderer
		$threejsContainerElem = $('#threejs-container');
		threeJsRenderer = new ThreeJsRenderer({
			//container to attach the Three.js-created renderer canvas
			rendererContainerElem: $threejsContainerElem,
			//width of the Three.js-created renderer canvas
			rendererCanvasWidth: window.innerWidth,
			//height of the Three.js-created renderer canvas
			rendererCanvasHeight: window.innerHeight-5,
			//the streaming canvas for background plane
			streamCanvasElem: canvas,
			camProjMatrixArray: camProjMatrixArray,
			modelsJsonFile: '/resources/models/models_floodTest.json',
			useDefaultLights: false
		});
		
		//create a skarf framework
		skarf = new SkArF({
			canvasElem: canvas,
			videoElem: video,
			arLib: jsArToolKitArLib,
			renderer: threeJsRenderer
		});
	}

	var terrainImageData;
	function filterTerrainImageAndGenerateHeight()
	{
		//draw image onto canvas
		//NO NEED TO DO THIS WHEN USING stackBlurImage()
        //imageCanvasElemContext.drawImage($scaledImageObj, 0, 0, terrainImageWidth, terrainImageHeight);

        //do pre-blur filtering first
        stackBlurImage('scaledTerrainImage', 'terrainImageCanvas', options.terrainPreBlur, false);

        //get filtered image data
        terrainImageData = imageCanvasElemContext.getImageData(0, 0, TERRAIN_RES, TERRAIN_RES).data;

		//update base layer
		baseLayer.loadFromImageData(terrainImageData, options.terrainHeight, options.terrainMidGreyIsLowest);

		//update the terrain obstacle
		terrainObstacle.update();
	}

	var $imageCanvasElem, imageCanvasElemContext;
	var $scaledImageObj, $origImageObj;
	function prepareTerrainImageElements()
	{
		//create canvas that is same size as terrain res so that one vertex maps to one resized pixel
		$imageCanvasElem = $(document.createElement('canvas'));
		$imageCanvasElem[0].id = 'terrainImageCanvas';
		$imageCanvasElem[0].width = TERRAIN_RES;
		$imageCanvasElem[0].height = TERRAIN_RES;
		$imageCanvasElem.css({'position':'fixed', 'top':'55px', 'left':0});
		$('body').append($imageCanvasElem);
		
		//get canvas context
        imageCanvasElemContext = $imageCanvasElem[0].getContext('2d');

		//load terrain image
		$scaledImageObj = $(new Image());
		$scaledImageObj[0].id = 'scaledTerrainImage';
		$scaledImageObj[0].onload = function()
		{
			//this function is triggered from $origImageObj setting this src
			
			//start filtering and changing heights
			filterTerrainImageAndGenerateHeight();
		};
		$scaledImageObj.css({'display':'none'});
		$('body').append($scaledImageObj);

		//load original terrain image, scale it using canvas, then set scaled image to $scaledImageObj
		$origImageObj = $(new Image());
		$origImageObj[0].onload = function()
		{
			//copy to scaled canvas to scale this image
			imageCanvasElemContext.drawImage($origImageObj[0], 0, 0, TERRAIN_RES, TERRAIN_RES);

			//get scaled data from canvas and set data for scaledImageObj
			$scaledImageObj[0].src = $imageCanvasElem[0].toDataURL();
		};
	}

	function loadTerrainImage(image)
	{
		console.log('Loading terrain image: ' + image);
		$origImageObj[0].src = image;
	}

	function sculptClearSculpts()
	{
		skulpt.getMesh('terrainMesh').clearCurrLayer();
	}

	var renderer, scene, camera, globalTransform;
	var keyLight, fillLight;
	var worldAxis, localAxis;
	var materials = [];
	var torusKnotMesh, boxMesh, sphereMesh;
	var groundPlaneMesh;
	function setupThreejsScene()
	{
		//attach a transform to marker transform 22
		globalTransform = new THREE.Object3D();
		globalTransform.matrixAutoUpdate = false;
		globalTransform.matrix.multiply(new THREE.Matrix4().makeScale(GLOBAL_SCALE, GLOBAL_SCALE, GLOBAL_SCALE));
		globalTransform.matrixWorldNeedsUpdate = true;
		threeJsRenderer.markerTransforms[22].add(globalTransform);

		//setup renderer
		threeJsRenderer.renderer.shadowMapEnabled = true;
		threeJsRenderer.renderer.shadowMapType = THREE.PCFShadowMap;
		threeJsRenderer.renderer.shadowMapSoft = true;

		// //create renderer
		// renderer = new THREE.WebGLRenderer({
		// 	antialias : true
		// });
		// renderer.setSize(window.innerWidth, window.innerHeight-5);
		// renderer.setClearColor('#081419', 1);
		// renderer.shadowMapEnabled = true;
		// renderer.shadowMapType = THREE.PCFShadowMap;
		// renderer.shadowMapSoft = true;
		// var $container = $('#threejs-container');
		// $container.append(renderer.domElement);

		// //create scene
		// scene = new THREE.Scene();

		// //create an overall transformation to hold the global transform
		// globalTransform = new THREE.Object3D();
		// globalTransform.matrixAutoUpdate = false;
		// globalTransform.matrix.multiply(GLOBAL_MAT);
		// globalTransform.matrixWorldNeedsUpdate = true;
		// scene.add(globalTransform);

		// //create camera
		// camera = new THREE.PerspectiveCamera(25, renderer.domElement.width/renderer.domElement.height, 0.1, 1000);
		// camera.position.set(4, 10, 19);
		// 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 plane for reference and for intersection test
		var groundPlaneGeom = new THREE.PlaneGeometry(TERRAIN_SIZE, TERRAIN_SIZE, 1, 1);  //much faster for intersection test when there are no divisions
		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 = true;
		groundPlaneMesh.receiveShadow = true;
		globalTransform.add(groundPlaneMesh);

		var torusKnotGeom = new THREE.TorusKnotGeometry(0.35, 0.1, 50, 8);
		var torusKnotMaterial = new THREE.MeshPhongMaterial({color:'#ff4444', side:THREE.DoubleSide});
		torusKnotMaterial.shininess = 50;
		materials.push(torusKnotMaterial);
		torusKnotMesh = new THREE.Mesh(torusKnotGeom, torusKnotMaterial);
		torusKnotMesh.position.x = -2;
		torusKnotMesh.position.y = 1;
		torusKnotMesh.castShadow = true;
		torusKnotMesh.receiveShadow = false;
		globalTransform.add(torusKnotMesh);

		var boxGeom = new THREE.CubeGeometry(1, 1, 1);
		var boxMaterial = new THREE.MeshPhongMaterial({color:'#ff4444', side:THREE.DoubleSide});
		materials.push(boxMaterial);
		boxMesh = new THREE.Mesh(boxGeom, boxMaterial);
		boxMesh.position.y = 1;
		boxMesh.castShadow = true;
		boxMesh.receiveShadow = false;
		globalTransform.add(boxMesh);

		var sphereGeom = new THREE.SphereGeometry(0.65, 10, 10);
		var sphereMaterial = new THREE.MeshPhongMaterial({color:'#ff4444', side:THREE.DoubleSide});
		sphereMaterial.shininess = 50;
		materials.push(sphereMaterial);
		sphereMesh = new THREE.Mesh(sphereGeom, sphereMaterial);
		sphereMesh.position.x = 2;
		sphereMesh.position.y = 1;
		sphereMesh.castShadow = true;
		sphereMesh.receiveShadow = false;
		globalTransform.add(sphereMesh);

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

		//FIXME: general lighting and shadows are not working

		//setup lights
		globalTransform.add(new THREE.AmbientLight(0x222222));

		keyLight = new THREE.SpotLight(0xffff00, 0.5);
		keyLight.position.set(15, 15, 15);  //FIXME: lights don't seem to follow transforms
		keyLight.target.position.set(0, 0, 0);
		keyLight.castShadow = true;
		keyLight.shadowCameraNear = 20;
		keyLight.shadowCameraFar = 33;
		keyLight.shadowCameraFov = 35;
		keyLight.shadowCameraVisible = true;
		keyLight.shadowBias = 0.0001;
		keyLight.shadowDarkness = 0.5;
		keyLight.shadowMapWidth = SHADOW_MAP_RES;
		keyLight.shadowMapHeight = SHADOW_MAP_RES;
		globalTransform.add(keyLight);

		fillLight = new THREE.SpotLight(0x00ff00, 0.5);
		fillLight.position.set(5, 2, 15);
		fillLight.target.position.set(0, 0, 0);
		globalTransform.add(fillLight);
	}

	var RANDOM_SEED = 1;
	Math.seedrandom(RANDOM_SEED);
	var waterMesh;
	var heightFieldWaterSim, heightFieldWaterSim_iWave, heightFieldWaterSim_mullerGdc2008, heightFieldWaterSim_mullerGdc2008_helloWorld, heightFieldWaterSim_xWater;
	var terrainObstacle;
	var iSegments = 3;
	var jSegments = 3;
	var iSegmentSize = WATER_SIZE / iSegments;
	var jSegmentSize = WATER_SIZE / jSegments;
	var halfWaterSize = WATER_SIZE / 2.0;
	var obstacles = [];
	var shouldUpdateObstacles;
	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({emissive:'#113366'});
		materials.push(waterMaterial);
		waterMesh = new THREE.Mesh(waterGeom, waterMaterial);
		waterMesh.castShadow = true;
		waterMesh.receiveShadow = true;
		globalTransform.add(waterMesh);

		//create the different water sims
		heightFieldWaterSim_iWave = new HeightFieldWaterSim_Tessendorf_iWave({
			mesh: waterMesh,
			size: WATER_SIZE,
			res: WATER_RES,
			dampingFactor: DAMPING_FACTOR,
			kernelRadius: 4,
			substeps: 3,
			meanHeight: MEAN_WATER_HEIGHT
		});
		heightFieldWaterSim_mullerGdc2008 = new HeightFieldWaterSim_Muller_GDC2008({
			mesh: waterMesh,
			size: WATER_SIZE,
			res: WATER_RES,
			dampingFactor: DAMPING_FACTOR,
			horizontalSpeed: 5.0,
			meanHeight: MEAN_WATER_HEIGHT
		});
		heightFieldWaterSim_mullerGdc2008_helloWorld = new HeightFieldWaterSim_Muller_GDC2008_HelloWorld({
			mesh: waterMesh,
			size: WATER_SIZE,
			res: WATER_RES,
			dampingFactor: DAMPING_FACTOR,
			meanHeight: MEAN_WATER_HEIGHT
		});
		heightFieldWaterSim_xWater = new HeightFieldWaterSim_xWater({
			mesh: waterMesh,
			size: WATER_SIZE,
			res: WATER_RES,
			dampingFactor: DAMPING_FACTOR,
			meanHeight: MEAN_WATER_HEIGHT
		});
		heightFieldWaterSim = heightFieldWaterSim_xWater;

		//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({color: '#ff0000'});
				materials.push(obstacleMaterial);
				obstacleMesh = new THREE.Mesh(obstacleGeom, obstacleMaterial);
				obstacleMesh.castShadow = true;
				obstacleMesh.receiveShadow = true;
				obstacleMesh.position.x = (iSegmentSize * i) - halfWaterSize + ((Math.random()) * iSegmentSize);
				// obstacleMesh.position.y = Math.random() * 2 - 1.0;
				obstacleMesh.position.z = (jSegmentSize * j) - halfWaterSize + ((Math.random()) * jSegmentSize);
				obstacleMesh.rotation.y = (Math.random() * 360);
				// obstacleMesh.visible = false;
				globalTransform.add(obstacleMesh);

				//create Obstacle
				var obstacle = new VoxelizedObstacle(obstacleMesh, WATER_GRID_SIZE, WATER_GRID_SIZE, WATER_GRID_SIZE, globalTransform.matrixWorld);
				heightFieldWaterSim_iWave.addObstacle(obstacle, 'obstacle'+obstacles.length);
				heightFieldWaterSim_mullerGdc2008.addObstacle(obstacle, 'obstacle'+obstacles.length);
				heightFieldWaterSim_mullerGdc2008_helloWorld.addObstacle(obstacle, 'obstacle'+obstacles.length);
				heightFieldWaterSim_xWater.addObstacle(obstacle, 'obstacle'+obstacles.length);
				obstacle.updateAlways = false;
				// obstacle.update();  //NOTE: this does not work because we are updating the obstacle before the mesh has updated

				obstacles.push(obstacle);
			}
		}

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

		shouldUpdateObstacles = true;

		//add terrain as an obstacle
		terrainObstacle = new TerrainObstacle(terrainMesh);
		heightFieldWaterSim.addObstacle(terrainObstacle, 'terrainObstacle');
		terrainObstacle.updateAlways = true;
	}

	var gui;
	var trackingFolder, waterSimFolder, terrainFolder, sculptFolder, renderingFolder, displayFolder;
	function setupGui() {

		gui = new dat.GUI();
		//gui.close();  //close GUI by default

		//Tracking folder
		trackingFolder = gui.addFolder('Tracking');
		var control = trackingFolder.add(options, 'trackingThreshold', 0, 255).name('Threshold');
		function trackingThreshold_onChange(value)
		{
			skarf.arLib.threshold = value;
		}
		control.onChange(trackingThreshold_onChange);

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

		control = waterSimFolder.add(options, 'waterSimType', waterSimTypes).name('Type');
		function changeWaterSimType(value)
		{
			//swap the sim
			heightFieldWaterSim = eval(value);

			//set mean height
			heightFieldWaterSim.setMeanHeight(currWaterHeight);

			//reset sim
			heightFieldWaterSim.reset();

			//load some presets so that that sim works well
			DISTURB_AMOUNT = waterSimTypeSettings[value].disturbAmount;
			DAMPING_FACTOR = waterSimTypeSettings[value].dampingFactor;
			heightFieldWaterSim.dampingFactor = DAMPING_FACTOR;
		}
		control.onChange(changeWaterSimType);
		changeWaterSimType(options.waterSimType);

		control = waterSimFolder.add(options, 'waterSimFloodRate', -0.1, 0.1).step(0.01).name('Flood Rate');

		//Terrain folder
		terrainFolder = gui.addFolder('Terrain');
		// terrainFolder.open();

		var control = terrainFolder.add(options, 'terrainImage', terrainImages).name('Image');
		function changeTerrainImage(value)
		{
			terrainImage = value;
			loadTerrainImage(value);

			//load some presets so that the loaded terrain looks nicer
			options.terrainPreBlur = terrainImageSettings[value].preblur;
			options.terrainHeight = terrainImageSettings[value].height;
			options.terrainMidGreyIsLowest = terrainImageSettings[value].midGreyIsLowest;

			//update display
			for (var i in terrainFolder.__controllers) {
				terrainFolder.__controllers[i].updateDisplay();
			}
		}
		control.onChange(changeTerrainImage);
		//changeTerrainPreBlur('/resources/images/clouds_256.png');

		control = terrainFolder.add(options, 'terrainShowImage').name('Show Image');
		function changeTerrainShowImage(value)
		{
			terrainShowImage = value;
			if (value)
			{
				$imageCanvasElem.css({'display':'block'});
			}
			else
			{
				$imageCanvasElem.css({'display':'none'});	
			}
		}
		control.onChange(changeTerrainShowImage);
		changeTerrainShowImage(options.terrainShowImage);

		control = terrainFolder.add(options, 'terrainMidGreyIsLowest').name('Mid Grey Lowest');
		function changeTerrainMidGreyIsLowest(value)
		{
			terrainMidGreyIsLowest = value;
			baseLayer.loadFromImageData(terrainImageData, options.terrainHeight, options.terrainMidGreyIsLowest);
			
			//update the terrain obstacle
			terrainObstacle.update();
		}
		control.onChange(changeTerrainMidGreyIsLowest);
		//changeTerrainMidGreyIsLowest(options.terrainMidGreyIsLowest);

		control = terrainFolder.add(options, 'terrainPreBlur', 0, 10).name('Pre-Blur');
		function changeTerrainPreBlur(value)
		{
			terrainPreBlur = value;
			filterTerrainImageAndGenerateHeight(value);
		}
		control.onChange(changeTerrainPreBlur);
		//changeTerrainPreBlur(options.terrainPreBlur);

		control = terrainFolder.add(options, 'terrainHeight', 0, 4).name('Height');
		function changeTerrainHeight(value)
		{
			terrainHeight = value;
			baseLayer.loadFromImageData(terrainImageData, options.terrainHeight, options.terrainMidGreyIsLowest);

			//update the terrain obstacle
			terrainObstacle.update();
		}
		control.onChange(changeTerrainHeight);
		//changeTerrainHeight(options.terrainHeight);

		//Sculpt folder
		sculptFolder = gui.addFolder('Sculpt');
		sculptFolder.open();

		control = sculptFolder.add(options, 'sculptSize', 0.1, 10.0).name('Size');
		function changeSculptSize(value)
		{
			skulpt.setBrushSize(value);
		}
		control.onChange(changeSculptSize);
		changeSculptSize(options.sculptSize);

		control = sculptFolder.add(options, 'sculptAmount', 0.01, 1.0).name('Amount');
		function changeSculptAmount(value)
		{
			skulpt.setBrushAmount(value);
		}
		control.onChange(changeSculptAmount);
		changeSculptAmount(options.sculptAmount);

		control = sculptFolder.add(options, 'sculptClearSculpts').name('Clear Sculpts');

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

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

		control = renderingFolder.add(options, 'renderingShadowCasters').name('Shadow Casters');
		function changeRenderingShadowCasters(value)
		{
			torusKnotMesh.visible = value;
			boxMesh.visible = value;
			sphereMesh.visible = value;
		}
		control.onChange(changeRenderingShadowCasters);
		changeRenderingShadowCasters(options.renderingShadowCasters);

		//Display folder
		displayFolder = gui.addFolder('Display');
		
		control = displayFolder.add(options, 'displayWireframe').name('Wireframe');
		function toggleWireframe(value)
		{
			skarf.renderer.setWireframeVisible(value);
			var i, len;
			for (i = 0, len = materials.length; i < len; i++)
			{
				materials[i].wireframe = value;
			}
		}
		control.onChange(toggleWireframe);
		toggleWireframe(options.displayWireframe);
		
		control = displayFolder.add(options, 'displayLocalAxis').name('Local Axis');
		function toggleLocalAxis(value)
		{
			skarf.renderer.setLocalAxisVisible(value);
		}
		control.onChange(toggleLocalAxis);
		toggleLocalAxis(options.displayLocalAxis);
	}

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

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

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

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

	var activateDisturb = false;
	function window_onKeyDown(event)
	{
		if (event.keyCode === 32)  //space
		{
			activateDisturb = true;
		}
	}

	function window_onKeyUp(event)
	{
		if (event.keyCode === 32)  //space
		{
			activateDisturb = false;
		}
	}

	var isSculpting = false;
	var isDisturbing = false;
	function window_onMouseDown(event)
	{
		if (event.ctrlKey)
		{
			//detect intersection and show cursor
			detectIntersectionAndShowSculptCursor();

			//do actual sculpting if clicked
			if (event.button === 0)  //LMB
			{
				isSculpting = true;
				sculptTerrain('add');
			}
			else if (event.button === 2)  //RMB
			{
				isSculpting = true;
				sculptTerrain('remove');
			}
			
		}
		else if (event.shiftKey)
		{
			//detect intersection and show cursor
			detectIntersectionAndShowSculptCursor();

			//do actual sculpting if clicked
			if (event.button === 0)  //LMB
			{
				isSculpting = true;
				sculptTerrain('flatten');
			}
		}
		else
		{
			//for water
			if (activateDisturb && event.button === 0)  //LMB
			{
				isDisturbing = true;

				//detect intersection
				intersectPoint = detectIntersection();
				if (intersectPoint)
				{
					heightFieldWaterSim.disturb(intersectPoint, DISTURB_AMOUNT);
				}
			}
		}
	}

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

		skulpt.hideCursor();

		if (event.ctrlKey)
		{
			//detect intersection and show cursor
			detectIntersectionAndShowSculptCursor();

			//do actual sculpting if clicked
			if (isSculpting)
			{
				if (event.button === 0)  //LMB
				{
					sculptTerrain('add');
				}
				else if (event.button === 2)  //RMB
				{
					sculptTerrain('remove');
				}
			}
		}
		else if (event.shiftKey)
		{
			//detect intersection and show cursor
			detectIntersectionAndShowSculptCursor();

			//do actual sculpting if necessary
			if (isSculpting)
			{
				if (event.button === 0)  //LMB
				{
					sculptTerrain('flatten');
				}
			}
		}
		else
		{
			//for water
			if (activateDisturb && isDisturbing)  //LMB
			{
				//detect intersection
				intersectPoint = detectIntersection();
				if (intersectPoint)
				{
					heightFieldWaterSim.disturb(intersectPoint, DISTURB_AMOUNT);
				}
			}
		}
	}

	function window_onMouseUp(event)
	{
		isSculpting = false;
		isDisturbing = false;
	}

	var projector = new THREE.Projector();
	var raycaster = new THREE.Raycaster();
	var intersectPoint = new THREE.Vector3();
	var mousePosNorm = new THREE.Vector2();
	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, threeJsRenderer.camera);
		raycaster.set(threeJsRenderer.camera.position, worldPos.sub(threeJsRenderer.camera.position).normalize());
		var intersectInfo = raycaster.intersectObject(groundPlaneMesh);
		
		//get intersection point
		if (intersectInfo && intersectInfo[0])
		{
			return intersectInfo[0].point;
		}
		else
		{
			return null;
		}
	}

	function detectIntersectionAndShowSculptCursor()
	{
		//detect intersection and show cursor
		intersectPoint = detectIntersection();
		if (intersectPoint)
		{
			skulpt.updateCursor(intersectPoint, skulptTerrainMesh);
			skulpt.showCursor();
		}
		else
		{
			skulpt.hideCursor();
		}
	}

	function sculptTerrain(operation)
	{
		skulpt.hideCursor();
		skulpt.setBrush(operation);
		skulpt.showCursor();
		var affectedVertexInfos = skulpt.sculpt(intersectPoint);

		//update the terrain obstacle
		terrainObstacle.update();

		//disturb water
		var i, len, vertexInfo;
	    for (i = 0, len = affectedVertexInfos.length; i < len; i++) {

	        vertexInfo = affectedVertexInfos[i];

	        //if terrain height change cuts across the current water height, it should disturb the water
	        if ((vertexInfo.oldDisplacement-currWaterHeight)*(vertexInfo.newDisplacement-currWaterHeight) < 0)  //change of sign
	        {
	        	//must spread to neighbours, otherwise will be blocked by the terrain itself
	        	heightFieldWaterSim.disturbNeighboursById(vertexInfo.id, vertexInfo.newDisplacement - vertexInfo.oldDisplacement);  //should really divide by 4.0, but can't really see the effect
	        }
	    }
	}

	function updateGeometry(geometry, fullUpdate)
	{
		geometry.verticesNeedUpdate = true;

		if (fullUpdate)
		{
			//geometry.dynamic = true;
			geometry.computeFaceNormals();  //must call this first before computeVertexNormals()
			geometry.computeVertexNormals();
			//geometry.computeCentroids();
			//geometry.computeBoundingBox();
			//geometry.computeBoundingSphere();
			
			geometry.normalsNeedUpdate = true;
		}
	}

	var terrainGeom, terrainMesh, skulptTerrainMesh;
	var skulpt;
	var baseLayer, sculptLayer;
	function setupSkulpt()
	{
		//create a terrain mesh for sculpting
		terrainGeom = new THREE.PlaneGeometry(TERRAIN_SIZE, TERRAIN_SIZE, TERRAIN_RES-1, TERRAIN_RES-1);
		terrainGeom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI/2));
		terrainGeom.computeFaceNormals();
	    terrainGeom.computeVertexNormals();
	    terrainGeom.normalsNeedUpdate = true;
		var terrainMaterial = new THREE.MeshPhongMaterial();
		materials.push(terrainMaterial);
		terrainMesh = new THREE.Mesh(terrainGeom, terrainMaterial);
		terrainMesh.castShadow = true;
		terrainMesh.receiveShadow = true;
		globalTransform.add(terrainMesh);

		//create a Skulpt first
		skulpt = new Skulpt(globalTransform);
		
		//create a SkulptMesh, add a layer
		skulptTerrainMesh = new SkulptTerrainMesh(terrainMesh, TERRAIN_SIZE, TERRAIN_RES);
		baseLayer = skulptTerrainMesh.addLayer('baseLayer');
		// baseLayer.addNoise(1, 0.5, 0.5, 0.5, 0, 0, 0);
		sculptLayer = skulptTerrainMesh.addLayer('sculptLayer1');

		//add mesh to Skulpt
		skulpt.addMesh(skulptTerrainMesh, 'terrainMesh');
	}

	var canvas;
	var hasSetupScene = false;
	$(document).ready(function()
	{ 
		//check for WebGL
		checkWebGl();

		console.log('Document is ready.');

		canvas = $('#mainCanvas')[0];

		//setup video stream
		setupVideoStream();
		
		//setup skarf
		setupSkarf();

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

				//animating mean height to increase flood levels
				currWaterHeight += options.waterSimFloodRate * dt;
				heightFieldWaterSim.setMeanHeight(currWaterHeight);
			}

			if (options.renderingShadowCasters)
			{
				torusKnotMesh.position.y = 1.5 + 0.25 * Math.sin(time*3-0.5);
				boxMesh.position.y = 1.5 + 0.25 * Math.sin(time*3);
				sphereMesh.position.y = 1.5 + 0.25 * Math.sin(time*3+0.5);
			}

			skarf.update();  //this will update the AR lib and renderer too
			stats.update();

			//update obstacles after rendering is done, so that meshes are updated and obstacles are good to update
			if (shouldUpdateObstacles) {
				console.log('updating obstacles');
				var i, len;
				for (i = 0, len = obstacles.length; i < len; i++)
				{
					obstacles[i].update();
				}
				shouldUpdateObstacles = false;
			}

			//setup our flood scene when marker transform has been loaded
			if (!hasSetupScene && threeJsRenderer.markerTransforms && threeJsRenderer.markerTransforms[22])
			{
				console.log('Marker loaded, loading flood scene');

				//setup scene
				setupThreejsScene();

				//setup Skulpt
				setupSkulpt()

				//load terrain image
				if (USE_TERRAIN_IMAGE)
				{
					prepareTerrainImageElements();
					loadTerrainImage(options.terrainImage);
				}

				//setup water
				setupWaterScene();

				//setup events
				setupEvents();

				//setup GUI
				setupGui();

				//update obstacles once during this init
				shouldUpdateObstacles = true;

				hasSetupScene = true;
			}

			requestAnimationFrame(loop);
		}
		loop();
		
	});
	</script>
</head>
<body>
	<div class="invisible" id="video-container">
		<div class="caption">&lt;video&gt;</div>
		<video id="mainVideo" width="640" height="480" autoplay="autoplay" muted="true"></video>
	</div>
	<div class="invisible" id="canvas-container">
		<div class="caption">&lt;canvas&gt;</div>
		<canvas id="mainCanvas" width="640" height="480"></canvas>
	</div>
	<div class="invisible" id="debugCanvas-container">
		<div class="caption">debug &lt;canvas&gt;</div>
		<canvas id="debugCanvas" width="640" height="480"></canvas>
	</div>
	<div id="threejs-container"></div>
	<div class="translucent" id="info-container">
		<div>Sculpt: [ Ctrl+LMB: add ] [ Ctrl+RMB: remove ] [ Shift+LMB: flatten ]</div>
		<div>Water Sim: [ Space+LMB: disturb water ]</div>
	</div>
</body>
</html>
{'fraction_non_alphanumeric': 0.1252854315725377, 'fraction_numerical': 0.023534784594306593, 'mean_word_length': 4.461589624210176, 'pattern_counts': {'":': 0, '<': 83, '<?xml version=': 0, '>': 77, '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': '523404', 'n_tokens_mistral': 12951, 'n_tokens_neox': 10892, 'n_words': 2098}