libpov_2025/minetest_biome_voronoi.html

317 lines
7.8 KiB
HTML
Raw Normal View History

2023-10-20 02:54:39 +02:00
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Minetest Biome Point Visualizer</title>
<style>
canvas {
border: 1px solid black;
}
</style>
2023-10-20 10:43:04 +02:00
<!-- Voronoi diagram API -->
2023-10-20 02:54:39 +02:00
<script src="./rhill-voronoi-core.js"></script>
</head>
<body>
<h1>Minetest Biome Point Visualizer</h1>
2023-10-20 10:43:04 +02:00
<div>
<!-- TODO: Add proper canvas browser fallback by listing the voronoi points -->
2023-10-20 12:34:37 +02:00
<canvas id="voronoiCanvas" width="500" height="500">
2023-10-20 02:54:39 +02:00
A voronoi diagram is supposed to be here but for some reason it cannot be displayed.
</canvas>
2023-10-20 10:43:04 +02:00
</div>
<div>
<form id="biomeForm">
2023-10-20 11:24:38 +02:00
<label for="biomeSelector">Biomes:<br>
2023-10-20 12:34:37 +02:00
<select id="biomeSelector" name="biomeList" size="8"></select>
2023-10-20 10:43:04 +02:00
</label>
<br>
2023-10-20 11:24:38 +02:00
<button id="addBiomeButton" type="button">Add</button>
<button id="removeBiomeButton" type="button">Remove</button>
2023-10-20 10:43:04 +02:00
<br>
2023-10-20 11:24:38 +02:00
<div id="biomeEditElements">
<label for="inputHeat">Heat:
<input id="inputHeat" type="number" value="50">
2023-10-20 10:43:04 +02:00
</label>
2023-10-20 11:24:38 +02:00
<label for="inputHumidity">Humidity:
<input id="inputHumidity" type="number" value="50">
2023-10-20 10:43:04 +02:00
</label>
<br>
2023-10-20 11:24:38 +02:00
<label for="minY">Min. Y:
<input id="minY" type="number" value="-31000">
2023-10-20 10:43:04 +02:00
</label>
2023-10-20 11:24:38 +02:00
<label for="maxY">Max. Y:
<input id="maxY" type="number" value="31000">
2023-10-20 10:43:04 +02:00
</label>
</div>
</form>
<div>
2023-10-20 02:54:39 +02:00
<script>
"use strict";
let biomePoints = [
2023-10-20 12:34:37 +02:00
{heat:50, humidity:50},
2023-10-20 02:54:39 +02:00
];
2023-10-20 10:43:04 +02:00
// FOREST-16 palette, plus black
const pointColor = "#913636";
2023-10-20 13:05:22 +02:00
const pointColorSelected = "#e19696";
2023-10-20 10:43:04 +02:00
const edgeColor = "#0f2c2e";
const gridColor = "#00000040";
const axisColor = "#000000";
const cellColors = [
"#64988e",
"#3d7085",
"#345644",
"#6b7f5c",
"#b0b17c",
"#e1c584",
"#c89660",
"#ad5f52",
"#692f11",
"#89542f",
"#796e63",
"#a17d5e",
"#b4a18f",
"#ecddba",
];
// define an array and randomize it
function shuffleArray(values) {
let index = values.length, randomIndex;
// While there remain elements to shuffle
while (index != 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * index);
index--; // And swap it with the current element.
[values[index], values[randomIndex]] = [values[randomIndex], values[index]];
}
return values;
}
2023-10-20 02:54:39 +02:00
function biomePointToVoronoiPoint(point) {
let newPoint = { x: point.heat, y: point.humidity }
return newPoint;
}
function voronoiPointToBiomePoint(point) {
let newPoint = { heat: point.x, humidity: point.y }
return newPoint;
}
function putPoint(context, point) {
2023-10-20 13:05:22 +02:00
context.beginPath();
2023-10-20 02:54:39 +02:00
context.moveTo(0, 0);
context.arc(point.heat, point.humidity, 2, 0, Math.PI * 2);
context.fill();
2023-10-20 13:05:22 +02:00
context.closePath();
2023-10-20 02:54:39 +02:00
};
2023-10-20 11:24:38 +02:00
const LIMIT_MIN = -37.5
const LIMIT_MAX = 137.5
2023-10-20 12:34:37 +02:00
const DRAW_MIN = LIMIT_MIN - 10
const DRAW_MAX = LIMIT_MAX + 10
2023-10-20 02:54:39 +02:00
const GRID_STEP = 10
function putGrid(context) {
context.lineWidth = 0.5;
2023-10-20 10:43:04 +02:00
context.strokeStyle = gridColor;
2023-10-20 11:24:38 +02:00
for (let x=0; x<=LIMIT_MAX; x+=GRID_STEP) {
2023-10-20 02:54:39 +02:00
context.beginPath();
2023-10-20 11:24:38 +02:00
context.moveTo(x, LIMIT_MIN);
context.lineTo(x, LIMIT_MAX);
2023-10-20 02:54:39 +02:00
context.stroke();
}
2023-10-20 11:24:38 +02:00
for (let x=-GRID_STEP; x>=LIMIT_MIN; x-=GRID_STEP) {
2023-10-20 02:54:39 +02:00
context.beginPath();
2023-10-20 11:24:38 +02:00
context.moveTo(x, LIMIT_MIN);
context.lineTo(x, LIMIT_MAX);
context.stroke();
}
for (let y=0; y<=LIMIT_MAX; y+=GRID_STEP) {
context.beginPath();
context.moveTo(LIMIT_MIN, y);
context.lineTo(LIMIT_MAX, y);
context.stroke();
}
for (let y=-GRID_STEP; y>=LIMIT_MIN; y-=GRID_STEP) {
context.beginPath();
context.moveTo(LIMIT_MIN, y);
context.lineTo(LIMIT_MAX, y);
2023-10-20 02:54:39 +02:00
context.stroke();
}
}
function putAxes(context) {
context.lineWidth = 0.75;
// heat axis (horizontal)
context.beginPath();
2023-10-20 11:24:38 +02:00
context.moveTo(LIMIT_MIN,0)
context.lineTo(LIMIT_MAX,0)
2023-10-20 10:43:04 +02:00
context.closePath();
2023-10-20 02:54:39 +02:00
context.stroke();
// humidity axis ()
context.beginPath();
2023-10-20 11:24:38 +02:00
context.moveTo(0,LIMIT_MIN)
context.lineTo(0,LIMIT_MAX)
2023-10-20 10:43:04 +02:00
context.closePath();
2023-10-20 02:54:39 +02:00
context.stroke();
}
// Cache diagram object for performance boost
let cachedVoronoiDiagram = null;
2023-10-20 13:05:22 +02:00
function getVoronoiDiagram(recalculate) {
if ((cachedVoronoiDiagram === null) || recalculate) {
let pad = 10;
let vbbox = {xl: LIMIT_MIN-pad, xr: LIMIT_MAX+pad, yt: LIMIT_MIN-pad, yb: LIMIT_MAX+pad};
let sites = []
for (let p of biomePoints) {
sites.push(biomePointToVoronoiPoint(p));
}
let voronoi = new Voronoi();
let diagram = null;
if (cachedVoronoiDiagram && recalculate) {
diagram = cachedVoronoiDiagram;
// This should improve performance
voronoi.recycle(diagram);
}
diagram = voronoi.compute(sites, vbbox);
cachedVoronoiDiagram = diagram;
return diagram;
} else {
return cachedVoronoiDiagram;
2023-10-20 02:54:39 +02:00
}
2023-10-20 12:34:37 +02:00
}
2023-10-20 10:43:04 +02:00
2023-10-20 13:05:22 +02:00
function getDrawContext() {
let canvas = document.getElementById("voronoiCanvas");
// TODO: Check for getContext support of browser
return canvas.getContext("2d");
}
function drawInit() {
let context = getDrawContext();
context.scale(voronoiCanvas.width/(LIMIT_MAX-LIMIT_MIN), voronoiCanvas.height/(LIMIT_MAX-LIMIT_MIN));
context.translate(-LIMIT_MIN, -LIMIT_MIN);
}
function draw(recalculate) {
2023-10-20 12:34:37 +02:00
let context = getDrawContext();
context.fillStyle = "#FFFFFF";
context.fillRect(DRAW_MIN, DRAW_MIN, DRAW_MAX-DRAW_MIN, DRAW_MAX-DRAW_MIN);
let diagram = getVoronoiDiagram(recalculate);
2023-10-20 12:34:37 +02:00
//let colors = shuffleArray(cellColors);
let colors = cellColors;
2023-10-20 10:43:04 +02:00
for (let c=0; c<diagram.cells.length; c++) {
let cell = diagram.cells[c]
2023-10-20 12:34:37 +02:00
let ccol = c % colors.length;
context.fillStyle = colors[ccol];
2023-10-20 10:43:04 +02:00
context.beginPath();
for (let h=0; h<cell.halfedges.length; h++) {
let halfedge = cell.halfedges[h]
let start = halfedge.getStartpoint()
let end = halfedge.getEndpoint()
if (h === 0) {
context.moveTo(start.x, start.y);
} else {
context.lineTo(start.x, start.y);
}
context.lineTo(end.x, end.y);
}
context.closePath();
context.fill();
}
2023-10-20 12:34:37 +02:00
if (biomePoints.length === 1 && diagram.cells.length === 1) {
context.fillStyle = colors[0];
context.fillRect(DRAW_MIN, DRAW_MIN, DRAW_MAX-DRAW_MIN, DRAW_MAX-DRAW_MIN);
}
2023-10-20 10:43:04 +02:00
2023-10-20 11:24:38 +02:00
//putAxes(context);
2023-10-20 10:43:04 +02:00
putGrid(context);
context.strokeStyle = edgeColor;
2023-10-20 13:05:22 +02:00
context.lineWidth = 2;
2023-10-20 02:54:39 +02:00
for (let e=0; e<diagram.edges.length; e++) {
let edge = diagram.edges[e];
context.beginPath();
context.moveTo(edge.va.x, edge.va.y);
context.lineTo(edge.vb.x, edge.vb.y);
2023-10-20 10:43:04 +02:00
context.closePath();
2023-10-20 02:54:39 +02:00
context.stroke();
}
2023-10-20 10:43:04 +02:00
2023-10-20 13:05:22 +02:00
for (let p=0; p<biomePoints.length; p++) {
if (p === biomeSelector.selectedIndex) {
context.fillStyle = pointColorSelected;
} else {
context.fillStyle = pointColor;
}
putPoint(context, biomePoints[p]);
2023-10-20 10:43:04 +02:00
}
2023-10-20 02:54:39 +02:00
}
2023-10-20 12:34:37 +02:00
window.addEventListener("load", drawInit);
2023-10-20 02:54:39 +02:00
window.addEventListener("load", draw);
2023-10-20 12:34:37 +02:00
window.addEventListener("load", rewriteBiomeSelector);
function rewriteBiomeSelector() {
biomeSelector.innerHTML = "";
for (let b=0; b<biomePoints.length; b++) {
let num = b+1;
let newElem = document.createElement("option");
newElem.value = num;
let newElemText = document.createTextNode("Biome #"+num);
newElem.append(newElemText);
biomeSelector.append(newElem);
}
}
2023-10-20 11:24:38 +02:00
2023-10-20 13:05:22 +02:00
biomeSelector.onchange = function() {
draw(false);
2023-10-20 13:05:22 +02:00
}
2023-10-20 11:24:38 +02:00
addBiomeButton.onclick = function() {
2023-10-20 12:34:37 +02:00
let he = Math.round(Math.random()*100);
let hu = Math.round(Math.random()*100);
biomePoints.push({heat:he, humidity:hu});
let num = biomePoints.length
let newElem = document.createElement("option");
newElem.value = "" + num;
let newElemText = document.createTextNode("Biome #"+num);
newElem.append(newElemText);
biomeSelector.append(newElem);
newElem.selected = "selected";
draw(true);
2023-10-20 11:24:38 +02:00
}
removeBiomeButton.onclick = function() {
2023-10-20 12:34:37 +02:00
if (biomeSelector.selectedOptions.length === 0) {
return;
}
2023-10-20 13:05:22 +02:00
let firstIndex = null;
2023-10-20 12:34:37 +02:00
for (let o=0; o<biomeSelector.selectedOptions.length; o++) {
let opt = biomeSelector.selectedOptions[o]
let index = opt.index
2023-10-20 13:05:22 +02:00
if (firstIndex === null) {
2023-10-20 12:34:37 +02:00
firstIndex = index;
}
biomePoints.splice(index, 1);
opt.remove();
}
2023-10-20 13:05:22 +02:00
if (firstIndex !== null && biomePoints.length > 0) {
let newIndex = firstIndex-1;
if (newIndex < 0) {
newIndex = 0;
}
2023-10-20 12:34:37 +02:00
biomeSelector.options[newIndex].selected = "selected";
}
draw(true);
2023-10-20 11:24:38 +02:00
}
2023-10-20 02:54:39 +02:00
</script>
</body>
</html>