Browse Source

Added terrain raycast example.

This example shows one way to visualize the operation and also
illustrates the need to triangulate any quad based geometry whose quads
are not planar.
michael 12 years ago
parent
commit
3379a02d23
1 changed files with 320 additions and 0 deletions
  1. 320 0
      examples/webgl_geometry_terrain_raycast.html

+ 320 - 0
examples/webgl_geometry_terrain_raycast.html

@@ -0,0 +1,320 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - geometry - terrain</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				color: #61443e;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+
+				background-color: #bfd1e5;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px; width: 100%;
+				padding: 5px;
+			}
+
+			a {
+
+				color: #a06851;
+			}
+
+		</style>
+	</head>
+	<body>
+
+		<div id="container"><br /><br /><br /><br /><br />Generating world...</div>
+		<div id="info"><a href="http://threejs.org" target="_blank">three.js</a> - webgl terrain raycasting demo<br />(left click: forward, right click: backward)</div>
+
+		<script src="../build/three.min.js"></script>
+
+		<script src="js/controls/OrbitControls.js"></script>
+
+		<script src="js/ImprovedNoise.js"></script>
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+
+		<script>
+
+			if ( ! Detector.webgl ) {
+
+				Detector.addGetWebGLMessage();
+				document.getElementById( 'container' ).innerHTML = "";
+
+			}
+
+			var container, stats;
+
+			var camera, controls, scene, renderer;
+
+			var mesh, texture;
+
+			var worldWidth = 256, worldDepth = 256,
+			worldHalfWidth = worldWidth / 2, worldHalfDepth = worldDepth / 2;
+
+			var clock = new THREE.Clock();
+
+			var shouldSphereFollowMouse = true;
+			var checkeredSphere;
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 20000 );
+
+				scene = new THREE.Scene();
+
+				controls = new THREE.OrbitControls(camera);
+				controls.center.set(0.0, 100.0, 0.0);
+				controls.userPanSpeed = 100;
+
+				data = generateHeight( worldWidth, worldDepth );
+
+				controls.center.y = data[ worldHalfWidth + worldHalfDepth * worldWidth ] + 500;
+				camera.position.y =  controls.center.y + 2000;
+				camera.position.x = 2000;
+
+				var geometry = new THREE.PlaneGeometry( 7500, 7500, worldWidth - 1, worldDepth - 1 );
+				geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
+
+				for ( var i = 0, l = geometry.vertices.length; i < l; i ++ ) {
+
+					geometry.vertices[ i ].y = data[ i ] * 10;
+
+				}
+
+				// PLEASE NOTE!! With raycasting faces must be planar!  PlaneGeometry is made up of
+				// quads and now that we have changed the height value of the verts, the quads are no
+				// longer planar.  We must break it down into triangles in order to preserve this information.
+				THREE.GeometryUtils.triangulateQuads(geometry);
+
+				texture = new THREE.Texture( generateTexture( data, worldWidth, worldDepth ), new THREE.UVMapping(), THREE.ClampToEdgeWrapping, THREE.ClampToEdgeWrapping );
+				texture.needsUpdate = true;
+
+				mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { map: texture } ) );
+				scene.add( mesh );
+
+				var sphereGeometry = new THREE.SphereGeometry(100, 20, 20);
+				checkeredSphere = new THREE.Mesh(sphereGeometry, new THREE.MeshBasicMaterial( { map: generateCheckerTexture() } ) );
+				checkeredSphere.position.y = 1000;
+
+				scene.add( checkeredSphere );
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				container.innerHTML = "";
+
+				container.appendChild( renderer.domElement );
+				container.addEventListener( 'mousemove', onMouseMove, false );
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild( stats.domElement );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				controls.handleResize();
+
+			}
+
+			function generateHeight( width, height ) {
+
+				var size = width * height, data = new Float32Array( size ),
+				perlin = new ImprovedNoise(), quality = 1, z = Math.random() * 100;
+
+				for ( var i = 0; i < size; i ++ ) {
+
+					data[ i ] = 0
+
+				}
+
+				for ( var j = 0; j < 4; j ++ ) {
+
+					for ( var i = 0; i < size; i ++ ) {
+
+						var x = i % width, y = ~~ ( i / width );
+						data[ i ] += Math.abs( perlin.noise( x / quality, y / quality, z ) * quality * 1.75 );
+
+
+					}
+
+					quality *= 5;
+
+				}
+
+				return data;
+
+			}
+
+			function generateTexture( data, width, height ) {
+
+				var canvas, canvasScaled, context, image, imageData,
+				level, diff, vector3, sun, shade;
+
+				vector3 = new THREE.Vector3( 0, 0, 0 );
+
+				sun = new THREE.Vector3( 1, 1, 1 );
+				sun.normalize();
+
+				canvas = document.createElement( 'canvas' );
+				canvas.width = width;
+				canvas.height = height;
+
+				context = canvas.getContext( '2d' );
+				context.fillStyle = '#000';
+				context.fillRect( 0, 0, width, height );
+
+				image = context.getImageData( 0, 0, canvas.width, canvas.height );
+				imageData = image.data;
+
+				for ( var i = 0, j = 0, l = imageData.length; i < l; i += 4, j ++ ) {
+
+					vector3.x = data[ j - 2 ] - data[ j + 2 ];
+					vector3.y = 2;
+					vector3.z = data[ j - width * 2 ] - data[ j + width * 2 ];
+					vector3.normalize();
+
+					shade = vector3.dot( sun );
+
+					imageData[ i ] = ( 96 + shade * 128 ) * ( 0.5 + data[ j ] * 0.007 );
+					imageData[ i + 1 ] = ( 32 + shade * 96 ) * ( 0.5 + data[ j ] * 0.007 );
+					imageData[ i + 2 ] = ( shade * 96 ) * ( 0.5 + data[ j ] * 0.007 );
+				}
+
+				context.putImageData( image, 0, 0 );
+
+				// Scaled 4x
+
+				canvasScaled = document.createElement( 'canvas' );
+				canvasScaled.width = width * 4;
+				canvasScaled.height = height * 4;
+
+				context = canvasScaled.getContext( '2d' );
+				context.scale( 4, 4 );
+				context.drawImage( canvas, 0, 0 );
+
+				image = context.getImageData( 0, 0, canvasScaled.width, canvasScaled.height );
+				imageData = image.data;
+
+				for ( var i = 0, l = imageData.length; i < l; i += 4 ) {
+
+					var v = ~~ ( Math.random() * 5 );
+
+					imageData[ i ] += v;
+					imageData[ i + 1 ] += v;
+					imageData[ i + 2 ] += v;
+
+				}
+
+				context.putImageData( image, 0, 0 );
+
+				return canvasScaled;
+
+			}
+
+			function generateCheckerTexture() {
+
+				var width = 128;
+				var height = 128;
+
+				var size = width * height;
+				var checkerSize = 16;
+				var checkerHalfSize = checkerSize / 2;
+				var data = new Uint8Array( 3 * size );
+
+				for ( var i = 0; i < height; ++i ) {
+
+					var index = i * width * 3;
+					var verticalShade = Math.floor((i / checkerSize) % 2);
+
+					for ( var j = 0; j < width; ++j ) {
+
+						var shade = Math.floor( (j / checkerSize + verticalShade) % 2);
+						shade *= 255.0;
+
+						data[ index + j * 3 ] 	  = shade;
+						data[ index + j * 3 + 1 ] = shade;
+						data[ index + j * 3 + 2 ] = shade;
+					}
+				}
+
+				var texture = new THREE.DataTexture( data, width, height, THREE.RGBFormat );
+				texture.needsUpdate = true;
+
+				return texture;
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				controls.update( clock.getDelta() );
+				renderer.render( scene, camera );
+
+			}
+
+			function onMouseMove( event ) {
+
+				if ( shouldSphereFollowMouse ) {
+
+					var mouseX = ( event.clientX / window.innerWidth ) * 2 - 1;
+					var mouseY = -( event.clientY / window.innerHeight ) * 2 + 1;
+
+					var vector = new THREE.Vector3( mouseX, mouseY, camera.near );
+
+					// Convert the [-1, 1] screen coordinate into a world coordinate on the near plane
+					var projector = new THREE.Projector();
+					projector.unprojectVector( vector, camera );
+
+					var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
+
+					// See if the ray from the camera into the world hits one of our meshes
+					var intersects = raycaster.intersectObject( mesh );
+					lastIntersects = intersects;
+
+					// Toggle rotation bool for meshes that we clicked
+					if ( intersects.length > 0 ) {
+						checkeredSphere.position = intersects[ 0 ].point;
+
+					}
+				}
+			}
+
+		</script>
+
+	</body>
+</html>