var renderer = new THREE.WebGLRenderer(); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement ); var updateFcts = []; var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 3000 ); camera.position.z = 200; window.addEventListener("resize", function() { let width = window.innerWidth; let height = window.innerHeight; renderer.setSize(width, height); camera.aspect = width / height; camera.updateProjectionMatrix(); }); ////////////////////////////////////////////////////////////////////////////////// // comment // ////////////////////////////////////////////////////////////////////////////////// // from @mrdoob http://www.mrdoob.com/lab/javascript/webgl/city/01/ var THREEx = THREEx || {} THREEx.ProceduralCity = function(){ // build the base geometry for each building var geometry = new THREE.CubeGeometry( 1, 1, 1 ); // translate the geometry to place the pivot point at the bottom instead of the center geometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, 0.5, 0 ) ); // get rid of the bottom face - it is never seen geometry.faces.splice( 3, 1 ); geometry.faceVertexUvs[0].splice( 3, 1 ); // change UVs for the top face // - it is the roof so it wont use the same texture as the side of the building // - set the UVs to the single coordinate 0,0. so the roof will be the same color // as a floor row. geometry.faceVertexUvs[0][2][0].set( 0, 0 ); geometry.faceVertexUvs[0][2][1].set( 0, 0 ); geometry.faceVertexUvs[0][2][2].set( 0, 0 ); geometry.faceVertexUvs[0][2][3].set( 0, 0 ); // buildMesh var buildingMesh= new THREE.Mesh( geometry ); // base colors for vertexColors. light is for vertices at the top, shaddow is for the ones at the bottom var light = new THREE.Color( 0xffffff ) var shadow = new THREE.Color( 0x303050 ) var cityGeometry= new THREE.Geometry(); for( var i = 0; i < 20000; i ++ ){ // put a random position buildingMesh.position.x = Math.floor( Math.random() * 200 - 100 ) * 10; buildingMesh.position.z = Math.floor( Math.random() * 200 - 100 ) * 10; // put a random rotation buildingMesh.rotation.y = Math.random()*Math.PI*2; // put a random scale buildingMesh.scale.x = Math.random() * Math.random() * Math.random() * Math.random() * 50 + 10; buildingMesh.scale.y = (Math.random() * Math.random() * Math.random() * buildingMesh.scale.x) * 8 + 8; buildingMesh.scale.z = buildingMesh.scale.x // establish the base color for the buildingMesh var value = 1 - Math.random() * Math.random(); var baseColor = new THREE.Color().setRGB( value + Math.random() * 0.1, value, value + Math.random() * 0.1 ); // set topColor/bottom vertexColors as adjustement of baseColor var topColor = baseColor.clone().multiply( light ); var bottomColor = baseColor.clone().multiply( shadow ); // set .vertexColors for each face var geometry = buildingMesh.geometry; for ( var j = 0, jl = geometry.faces.length; j < jl; j ++ ) { if ( j === 2 ) { // set face.vertexColors on root face geometry.faces[ j ].vertexColors = [ baseColor, baseColor, baseColor, baseColor ]; } else { // set face.vertexColors on sides faces geometry.faces[ j ].vertexColors = [ topColor, bottomColor, bottomColor, topColor ]; } } // merge it with cityGeometry - very important for performance THREE.GeometryUtils.merge( cityGeometry, buildingMesh ); } // generate the texture var texture = new THREE.Texture( generateTextureCanvas() ); texture.anisotropy = renderer.getMaxAnisotropy(); texture.needsUpdate = true; // build the mesh var material = new THREE.MeshLambertMaterial({ map : texture, vertexColors : THREE.VertexColors }); var mesh = new THREE.Mesh(cityGeometry, material ); return mesh function generateTextureCanvas(){ // build a small canvas 32x64 and paint it in white var canvas = document.createElement( 'canvas' ); canvas.width = 32; canvas.height = 64; var context = canvas.getContext( '2d' ); // plain it in white context.fillStyle = '#ffffff'; context.fillRect( 0, 0, 32, 64 ); // draw the window rows - with a small noise to simulate light variations in each room for( var y = 2; y < 64; y += 2 ){ for( var x = 0; x < 32; x += 2 ){ var value = Math.floor( Math.random() * 64 ); context.fillStyle = 'rgb(' + [value, value, value].join( ',' ) + ')'; context.fillRect( x, y, 2, 1 ); } } // build a bigger canvas and copy the small one in it // This is a trick to upscale the texture without filtering var canvas2 = document.createElement( 'canvas' ); canvas2.width = 512; canvas2.height = 1024; var context = canvas2.getContext( '2d' ); // disable smoothing context.imageSmoothingEnabled = false; context.webkitImageSmoothingEnabled = false; context.mozImageSmoothingEnabled = false; // then draw the image context.drawImage( canvas, 0, 0, canvas2.width, canvas2.height ); // return the just built canvas2 return canvas2; } } var city = new THREEx.ProceduralCity() scene.add(city) ////////////////////////////////////////////////////////////////////////////////// // add an object and make it move // ////////////////////////////////////////////////////////////////////////////////// var light = new THREE.HemisphereLight( 0xfffff0, 0x101020, 1.25 ); light.position.set( 0.75, 1, 0.25 ); scene.add( light ); ////////////////////////////////////////////////////////////////////////////////// // Camera Controls // ////////////////////////////////////////////////////////////////////////////////// var mouse = {x : 0, y : 0} document.addEventListener('mousemove', function(event){ mouse.x = (event.clientX / window.innerWidth ) - 0.5 mouse.y = (event.clientY / window.innerHeight) - 0.5 }, false) updateFcts.push(function(delta, now){ camera.position.x += (mouse.x*300 - camera.position.x) * (delta*3) camera.position.y += (mouse.y*300 - (camera.position.y-200)) * (delta*3) camera.lookAt( scene.position ) }) ////////////////////////////////////////////////////////////////////////////////// // render the scene // ////////////////////////////////////////////////////////////////////////////////// updateFcts.push(function(){ renderer.render( scene, camera ); }) ////////////////////////////////////////////////////////////////////////////////// // loop runner // ////////////////////////////////////////////////////////////////////////////////// var lastTimeMsec= null requestAnimationFrame(function animate(nowMsec){ // keep looping requestAnimationFrame( animate ); // measure time lastTimeMsec = lastTimeMsec || nowMsec-1000/60 var deltaMsec = Math.min(200, nowMsec - lastTimeMsec) lastTimeMsec = nowMsec // call each update function updateFcts.forEach(function(updateFn){ updateFn(deltaMsec/1000, nowMsec/1000) }) })