libpov_2025/mibpov.js

1337 lines
39 KiB
JavaScript
Raw Normal View History

2023-10-20 13:31:23 +02:00
"use strict";
2023-10-21 14:19:42 +02:00
// Default values for the min. and max. Y of biomes
2023-10-20 14:39:49 +02:00
const MIN_Y_DEFAULT = -31000
const MAX_Y_DEFAULT = 31000
2023-10-21 14:19:42 +02:00
// Draw a grid line every GRID_STEP units
const GRID_STEP = 10
2023-10-21 19:26:24 +02:00
// Distance from the point's center in which a point can
// be selected by clicking on it
const POINT_SELECT_DISTANCE = 25
2023-10-23 18:09:23 +02:00
// Distance a point has to be dragged after a single click
// before drag-n-drop starts.
// Normally, the user can drag-n-drop a point
// when clickong and dragging it *after* it has already been
// selected; there's no distance requirement.
// But it is also possible to select the point (by clicking
// on it) and dragging it with the same mouse click. But for
// this method, the mouse had to move the distance below
// from the point coordinates (in pixels) before drag-n-drop
// activates.
const ONE_CLICK_DRAG_DROP_DISTANCE = 30
2023-10-23 08:48:00 +02:00
// Name to display if empty
2023-10-23 03:10:59 +02:00
const FALLBACK_NAME = "(no name)"
// Symbol for storing the biome ID in site objects
// for the Voronoi script
const biomeIDSymbol = Symbol("Biome ID");
2023-10-23 08:48:00 +02:00
// Colors (mostly self-explanatory)
const POINT_COLOR = "#913636";
const POINT_COLOR_SELECTED = "#e19696";
const EDGE_COLOR = "#0f2c2e";
const GRID_COLOR = "#00000040";
const AXIS_COLOR = "#000000";
// Color to be used when the diagram is cleared
const CLEAR_COLOR = "#ecddba";
// list of possible cell colors
const CELL_COLORS = [
2023-10-20 13:31:23 +02:00
"#64988e",
"#3d7085",
"#345644",
"#6b7f5c",
2023-10-21 12:08:24 +02:00
"#868750",
"#a7822c",
"#a06e38",
2023-10-20 13:31:23 +02:00
"#ad5f52",
"#692f11",
"#89542f",
"#796e63",
"#a17d5e",
2023-10-21 12:08:24 +02:00
"#5a3f20",
"#836299",
2023-10-20 13:31:23 +02:00
];
2023-10-23 08:48:00 +02:00
const CELL_COLOR_NEUTRAL = "#888888";
/* Status variables for the diagram calculations */
2023-10-20 13:31:23 +02:00
2023-10-21 17:12:43 +02:00
// Min. and max. mathematically possible values for heat and humidity
let limit_heat_min, limit_heat_max;
let limit_humidity_min, limit_humidity_max;
2023-10-21 17:12:43 +02:00
// Draw area. Slightly larger than the value area to avoid
// ugly edge rendering problems
const DRAW_OFFSET = 10
let draw_heat_min, draw_heat_max;
let draw_humidity_min, draw_humidity_max;
2023-10-21 17:12:43 +02:00
// The point that is considered the middle of heat/humidity;
// mathematically this value is the most probable.
let midpoint_heat;
let midpoint_humidity;
2023-10-21 17:12:43 +02:00
// Biome noise settings
const NOISE_OFFSET_DEFAULT = 50;
const NOISE_SCALE_DEFAULT = 50;
const NOISE_PERSISTENCE_DEFAULT = 0.5;
const NOISE_OCTAVES_DEFAULT = 3;
const NOISE_ABSVALUE_DEFAULT = false;
2023-10-23 08:48:00 +02:00
// Current noise values
let noises = {
heat: {
offset: NOISE_OFFSET_DEFAULT,
scale: NOISE_SCALE_DEFAULT,
octaves: NOISE_OCTAVES_DEFAULT,
persistence: NOISE_PERSISTENCE_DEFAULT,
absvalue: NOISE_ABSVALUE_DEFAULT,
},
humidity: {
offset: NOISE_OFFSET_DEFAULT,
scale: NOISE_SCALE_DEFAULT,
octaves: NOISE_OCTAVES_DEFAULT,
persistence: NOISE_PERSISTENCE_DEFAULT,
absvalue: NOISE_ABSVALUE_DEFAULT,
},
};
function updateAreaVarsFor(noiseType) {
let noise = noises[noiseType];
let is_absolute = noise.absvalue === true
2023-10-21 17:12:43 +02:00
// Calculate min. and max. possible values
// Octaves
let [o_min, o_max] = [0, 0]
for (let o=1; o<=noise.octaves; o++) {
2023-10-21 17:12:43 +02:00
let exp = o-1
// Calculate the two possible extreme values
// with the octave value being either at 1 or -1.
let limit1 = (1 * noise.persistence ** exp)
2023-10-21 17:12:43 +02:00
let limit2
if (!is_absolute) {
limit2 = (-1 * noise.persistence ** exp)
2023-10-21 17:12:43 +02:00
} else {
// If absvalue is set, one of the
// limits is always 0 because we
// can't get lower.
limit2 = 0
}
// To add to the maximum, pick the higher value
if (limit1 > limit2) {
o_max = o_max + limit1
} else {
o_max = o_max + limit2
}
// To add to the minimum, pick the LOWER value
if (limit1 > limit2) {
o_min = o_min + limit2
} else {
o_min = o_min + limit1
}
}
// Add offset and scale to min/max value (final step)
let min_value = noise.offset + noise.scale * o_min
let max_value = noise.offset + noise.scale * o_max
2023-10-21 17:12:43 +02:00
// Bring the 2 values in the correct order
// (min_value might be bigger for negative scale)
if (min_value > max_value) {
[min_value, max_value] = [max_value, min_value]
}
// Update globals
let limit_min = min_value;
let limit_max = max_value;
let draw_min = limit_min - DRAW_OFFSET
let draw_max = limit_max + DRAW_OFFSET
let midpoint = min_value + (max_value - min_value) / 2
if (noiseType === "heat") {
limit_heat_min = limit_min;
limit_heat_max = limit_max;
draw_heat_min = draw_min;
draw_heat_max = draw_max;
midpoint_heat = midpoint;
} else if (noiseType === "humidity") {
limit_humidity_min = limit_min;
limit_humidity_max = limit_max;
draw_humidity_min = draw_min;
draw_humidity_max = draw_max;
midpoint_humidity = midpoint;
} else {
console.log("ERROR! updateAreaVars() called with wrong noise_type!")
}
}
function updateAreaVars() {
updateAreaVarsFor("heat");
updateAreaVarsFor("humidity");
2023-10-21 17:12:43 +02:00
// Update element
rangeDisplay.innerHTML = "heat range: <span class='statHeat'>["+(+limit_heat_min)+", "+(+limit_heat_max)+"]</span>; " +
"humidity range: <span class='statHumidity'>["+(+limit_humidity_min)+", "+(+limit_humidity_max)+"]</span>";
2023-10-21 17:12:43 +02:00
}
updateAreaVars();
2023-10-21 13:59:19 +02:00
// If true, point names are shown in diagram
let showNames = true;
// If true, points are shown in diagram
let showPoints = true;
// If true, cells are colorized in diagram
let showCellColors = true;
// If true, show the grid in the diagram
let showGrid = true;
2023-10-23 02:47:00 +02:00
// If true, show the heat/humidity axes
let showAxes = false;
2023-10-21 13:59:19 +02:00
2023-10-21 17:12:43 +02:00
// Set to true if the draw canvas currently shows an error message
let drawError = false;
2023-10-23 08:48:00 +02:00
// The last ID assigned to a biome (0 = none assigned yet).
2023-10-21 13:59:19 +02:00
let lastBiomeID = 0;
let biomePoints = [];
2023-10-23 08:48:00 +02:00
// Add a biome to the biome list
2023-10-21 13:59:19 +02:00
function addBiome(biomeDef) {
biomeDef.id = lastBiomeID;
biomePoints.push(biomeDef);
2023-10-23 08:48:00 +02:00
// The biome ID is just a simple ascending number
2023-10-21 13:59:19 +02:00
lastBiomeID++;
}
// Add a default biome at the midpoint
2023-10-23 03:10:59 +02:00
addBiome({name: "default", heat:midpoint_heat, humidity:midpoint_humidity, min_y: MIN_Y_DEFAULT, max_y: MAX_Y_DEFAULT})
2023-10-21 13:59:19 +02:00
2023-10-23 08:48:00 +02:00
// Get the Y vale of the widget to set the Y altitude
2023-10-21 13:59:19 +02:00
function getViewY() {
if (!inputViewY) {
return 0;
}
return inputViewY.value;
}
2023-10-23 08:48:00 +02:00
// Returns the biome point by its given ID
2023-10-20 16:31:11 +02:00
// or null if it couldn't be found
function getBiomeByID(id) {
for(let b=0; b<biomePoints.length; b++) {
let biome = biomePoints[b];
if (biome.id === id) {
return biome;
}
2023-10-20 13:31:23 +02:00
}
2023-10-20 16:31:11 +02:00
return null;
}
2023-10-23 03:10:59 +02:00
// Returns a new biome name for displaying
function generateBiomeName(id) {
return +id;
2023-10-20 13:31:23 +02:00
}
2023-10-23 08:48:00 +02:00
// Converts a biome point to a point object to be passed to the Voronoi API
2023-10-20 13:31:23 +02:00
function biomePointToVoronoiPoint(point) {
// Apart from x and y, we also add the biome ID to the Voronoi point
// so we can re-identify to which biome it belongs to when the
// object is returned from the Voronoi script output.
let newPoint = { x: point.heat, y: point.humidity, [biomeIDSymbol]: point.id }
2023-10-20 13:31:23 +02:00
return newPoint;
}
2023-10-23 08:48:00 +02:00
// Converts a point object for the Voronoi API to a biome point
2023-10-20 13:31:23 +02:00
function voronoiPointToBiomePoint(point) {
let newPoint = { heat: point.x, humidity: point.y, id: point[biomeIDSymbol] }
2023-10-20 13:31:23 +02:00
return newPoint;
}
2023-10-23 08:48:00 +02:00
/***** Draw functions *****/
/* Note: All of these need a draw context. */
/* Put the name of the given point on the draw context */
2023-10-21 11:35:22 +02:00
function putPointName(context, point) {
let he = point.heat
let hu = point.humidity
if (he < limit_heat_min || he > limit_heat_max || hu < limit_humidity_min || hu > limit_humidity_max) {
2023-10-21 11:35:22 +02:00
return;
}
let [x, y] = biomeCoordsToCanvasPixelCoords(he, hu);
let w = voronoiCanvas.width;
let h = voronoiCanvas.height;
if (x > w/2) {
2023-10-21 11:35:22 +02:00
context.textAlign = "right";
x = x-5;
2023-10-21 11:35:22 +02:00
} else {
context.textAlign = "left";
x = x+5;
2023-10-21 11:35:22 +02:00
}
if (y < h/2) {
2023-10-21 12:08:24 +02:00
context.textBaseline = "top";
} else {
context.textBaseline = "alphabetic";
}
context.font = "120% sans-serif"
2023-10-23 03:10:59 +02:00
let displayName = point.name;
if (displayName === "") {
displayName = FALLBACK_NAME;
}
context.fillText(displayName, x, y);
2023-10-21 11:35:22 +02:00
}
2023-10-23 08:48:00 +02:00
/* Put the given point on the draw context */
2023-10-20 13:31:23 +02:00
function putPoint(context, point) {
const ARROW_SIZE_SIDE = 7;
const ARROW_SIZE_CORNER = 9;
let he = point.heat
let hu = point.humidity
let [x, y] = biomeCoordsToCanvasPixelCoords(he, hu);
let w = voronoiCanvas.width;
let h = voronoiCanvas.height;
let [limit_x_min, limit_y_min] = biomeCoordsToCanvasPixelCoords(limit_heat_min, limit_humidity_min);
let [limit_x_max, limit_y_max] = biomeCoordsToCanvasPixelCoords(limit_heat_max, limit_humidity_max);
2023-10-20 15:23:44 +02:00
// Point is out of bounds: Draw an arrow at the border
if (he < limit_heat_min || he > limit_heat_max || hu < limit_humidity_min || hu > limit_humidity_max) {
2023-10-20 15:23:44 +02:00
context.beginPath();
// bottom left corner
if (he < limit_heat_min && hu < limit_humidity_min) {
context.moveTo(limit_x_min, limit_y_min);
context.lineTo(limit_x_min + ARROW_SIZE_CORNER, limit_y_min);
context.lineTo(limit_x_min, limit_y_min - ARROW_SIZE_CORNER);
2023-10-20 15:23:44 +02:00
context.closePath();
context.fill();
// bottom right corner
} else if (he > limit_heat_max && hu < limit_humidity_min) {
context.moveTo(limit_x_max, limit_y_min);
context.lineTo(limit_x_max - ARROW_SIZE_CORNER, limit_y_min);
context.lineTo(limit_x_max, limit_y_min - ARROW_SIZE_CORNER);
2023-10-20 15:23:44 +02:00
context.closePath();
context.fill();
// top left corner
} else if (he < limit_heat_min && hu > limit_humidity_max) {
context.moveTo(limit_x_min, limit_y_max);
context.lineTo(limit_x_min + ARROW_SIZE_CORNER, limit_y_max);
context.lineTo(limit_x_min, limit_y_max + ARROW_SIZE_CORNER);
2023-10-20 15:23:44 +02:00
context.closePath();
context.fill();
// top right corner
} else if (he > limit_heat_max && hu > limit_humidity_max) {
context.moveTo(limit_x_max, limit_y_max);
context.lineTo(limit_x_max - ARROW_SIZE_CORNER, limit_y_max);
context.lineTo(limit_x_max, limit_y_max + ARROW_SIZE_CORNER);
2023-10-20 15:23:44 +02:00
context.closePath();
context.fill();
// left side
} else if (he < limit_heat_min) {
context.moveTo(limit_x_min, y);
context.lineTo(limit_x_min + ARROW_SIZE_SIDE, y + ARROW_SIZE_SIDE);
context.lineTo(limit_x_min + ARROW_SIZE_SIDE, y - ARROW_SIZE_SIDE);
2023-10-20 15:23:44 +02:00
context.closePath();
context.fill();
// right side
} else if (he > limit_heat_max) {
context.moveTo(limit_x_max, y);
context.lineTo(limit_x_max - ARROW_SIZE_SIDE, y + ARROW_SIZE_SIDE);
context.lineTo(limit_x_max - ARROW_SIZE_SIDE, y - ARROW_SIZE_SIDE);
2023-10-20 15:23:44 +02:00
context.closePath();
context.fill();
// bottom side
} else if (hu < limit_humidity_min) {
context.moveTo(x, limit_y_min);
context.lineTo(x - ARROW_SIZE_SIDE, limit_y_min - ARROW_SIZE_SIDE);
context.lineTo(x + ARROW_SIZE_SIDE, limit_y_min - ARROW_SIZE_SIDE);
2023-10-20 15:23:44 +02:00
context.closePath();
context.fill();
// top side
} else if (hu > limit_humidity_max) {
context.moveTo(x, limit_y_max);
context.lineTo(x - ARROW_SIZE_SIDE, limit_y_max + ARROW_SIZE_SIDE);
context.lineTo(x + ARROW_SIZE_SIDE, limit_y_max + ARROW_SIZE_SIDE);
2023-10-20 15:23:44 +02:00
context.closePath();
context.fill();
}
// Point is in bounds: Draw a dot
} else {
context.beginPath();
context.moveTo(0, 0);
context.arc(x, y, 5, 0, Math.PI * 2);
2023-10-20 15:23:44 +02:00
context.closePath();
context.fill();
}
2023-10-20 13:31:23 +02:00
};
2023-10-23 08:48:00 +02:00
/* Put the grid on the draw context */
2023-10-20 13:31:23 +02:00
function putGrid(context) {
let [limit_x_min, limit_y_min] = biomeCoordsToCanvasPixelCoords(limit_heat_min, limit_humidity_min);
let [limit_x_max, limit_y_max] = biomeCoordsToCanvasPixelCoords(limit_heat_max, limit_humidity_max);
context.lineWidth = 2;
2023-10-23 08:48:00 +02:00
context.strokeStyle = GRID_COLOR;
for (let he=0; he<=limit_heat_max; he+=GRID_STEP) {
let [x, _] = biomeCoordsToCanvasPixelCoords(he, 0);
2023-10-20 13:31:23 +02:00
context.beginPath();
context.moveTo(x, limit_y_min);
context.lineTo(x, limit_y_max);
2023-10-20 13:31:23 +02:00
context.stroke();
}
for (let he=-GRID_STEP; he>=limit_heat_min; he-=GRID_STEP) {
let [x, _] = biomeCoordsToCanvasPixelCoords(he, 0);
2023-10-20 13:31:23 +02:00
context.beginPath();
context.moveTo(x, limit_y_min);
context.lineTo(x, limit_y_max);
2023-10-20 13:31:23 +02:00
context.stroke();
}
for (let hu=0; hu<=limit_humidity_max; hu+=GRID_STEP) {
let [_, y] = biomeCoordsToCanvasPixelCoords(0, hu);
2023-10-20 13:31:23 +02:00
context.beginPath();
context.moveTo(limit_x_min, y);
context.lineTo(limit_x_max, y);
2023-10-20 13:31:23 +02:00
context.stroke();
}
for (let hu=-GRID_STEP; hu>=limit_humidity_min; hu-=GRID_STEP) {
let [_, y] = biomeCoordsToCanvasPixelCoords(0, hu);
2023-10-20 13:31:23 +02:00
context.beginPath();
context.moveTo(limit_x_min, y);
context.lineTo(limit_x_max, y);
2023-10-20 13:31:23 +02:00
context.stroke();
}
}
2023-10-23 08:48:00 +02:00
/* Put the labelled heat/humidity axes on the draw context */
2023-10-23 02:47:00 +02:00
function putAxes(context) {
const AXIS_ARROW_SIZE = 8;
context.lineWidth = 2;
2023-10-23 08:48:00 +02:00
context.strokeStyle = AXIS_COLOR;
2023-10-23 02:47:00 +02:00
let [x0, y0] = biomeCoordsToCanvasPixelCoords(0, 0);
let tick_heat = (limit_heat_max - limit_heat_min) * (100/175);
let tick_humidity = (limit_humidity_max - limit_humidity_min) * (100/175);
let [tx, ty] = biomeCoordsToCanvasPixelCoords(tick_heat, tick_humidity);
let w = voronoiCanvas.width;
let h = voronoiCanvas.height;
// horizontal axis
context.beginPath();
context.moveTo(0, y0);
context.lineTo(w, y0);
// midpoint tick
context.moveTo(tx, y0);
context.lineTo(tx, y0 + AXIS_ARROW_SIZE);
// arrow
context.moveTo(w, y0);
context.lineTo(w - AXIS_ARROW_SIZE, y0 - AXIS_ARROW_SIZE);
context.moveTo(w, y0);
context.lineTo(w - AXIS_ARROW_SIZE, y0 + AXIS_ARROW_SIZE);
context.stroke();
context.closePath();
// vertical axis
context.beginPath();
context.moveTo(x0, 0);
context.lineTo(x0, h);
// midpoint tick
context.moveTo(x0, ty);
context.lineTo(x0 - AXIS_ARROW_SIZE, ty);
// arrow
context.moveTo(x0, 0);
context.lineTo(x0 - AXIS_ARROW_SIZE, AXIS_ARROW_SIZE);
context.moveTo(x0, 0);
context.lineTo(x0 + AXIS_ARROW_SIZE, AXIS_ARROW_SIZE);
context.stroke();
context.closePath();
// axis+midpoint labels
context.fillStyle = "black";
// heat label
context.textAlign = "right";
context.textBaseline = "top";
context.font = "100% sans-serif";
context.fillText("heat", w - AXIS_ARROW_SIZE*2, y0 + 4);
context.textAlign = "center";
context.fillText(Math.round(tick_heat), tx, y0 + AXIS_ARROW_SIZE+2);
// humidity label
context.textAlign = "right";
context.textBaseline = "bottom";
context.font = "100% sans-serif";
context.save();
context.rotate(-Math.PI/2);
context.fillText("humidity", -AXIS_ARROW_SIZE*2, x0 - 4);
context.restore();
context.textAlign = "right";
context.textBaseline = "middle";
context.fillText(Math.round(tick_humidity), x0 - AXIS_ARROW_SIZE-2, ty);
}
2023-10-20 13:31:23 +02:00
// Cache diagram object for performance boost
let cachedVoronoiDiagram = null;
2023-10-23 08:48:00 +02:00
/* Given the list of biome points, returns a Voronoi diagram object
(which may have been cached). If recalculate is true, a recalculation is forced. */
2023-10-20 17:22:48 +02:00
function getVoronoiDiagram(points, recalculate) {
// Calculate bounding box, defaults to heat/humidity limits ...
let vbbox = {xl: limit_heat_min, xr: limit_heat_max, yb: limit_humidity_max, yt: limit_humidity_min};
const BUFFER_ZONE = 1;
// ... unless a point is out of bounds,
// then we increase the bounding box size
for (let p of points) {
if (p.heat < vbbox.xl) {
vbbox.xl = p.heat - BUFFER_ZONE;
} else if (p.heat > vbbox.xr) {
vbbox.xr = p.heat + BUFFER_ZONE;
}
if (p.humidity < vbbox.yt) {
vbbox.yt = p.humidity - BUFFER_ZONE;
} else if (p.humidity > vbbox.yb) {
vbbox.yb = p.humidity + BUFFER_ZONE;
}
}
2023-10-20 13:31:23 +02:00
if ((cachedVoronoiDiagram === null) || recalculate) {
let sites = []
2023-10-20 17:22:48 +02:00
for (let p of points) {
2023-10-20 13:31:23 +02:00
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-23 08:48:00 +02:00
/* Returns the context object required to draw on the canvas */
2023-10-20 13:31:23 +02:00
function getDrawContext() {
let canvas = document.getElementById("voronoiCanvas");
if (canvas.getContext) {
return canvas.getContext("2d");
} else {
return null;
}
2023-10-20 13:31:23 +02:00
}
2023-10-21 17:12:43 +02:00
// Clear draw area
function clear(context) {
if (!context) {
context = getDrawContext();
if (!context) {
2023-10-23 18:09:23 +02:00
return false;
}
2023-10-21 17:12:43 +02:00
}
2023-10-23 08:48:00 +02:00
context.fillStyle = CLEAR_COLOR;
context.fillRect(-DRAW_OFFSET, -DRAW_OFFSET, voronoiCanvas.width+DRAW_OFFSET, voronoiCanvas.height+DRAW_OFFSET);
return true;
2023-10-21 17:12:43 +02:00
}
2023-10-23 08:48:00 +02:00
/* Returns all biome points except those whose Y limits fall out of the
given y value */
function getRenderedPoints(y) {
2023-10-20 17:22:48 +02:00
let points = [];
for (let p=0; p<biomePoints.length; p++) {
let point = biomePoints[p];
if (y >= point.min_y && y <= point.max_y) {
points.push(point);
}
}
return points;
}
2023-10-21 17:12:43 +02:00
2023-10-23 08:48:00 +02:00
/* Given a biome ID, returns the matching HTML element from the
biome list widget or null if none */
2023-10-21 19:26:24 +02:00
function getBiomeIDFromHTMLElement(elem) {
let strID = elem.id;
if (strID && strID.startsWith("biome_list_element_")) {
let slice = strID.slice(19);
if (slice) {
return +slice;
}
}
return null;
}
2023-10-23 08:48:00 +02:00
/* Returns both the ID of selected biome and the associated HTML element from
the biome list widget, or null if nothing is selected.
Result is returned in the form [id, htmlElement] or [null, null] */
2023-10-21 19:26:24 +02:00
function getSelectedBiomeIDAndElement() {
if (biomeSelector.selectedIndex === -1) {
return [null, null];
}
let elem = biomeSelector.options[biomeSelector.selectedIndex];
let biomeID = getBiomeIDFromHTMLElement(elem);
if (biomeID !== null) {
return [biomeID, elem];
}
return [null, null];
}
2023-10-23 08:48:00 +02:00
/* Draws the diagram on the voronoiCanvas.
Will (re-)calculate the Voronoi diagram if recalculate is true;
otherwise it may re-use a previous diagram for performance reasons. */
function draw(recalculate) {
let context = getDrawContext();
let w = voronoiCanvas.width;
let h = voronoiCanvas.height;
2023-10-23 08:48:00 +02:00
let y = getViewY();
// shorter function name (for "convert")
let conv = biomeCoordsToCanvasPixelCoords
if (!context) {
if (!voronoiCanvas.hidden) {
voronoiCanvas.hidden = true;
2023-10-21 21:35:20 +02:00
coordinateDisplay.hidden = true;
altitudeDisplay.hidden = true;
rangeDisplay.hidden = true;
configDiv.hidden = true;
errorMessage.innerText = "ERROR: Could not get the canvas context which means this tool won't work for you. Maybe your browser does not support the HTML canvas element properly.";
}
return false;
}
2023-10-21 17:12:43 +02:00
clear(context);
// Render a special message if the value range is tiny
if ((limit_heat_max - limit_heat_min < 0.01) || (limit_humidity_max - limit_humidity_min < 0.01)) {
2023-10-21 17:12:43 +02:00
context.textAlign = "center";
context.fillStyle = "black";
context.textBaseline = "middle";
context.font = "200% sans-serif";
let msg = "Value range is too small.";
context.fillText(msg, w/2, h/2);
2023-10-21 17:12:43 +02:00
drawError = true;
2023-10-21 21:35:20 +02:00
updateAltitudeText();
return true;
2023-10-21 17:12:43 +02:00
}
let points = getRenderedPoints(y);
2023-10-21 17:12:43 +02:00
// Render a special message if there are no biomes
if (points.length === 0) {
context.textAlign = "center";
context.fillStyle = "black";
context.textBaseline = "middle";
context.font = "200% sans-serif";
let msg;
if (biomePoints.length === 0) {
msg = "No biomes.";
} else {
msg = "No biomes in this Y altitude.";
2023-10-21 17:12:43 +02:00
}
context.fillText(msg, w/2, h/2);
2023-10-21 17:12:43 +02:00
drawError = true;
2023-10-21 21:35:20 +02:00
updateAltitudeText();
return true;
2023-10-21 17:12:43 +02:00
}
drawError = false;
2023-10-21 21:35:20 +02:00
updateAltitudeText();
2023-10-21 17:12:43 +02:00
2023-10-20 17:22:48 +02:00
let diagram = getVoronoiDiagram(points, recalculate);
2023-10-20 16:31:11 +02:00
let createHalfedgesPath = function(context, cell) {
context.beginPath();
for (let h=0; h<cell.halfedges.length; h++) {
let halfedge = cell.halfedges[h]
let start = halfedge.getStartpoint()
let end = halfedge.getEndpoint()
let [cx1, cy1] = conv(start.x, start.y);
let [cx2, cy2] = conv(end.x, end.y);
if (h === 0) {
context.moveTo(cx1, cy1);
} else {
context.lineTo(cx1, cy1);
}
context.lineTo(cx2, cy2);
}
context.closePath();
}
2023-10-20 16:31:11 +02:00
// Render cell colors
2023-10-21 13:59:19 +02:00
if (showCellColors) {
2023-10-23 08:48:00 +02:00
let colors = CELL_COLORS;
/* Before we paint the cells,
we paint the cell (half)edges in their
cell color. Painting the cells alone
leaves a tiny little space between the
cells unpainted, so there is an ugly boundary
between space. Painting the edges before
painting the cells should ensure these ugly
boundaries are gone */
for (let c=0; c<diagram.cells.length; c++) {
let cell = diagram.cells[c];
// We use the biomeID to select a color
let biomeID = cell.site[biomeIDSymbol];
// This works because the biome ID is a number
2023-10-24 14:39:34 +02:00
let biome = getBiomeByID(biomeID);
if (biome.color) {
context.strokeStyle = biome.color;
} else {
let ccol = biomeID % CELL_COLORS.length;
context.strokeStyle = CELL_COLORS[ccol];
}
/* The line can be quite thick because
most of it will be painted over by the
actual cell themselves anyway */
context.lineWidth = 6;
createHalfedgesPath(context, cell);
context.stroke();
}
// Paint the cells
2023-10-21 13:59:19 +02:00
for (let c=0; c<diagram.cells.length; c++) {
let cell = diagram.cells[c];
// We use the biomeID to select a color
let biomeID = cell.site[biomeIDSymbol];
// This works because the biome ID is a number
2023-10-24 14:39:34 +02:00
let biome = getBiomeByID(biomeID);
if (biome.color) {
context.fillStyle = biome.color;
} else {
let ccol = biomeID % CELL_COLORS.length;
context.fillStyle = CELL_COLORS[ccol];
}
2023-10-21 13:59:19 +02:00
createHalfedgesPath(context, cell);
2023-10-21 13:59:19 +02:00
context.fill();
2023-10-20 13:31:23 +02:00
}
2023-10-21 13:59:19 +02:00
// If there's only 1 cell, we have to manually colorize it because
// the Voronoi script doesn't return that area in this special case.
if (points.length === 1 && diagram.cells.length === 1) {
// 1 cell means the whole area is filled
context.fillStyle = colors[points[0].id % colors.length];
context.fillRect(-DRAW_OFFSET, -DRAW_OFFSET, w+DRAW_OFFSET, h+DRAW_OFFSET);
2023-10-21 13:59:19 +02:00
}
} else {
// Use a "neutral" background color for the whole area if cell colors are disabled
2023-10-23 08:48:00 +02:00
context.fillStyle = CELL_COLOR_NEUTRAL;
context.fillRect(-DRAW_OFFSET, -DRAW_OFFSET, w+DRAW_OFFSET, h+DRAW_OFFSET);
2023-10-20 13:31:23 +02:00
}
2023-10-20 17:24:06 +02:00
if (points.length > 0) {
2023-10-21 14:19:42 +02:00
if (showGrid) {
2023-10-21 13:59:19 +02:00
putGrid(context);
}
2023-10-23 02:47:00 +02:00
if (showAxes) {
putAxes(context);
}
2023-10-20 17:24:06 +02:00
}
2023-10-20 13:31:23 +02:00
2023-10-20 16:31:11 +02:00
// Render Voronoi cell edges
context.lineWidth = 2.5;
2023-10-20 13:31:23 +02:00
for (let e=0; e<diagram.edges.length; e++) {
let edge = diagram.edges[e];
2023-10-20 15:32:14 +02:00
if (edge.rSite === null) {
context.strokeStyle = "transparent";
} else {
2023-10-23 08:48:00 +02:00
context.strokeStyle = EDGE_COLOR;
2023-10-20 15:32:14 +02:00
}
let [eax, eay] = conv(edge.va.x, edge.va.y);
let [ebx, eby] = conv(edge.vb.x, edge.vb.y);
2023-10-20 13:31:23 +02:00
context.beginPath();
context.moveTo(eax, eay);
context.lineTo(ebx, eby);
2023-10-20 13:31:23 +02:00
context.closePath();
context.stroke();
}
2023-10-21 19:26:24 +02:00
let [selElemID, _] = getSelectedBiomeIDAndElement();
2023-10-20 20:11:00 +02:00
2023-10-20 16:31:11 +02:00
// Render biome points
2023-10-21 13:59:19 +02:00
if (showPoints) {
for (let point of points) {
let pointID = point.id;
// Highlight selected point
if (selElemID !== null && pointID === selElemID) {
2023-10-23 08:48:00 +02:00
context.fillStyle = POINT_COLOR_SELECTED;
2023-10-21 13:59:19 +02:00
} else {
2023-10-23 08:48:00 +02:00
context.fillStyle = POINT_COLOR;
2023-10-21 13:59:19 +02:00
}
putPoint(context, point);
2023-10-20 13:31:23 +02:00
}
2023-10-21 13:59:19 +02:00
}
if (showNames) {
// Render biome point names
for (let point of points) {
let pointID = point.id;
// Highlight selected point
if (selElemID !== null && pointID === selElemID) {
context.fillStyle = "#FF8888FF";
} else {
context.fillStyle = "#FFFFFFAA";
}
putPointName(context, point);
2023-10-21 12:08:24 +02:00
}
2023-10-20 13:31:23 +02:00
}
return true;
2023-10-20 13:31:23 +02:00
}
2023-10-23 08:48:00 +02:00
/* Clears the biome list widget and (re-adds) the list elements
for the biomes from scratch. */
function repopulateBiomeSelector() {
2023-10-20 13:31:23 +02:00
biomeSelector.innerHTML = "";
for (let b=0; b<biomePoints.length; b++) {
let num = b+1;
let newElem = document.createElement("option");
newElem.value = num;
2023-10-20 20:11:00 +02:00
newElem.id = "biome_list_element_" + biomePoints[b].id;
2023-10-23 03:10:59 +02:00
let displayName = biomePoints[b].name;
if (displayName === "") {
displayName = FALLBACK_NAME;
}
let newElemText = document.createTextNode(displayName);
2023-10-20 13:31:23 +02:00
newElem.append(newElemText);
biomeSelector.append(newElem);
}
}
2023-10-23 08:48:00 +02:00
/* Update the status (like disabled or value) of all widgets that
affect the diagram, based on the internal data */
function updateWidgetStates() {
if (biomePoints.length === 0 || biomeSelector.selectedIndex === -1) {
removeBiomeButton.disabled = "disabled";
inputHeat.disabled = "disabled";
inputHumidity.disabled = "disabled";
2023-10-20 16:12:20 +02:00
inputMinY.disabled = "disabled";
inputMaxY.disabled = "disabled";
2023-10-23 03:10:59 +02:00
inputBiomeName.disabled = "disabled";
2023-10-24 14:39:34 +02:00
inputBiomeColor.disabled = "disabled";
} else {
removeBiomeButton.disabled = "";
inputHeat.disabled = "";
inputHumidity.disabled = "";
2023-10-20 16:12:20 +02:00
inputMinY.disabled = "";
inputMaxY.disabled = "";
2023-10-23 03:10:59 +02:00
inputBiomeName.disabled = "";
2023-10-24 14:39:34 +02:00
inputBiomeColor.disabled = "";
if (biomeSelector.selectedIndex !== -1) {
let selected = biomeSelector.options[biomeSelector.selectedIndex];
let point = biomePoints[biomeSelector.selectedIndex];
inputHeat.value = point.heat;
inputHumidity.value = point.humidity;
inputMinY.value = point.min_y;
inputMaxY.value = point.max_y;
2023-10-23 03:10:59 +02:00
inputBiomeName.value = point.name;
2023-10-24 14:39:34 +02:00
inputBiomeColor.value = point.color;
}
}
inputNoiseHeatOffset.value = noises.heat.offset;
inputNoiseHeatScale.value = noises.heat.scale;
inputNoiseHeatOctaves.value = noises.heat.octaves;
inputNoiseHeatPersistence.value = noises.heat.persistence;
inputNoiseHumidityOffset.value = noises.humidity.offset;
inputNoiseHumidityScale.value = noises.humidity.scale;
inputNoiseHumidityOctaves.value = noises.humidity.octaves;
inputNoiseHumidityPersistence.value = noises.humidity.persistence;
}
2023-10-23 08:48:00 +02:00
/* To be called when a biome value like heat was changed.
Will update internal data, biome list and the diagram.
* pointField: Name of the field it affects (same as in biomePoints)
* value: The value to set */
function onChangeBiomeValueWidget(pointField, value) {
if (value === null) {
return;
}
if (biomeSelector.selectedIndex === -1) {
return;
}
let selected = biomeSelector.options[biomeSelector.selectedIndex];
if (selected === null) {
return;
}
let point = biomePoints[biomeSelector.selectedIndex];
2023-10-21 17:12:43 +02:00
point[pointField] = value;
2023-10-23 03:10:59 +02:00
let displayName = point.name;
if (displayName === "") {
displayName = FALLBACK_NAME;
}
selected.innerText = displayName;
2023-10-23 08:48:00 +02:00
draw(true);
2023-10-20 13:31:23 +02:00
}
2023-10-20 16:31:11 +02:00
2023-10-23 08:48:00 +02:00
// Select the given point.
2023-10-24 14:39:34 +02:00
// point is a point from biomePoints.
// returns [selected, alreadySelected]
// where seleted is true if the point has been selected
// and alreadySelected is true if the point was selected before
function selectPoint(point) {
for (let elem of biomeSelector.options) {
let strID = elem.id;
let elemID = null;
2023-10-21 19:26:24 +02:00
let biomeID = getBiomeIDFromHTMLElement(elem);
2023-10-24 14:39:34 +02:00
if ((biomeID !== null) && (point.id === biomeID)) {
if (elem.selected === true) {
return [true, true];
}
2023-10-24 14:39:34 +02:00
elem.selected = "selected";
draw(true);
updateWidgetStates();
return [true, false];
}
}
return [false, false];
}
2023-10-23 08:48:00 +02:00
// Returns the distance between the two 2D points (x1, y1) and (x2, y2)
function getDistance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1)**2 + (y2 - y1)**2);
}
2023-10-23 08:48:00 +02:00
// Converts (x, y) canvas pixel coordinates to biome coordinates;
// returns [heat, humidity]
2023-10-21 01:55:49 +02:00
function canvasPixelCoordsToBiomeCoords(x, y) {
let w = (voronoiCanvas.width/(limit_heat_max-limit_heat_min));
let h = (voronoiCanvas.height/(limit_humidity_max-limit_humidity_min));
let heat = Math.round((x + limit_heat_min * w) / w);
2023-10-22 12:53:47 +02:00
// This also flips the Y axis
let humidity = limit_humidity_min + (limit_humidity_max - (Math.round((y + limit_humidity_min * h) / h)));
2023-10-21 01:55:49 +02:00
return [heat, humidity];
}
2023-10-23 08:48:00 +02:00
// Converts heat and humidity coordinates to canvas pixel coordinates;
// returns [x, y]
2023-10-21 01:55:49 +02:00
function biomeCoordsToCanvasPixelCoords(heat, humidity) {
let w = (voronoiCanvas.width/(limit_heat_max-limit_heat_min));
let h = (voronoiCanvas.height/(limit_humidity_max-limit_humidity_min));
let pixelX = heat * w - limit_heat_min * w;
2023-10-22 12:53:47 +02:00
// This also flips the Y axis
let pixelY = voronoiCanvas.height - (humidity * h - limit_humidity_min * h);
2023-10-21 01:55:49 +02:00
return [pixelX, pixelY];
}
2023-10-23 08:48:00 +02:00
// Given a (x, y) pixel positition on the canvas, returns
// the point that is nearest to it or null if none.
// maxDist is the maximum allowed point distance,
// if it is exceeded, null is returned.
function getNearestPointFromCanvasPos(x, y, maxDist) {
let nearestPoint = null;
let nearestDist = null;
let points = getRenderedPoints(getViewY());
for (let i=0; i<points.length; i++) {
let point = points[i];
2023-10-21 01:55:49 +02:00
let [pixelX, pixelY] = biomeCoordsToCanvasPixelCoords(point.heat, point.humidity);
let dist = getDistance(x, y, pixelX, pixelY);
if (nearestPoint === null) {
nearestPoint = point;
nearestDist = dist;
} else if (dist < nearestDist) {
nearestPoint = point;
nearestDist = dist;
}
}
if (nearestDist < maxDist) {
return nearestPoint;
} else {
return null;
}
}
2023-10-23 08:48:00 +02:00
// Whether the mouse is currently pressed
let mouseIsDown = false;
2023-10-24 14:39:34 +02:00
// Coordinates of where the drag-n-drop started
// or is about to start
let dragDropStartPos = null;
2023-10-23 08:48:00 +02:00
// ID of point being currently dragged by the user
// or null if none.
let dragDropPointID = null;
2023-10-21 01:08:35 +02:00
2023-10-24 14:39:34 +02:00
// Current drag-n-drop state:
// 0 = no drag-n-drop
// 1 = preparing drag-n-drop right after selection
// 2 = drag-n-drop active
let dragDropState = 0;
2023-10-23 18:09:23 +02:00
2023-10-23 08:48:00 +02:00
/* Move and update the biome point while the user
is dragging it */
2023-10-21 01:08:35 +02:00
function updatePointWhenDragged(pointID) {
2023-10-21 17:12:43 +02:00
if (pointID !== null && !drawError) {
let selectedPoint = null;
let points = getRenderedPoints(getViewY());
for (let i=0; i<points.length; i++) {
if (points[i].id === dragDropPointID) {
selectedPoint = points[i];
2023-10-21 01:55:49 +02:00
let [newHeat, newHumidity] = canvasPixelCoordsToBiomeCoords(event.offsetX, event.offsetY);
selectedPoint.heat = newHeat;
selectedPoint.humidity = newHumidity;
2023-10-23 08:48:00 +02:00
draw(true);
updateWidgetStates();
2023-10-21 19:26:24 +02:00
let [elemID, elem] = getSelectedBiomeIDAndElement();
if (elemID !== null && points[i].id === elemID) {
2023-10-23 03:10:59 +02:00
let displayName = selectedPoint.name;
if (displayName === "") {
displayName = FALLBACK_NAME;
}
elem.innerText = displayName;
2023-10-21 19:26:24 +02:00
break;
2023-10-21 01:08:35 +02:00
}
return;
}
}
}
2023-10-21 01:08:35 +02:00
}
2023-10-23 08:48:00 +02:00
/* Updates the text showing the current altitude (Y height)
the diagram currently applies */
function updateAltitudeText() {
altitudeDisplay.innerHTML = "showing diagram for altitude Y=<span class='statAltitude'>"+inputViewY.value+"</span>";
}
/* Update the text that shows the biome coordinates
of the cursor when it's on the diagram */
2023-10-21 17:12:43 +02:00
function updateCoordinateDisplay(pixelX, pixelY) {
// show coordinates
let [heat, humidity] = canvasPixelCoordsToBiomeCoords(pixelX, pixelY);
if (!drawError) {
let html = "cursor coordinates: heat=<span class='statHeat'>"+heat+"</span>; humidity=<span class='statHumidity'>"+humidity+"</span>";
2023-10-21 17:12:43 +02:00
coordinateDisplay.innerHTML = html;
} else {
coordinateDisplay.innerHTML = "&nbsp;";
}
}
2023-10-23 08:48:00 +02:00
/* Updates and changes the cursor type on the diagram
canvas depending on whether we can select, drag or do nothing
at the pointed position */
2023-10-21 19:26:24 +02:00
function updateDragDropCursorStatus() {
if (drawError || !showPoints) {
2023-10-23 08:48:00 +02:00
// a special message is shown; use auto cursor
2023-10-21 19:26:24 +02:00
voronoiCanvas.style.cursor = "auto";
return
}
let nearest = getNearestPointFromCanvasPos(event.offsetX, event.offsetY, POINT_SELECT_DISTANCE);
if (nearest !== null) {
let [id, elem] = getSelectedBiomeIDAndElement();
if (id !== null && nearest.id === id) {
2023-10-23 08:48:00 +02:00
// This cursor indicates we can grab the point
2023-10-21 19:26:24 +02:00
voronoiCanvas.style.cursor = "grab";
} else {
2023-10-23 08:48:00 +02:00
// This cursor indicates we can grab the point
2023-10-21 19:26:24 +02:00
voronoiCanvas.style.cursor = "crosshair";
}
} else {
2023-10-23 08:48:00 +02:00
// Default cursor when a click doesn't to anything
2023-10-21 19:26:24 +02:00
voronoiCanvas.style.cursor = "auto";
}
}
2023-10-23 08:48:00 +02:00
/* Initializes checkbox variables of the view settings */
function checkboxVarsInit() {
showNames = inputCheckboxNames.checked;
showPoints = inputCheckboxPoints.checked;
showCellColors = inputCheckboxCellColors.checked;
showGrid = inputCheckboxGrid.checked;
showAxes = inputCheckboxAxes.checked;
}
/* Collapses/Expands a config section */
function toggleConfigSectionDisplay(headerLink, container) {
if (container.style.display !== "none") {
headerLink.innerText = "▶";
container.style.display = "none";
} else {
headerLink.innerText = "▼";
container.style.display = "block";
}
}
/* Unhide the main content. Used to disable the noscript
handling because the website starts with the main content
container hidden so the noscript version of the page
isn't cluttered. */
function unhideContent() {
mainContentContainer.hidden = false;
/* Also hide the container holding the noscript error
message to avoid spacing issues */
noscriptContainer.hidden = true;
}
/***** EVENTS *****/
/* Canvas events */
2023-10-21 01:08:35 +02:00
voronoiCanvas.onmousemove = function(event) {
2023-10-24 14:39:34 +02:00
// update drag-n-drop state
if (dragDropState !== 2 && dragDropPointID !== null && mouseIsDown && dragDropStartPos !== null) {
let dist = getDistance(dragDropStartPos.x, dragDropStartPos.y, event.offsetX, event.offsetY)
if (dragDropState === 1 && dist >= 1) {
dragDropState = 2;
} else if ((dragDropState === 0 || dragDropState === 1) && dist > ONE_CLICK_DRAG_DROP_DISTANCE) {
dragDropState = 2;
2023-10-23 18:09:23 +02:00
}
}
2023-10-21 01:48:45 +02:00
// drag-n-drop
2023-10-24 14:39:34 +02:00
if (dragDropState === 2) {
2023-10-21 01:08:35 +02:00
updatePointWhenDragged(dragDropPointID);
}
2023-10-21 17:12:43 +02:00
updateCoordinateDisplay(event.offsetX, event.offsetY);
2023-10-21 19:26:24 +02:00
updateDragDropCursorStatus();
2023-10-21 01:55:49 +02:00
}
voronoiCanvas.onmouseenter = function(event) {
2023-10-21 17:12:43 +02:00
updateCoordinateDisplay(event.offsetX, event.offsetY);
2023-10-21 19:26:24 +02:00
updateDragDropCursorStatus();
2023-10-21 01:08:35 +02:00
}
2023-10-21 01:55:49 +02:00
2023-10-21 01:08:35 +02:00
voronoiCanvas.onmousedown = function(event) {
2023-10-21 01:48:45 +02:00
// select point by clicking.
// initiate drag-n-drop if already selected.
2023-10-21 01:08:35 +02:00
mouseIsDown = true;
2023-10-21 13:59:19 +02:00
if (!showPoints) {
// Points need to be shown for drag-n-drop to work
return;
}
2023-10-21 19:26:24 +02:00
let nearest = getNearestPointFromCanvasPos(event.offsetX, event.offsetY, POINT_SELECT_DISTANCE);
2023-10-21 01:08:35 +02:00
if (nearest !== null) {
let success, alreadySelected
[success, alreadySelected] = selectPoint(nearest);
2023-10-23 18:09:23 +02:00
dragDropPointID = nearest.id;
2023-10-24 14:39:34 +02:00
if (success) {
let [x, y] = biomeCoordsToCanvasPixelCoords(nearest.heat, nearest.humidity);
dragDropStartPos = { x: x, y: y };
}
2023-10-21 01:08:35 +02:00
if (alreadySelected) {
2023-10-24 14:39:34 +02:00
dragDropState = 1;
2023-10-21 01:08:35 +02:00
}
2023-10-21 19:26:24 +02:00
updateDragDropCursorStatus();
2023-10-21 01:08:35 +02:00
}
}
voronoiCanvas.onmouseup = function(event) {
2023-10-21 01:48:45 +02:00
// end drag-n-drop
2023-10-24 14:39:34 +02:00
if (dragDropState === 2) {
updatePointWhenDragged(dragDropPointID);
}
2023-10-21 01:08:35 +02:00
mouseIsDown = false;
2023-10-24 14:39:34 +02:00
dragDropStartPos = null;
dragDropPointID = null;
2023-10-24 14:39:34 +02:00
dragDropState = 0;
}
voronoiCanvas.onmouseleave = function() {
2023-10-21 01:48:45 +02:00
// end drag-n-drop
mouseIsDown = false;
2023-10-24 14:39:34 +02:00
dragDropStartPos = null;
dragDropPointID = null;
2023-10-24 14:39:34 +02:00
dragDropState = 0;
2023-10-21 01:48:45 +02:00
coordinateDisplay.innerHTML = "&nbsp;";
}
2023-10-23 08:48:00 +02:00
/* Biome list events */
biomeSelector.onchange = function() {
draw(false);
if (biomeSelector.selectedIndex !== -1) {
let selected = biomeSelector.options[biomeSelector.selectedIndex];
let point = biomePoints[biomeSelector.selectedIndex];
inputHeat.value = point.heat;
inputHumidity.value = point.humidity;
inputMinY.value = point.min_y;
inputMaxY.value = point.max_y;
}
updateWidgetStates();
}
addBiomeButton.onclick = function() {
let he = Math.round(limit_heat_min + Math.random() * (limit_heat_max - limit_heat_min));
let hu = Math.round(limit_humidity_min + Math.random() * (limit_humidity_max - limit_humidity_min));
let newPoint = {
id: lastBiomeID,
name: generateBiomeName(lastBiomeID),
heat: he,
humidity: hu,
min_y: MIN_Y_DEFAULT,
max_y: MAX_Y_DEFAULT,
2023-10-24 14:39:34 +02:00
color: null,
2023-10-23 08:48:00 +02:00
};
biomePoints.push(newPoint);
let num = biomePoints.length
let newElem = document.createElement("option");
newElem.id = "biome_list_element_" + lastBiomeID;
newElem.value = "" + num;
let newElemText = document.createTextNode(newPoint.name);
newElem.append(newElemText);
biomeSelector.append(newElem);
newElem.selected = "selected";
draw(true);
updateWidgetStates();
lastBiomeID++;
}
removeBiomeButton.onclick = function() {
if (biomeSelector.selectedOptions.length === 0) {
return;
}
let firstIndex = null;
for (let o=0; o<biomeSelector.selectedOptions.length; o++) {
let opt = biomeSelector.selectedOptions[o]
let index = opt.index
if (firstIndex === null) {
firstIndex = index;
}
biomePoints.splice(index, 1);
opt.remove();
}
if (firstIndex !== null && biomePoints.length > 0) {
let newIndex = firstIndex-1;
if (newIndex < 0) {
newIndex = 0;
}
biomeSelector.options[newIndex].selected = "selected";
}
draw(true);
updateWidgetStates();
}
/* Biome editing widgets events */
inputHeat.oninput = function() {
onChangeBiomeValueWidget("heat", +this.value);
}
inputHumidity.oninput = function() {
onChangeBiomeValueWidget("humidity", +this.value);
}
inputMinY.oninput = function() {
onChangeBiomeValueWidget("min_y", +this.value);
}
inputMaxY.oninput = function() {
onChangeBiomeValueWidget("max_y", +this.value);
}
inputBiomeName.oninput = function() {
onChangeBiomeValueWidget("name", this.value);
}
2023-10-24 14:39:34 +02:00
inputBiomeColor.oninput = function() {
onChangeBiomeValueWidget("color", this.value);
}
2023-10-23 08:48:00 +02:00
/* Diagram view settings events */
inputViewY.oninput = function() {
draw(true);
updateAltitudeText();
}
2023-10-21 13:59:19 +02:00
inputCheckboxNames.onchange = function() {
showNames = this.checked;
2023-10-23 08:48:00 +02:00
draw(true);
2023-10-21 13:59:19 +02:00
}
inputCheckboxPoints.onchange = function() {
showPoints = this.checked;
2023-10-23 08:48:00 +02:00
draw(true);
2023-10-21 13:59:19 +02:00
}
inputCheckboxCellColors.onchange = function() {
showCellColors = this.checked;
2023-10-23 08:48:00 +02:00
draw(true);
2023-10-21 13:59:19 +02:00
}
inputCheckboxGrid.onchange = function() {
showGrid = this.checked;
2023-10-23 08:48:00 +02:00
draw(true);
2023-10-21 13:59:19 +02:00
}
2023-10-23 08:48:00 +02:00
/* Noise parameters events */
2023-10-23 02:47:00 +02:00
inputCheckboxAxes.onchange = function() {
showAxes = this.checked;
2023-10-23 08:48:00 +02:00
draw(true);
2023-10-23 02:47:00 +02:00
}
2023-10-21 21:00:51 +02:00
inputNoiseHeatScale.oninput = function() {
noises.heat.scale = +this.value;
clear();
updateAreaVars();
2023-10-23 08:48:00 +02:00
draw(true);
}
2023-10-21 21:00:51 +02:00
inputNoiseHeatOffset.oninput = function() {
noises.heat.offset = +this.value;
clear();
updateAreaVars();
2023-10-23 08:48:00 +02:00
draw(true);
}
2023-10-21 21:00:51 +02:00
inputNoiseHeatPersistence.oninput = function() {
noises.heat.persistence = +this.value;
2023-10-21 17:12:43 +02:00
clear();
updateAreaVars();
2023-10-23 08:48:00 +02:00
draw(true);
2023-10-21 17:12:43 +02:00
}
2023-10-21 21:00:51 +02:00
inputNoiseHeatOctaves.oninput = function() {
noises.heat.octaves = +this.value;
2023-10-21 17:12:43 +02:00
clear();
updateAreaVars();
2023-10-23 08:48:00 +02:00
draw(true);
2023-10-21 17:12:43 +02:00
}
2023-10-21 21:00:51 +02:00
inputNoiseHumidityScale.oninput = function() {
noises.humidity.scale = +this.value;
2023-10-21 17:12:43 +02:00
clear();
updateAreaVars();
2023-10-23 08:48:00 +02:00
draw(true);
2023-10-21 17:12:43 +02:00
}
2023-10-21 21:00:51 +02:00
inputNoiseHumidityOffset.oninput = function() {
noises.humidity.offset = +this.value;
clear();
updateAreaVars();
2023-10-23 08:48:00 +02:00
draw(true);
}
2023-10-21 21:00:51 +02:00
inputNoiseHumidityPersistence.oninput = function() {
noises.humidity.persistence = +this.value;
clear();
updateAreaVars();
2023-10-23 08:48:00 +02:00
draw(true);
}
2023-10-21 21:00:51 +02:00
inputNoiseHumidityOctaves.oninput = function() {
noises.humidity.octaves = +this.value;
2023-10-21 17:12:43 +02:00
clear();
updateAreaVars();
2023-10-23 08:48:00 +02:00
draw(true);
2023-10-21 17:12:43 +02:00
}
inputNoiseReset.onclick = function() {
noises.heat.offset = NOISE_OFFSET_DEFAULT;
noises.heat.scale = NOISE_SCALE_DEFAULT;
noises.heat.octaves = NOISE_OCTAVES_DEFAULT;
noises.heat.persistence = NOISE_PERSISTENCE_DEFAULT;
noises.heat.absvalue = NOISE_ABSVALUE_DEFAULT;
inputNoiseHeatOffset.value = noises.heat.offset;
inputNoiseHeatScale.value = noises.heat.scale;
inputNoiseHeatOctaves.value = noises.heat.octaves;
inputNoiseHeatPersistence.value = noises.heat.persistence;
noises.humidity.offset = NOISE_OFFSET_DEFAULT;
noises.humidity.scale = NOISE_SCALE_DEFAULT;
noises.humidity.octaves = NOISE_OCTAVES_DEFAULT;
noises.humidity.persistence = NOISE_PERSISTENCE_DEFAULT;
noises.humidity.absvalue = NOISE_ABSVALUE_DEFAULT;
inputNoiseHumidityOffset.value = noises.humidity.offset;
inputNoiseHumidityScale.value = noises.humidity.scale;
inputNoiseHumidityOctaves.value = noises.humidity.octaves;
inputNoiseHumidityPersistence.value = noises.humidity.persistence;
2023-10-21 17:12:43 +02:00
clear();
updateAreaVars();
2023-10-23 08:48:00 +02:00
draw(true);
2023-10-21 17:12:43 +02:00
}
2023-10-23 08:48:00 +02:00
/* Events for collapsing/extending config section with the arrow thingie */
2023-10-20 16:31:11 +02:00
2023-10-22 02:11:36 +02:00
biomeConfigHeaderLink.onclick = function() {
toggleConfigSectionDisplay(this, biomeConfigContainer);
}
viewConfigHeaderLink.onclick = function() {
toggleConfigSectionDisplay(this, viewConfigContainer);
}
noiseConfigHeaderLink.onclick = function() {
toggleConfigSectionDisplay(this, noiseConfigContainer);
}
2023-10-23 08:48:00 +02:00
/* Load events */
2023-10-23 01:46:24 +02:00
2023-10-21 13:59:19 +02:00
window.addEventListener("load", checkboxVarsInit);
2023-10-20 17:22:48 +02:00
window.addEventListener("load", function() {
2023-10-23 08:48:00 +02:00
draw(true);
2023-10-20 17:22:48 +02:00
})
2023-10-23 08:48:00 +02:00
window.addEventListener("load", repopulateBiomeSelector);
2023-10-20 16:31:11 +02:00
window.addEventListener("load", updateWidgetStates);
2023-10-21 21:35:20 +02:00
window.addEventListener("load", updateAltitudeText);
2023-10-23 01:46:24 +02:00
window.addEventListener("load", unhideContent);