Skip to content

Instantly share code, notes, and snippets.

@clindsey
Created February 16, 2017 07:07
Show Gist options
  • Save clindsey/0d7d02d658b5823e60d3c70b2cfe86eb to your computer and use it in GitHub Desktop.
Save clindsey/0d7d02d658b5823e60d3c70b2cfe86eb to your computer and use it in GitHub Desktop.
icosahedron-texture-5.2.0
<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%);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment