|
@@ -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>
|