From
http://learningthreejs.com/blog/2013/08/02/how-to-do-a-procedural-city-in-100lines/
Updated to work with latest three.js
A Pen by Andreas Borgen on CodePen.
From
http://learningthreejs.com/blog/2013/08/02/how-to-do-a-procedural-city-in-100lines/
Updated to work with latest three.js
A Pen by Andreas Borgen on CodePen.
var _size = 800; //2000; | |
function buildCity() { | |
// build the base geometry for each building | |
var geometry = new THREE.BoxGeometry(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)); | |
var uv = geometry.faceVertexUvs[0]; | |
//console.log(uv); | |
// get rid of the bottom faces (face indexes 6 & 7) - they are never seen | |
geometry.faces.splice(6, 2); | |
uv.splice(6, 2); | |
// change UVs for the top faces (face indexes 4 & 5) | |
// - 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. | |
//uv[4][0].set(0, 0); | |
//uv[4][1].set(0, 0); | |
//uv[4][2].set(0, 0); | |
uv[4].concat(uv[5]).forEach(function(u) { u.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 = (_size*_size/200); i--; ) { | |
// put a random position | |
buildingMesh.position.x = Math.floor(Math.random()*_size - _size/2); | |
buildingMesh.position.z = Math.floor(Math.random()*_size - _size/2); | |
// 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); | |
var baseColor = new THREE.Color().setRGB(value + Math.random() * 0.1, value + Math.random() * 0.1, 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; | |
geometry.faces.forEach(function(face, i) { | |
// set face.vertexColors on root face | |
if ((i === 4) || (i === 5)) { | |
face.vertexColors = [baseColor, baseColor, baseColor]; | |
} | |
// set face.vertexColors on sides faces | |
else { | |
face.vertexColors = (i % 2) ? [bottomColor, bottomColor, topColor] | |
: [topColor, bottomColor, topColor]; | |
} | |
}); | |
//http://stackoverflow.com/questions/24422289/three-js-merging-geometries-and-mesh | |
// | |
// // merge it with cityGeometry - very important for performance | |
// THREE.GeometryUtils.merge(cityGeometry, buildingMesh); | |
// | |
buildingMesh.updateMatrix(); | |
cityGeometry.merge( buildingMesh.geometry, buildingMesh.matrix ); | |
} | |
// generate the texture | |
var texture = new THREE.Texture(generateTexture()); | |
texture.anisotropy = renderer.capabilities.getMaxAnisotropy(); | |
texture.needsUpdate = true; | |
// build the mesh | |
var material = new THREE.MeshLambertMaterial({ | |
map: texture, | |
vertexColors: THREE.VertexColors | |
}); | |
var cityMesh = new THREE.Mesh(cityGeometry, material); | |
return cityMesh; | |
function generateTexture() { | |
// 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; | |
context.msImageSmoothingEnabled = false; | |
// then draw the image | |
context.drawImage(canvas, 0, 0, canvas2.width, canvas2.height); | |
// return the just built canvas2 | |
//console.log(canvas2); | |
return canvas2; | |
} | |
} | |
var updateFcts = []; | |
var scene = new THREE.Scene(), | |
mist = 0xdd4411; //'orangered'; | |
scene.fog = new THREE.FogExp2(mist/*0xd0e0f0*/, 0.003); | |
var renderer = new THREE.WebGLRenderer({ | |
antialias: false | |
}); | |
renderer.setSize(window.innerWidth-10, window.innerHeight); | |
document.body.appendChild(renderer.domElement); | |
//https://github.com/mrdoob/three.js/issues/4512 | |
renderer.setClearColor(mist/*0xd8e7ff*/); | |
////////////////////////////////////////////////////////////////////////////////// | |
// comment // | |
////////////////////////////////////////////////////////////////////////////////// | |
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, _size); | |
camera.position.y = 150; | |
////////////////////////////////////////////////////////////////////////////////// | |
// 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); | |
var material = new THREE.MeshBasicMaterial({ | |
color: 0x101018 | |
}) | |
var geometry = new THREE.PlaneBufferGeometry(_size, _size) | |
var plane = new THREE.Mesh(geometry, material); | |
plane.rotation.x = -90 * Math.PI / 180; | |
scene.add(plane); | |
////////////////////////////////////////////////////////////////////////////////// | |
// comment // | |
////////////////////////////////////////////////////////////////////////////////// | |
var city = buildCity() | |
scene.add(city) | |
////////////////////////////////////////////////////////////////////////////////// | |
// Camera Controls // | |
////////////////////////////////////////////////////////////////////////////////// | |
var controls = new THREE.FirstPersonControls(camera, renderer.domElement); | |
controls.movementSpeed = 20; | |
controls.lookSpeed = 0.05; | |
controls.lookVertical = true; | |
//http://stackoverflow.com/questions/14638135/firstpersoncontrols-start-by-looking-at-an-object | |
controls.lon = 45; | |
controls.lat = -25; | |
updateFcts.push(function(delta, now) { | |
controls.update(delta); | |
}) | |
////////////////////////////////////////////////////////////////////////////////// | |
// 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) | |
}) | |
}) |
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script> | |
<script src="//cdn.rawgit.com/mrdoob/three.js/r87/examples/js/controls/FirstPersonControls.js"></script> |
canvas { | |
display: block; | |
/*background-color: #d8e7ff;*/ | |
} |