applied a few things i picked up from http://www.redblobgames.com/maps/terrain-from-noise/
A Pen by not important on CodePen.
applied a few things i picked up from http://www.redblobgames.com/maps/terrain-from-noise/
A Pen by not important on CodePen.
| <div class="o-wrapper"> | |
| <canvas | |
| id="js-canvas" | |
| width="410" | |
| height="410" | |
| ></canvas> | |
| <h1 id="js-loading" class="c-loading">Generating textures...</h1> | |
| </div> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script> | |
| <script src="https://codepen.io/clindsey/pen/egQpvE.js"></script> <!-- triangle-textures-4.0.0, GeoGenTextures --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.2/lib/alea.js"></script> | |
| <script src="https://cdn.rawgit.com/jwagner/simplex-noise.js/87440528bcf8ec89840e974d8f76cfe3da548c37/simplex-noise.min.js"></script> | |
| <script>(function(){var script=document.createElement('script');script.onload=function(){var stats=new Stats();document.body.appendChild(stats.dom);requestAnimationFrame(function loop(){stats.update();requestAnimationFrame(loop)});};script.src='//rawgit.com/mrdoob/stats.js/master/build/stats.min.js';document.head.appendChild(script);})()</script> |
| setTimeout(() => { | |
| const elevationSeed = +(new Date()); // 5625463739 | |
| const elevationSimplex = new SimplexNoise(new alea(elevationSeed)); // 5625463739 | |
| const moistureSimplex = new SimplexNoise(new alea(elevationSeed + 0x100)); // 5625463739 | |
| const textureSize = 64; // must be 2's compliment | |
| const segmentIterations = 3; // tesselates a faces into domains, increasing variation | |
| const textureIterations = 2; // tesselates domains, increasing number of steps between colors | |
| const geometryIterations = 2; // icosahedron face count, try to keep this as low as possible | |
| const noiseFactor = 1; // smaller values are boring, bigger are happening | |
| const geometryRadius = 100; | |
| const scene = new THREE.Scene(); | |
| buildLights(scene); | |
| const camera = new THREE.PerspectiveCamera(75, 1, 1, 10000); // refactor, magic numbers | |
| camera.position.z = 240; | |
| const mesh = buildMesh(segmentIterations, textureIterations, geometryIterations, textureSize, geometryRadius, noiseFactor, elevationSimplex, moistureSimplex); | |
| scene.add(mesh); | |
| const renderer = new THREE.WebGLRenderer({canvas: document.getElementById('js-canvas'), alpha: true}); | |
| animate(renderer, scene, camera, mesh); | |
| document.getElementById('js-loading').style.display = 'none'; | |
| // document.body.appendChild(generateTextureOutput(mesh.material.materials.map(m => m.map), textureSize, textureSize)); | |
| }, 0); | |
| function animate (renderer, scene, camera, mesh) { | |
| mesh.rotation.x += 0.004; | |
| mesh.rotation.y += 0.008; | |
| renderer.render(scene, camera); | |
| requestAnimationFrame(() => { | |
| animate(renderer, scene, camera, mesh); | |
| }); | |
| } | |
| function buildLights (scene) { | |
| const ambientLight = new THREE.AmbientLight(0x000000); | |
| scene.add(ambientLight); | |
| const lights = [ | |
| new THREE.PointLight(0xffffff, 1, 0), | |
| new THREE.PointLight(0xffffff, 1, 0), | |
| new THREE.PointLight(0xffffff, 1, 0) | |
| ]; | |
| lights[0].position.set(0, 2000, 0); | |
| lights[1].position.set(0, 100, 400); | |
| lights[2].position.set(-1000, -2000, -1000); | |
| scene.add(lights[0]); | |
| scene.add(lights[1]); | |
| scene.add(lights[2]); | |
| } | |
| function generateTextureOutput (textures, sW, sH) { | |
| const canvasEl = document.createElement('canvas'); | |
| const $ = canvasEl.getContext('2d'); | |
| const size = Math.ceil(Math.sqrt(textures.length)); | |
| canvasEl.width = sW * size; | |
| canvasEl.height = sH * size; | |
| textures.forEach(({image}, index) => { | |
| const x = index % size; | |
| const y = Math.floor(index / size); | |
| $.drawImage(image, 0, 0, sW, sH, x * sW, y * sH, sW, sH); | |
| }); | |
| return canvasEl; | |
| } | |
| function buildMesh (segmentIterations, textureIterations, geometryIterations, textureSize, radius, factor, elevationSimplex, moistureSimplex) { | |
| const geometry = new THREE.IcosahedronGeometry(radius, geometryIterations); | |
| const materials = []; | |
| const pointsUp = GeoGenTextures.buildPoints(3, Math.PI * 1.5); | |
| const vec0 = pointsUp[0].map(i => 0.5 - i / 2); | |
| const vec1 = pointsUp[1].map(i => 0.5 - i / 2); | |
| const vec2 = pointsUp[2].map(i => 0.5 - i / 2); | |
| geometry.faceVertexUvs[0] = geometry.faces.map((face, index) => { | |
| geometry.faces[index].materialIndex = index; | |
| const {a, b, c} = geometry.faces[index]; | |
| const material = createMaterial(textureIterations, segmentIterations, a, b, c, geometry.vertices, radius * 2, textureSize, factor, elevationSimplex, moistureSimplex); | |
| materials.push(material); | |
| return [ | |
| new THREE.Vector2(vec0[0], vec0[1]), | |
| new THREE.Vector2(vec1[0], vec1[1]), | |
| new THREE.Vector2(vec2[0], vec2[1]) | |
| ]; | |
| }); | |
| geometry.computeFaceNormals(); | |
| geometry.dynamic = true; | |
| geometry.uvsNeedUpdate = true; | |
| return new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials)); | |
| } | |
| function colorPicker (vertices, order, colorLookup, radius, elevationSimplex, moistureSimplex, f) { | |
| return factors => { | |
| const [x, y, z] = factors.map((factor, i) => ([ | |
| factor * vertices[order[i]].x, | |
| factor * vertices[order[i]].y, | |
| factor * vertices[order[i]].z | |
| ])).reduce(([pX, pY, pZ], [cX, cY, cZ]) => ([pX + cX, pY + cY, pZ + cZ]), [0, 0, 0]); | |
| const fX = Math.round((x / radius) * 1000000) / 1000000; | |
| const fY = Math.round((y / radius) * 1000000) / 1000000; | |
| const fZ = Math.round((z / radius) * 1000000) / 1000000; | |
| let e = 1 * elevationSimplex.noise3D((1 * f) * fX, (1 * f) * fY, (1 * f) * fZ); | |
| e += 0.5 * elevationSimplex.noise3D((2 * f) * fX, (2 * f) * fY, (2 * f) * fZ); | |
| e += 0.25 * elevationSimplex.noise3D((4 * f) * fX, (4 * f) * fY, (4 * f) * fZ); | |
| e += 0.13 * elevationSimplex.noise3D((8 * f) * fX, (8 * f) * fY, (8 * f) * fZ); | |
| e += 0.06 * elevationSimplex.noise3D((16 * f) * fX, (16 * f) * fY, (16 * f) * fZ); | |
| e += 0.03 * elevationSimplex.noise3D((32 * f) * fX, (32 * f) * fY, (32 * f) * fZ); | |
| e = (e + (1.97 / 2)) / 1.97; | |
| e = Math.pow(e, 3); | |
| let m = 1 * moistureSimplex.noise3D((1 * f) * fX, (1 * f) * fY, (1 * f) * fZ); | |
| m += 0.75 * moistureSimplex.noise3D((2 * f) * fX, (2 * f) * fY, (2 * f) * fZ); | |
| m += 0.33 * moistureSimplex.noise3D((4 * f) * fX, (4 * f) * fY, (4 * f) * fZ); | |
| m += 0.33 * moistureSimplex.noise3D((8 * f) * fX, (8 * f) * fY, (8 * f) * fZ); | |
| m += 0.33 * moistureSimplex.noise3D((16 * f) * fX, (16 * f) * fY, (16 * f) * fZ); | |
| m += 0.5 * moistureSimplex.noise3D((32 * f) * fX, (32 * f) * fY, (32 * f) * fZ); | |
| m = (m + (3.24 / 2)) / 3.24; | |
| m = Math.pow(m, 1); | |
| return colorLookup(e, m); | |
| }; | |
| } | |
| function createMaterial (textureIterations, segmentIterations, a, b, c, vertices, radius, textureSize, factor, elevationSimplex, moistureSimplex) { // refactor, use better variable names than `a, b, c` | |
| const order = [b, a, c]; // refactor, bad variable name | |
| const mapColorPicker = colorPicker(vertices, order, mapColorLookup, radius, elevationSimplex, moistureSimplex, factor); | |
| const specularColorPicker = colorPicker(vertices, order, specularColorLookup, radius, elevationSimplex, moistureSimplex, factor); | |
| const bumpColorPicker = colorPicker(vertices, order, bumpColorLookup, radius, elevationSimplex, moistureSimplex, factor); | |
| const material = new THREE.MeshPhongMaterial({ | |
| map: new THREE.Texture(GeoGenTextures.createNestedTile(textureSize, textureSize, segmentIterations, textureIterations, mapColorPicker, true)), | |
| specularMap: new THREE.Texture(GeoGenTextures.createNestedTile(textureSize, textureSize, segmentIterations, textureIterations, specularColorPicker, true)), | |
| specular: new THREE.Color(0x222222), | |
| side: THREE.DoubleSide, | |
| bumpMap: new THREE.Texture(GeoGenTextures.createNestedTile(textureSize, textureSize, segmentIterations, textureIterations, bumpColorPicker, true)), | |
| bumpScale: 1.5, | |
| wireframe: false | |
| }); | |
| material.map.needsUpdate = true; | |
| material.bumpMap.needsUpdate = true; | |
| material.specularMap.needsUpdate = true; | |
| return material; | |
| } | |
| const OCEAN = '44447a'; | |
| const BEACH = '9e8e77'; | |
| const TROPICAL_RAIN_FOREST = '337755'; | |
| const TROPICAL_SEASONAL_FOREST = '559944'; | |
| const GRASSLAND = '88aa55'; | |
| const SUBTROPICAL_DESERT = 'd2b98b'; | |
| const TEMPERATE_RAIN_FOREST = '448855'; | |
| const TEMPERATE_DECIDUOUS_FOREST = '679459'; | |
| const TEMPERATE_DESERT = 'c9d29b'; | |
| const TAIGA = '99aa77'; | |
| const SHRUBLAND = '889977'; | |
| const SNOW = 'dddde4'; | |
| const TUNDRA = 'bbbbaa'; | |
| const BARE = '888888'; | |
| const SCORCHED = '555555'; | |
| function mapColorLookup (e, m) { // taken from http://www.redblobgames.com/maps/terrain-from-noise/ | |
| if (e < 0.1) return OCEAN; | |
| if (e < 0.12) return BEACH; | |
| if (e > 0.8) { | |
| if (m < 0.1) return SCORCHED; | |
| if (m < 0.2) return BARE; | |
| if (m < 0.5) return TUNDRA; | |
| return SNOW; | |
| } | |
| if (e > 0.6) { | |
| if (m < 0.33) return TEMPERATE_DESERT; | |
| if (m < 0.66) return SHRUBLAND; | |
| return TAIGA; | |
| } | |
| if (e > 0.3) { | |
| if (m < 0.16) return TEMPERATE_DESERT; | |
| if (m < 0.50) return GRASSLAND; | |
| if (m < 0.83) return TEMPERATE_DECIDUOUS_FOREST; | |
| return TEMPERATE_RAIN_FOREST; | |
| } | |
| if (m < 0.16) return SUBTROPICAL_DESERT; | |
| if (m < 0.33) return GRASSLAND; | |
| if (m < 0.66) return TROPICAL_SEASONAL_FOREST; | |
| return TROPICAL_RAIN_FOREST; | |
| } | |
| function bumpColorLookup (e, m) { | |
| if (e < 0.1) { | |
| return '333333'; | |
| } | |
| if (e < 0.12) { | |
| return '666666'; | |
| } | |
| if (e > 0.8) { | |
| return 'ffffff'; | |
| } | |
| if (e > 0.3) { | |
| return 'cccccc'; | |
| } | |
| return '999999'; | |
| } | |
| function specularColorLookup (e, m) { | |
| if (e < 0.1) { | |
| return 'ffffff'; | |
| } | |
| return '000000'; | |
| } |
| body { | |
| background-color: #3b3251; | |
| font-family: sans-serif; | |
| color: white; | |
| } | |
| .o-wrapper { | |
| position: relative; | |
| } | |
| canvas { | |
| display: block; | |
| margin: 0 auto; | |
| } | |
| .c-loading { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| } |