"use strict"; // Default values for the min. and max. Y of biomes const MIN_Y_DEFAULT = -31000 const MAX_Y_DEFAULT = 31000 // Draw a grid line every GRID_STEP units const GRID_STEP = 10 // Distance from the point's center in which a point can // be selected by clicking on it const POINT_SELECT_DISTANCE = 25 // 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 // Name to display if empty const FALLBACK_NAME = "(no name)" // Symbol for storing the biome ID in site objects // for the Voronoi script const biomeIDSymbol = Symbol("Biome ID"); // 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 = [ "#64988e", "#3d7085", "#345644", "#6b7f5c", "#868750", "#a7822c", "#a06e38", "#ad5f52", "#692f11", "#89542f", "#796e63", "#a17d5e", "#5a3f20", "#836299", ]; const CELL_COLOR_NEUTRAL = "#888888"; /* Status variables for the diagram calculations */ // Min. and max. mathematically possible values for heat and humidity let limit_heat_min, limit_heat_max; let limit_humidity_min, limit_humidity_max; // 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; // The point that is considered the middle of heat/humidity; // mathematically this value is the most probable. let midpoint_heat; let midpoint_humidity; // 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; // 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 // Calculate min. and max. possible values // Octaves let [o_min, o_max] = [0, 0] for (let o=1; o<=noise.octaves; o++) { 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) let limit2 if (!is_absolute) { limit2 = (-1 * noise.persistence ** exp) } 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 // 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"); // Update element rangeDisplay.innerHTML = "heat range: ["+(+limit_heat_min)+", "+(+limit_heat_max)+"]; " + "humidity range: ["+(+limit_humidity_min)+", "+(+limit_humidity_max)+"]"; } updateAreaVars(); // 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; // If true, show the heat/humidity axes let showAxes = false; // Set to true if the draw canvas currently shows an error message let drawError = false; // The last ID assigned to a biome (0 = none assigned yet). let lastBiomeID = 0; let biomePoints = []; // Add a biome to the biome list function addBiome(biomeDef) { biomeDef.id = lastBiomeID; biomePoints.push(biomeDef); // The biome ID is just a simple ascending number lastBiomeID++; } // Add a default biome at the midpoint addBiome({name: "default", heat:midpoint_heat, humidity:midpoint_humidity, min_y: MIN_Y_DEFAULT, max_y: MAX_Y_DEFAULT}) // Get the Y vale of the widget to set the Y altitude function getViewY() { if (!inputViewY) { return 0; } return inputViewY.value; } // Returns the biome point by its given ID // or null if it couldn't be found function getBiomeByID(id) { for(let b=0; b limit_heat_max || hu < limit_humidity_min || hu > limit_humidity_max) { return; } let [x, y] = biomeCoordsToCanvasPixelCoords(he, hu); let w = voronoiCanvas.width; let h = voronoiCanvas.height; if (x > w/2) { context.textAlign = "right"; x = x-5; } else { context.textAlign = "left"; x = x+5; } if (y < h/2) { context.textBaseline = "top"; } else { context.textBaseline = "alphabetic"; } context.font = "120% sans-serif" let displayName = point.name; if (displayName === "") { displayName = FALLBACK_NAME; } context.fillText(displayName, x, y); } /* Put the given point on the draw context */ 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); // 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) { 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); 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); 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); 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); 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); 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); 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); 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); 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); context.closePath(); context.fill(); } }; /* Put the grid on the draw context */ 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; context.strokeStyle = GRID_COLOR; for (let he=0; he<=limit_heat_max; he+=GRID_STEP) { let [x, _] = biomeCoordsToCanvasPixelCoords(he, 0); context.beginPath(); context.moveTo(x, limit_y_min); context.lineTo(x, limit_y_max); context.stroke(); } for (let he=-GRID_STEP; he>=limit_heat_min; he-=GRID_STEP) { let [x, _] = biomeCoordsToCanvasPixelCoords(he, 0); context.beginPath(); context.moveTo(x, limit_y_min); context.lineTo(x, limit_y_max); context.stroke(); } for (let hu=0; hu<=limit_humidity_max; hu+=GRID_STEP) { let [_, y] = biomeCoordsToCanvasPixelCoords(0, hu); context.beginPath(); context.moveTo(limit_x_min, y); context.lineTo(limit_x_max, y); context.stroke(); } for (let hu=-GRID_STEP; hu>=limit_humidity_min; hu-=GRID_STEP) { let [_, y] = biomeCoordsToCanvasPixelCoords(0, hu); context.beginPath(); context.moveTo(limit_x_min, y); context.lineTo(limit_x_max, y); context.stroke(); } } /* Put the labelled heat/humidity axes on the draw context */ function putAxes(context) { const AXIS_ARROW_SIZE = 8; context.lineWidth = 2; context.strokeStyle = AXIS_COLOR; 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); } // Cache diagram object for performance boost let cachedVoronoiDiagram = null; /* Given the list of biome points, returns a Voronoi diagram object (which may have been cached). If recalculate is true, a recalculation is forced. */ 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; } } if ((cachedVoronoiDiagram === null) || recalculate) { let sites = [] for (let p of points) { 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; } } /* Returns the context object required to draw on the canvas */ function getDrawContext() { let canvas = document.getElementById("voronoiCanvas"); if (canvas.getContext) { return canvas.getContext("2d"); } else { return null; } } // Clear draw area function clear(context) { if (!context) { context = getDrawContext(); if (!context) { return false; } } context.fillStyle = CLEAR_COLOR; context.fillRect(-DRAW_OFFSET, -DRAW_OFFSET, voronoiCanvas.width+DRAW_OFFSET, voronoiCanvas.height+DRAW_OFFSET); return true; } /* Returns all biome points except those whose Y limits fall out of the given y value */ function getRenderedPoints(y) { let points = []; for (let p=0; p= point.min_y && y <= point.max_y) { points.push(point); } } return points; } /* Given a biome ID, returns the matching HTML element from the biome list widget or null if none */ 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; } /* 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] */ 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]; } /* 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; let y = getViewY(); // shorter function name (for "convert") let conv = biomeCoordsToCanvasPixelCoords if (!context) { if (!voronoiCanvas.hidden) { voronoiCanvas.hidden = true; 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; } 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)) { 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); drawError = true; updateAltitudeText(); return true; } let points = getRenderedPoints(y); // 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."; } context.fillText(msg, w/2, h/2); drawError = true; updateAltitudeText(); return true; } drawError = false; updateAltitudeText(); let diagram = getVoronoiDiagram(points, recalculate); let createHalfedgesPath = function(context, cell) { context.beginPath(); for (let h=0; h 0) { if (showGrid) { putGrid(context); } if (showAxes) { putAxes(context); } } // Render Voronoi cell edges context.lineWidth = 2.5; for (let e=0; e"; } /* Update the text that shows the biome coordinates of the cursor when it's on the diagram */ function updateCoordinateDisplay(pixelX, pixelY) { // show coordinates let [heat, humidity] = canvasPixelCoordsToBiomeCoords(pixelX, pixelY); if (!drawError) { let html = "cursor coordinates: heat="+heat+"; humidity="+humidity+""; coordinateDisplay.innerHTML = html; } else { coordinateDisplay.innerHTML = " "; } } /* Updates and changes the cursor type on the diagram canvas depending on whether we can select, drag or do nothing at the pointed position */ function updateDragDropCursorStatus() { if (drawError || !showPoints) { // a special message is shown; use auto cursor 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) { // This cursor indicates we can grab the point voronoiCanvas.style.cursor = "grab"; } else { // This cursor indicates we can grab the point voronoiCanvas.style.cursor = "crosshair"; } } else { // Default cursor when a click doesn't to anything voronoiCanvas.style.cursor = "auto"; } } /* 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 */ voronoiCanvas.onmousemove = function(event) { // 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; } } // drag-n-drop if (dragDropState === 2) { updatePointWhenDragged(dragDropPointID); } updateCoordinateDisplay(event.offsetX, event.offsetY); updateDragDropCursorStatus(); } voronoiCanvas.onmouseenter = function(event) { updateCoordinateDisplay(event.offsetX, event.offsetY); updateDragDropCursorStatus(); } voronoiCanvas.onmousedown = function(event) { // select point by clicking. // initiate drag-n-drop if already selected. mouseIsDown = true; if (!showPoints) { // Points need to be shown for drag-n-drop to work return; } let nearest = getNearestPointFromCanvasPos(event.offsetX, event.offsetY, POINT_SELECT_DISTANCE); if (nearest !== null) { let success, alreadySelected [success, alreadySelected] = selectPoint(nearest); dragDropPointID = nearest.id; if (success) { let [x, y] = biomeCoordsToCanvasPixelCoords(nearest.heat, nearest.humidity); dragDropStartPos = { x: x, y: y }; } if (alreadySelected) { dragDropState = 1; } updateDragDropCursorStatus(); } } voronoiCanvas.onmouseup = function(event) { // end drag-n-drop if (dragDropState === 2) { updatePointWhenDragged(dragDropPointID); } mouseIsDown = false; dragDropStartPos = null; dragDropPointID = null; dragDropState = 0; } voronoiCanvas.onmouseleave = function() { // end drag-n-drop mouseIsDown = false; dragDropStartPos = null; dragDropPointID = null; dragDropState = 0; coordinateDisplay.innerHTML = " "; } /* 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, color: null, }; 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 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); } inputBiomeColor.oninput = function() { onChangeBiomeValueWidget("color", this.value); } /* Diagram view settings events */ inputViewY.oninput = function() { draw(true); updateAltitudeText(); } inputCheckboxNames.onchange = function() { showNames = this.checked; draw(true); } inputCheckboxPoints.onchange = function() { showPoints = this.checked; draw(true); } inputCheckboxCellColors.onchange = function() { showCellColors = this.checked; draw(true); } inputCheckboxGrid.onchange = function() { showGrid = this.checked; draw(true); } /* Noise parameters events */ inputCheckboxAxes.onchange = function() { showAxes = this.checked; draw(true); } inputNoiseHeatScale.oninput = function() { noises.heat.scale = +this.value; clear(); updateAreaVars(); draw(true); } inputNoiseHeatOffset.oninput = function() { noises.heat.offset = +this.value; clear(); updateAreaVars(); draw(true); } inputNoiseHeatPersistence.oninput = function() { noises.heat.persistence = +this.value; clear(); updateAreaVars(); draw(true); } inputNoiseHeatOctaves.oninput = function() { noises.heat.octaves = +this.value; clear(); updateAreaVars(); draw(true); } inputNoiseHumidityScale.oninput = function() { noises.humidity.scale = +this.value; clear(); updateAreaVars(); draw(true); } inputNoiseHumidityOffset.oninput = function() { noises.humidity.offset = +this.value; clear(); updateAreaVars(); draw(true); } inputNoiseHumidityPersistence.oninput = function() { noises.humidity.persistence = +this.value; clear(); updateAreaVars(); draw(true); } inputNoiseHumidityOctaves.oninput = function() { noises.humidity.octaves = +this.value; clear(); updateAreaVars(); draw(true); } 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; clear(); updateAreaVars(); draw(true); } /* Events for collapsing/extending config section with the arrow thingie */ biomeConfigHeaderLink.onclick = function() { toggleConfigSectionDisplay(this, biomeConfigContainer); } viewConfigHeaderLink.onclick = function() { toggleConfigSectionDisplay(this, viewConfigContainer); } noiseConfigHeaderLink.onclick = function() { toggleConfigSectionDisplay(this, noiseConfigContainer); } /* Load events */ window.addEventListener("load", checkboxVarsInit); window.addEventListener("load", function() { draw(true); }) window.addEventListener("load", repopulateBiomeSelector); window.addEventListener("load", updateWidgetStates); window.addEventListener("load", updateAltitudeText); window.addEventListener("load", unhideContent);