Skip to content

Instantly share code, notes, and snippets.

@jessb321
Created January 2, 2019 21:22
Show Gist options
  • Save jessb321/b1a0bb5ca5a93caf0ea5b168703726ba to your computer and use it in GitHub Desktop.
Save jessb321/b1a0bb5ca5a93caf0ea5b168703726ba to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Terrain</title>
</head>
<body style='background: #000'>
<canvas id='display' width='1' height='1' />
</body>
<script src="index.js"></script>
</html>
class Terrain {
constructor(detail) {
this.size = 2 ** detail + 1;
this.max = this.size - 1;
this.map = new Float32Array(this.size * this.size);
}
get(x, y) {
if (x < 0 || x > this.max || y < 0 || y > this.max) {
return -1;
}
return this.map[x + this.size * y];
}
set(x, y, val) {
this.map[x + this.size * y] = val;
}
generate(roughness) {
this.set(0, 0, this.max);
this.set(this.max, 0, this.max / 2);
this.set(this.max, this.max, 0);
this.set(0, this.max, this.max / 2);
this.divide(roughness, this.max);
}
divide(roughness, size) {
let x;
let y;
const half = size / 2;
const scale = roughness * size;
if (half < 1) {
return;
}
for (y = half; y < this.max; y += size) {
for (x = half; x < this.max; x += size) {
this.square(x, y, half, Math.random() * scale * 2 - scale);
}
}
for (y = 0; y <= this.max; y += half) {
for (x = (y + half) % size; x <= this.max; x += size) {
this.diamond(x, y, half, Math.random() * scale * 2 - scale);
}
}
this.divide(roughness, size / 2, scale);
}
average(values) {
const valid = values.filter(val => val !== -1);
const total = valid.reduce((sum, val) => sum + val, 0);
return total / valid.length;
}
square(x, y, size, offset) {
const ave = this.average([
this.get(x - size, y - size), // Upper left
this.get(x + size, y - size), // Upper right
this.get(x + size, y + size), // Lower right
this.get(x - size, y + size) // Lower left
]);
this.set(x, y, ave + offset);
}
diamond(x, y, size, offset) {
const ave = this.average([
this.get(x, y - size), // Top
this.get(x + size, y), // Right
this.get(x, y + size), // Bottom
this.get(x - size, y) // Left
]);
this.set(x, y, ave + offset);
}
draw(ctx, width, height) {
const waterVal = this.size * 0.3;
for (let y = 0; y < this.size; y++) {
for (let x = 0; x < this.size; x++) {
const val = this.get(x, y);
const top = this.project(x, y, val);
const bottom = this.project(x + 1, y, 0);
const water = this.project(x, y, waterVal);
const style = this.brightness(x, y, this.get(x + 1, y) - val);
this.rect(ctx, top, bottom, style);
this.rect(ctx, water, bottom, 'rgba(50, 150, 200, 0.15)');
}
}
}
rect(ctx, a, b, style) {
if (b.y < a.y) {
return;
}
ctx.fillStyle = style;
ctx.fillRect(a.x, a.y, b.x - a.x, b.y - a.y);
}
brightness(x, y, slope) {
if (y === this.max || x === this.max) {
return '#000';
}
const b = ~~(slope * 50) + 128;
return ['rgba(', b, ',', b, ',', b, ',1)'].join('');
}
iso(x, y) {
return {
x: 0.5 * (this.size + x - y),
y: 0.5 * (x + y)
};
}
project(flatX, flatY, flatZ) {
const point = this.iso(flatX, flatY);
const x0 = width * 0.5;
const y0 = height * 0.2;
const z = this.size * 0.5 - flatZ + point.y * 0.75;
const x = (point.x - this.size * 0.5) * 6;
const y = (this.size - point.y) * 0.005 + 1;
return {
x: x0 + x / y,
y: y0 + z / y
};
}
}
const display = document.getElementById('display');
const ctx = display.getContext('2d');
var width = (display.width = window.innerWidth);
var height = (display.height = window.innerHeight);
const terrain = new Terrain(9);
terrain.generate(0.7);
terrain.draw(ctx, width, height);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment