├── LICENSE ├── README.md ├── css └── main.css ├── index.html ├── js ├── terrainGenerator.js └── three.min.js └── screenshot.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Victor Ribeiro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terrain Generator 2 | 3 | A 3D terrain generator from height map using Three.js 4 | 5 | !["Terrain Generator"](screenshot.png) 6 | 7 | A live version is hosted [here](https://victorribeiro.com/terrainGenerator/) 8 | 9 | ## How to use 10 | 11 | * New - generate e new height map 12 | * Blur - smooth the height map 13 | * Contrast - accentuates the height map 14 | * Invert - invert the height map 15 | * Color - display the height map in colors. Black means water level and white means mountains 16 | * 3D - generates the 3D map or update it based on the height map 17 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | height: 100%; 3 | } 4 | body { 5 | margin: 0; 6 | } 7 | button{ 8 | font-size: 1em; 9 | padding: 0.5em; 10 | } 11 | #main{ 12 | display: flex; 13 | flex-flow: row wrap; 14 | align-items: center; 15 | justify-content: space-around; 16 | height: 88%; 17 | } 18 | #toolbar{ 19 | display: flex; 20 | flex-flow: row wrap; 21 | align-items: center; 22 | justify-content: space-around; 23 | height: 12%; 24 | } 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Terrain Generator 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /js/terrainGenerator.js: -------------------------------------------------------------------------------- 1 | /*jshint esversion: 6, strict: implied */ 2 | /*globals THREE */ 3 | 4 | let s = 1; 5 | let w = 256; 6 | let h = 256; 7 | let map; 8 | let canvas = document.createElement('canvas'); 9 | canvas.width = w * s; 10 | canvas.height = h * s; 11 | let main = document.createElement('div'); 12 | main.id = "main"; 13 | main.appendChild(canvas); 14 | let c = canvas.getContext('2d'); 15 | let colorful = false; 16 | 17 | init(); 18 | draw(colorful); 19 | createToolBar(); 20 | document.body.appendChild(main); 21 | 22 | function init() { 23 | map = []; 24 | for (let i = 0; i < h; i++) { 25 | let row = []; 26 | for (let j = 0; j < w; j++) { 27 | row.push(Math.floor(Math.random() * 256)); 28 | } 29 | map.push(row); 30 | } 31 | } 32 | 33 | function createToolBar(){ 34 | let tools = { 35 | "New" : init, 36 | "Blur" : blur, 37 | "Contrast" : contrast, 38 | "Invert" : invert, 39 | "Color" : toggle, 40 | "3D" : createTerrain 41 | }; 42 | let toolbar = document.createElement('div'); 43 | toolbar.id = "toolbar"; 44 | for (let key in tools) { 45 | if (!tools.hasOwnProperty(key)) { continue; } 46 | let btn = document.createElement('button'); 47 | btn.innerText = key; 48 | btn.onclick = tools[key]; 49 | btn.addEventListener("click", draw); 50 | toolbar.appendChild(btn); 51 | } 52 | document.body.appendChild(toolbar); 53 | } 54 | 55 | function draw() { 56 | for (let i = 0; i < h; i++) { 57 | for (let j = 0; j < w; j++) { 58 | let cl = map[i][j]; 59 | if (colorful) { 60 | if (map[i][j] / 255 < 0.3) { 61 | c.fillStyle = "blue"; 62 | } else if (map[i][j] / 255 < 0.4) { 63 | c.fillStyle = "orange"; 64 | } else if (map[i][j] / 255 < 0.6) { 65 | c.fillStyle = "green"; 66 | } else if (map[i][j] / 255 < 0.9) { 67 | c.fillStyle = "brown"; 68 | } else { 69 | c.fillStyle = "white"; 70 | } 71 | } else { 72 | c.fillStyle = 'rgb(' + cl + ',' + cl + ',' + cl + ')'; 73 | } 74 | c.fillRect(j * s, i * s, s, s); 75 | } 76 | } 77 | } 78 | 79 | function blur() { 80 | for (let i = 0; i < h; i++) { 81 | for (let j = 0; j < w; j++) { 82 | let ln, rn, tn, bn; 83 | if (i === 0) { 84 | tn = map[h - 1][j]; 85 | } else { 86 | tn = map[i - 1][j]; 87 | } 88 | if (j === 0) { 89 | ln = map[i][w - 1]; 90 | } else { 91 | ln = map[i][j - 1]; 92 | } 93 | if (i === h - 1) { 94 | bn = map[0][j]; 95 | } else { 96 | bn = map[i + 1][j]; 97 | } 98 | if (j === w - 1) { 99 | rn = map[i][0]; 100 | } else { 101 | rn = map[i][j + 1]; 102 | } 103 | map[i][j] = Math.floor((tn + ln + rn + bn) / 4); 104 | } 105 | } 106 | } 107 | 108 | function contrast() { 109 | let black = 255, white = 0; 110 | for (let i = 0; i < h; i++) { 111 | for (let j = 0; j < w; j++) { 112 | if (map[i][j] < black) { 113 | black = map[i][j]; 114 | } 115 | if (map[i][j] > white) { 116 | white = map[i][j]; 117 | } 118 | } 119 | } 120 | for (let i = 0; i < h; i++) { 121 | for (let j = 0; j < w; j++) { 122 | map[i][j] = (map[i][j] - black) / (white - black) * 255; 123 | } 124 | } 125 | } 126 | 127 | function invert() { 128 | for (let i = 0; i < h; i++) { 129 | for (let j = 0; j < w; j++) { 130 | map[i][j] = 255 - map[i][j]; 131 | } 132 | } 133 | } 134 | 135 | // noinspection JSUnusedGlobalSymbols 136 | function diagonal() { 137 | for (let i = 0; i < h; i++) { 138 | for (let j = 0; j < w; j++) { 139 | map[i][j] = Math.floor((map[i][j] + map[(i + 1) % h][(j + 1) % w]) / 2); 140 | } 141 | } 142 | } 143 | 144 | function toggle() { 145 | colorful = !colorful; 146 | } 147 | 148 | document.addEventListener('keydown', function(e) { 149 | switch (e.keyCode) { 150 | case 65 : 151 | createTerrain(); 152 | break; 153 | case 66 : 154 | blur(); 155 | break; 156 | case 67 : 157 | contrast(); 158 | break; 159 | case 73 : 160 | invert(); 161 | break; 162 | case 76 : 163 | toggle(); 164 | break; 165 | case 78 : 166 | init(); 167 | break; 168 | } 169 | draw(colorful); 170 | }); 171 | 172 | 173 | function createTerrain(){ 174 | /* 175 | Following the tutorial - Learning 3D Graphics With Three.js | Procedural Geometry 176 | https://steemit.com/utopian-io/@clayjohn/learning-3d-graphics-with-three-js-or-procedural-geometry 177 | */ 178 | let scene = new THREE.Scene(); 179 | let camera = new THREE.PerspectiveCamera( 75, w*s/h*s, 0.1, 1000 ); 180 | 181 | let t = document.getElementById('terrain'); 182 | if (t) { 183 | t.remove(); 184 | //return; 185 | } 186 | 187 | let renderer = new THREE.WebGLRenderer(); 188 | renderer.domElement.id = 'terrain'; 189 | renderer.setSize( w*s, h*s ); 190 | renderer.setClearColor(0x7EC0EE); 191 | //document.body.appendChild( renderer.domElement ); 192 | main.appendChild( renderer.domElement ); 193 | 194 | let sun = new THREE.PointLight(0xFCD440,1); 195 | sun.position.x = 3; 196 | sun.position.y = 5; 197 | scene.add( sun ); 198 | 199 | let env = new THREE.AmbientLight(0x7EC0EE,0.3); 200 | env.position.y = 5; 201 | scene.add( env ); 202 | 203 | let terrain_geometry = makeTile(0.1); 204 | let terrain_material = new THREE.MeshLambertMaterial({color: new THREE.Color(0.9, 0.55, 0.4)}); 205 | let terrain = new THREE.Mesh(terrain_geometry, terrain_material); 206 | terrain.position.x = -10; 207 | terrain.position.z = -10; 208 | terrain.updateMatrixWorld(true); 209 | scene.add(terrain); 210 | 211 | camera.position.y = 2; 212 | 213 | let a = 0; 214 | 215 | let animate = function() { 216 | requestAnimationFrame(animate); 217 | renderer.render(scene, camera); 218 | 219 | camera.position.x = Math.cos(a) * 3; 220 | camera.position.z = Math.sin(a) * 3; 221 | camera.lookAt( 0, 0, 0 ); 222 | a += 0.005; 223 | }; 224 | 225 | function makeTile(size) { 226 | let geometry = new THREE.BufferGeometry(); 227 | geometry.vertices = []; 228 | geometry.faces = []; 229 | for (let i = 0; i < h; i++) { 230 | for (let j = 0; j < w; j++) { 231 | let z = j * size + 1 * size; 232 | let x = i * size + 1 * size; 233 | let y = map[i][j]/255; 234 | let position = new THREE.Vector3(x, y, z); 235 | let addFace = (i > 0) && (j > 0); 236 | makeQuad(geometry, position, addFace, w); 237 | } 238 | } 239 | geometry.setFromPoints(geometry.faces); 240 | geometry.computeVertexNormals(); 241 | geometry.normalsNeedUpdate = true; 242 | 243 | return geometry; 244 | } 245 | 246 | function makeQuad(geometry, position, addFace, verts) { 247 | geometry.vertices.push(position); 248 | 249 | if (addFace) { 250 | let index1 = geometry.vertices.length - 1; 251 | let index2 = index1 - 1; 252 | let index3 = index1 - verts; 253 | let index4 = index1 - verts - 1; 254 | 255 | // Triangle 1 256 | geometry.faces.push(geometry.vertices[index2]); 257 | geometry.faces.push(geometry.vertices[index3]); 258 | geometry.faces.push(geometry.vertices[index1]); 259 | 260 | // Triangle 2 261 | geometry.faces.push(geometry.vertices[index2]); 262 | geometry.faces.push(geometry.vertices[index4]); 263 | geometry.faces.push(geometry.vertices[index3]); 264 | } 265 | } 266 | 267 | animate(); 268 | } 269 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorqribeiro/terrainGenerator/8a26e1e3dcdcc2d5f9cd192a0f1ea23a5e94265d/screenshot.png --------------------------------------------------------------------------------