├── 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 | 
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
--------------------------------------------------------------------------------