Skip to content

Commit

Permalink
procgen based on triangular-distributed node
Browse files Browse the repository at this point in the history
  • Loading branch information
jmdejong committed Sep 27, 2023
0 parents commit 0da6662
Show file tree
Hide file tree
Showing 8 changed files with 3,706 additions and 0 deletions.
3,213 changes: 3,213 additions & 0 deletions FastNoiseLite.js

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions display.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use strict";

class Display {

constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
}

line(v1, v2, color, width) {
if (!color) {
color = "black";
}
this.ctx.strokeStyle = color;
if (!width) {
width = 1;
}
this.ctx.lineWidth = width;
this.ctx.lineCap = "round";
this.ctx.beginPath();
this.ctx.moveTo(v1.x, v1.y);
this.ctx.lineTo(v2.x, v2.y);
this.ctx.stroke()
}

circle(center, radius, color) {
if (!color) {
color = "black";
}
this.ctx.fillStyle = color;
this.ctx.beginPath();
this.ctx.arc(center.x, center.y, radius, 0, Math.PI * 2);
this.ctx.fill();
}
}
19 changes: 19 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>
Elaborate
</title>
<script src="FastNoiseLite.js"></script>
<script src="priorityqueue.js"></script>
<script src="util.js"></script>
<script src="vec2.js"></script>
<script src="display.js"></script>
<script src="main.js"></script>
<link href="style.css" rel="stylesheet" />
</head>
<body>
<canvas id="worldlevel"></canvas>
</body>
</html>
232 changes: 232 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
"use strict";

const TRIHEIGHT = 0.866;

class Node {
constructor(id, pos) {
this.id = id;
this.pos = pos;
this.drain = null;
this.sea = 0;
this.water = 0;
this.height = 0;
}

neighbours() {
return [vec2(this.id.x + 1, this.id.y), vec2(this.id.x - 1, this.id.y), vec2(this.id.x, this.id.y + 1), vec2(this.id.x, this.id.y - 1)];
}
}

class PriorityFringe {
constructor() {
this.items = new PriorityQueue(node => node.height);
}
put(item) {
this.items.add(item);
}
take() {
return this.items.remove()
}
isEmpty() {
return this.items.heap.length === 0;
}

forEach(fn) {
this.items.heap.forEach(fn);
}
}
class RandomFringe {
constructor(seed) {
this.seed = seed;
this.items = [];
}
put(item) {
this.items.push(item);
}
take() {
this.seed = hash(this.seed);
let ind = Math.abs(this.seed) % this.items.length;
let last = this.items.pop();
if (ind === this.items.length) {
return last;
} else {
let item = this.items[ind];
this.items[ind] = last;
return item;
}
}
isEmpty() {
return this.items.length === 0;
}

forEach(fn) {
this.items.forEach(fn);
}
}


class World {
constructor(size, nodeSize, seed) {
this.size = size;
this.seed = seed;
this.nodes = new Map();
this.ns = nodeSize;
this.nodedim = this.size.mult(1/this.ns);
this._topY = 0
this._bottomY = Math.ceil(this.nodedim.y/TRIHEIGHT)

for (let y=this._topY; y<=this._bottomY; ++y) {
let xo = -y/2|0
for (let x=this._leftX(y); x<=this._rightX(y); ++x) {
let nv = vec2(x, y);
let a = randf(nv, 930)*2*Math.PI;
let r = randf(nv, 872);
let off = vec2(Math.cos(a), Math.sin(a)).mult(0.45*(1-r*r));
let pos = vec2(x + y/2, y*TRIHEIGHT).add(off).mult(this.ns);
let node = new Node(nv, pos)
this.nodes.set(nv.hash(), node);
}
}
}

_leftX(y) {
return -y/2|0;
}
_rightX(y) {
return this._leftX(y) + this.nodedim.x;
}

edges() {
let edges = [];
for (let x=this._leftX(this._topY); x<=this._rightX(this._topY); ++x) {
edges.push(this.nodes.get(vec2(x, this._topY).hash()));
}
for (let x=this._leftX(this._bottomY); x<=this._rightX(this._bottomY); ++x) {
edges.push(this.nodes.get(vec2(x, this._bottomY).hash()));
}
for (let y=this._topY + 1; y<this._bottomY; ++y) {
edges.push(this.nodes.get(vec2(this._leftX(y), y).hash()));
edges.push(this.nodes.get(vec2(this._rightX(y), y).hash()));
}
return edges.filter(e => e);
}

neighbours(node) {
return [vec2(1, 0), vec2(-1, 0), vec2(0, 1), vec2(0, -1), vec2(1, -1), vec2(-1, 1)]
.map(v => {
return this.nodes.get(v.add(node.id).hash())
})
.filter(v => v);
}

prepare(frequency, edge) {
let noise = new FastNoiseLite(this.seed);
noise.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
noise.SetFractalType(FastNoiseLite.FractalType.FBm);
noise.SetFractalOctaves(8);
noise.SetFrequency(frequency);
// let edge = 256;
for (let node of this.nodes.values()) {//this.nodes.values().forEach(node => {
node.height = noise.GetNoise(node.pos.x, node.pos.y) + 0.5
let d = Math.min(Math.min(node.pos.x, this.size.x - node.pos.x), Math.min(node.pos.y, this.size.y - node.pos.y));
if (d < edge) {
node.height = -0.5 + (0.5 + node.height) * d / edge;
}
}
}


land() {
let fringe = new PriorityFringe(hash(this.seed ^ 2245));
let visited = new Set();
for (let node of this.edges()) {
node.sea = 1;
fringe.put(node);
visited.add(node.id.hash());
}
while (!fringe.isEmpty()) {
let node = fringe.take();
for (let neighbour of this.neighbours(node)) {
if (!visited.has(neighbour.id.hash())) {
if (neighbour.height < node.height) {
neighbour.height = node.height + randf(neighbour.id, 3627) * 0.001;
} else {
// neighbour.height -= 0.1 * (neighbour.height - node.height);
}
if (neighbour.height < 0) {
neighbour.sea = 1;
} else {
neighbour.drain = node.id;
}
fringe.put(neighbour);
visited.add(neighbour.id.hash());
}
}
}
}

drain(w) {
let wetness = w *this.ns*this.ns
for (let node of this.nodes.values()) {
while (!node.sea) {
node.water += wetness;
node = this.nodes.get(node.drain.hash());
}
}
}

draw(id) {
if (!id) {
id = "worldlevel";
}
let canvas = document.getElementById(id);
canvas.width = this.size.x;
canvas.height = this.size.y;
let display = new Display(canvas)
for (let node of this.nodes.values()) {
if (node.sea) {
display.circle(node.pos, this.ns / 2, "#00a");
} else {
let h = node.height*0.8;
let r = clamp(h*2, 0, 1);
let g = clamp(Math.min(h+0.8, 1.8 - h*1.5), 0, 1);
let color = `rgb(${r*255}, ${g*255}, 0)`;
display.circle(node.pos, this.ns / 2, color);
}
}
for (let node of this.nodes.values()) {
if (node.sea) {
for (let neighbour of this.neighbours(node)) {
// let neighbour = this.nodes.get(v.hash());
if (neighbour && neighbour.sea) {
display.line(node.pos, neighbour.pos, "#008", this.ns/2);
}
}
} else {
if (node.water < 1) {
continue;
}
let drain = this.nodes.get(node.drain.hash());
display.line(node.pos, drain.pos, "#22f", clamp(Math.sqrt(node.water)/5, 0.5, 5));
}
}
}

posof(node) {
return vec2(16 *node.x + (hash(11 + hash(node.x * 13 + hash(node.y * 17+ 531) + 872)) % 7), 16 * node.y + (hash(23 + hash(node.x * 5 +hash(node.y * 7))) % 7));
}
}



