import { memoize } from "./memoize"; const smoothstep = x => x*x*(3 - 2*x); function interpolate(a, b, x, f = smoothstep) { const val = a + f(x) * (b - a); return val; } const sigmoid = (a, b, f = smoothstep) => x => { if (x < a) return 0; if (x > b) return 1; return f((x - a) / (b - a)); }; // super ghetto random function xorshift(x) { x ^= x << 13; x ^= x >> 7; x ^= x << 17; return x; } export function random(seed, z, x) { return xorshift(1337 * z + seed + 80085 * x); } export function random3d(seed, z, x, y) { return xorshift(1337 * z + seed + 80085 * x + 13 * y); } function ghettoPerlinNoise(seed, x, y, gridSize = 16) { const dot = (vx, vy) => vx[0] * vy[0] + vx[1] * vy[1]; const randGrad = (x0, y0) => { const rand = random(seed, x0, y0); return [Math.sin(rand), Math.cos(rand)]; }; const x0 = Math.floor(x / gridSize); const y0 = Math.floor(y / gridSize); const sx = x / gridSize - x0; const sy = y / gridSize - y0; const n0 = dot(randGrad(x0, y0), [sx, sy]); const n1 = dot(randGrad(x0 + 1, y0), [sx - 1, sy]); const n2 = dot(randGrad(x0, y0 + 1), [sx, sy - 1]); const n3 = dot(randGrad(x0 + 1, y0 + 1), [sx - 1, sy - 1]); return 1.5 * interpolate(interpolate(n0, n1, sx), interpolate(n2, n3, sx), sy); } function gpn3d(seed, x, y, z, gridSize = 16) { const dot = (u, v) => u[0] * v[0] + u[1] * v[1] + u[2] * v[2]; const randGrad = (x0, y0, z0) => { const rand0 = random3d(seed, x0, y0, z0); const rand1 = random3d(seed+1, x0, y0, z0); return [Math.cos(rand0), Math.sin(rand1) * Math.sin(rand0), Math.cos(rand1) * Math.sin(rand0)]; }; const x0 = Math.floor(x / gridSize); const y0 = Math.floor(y / gridSize); const z0 = Math.floor(z / gridSize); const sx = x / gridSize - x0; const sy = y / gridSize - y0; const sz = z / gridSize - z0; const n0 = dot(randGrad(x0, y0, z0), [sx, sy, sz]); const n1 = dot(randGrad(x0 + 1, y0, z0), [sx - 1, sy, sz]); const n2 = dot(randGrad(x0, y0 + 1, z0), [sx, sy - 1, sz]); const n3 = dot(randGrad(x0 + 1, y0 + 1, z0), [sx - 1, sy - 1, sz]); const n4 = dot(randGrad(x0, y0, z0 + 1), [sx, sy, sz - 1]); const n5 = dot(randGrad(x0 + 1, y0, z0 + 1), [sx - 1, sy, sz - 1]); const n6 = dot(randGrad(x0, y0 + 1, z0 + 1), [sx, sy - 1, sz - 1]); const n7 = dot(randGrad(x0 + 1, y0 + 1, z0 + 1), [sx - 1, sy - 1, sz - 1]); return 1.5 * interpolate( interpolate(interpolate(n0, n1, sx), interpolate(n2, n3, sx), sy), interpolate(interpolate(n4, n5, sx), interpolate(n6, n7, sx), sy), sz, ); } function cliffPerlin(seed, x, y) { const noise1 = ghettoPerlinNoise(seed, x, y); const noise2 = ghettoPerlinNoise(seed+1, x, y); const softerEdge = x => { if (x < 0.1) return 0.5 - 5 * x; if (x > 0.8) return 1.0 - 2.5 * (x - 0.8); return (x - 0.1) / 0.8; }; return interpolate(-1, 1, 0.5 * (1 + Math.atan2(noise1, noise2) / Math.PI), softerEdge); } function _fractalGpn3d(seed, x, y, z) { const lacunarity = 3.4; const persistence = 0.28; let value = 0; let power = 0.6; let scale = 0.7; const noises = [gpn3d, gpn3d, gpn3d]; for (const noiseFun of noises) { const noise = noiseFun(seed, scale * x, scale * y, scale * z); value += noise * power; power *= persistence; scale *= lacunarity; } return value; } const fractalGpn3d = memoize(_fractalGpn3d); export function checkCave(seed, x, y, z) { const nx = Math.floor(x / 8) * 8; const ny = Math.floor(y / 4) * 4; const nz = Math.floor(z / 8) * 8; const sx = (x - nx) / 8; const sy = (y - ny) / 4; const sz = (z - nz) / 8; const itp = interpolate; const n = (x, y, z) => fractalGpn3d(seed, x / 3, y, z / 3); const noise = itp( itp(itp(n(nx, ny, nz), n(nx + 8, ny, nz), sx), itp(n(nx, ny + 4, nz), n(nx + 8, ny + 4, nz), sx), sy ), itp(itp(n(nx, ny, nz + 8), n(nx + 8, ny, nz + 8), sx), itp(n(nx, ny + 4, nz + 8), n(nx + 8, ny + 4, nz + 8), sx), sy ), sz); //if (n(nx, ny, nz) > 0.3) throw Error('break!'); return noise > 0.28 && noise < 0.55; } export function makeTerrain(seed, x, y) { const lacunarity = 2.1; const persistence = 0.35; const noiseMap = (x, y) => cliffPerlin(seed, x / 2, y / 2); const fractalNoise = (x, y) => { let value = 0; let power = 0.6; let scale = 0.1; const noises = [ghettoPerlinNoise, ghettoPerlinNoise, cliffPerlin, cliffPerlin]; for (const noiseFun of noises) { const noise = noiseFun(seed, scale * x, scale * y); value += noise * power; power *= persistence; scale *= lacunarity; } return interpolate(-0.2, 0.6, value);//, x=>x*x); // between 0 and 1 } const scaledNoise = (x, y) => noiseMap(x / 1, y / 1); const outputNoise = fractalNoise; // const outputNoise = scaledNoise; const terrain = new Uint8Array(16 * 16); for (let i = 0; i < 16; i++) { for (let j = 0; j < 16; j++) { terrain[i * 16 + j] = Math.floor(64 + 64 * outputNoise(x + i, y + j)); } } return terrain; } function colorInterp(x) { const ocean = [0.0, 0.0, 0.5]; const grass = [0.2, 0.7, 0.2]; const mountain = [0.4, 0.3, 0.1]; const snow = [0.9, 0.9, 0.9]; const grassHeight = 75 / 255; const mountainHeight = 100 / 255; const interp = dim => { return x => { if (x < grassHeight) { return interpolate(ocean[dim], grass[dim], x / grassHeight, sigmoid(0.6, 0.95)); } else if (x < mountainHeight) { return interpolate(grass[dim], mountain[dim], (x - grassHeight) / (mountainHeight - grassHeight)); } else { return interpolate(mountain[dim], snow[dim], (x - mountainHeight) / (1.0 - mountainHeight), sigmoid(0, 0.2)); } }; }; return { r: 255 * interp(0)(x/255), g: 255 * interp(1)(x/255), b: 255 * interp(2)(x/255), }; } function main() { const canvas = document.querySelector('#canvas'); const ctx = canvas.getContext('2d'); const seed = 1337; ctx.fillStyle = 'gray'; ctx.fill(); for (let i = 0; i < canvas.width; i += 16) { for (let j = 0; j < canvas.height; j += 16) { const imgdat = ctx.createImageData(16, 16); const dat = imgdat.data; const chunk = makeTerrain(seed, i - canvas.width / 2, j - canvas.height / 2); for (let k = 0; k < 16; k++) { for (let l = 0; l < 16; l++) { const offset = 4 * (16 * (15 - l) + k); const val = chunk[16 * k + l]; dat[offset + 0] = colorInterp(val).r; dat[offset + 1] = colorInterp(val).g; dat[offset + 2] = colorInterp(val).b; dat[offset + 3] = 255; } } ctx.putImageData(imgdat, i, canvas.height - 16 - j); } } } //window.onload = main;