Treat heat and humidity separately in limits

This commit is contained in:
Wuzzy 2023-10-21 17:55:32 +02:00
parent a3ae3738ce
commit b5a1e52f7a
2 changed files with 214 additions and 152 deletions

View File

@ -36,14 +36,17 @@ const cellColors = [
];
// Min. and max. mathematically possible values for heat and humidity
let limit_min, limit_max;
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_min, draw_max;
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;
let midpoint_heat;
let midpoint_humidity;
// Biome noise settings
const NOISE_OFFSET_DEFAULT = 50;
@ -51,25 +54,41 @@ const NOISE_SCALE_DEFAULT = 50;
const NOISE_PERSISTENCE_DEFAULT = 0.5;
const NOISE_OCTAVES_DEFAULT = 3;
const NOISE_ABSVALUE_DEFAULT = false;
let noiseOffset = NOISE_OFFSET_DEFAULT;
let noiseScale = NOISE_SCALE_DEFAULT;
let noiseOctaves = NOISE_OCTAVES_DEFAULT;
let noisePersistence = NOISE_PERSISTENCE_DEFAULT;
let noiseAbsvalue = NOISE_ABSVALUE_DEFAULT;
function updateAreaVars() {
let is_absolute = noiseAbsvalue === true
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) {
console.log("noiseType "+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<=noiseOctaves; o++) {
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 * noisePersistence ** exp)
let limit1 = (1 * noise.persistence ** exp)
let limit2
if (!is_absolute) {
limit2 = (-1 * noisePersistence ** exp)
limit2 = (-1 * noise.persistence ** exp)
} else {
// If absvalue is set, one of the
// limits is always 0 because we
@ -92,8 +111,8 @@ function updateAreaVars() {
}
}
// Add offset and scale to min/max value (final step)
let min_value = noiseOffset + noiseScale * o_min
let max_value = noiseOffset + noiseScale * o_max
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)
@ -102,16 +121,34 @@ function updateAreaVars() {
}
// Update globals
limit_min = min_value;
limit_max = max_value;
console.log("limit_min = "+limit_min);
console.log("limit_max = "+limit_max);
draw_min = limit_min - DRAW_OFFSET
draw_max = limit_max + DRAW_OFFSET
midpoint = min_value + (max_value - min_value) / 2
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;
console.log("Heat= "+limit_heat_min+" - "+limit_heat_max);
} 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
statsInfo.innerText = "Value range: ["+(+limit_min)+", "+(+limit_max)+"]"
statsInfo.innerText = "Heat value range: ["+(+limit_heat_min)+", "+(+limit_heat_max)+"]; " +
"humidity value range: ["+(+limit_humidity_min)+", "+(+limit_humidity_max)+"]";
}
updateAreaVars();
@ -136,7 +173,7 @@ function addBiome(biomeDef) {
}
// Add a default biome at the midpoint
addBiome({name: generateBiomeName(midpoint, midpoint), heat:midpoint, humidity:midpoint, min_y: MIN_Y_DEFAULT, max_y: MAX_Y_DEFAULT})
addBiome({name: generateBiomeName(midpoint_heat, midpoint_humidity), heat:midpoint_heat, humidity:midpoint_humidity, min_y: MIN_Y_DEFAULT, max_y: MAX_Y_DEFAULT})
function getViewY() {
if (!inputViewY) {
@ -176,17 +213,17 @@ function voronoiPointToBiomePoint(point) {
function putPointName(context, point) {
let x = point.heat
let y = point.humidity - 1
if (x < limit_min || x > limit_max || y < limit_min || y > limit_max) {
if (x < limit_heat_min || x > limit_heat_max || y < limit_humidity_min || y > limit_humidity_max) {
return;
}
if (x > midpoint) {
if (x > midpoint_heat) {
context.textAlign = "right";
x = x-2;
} else {
context.textAlign = "left";
x = x+2;
}
if (y < midpoint) {
if (y < midpoint_humidity) {
context.textBaseline = "top";
} else {
context.textBaseline = "alphabetic";
@ -200,62 +237,62 @@ function putPoint(context, point) {
let x = point.heat
let y = point.humidity
// Point is out of bounds: Draw an arrow at the border
if (x < limit_min || x > limit_max || y < limit_min || y > limit_max) {
if (x < limit_heat_min || x > limit_heat_max || y < limit_humidity_min || y > limit_humidity_max) {
context.beginPath();
// top left corner
if (x < limit_min && y < limit_min) {
context.moveTo(limit_min, limit_min);
context.lineTo(limit_min + ARROW_SIZE_CORNER, limit_min);
context.lineTo(limit_min, limit_min + ARROW_SIZE_CORNER);
if (x < limit_heat_min && y < limit_humidity_min) {
context.moveTo(limit_heat_min, limit_humidity_min);
context.lineTo(limit_heat_min + ARROW_SIZE_CORNER, limit_humidity_min);
context.lineTo(limit_heat_min, limit_humidity_min + ARROW_SIZE_CORNER);
context.closePath();
context.fill();
// top right corner
} else if (x > limit_max && y < limit_min) {
context.moveTo(limit_max, limit_min);
context.lineTo(limit_max - ARROW_SIZE_CORNER, limit_min);
context.lineTo(limit_max, limit_min + ARROW_SIZE_CORNER);
} else if (x > limit_heat_max && y < limit_humidity_min) {
context.moveTo(limit_heat_max, limit_humidity_min);
context.lineTo(limit_heat_max - ARROW_SIZE_CORNER, limit_humidity_min);
context.lineTo(limit_heat_max, limit_humidity_min + ARROW_SIZE_CORNER);
context.closePath();
context.fill();
// bottom left corner
} else if (x < limit_min && y > limit_max) {
context.moveTo(limit_min, limit_max);
context.lineTo(limit_min + ARROW_SIZE_CORNER, limit_max);
context.lineTo(limit_min, limit_max - ARROW_SIZE_CORNER);
} else if (x < limit_heat_min && y > limit_humidity_max) {
context.moveTo(limit_heat_min, limit_humidity_max);
context.lineTo(limit_heat_min + ARROW_SIZE_CORNER, limit_humidity_max);
context.lineTo(limit_heat_min, limit_humidity_max - ARROW_SIZE_CORNER);
context.closePath();
context.fill();
// top right corner
} else if (x > limit_max && y > limit_max) {
context.moveTo(limit_max, limit_max);
context.lineTo(limit_max - ARROW_SIZE_CORNER, limit_max);
context.lineTo(limit_max, limit_max - ARROW_SIZE_CORNER);
} else if (x > limit_heat_max && y > limit_humidity_max) {
context.moveTo(limit_heat_max, limit_humidity_max);
context.lineTo(limit_heat_max - ARROW_SIZE_CORNER, limit_humidity_max);
context.lineTo(limit_heat_max, limit_humidity_max - ARROW_SIZE_CORNER);
context.closePath();
context.fill();
// left side
} else if (x < limit_min) {
context.moveTo(limit_min, y);
context.lineTo(limit_min + ARROW_SIZE_SIDE, y + ARROW_SIZE_SIDE);
context.lineTo(limit_min + ARROW_SIZE_SIDE, y - ARROW_SIZE_SIDE);
} else if (x < limit_heat_min) {
context.moveTo(limit_heat_min, y);
context.lineTo(limit_heat_min + ARROW_SIZE_SIDE, y + ARROW_SIZE_SIDE);
context.lineTo(limit_heat_min + ARROW_SIZE_SIDE, y - ARROW_SIZE_SIDE);
context.closePath();
context.fill();
// right side
} else if (x > limit_max) {
context.moveTo(limit_max, y);
context.lineTo(limit_max - ARROW_SIZE_SIDE, y + ARROW_SIZE_SIDE);
context.lineTo(limit_max - ARROW_SIZE_SIDE, y - ARROW_SIZE_SIDE);
} else if (x > limit_heat_max) {
context.moveTo(limit_heat_max, y);
context.lineTo(limit_heat_max - ARROW_SIZE_SIDE, y + ARROW_SIZE_SIDE);
context.lineTo(limit_heat_max - ARROW_SIZE_SIDE, y - ARROW_SIZE_SIDE);
context.closePath();
context.fill();
// top side
} else if (y < limit_min) {
context.moveTo(x, limit_min);
context.lineTo(x - ARROW_SIZE_SIDE, limit_min + ARROW_SIZE_SIDE);
context.lineTo(x + ARROW_SIZE_SIDE, limit_min + ARROW_SIZE_SIDE);
} else if (y < limit_humidity_min) {
context.moveTo(x, limit_humidity_min);
context.lineTo(x - ARROW_SIZE_SIDE, limit_humidity_min + ARROW_SIZE_SIDE);
context.lineTo(x + ARROW_SIZE_SIDE, limit_humidity_min + ARROW_SIZE_SIDE);
context.closePath();
context.fill();
// bottom side
} else if (y > limit_max) {
context.moveTo(x, limit_max);
context.lineTo(x - ARROW_SIZE_SIDE, limit_max - ARROW_SIZE_SIDE);
context.lineTo(x + ARROW_SIZE_SIDE, limit_max - ARROW_SIZE_SIDE);
} else if (y > limit_humidity_max) {
context.moveTo(x, limit_humidity_max);
context.lineTo(x - ARROW_SIZE_SIDE, limit_humidity_max - ARROW_SIZE_SIDE);
context.lineTo(x + ARROW_SIZE_SIDE, limit_humidity_max - ARROW_SIZE_SIDE);
context.closePath();
context.fill();
}
@ -272,28 +309,28 @@ function putPoint(context, point) {
function putGrid(context) {
context.lineWidth = 0.5;
context.strokeStyle = gridColor;
for (let x=0; x<=limit_max; x+=GRID_STEP) {
for (let x=0; x<=limit_heat_max; x+=GRID_STEP) {
context.beginPath();
context.moveTo(x, limit_min);
context.lineTo(x, limit_max);
context.moveTo(x, limit_humidity_min);
context.lineTo(x, limit_humidity_max);
context.stroke();
}
for (let x=-GRID_STEP; x>=limit_min; x-=GRID_STEP) {
for (let x=-GRID_STEP; x>=limit_heat_min; x-=GRID_STEP) {
context.beginPath();
context.moveTo(x, limit_min);
context.lineTo(x, limit_max);
context.moveTo(x, limit_humidity_min);
context.lineTo(x, limit_humidity_max);
context.stroke();
}
for (let y=0; y<=limit_max; y+=GRID_STEP) {
for (let y=0; y<=limit_humidity_max; y+=GRID_STEP) {
context.beginPath();
context.moveTo(limit_min, y);
context.lineTo(limit_max, y);
context.moveTo(limit_heat_min, y);
context.lineTo(limit_heat_max, y);
context.stroke();
}
for (let y=-GRID_STEP; y>=limit_min; y-=GRID_STEP) {
for (let y=-GRID_STEP; y>=limit_humidity_min; y-=GRID_STEP) {
context.beginPath();
context.moveTo(limit_min, y);
context.lineTo(limit_max, y);
context.moveTo(limit_heat_min, y);
context.lineTo(limit_heat_max, y);
context.stroke();
}
}
@ -303,7 +340,7 @@ let cachedVoronoiDiagram = null;
function getVoronoiDiagram(points, recalculate) {
if ((cachedVoronoiDiagram === null) || recalculate) {
let vbbox = {xl: limit_min, xr: limit_max, yt: limit_min, yb: limit_max};
let vbbox = {xl: limit_heat_min, xr: limit_heat_max, yt: limit_humidity_min, yb: limit_humidity_max};
let sites = []
for (let p of points) {
sites.push(biomePointToVoronoiPoint(p));
@ -335,30 +372,9 @@ function clear(context) {
context = getDrawContext();
}
context.fillStyle = clearColor;
context.fillRect(draw_min, draw_min, draw_max-draw_min, draw_max-draw_min);
context.fillRect(draw_heat_min, draw_humidity_min, draw_heat_max-draw_heat_min, draw_humidity_max-draw_humidity_min);
}
function drawInit(contextRestore) {
/*
let context = getDrawContext();
if (contextRestore) {
context.restore();
}
context.save();
// Clear area
clear(context);
// Don't scale and translate if value range is tiny; an error message will be triggered in draw()
if (limit_max - limit_min < 0.01) {
return;
}
// New range
context.scale(voronoiCanvas.width/(limit_max-limit_min), voronoiCanvas.height/(limit_max-limit_min));
context.translate(-limit_min, -limit_min);
*/
}
function getRenderedPoints(y) {
let points = [];
for (let p=0; p<biomePoints.length; p++) {
@ -377,7 +393,7 @@ function draw(y, recalculate) {
context.restore();
// Render a special message if the value range is tiny
if (limit_max - limit_min < 0.01) {
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";
@ -408,8 +424,8 @@ function draw(y, recalculate) {
drawError = false;
context.save();
context.scale(voronoiCanvas.width/(limit_max-limit_min), voronoiCanvas.height/(limit_max-limit_min));
context.translate(-limit_min, -limit_min);
context.scale(voronoiCanvas.width/(limit_heat_max-limit_heat_min), voronoiCanvas.height/(limit_humidity_max-limit_humidity_min));
context.translate(-limit_heat_min, -limit_humidity_min);
let diagram = getVoronoiDiagram(points, recalculate);
@ -444,12 +460,12 @@ function draw(y, recalculate) {
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_min, draw_min, draw_max-draw_min, draw_max-draw_min);
context.fillRect(draw_heat_min, draw_humidity_min, draw_heat_max-draw_heat_min, draw_humidity_max-draw_humidity_min);
}
} else {
// Use a "neutral" background color for the whole area if cell colors are disabled
context.fillStyle = cellColorNeutral;
context.fillRect(draw_min, draw_min, draw_max-draw_min, draw_max-draw_min);
context.fillRect(draw_heat_min, draw_humidity_min, draw_heat_max-draw_heat_min, draw_humidity_max-draw_humidity_min);
}
if (points.length > 0) {
@ -549,10 +565,14 @@ function updateWidgetStates() {
inputMaxY.value = point.max_y;
}
}
inputNoiseOffset.value = noiseOffset;
inputNoiseScale.value = noiseScale;
inputNoiseOctaves.value = noiseOctaves;
inputNoisePersistence.value = noisePersistence;
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;
}
biomeSelector.onchange = function() {
@ -603,15 +623,15 @@ inputViewY.onchange = function() {
}
addBiomeButton.onclick = function() {
let he = Math.round(limit_min + Math.random() * (limit_max - limit_min));
let hu = Math.round(limit_min + Math.random() * (limit_max - limit_min));
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(he, hu),
heat: he,
humidity: hu,
min_y: MIN_Y_DEFAULT,
max_y: MAX_Y_DEFAULT,
humidity: hu,
};
biomePoints.push(newPoint);
let num = biomePoints.length
@ -684,17 +704,17 @@ function getDistance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1)**2 + (y2 - y1)**2);
}
function canvasPixelCoordsToBiomeCoords(x, y) {
let w = (voronoiCanvas.width/(limit_max-limit_min));
let h = (voronoiCanvas.height/(limit_max-limit_min));
let heat = Math.round((x + limit_min * w) / w);
let humidity = Math.round((y + limit_min * w) / w);
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);
let humidity = Math.round((y + limit_humidity_min * w) / w);
return [heat, humidity];
}
function biomeCoordsToCanvasPixelCoords(heat, humidity) {
let w = (voronoiCanvas.width/(limit_max-limit_min));
let h = (voronoiCanvas.height/(limit_max-limit_min));
let pixelX = heat * w - limit_min * w;
let pixelY = humidity * h - limit_min * h;
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;
let pixelY = humidity * h - limit_humidity_min * h;
return [pixelX, pixelY];
}
@ -827,47 +847,77 @@ inputCheckboxGrid.onchange = function() {
showGrid = this.checked;
draw(getViewY(), true);
}
inputNoiseScale.onchange = function() {
noiseScale = +this.value;
inputNoiseHeatScale.onchange = function() {
noises.heat.scale = +this.value;
clear();
updateAreaVars();
drawInit(true);
draw(getViewY(), true);
}
inputNoiseOffset.onchange = function() {
noiseOffset = +this.value;
inputNoiseHeatOffset.onchange = function() {
noises.heat.offset = +this.value;
clear();
updateAreaVars();
drawInit(true);
draw(getViewY(), true);
}
inputNoisePersistence.onchange = function() {
noisePersistence = +this.value;
inputNoiseHeatPersistence.onchange = function() {
noises.heat.persistence = +this.value;
clear();
updateAreaVars();
drawInit(true);
draw(getViewY(), true);
}
inputNoiseOctaves.onchange = function() {
noiseOctaves = +this.value;
inputNoiseHeatOctaves.onchange = function() {
noises.heat.octaves = +this.value;
clear();
updateAreaVars();
draw(getViewY(), true);
}
inputNoiseHumidityScale.onchange = function() {
noises.humidity.scale = +this.value;
clear();
updateAreaVars();
draw(getViewY(), true);
}
inputNoiseHumidityOffset.onchange = function() {
noises.humidity.offset = +this.value;
clear();
updateAreaVars();
draw(getViewY(), true);
}
inputNoiseHumidityPersistence.onchange = function() {
noises.humidity.persistence = +this.value;
clear();
updateAreaVars();
draw(getViewY(), true);
}
inputNoiseHumidityOctaves.onchange = function() {
noises.humidity.octaves = +this.value;
clear();
updateAreaVars();
drawInit(true);
draw(getViewY(), true);
}
inputNoiseReset.onclick = function() {
noiseOffset = NOISE_OFFSET_DEFAULT;
noiseScale = NOISE_SCALE_DEFAULT;
noiseOctaves = NOISE_OCTAVES_DEFAULT;
noisePersistence = NOISE_PERSISTENCE_DEFAULT;
noiseAbsvalue = NOISE_ABSVALUE_DEFAULT;
inputNoiseOffset.value = noiseOffset;
inputNoiseScale.value = noiseScale;
inputNoiseOctaves.value = noiseOctaves;
inputNoisePersistence.value = noisePersistence;
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();
drawInit(true);
draw(getViewY(), true);
}
@ -879,7 +929,6 @@ function checkboxVarsInit() {
}
window.addEventListener("load", checkboxVarsInit);
window.addEventListener("load", drawInit);
window.addEventListener("load", function() {
draw(getViewY(), true);
})

View File

@ -78,21 +78,34 @@ A voronoi diagram is supposed to be here but for some reason it cannot be displa
</form>
</div>
<div>
<h2>Heat/humidity noise parameters</h2>
<h2>Noise parameters</h2>
<form id="noiseForm">
<label for="inputNoiseOffset">Offset:</label>
<input id="inputNoiseOffset" type="number" value="50">
<label for="inputNoiseScale">Scale:</label>
<input id="inputNoiseScale" type="number" value="50">
<label for="inputNoiseOctaves">Octaves:</label>
<input id="inputNoiseOctaves" type="number" value="3" step="1" min="1">
<label for="inputNoisePersistence">Persistence:</label>
<input id="inputNoisePersistence" type="number" value="0.5" step=0.1>
<button id="inputNoiseReset" type="button">Reset</button>
<h3>Heat</h3>
<label for="inputNoiseHeatOffset">Offset:</label>
<input id="inputNoiseHeatOffset" type="number" value="50">
<label for="inputNoiseHeatScale">Scale:</label>
<input id="inputNoiseHeatScale" type="number" value="50">
<label for="inputNoiseHeatOctaves">Octaves:</label>
<input id="inputNoiseHeatOctaves" type="number" value="3" step="1" min="1">
<label for="inputNoiseHeatPersistence">Persistence:</label>
<input id="inputNoiseHeatPersistence" type="number" value="0.5" step=0.1>
<h3>Humidity</h3>
<label for="inputNoiseHumidityOffset">Offset:</label>
<input id="inputNoiseHumidityOffset" type="number" value="50">
<label for="inputNoiseHumidityScale">Scale:</label>
<input id="inputNoiseHumidityScale" type="number" value="50">
<label for="inputNoiseHumidityOctaves">Octaves:</label>
<input id="inputNoiseHumidityOctaves" type="number" value="3" step="1" min="1">
<label for="inputNoiseHumidityPersistence">Persistence:</label>
<input id="inputNoiseHumidityPersistence" type="number" value="0.5" step=0.1>
</form>
</div>
<script src="./biome-ui.js"></script>