function main() {
let seed = Math.random() * 1e6 | 0;
let size = 1024;
let world = time("world", () => new World(vec2(size, size), 16, seed));
time("prepare", () => world.prepare(0.005, size / 8));
time("land", () => world.land());
time("drain", () => world.drain(0.005));
time("draw", () => world.draw());
window.world = world;
}
window.addEventListener("load", main);
101 changes: 101 additions & 0 deletions priorityqueue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"use strict";

class PriorityQueue {
constructor(keyfn) {
this.keyfn = keyfn;
this.heap = [];
}

// Helper Methods
getLeftChildIndex(parentIndex) {
return 2 * parentIndex + 1;
}

getRightChildIndex(parentIndex) {
return 2 * parentIndex + 2;
}

getParentIndex(childIndex) {
return Math.floor((childIndex - 1) / 2);
}

hasLeftChild(index) {
return this.getLeftChildIndex(index) < this.heap.length;
}

hasRightChild(index) {
return this.getRightChildIndex(index) < this.heap.length;
}

hasParent(index) {
return this.getParentIndex(index) >= 0;
}

leftChild(index) {
return this.heap[this.getLeftChildIndex(index)];
}

rightChild(index) {
return this.heap[this.getRightChildIndex(index)];
}

parent(index) {
return this.heap[this.getParentIndex(index)];
}

swap(indexOne, indexTwo) {
const temp = this.heap[indexOne];
this.heap[indexOne] = this.heap[indexTwo];
this.heap[indexTwo] = temp;
}

peek() {
if (this.heap.length === 0) {
return null;
}
return this.heap[0];
}

// Removing an element will remove the
// top element with highest priority then
// heapifyDown will be called
remove() {
if (this.heap.length === 0) {
return null;
}
const item = this.heap[0];
this.heap[0] = this.heap[this.heap.length - 1];
this.heap.pop();
this.heapifyDown();
return item;
}

add(item) {
this.heap.push(item);
this.heapifyUp();
}

heapifyUp() {
let index = this.heap.length - 1;
while (this.hasParent(index) && this.keyfn(this.parent(index)) > this.keyfn(this.heap[index])) {
this.swap(this.getParentIndex(index), index);
index = this.getParentIndex(index);
}
}

heapifyDown() {
let index = 0;
while (this.hasLeftChild(index)) {
let smallerChildIndex = this.getLeftChildIndex(index);
if (this.hasRightChild(index) && this.keyfn(this.rightChild(index)) < this.keyfn(this.leftChild(index))) {
smallerChildIndex = this.getRightChildIndex(index);
}
if (this.keyfn(this.heap[index]) < this.keyfn(this.heap[smallerChildIndex])) {
break;
} else {
this.swap(index, smallerChildIndex);
}
index = smallerChildIndex;
}
}
}
7 changes: 7 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

canvas {
border: 2px solid black;
image-rendering: pixelated;
/* background-color: black; */
/* width: 32%; */
}
Loading

0 comments on commit 0da6662

Please sign in to comment.