2023-10-20 13:31:23 +02:00
"use strict" ;
2023-10-20 14:03:45 +02:00
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-24 23:17:22 +02:00
// Size of the resizing corner
2023-10-25 03:39:29 +02:00
const RESIZE _CORNER = 14 ;
2023-10-24 23:17:22 +02:00
// Minimum canvas side length (px)
const MIN _CANVAS _SIZE = 100 ;
2023-10-24 23:33:00 +02:00
// Minimum required distance of canvas from the right page side
const CANVAS _PAGE _MARGIN _RIGHT = 20
2023-10-27 14:35:41 +02:00
// Minimum and maximum value for heat and humidity
2023-10-27 16:03:17 +02:00
const MIN _HEAT _HUMIDITY _VALUE = - 1e6
const MAX _HEAT _HUMIDITY _VALUE = 1e6
2023-10-27 14:35:41 +02:00
2023-10-24 17:47:55 +02:00
// Grid widths. We use lower grid widths
// as the grid becomes more crammed.
// There are 4 levels from 0 to 3.
// Level 3 is the full width, level 1 is the
// smallest with and level 0 renders no grid lines
// when the grid would become too crammed.
const GRID _WIDTH _LEVEL _3 = 2 ; // full width
const GRID _WIDTH _LEVEL _2 = 1 ; // reduced width
const GRID _WIDTH _LEVEL _1 = 0.5 ; // more reduced width
// the grid thesholds are the "grid lines to pixel"
// ratio. The more grid lines therere are per pixel,
// the lower the grid level.
// e.g. grid level 2 triggers if ratio is above
// GRID_THRESHOLD_LEVEL_2. Grid level 3 is used
// if no grid thresholds are triggered.
const GRID _THRESHOLD _LEVEL _2 = 0.08 ;
const GRID _THRESHOLD _LEVEL _1 = 0.16 ;
const GRID _THRESHOLD _LEVEL _0 = 0.24 ; // this will disable grid rendering
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)"
2023-10-21 02:18:29 +02:00
// 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
2023-10-27 16:18:41 +02:00
// note: These MUST be in "#xxxxxx" format
// for the hexColorToRGBColor function to work.
2023-10-23 08:48:00 +02:00
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" ;
2023-10-24 16:06:15 +02:00
const COLOR _BUTTON _BORDER _SELECTED = "#FF0000" ;
2023-10-23 08:48:00 +02:00
/* 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
2023-10-21 17:55:32 +02:00
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
2023-10-21 17:55:32 +02:00
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.
2023-10-21 17:55:32 +02:00
let midpoint _heat ;
let midpoint _humidity ;
2023-10-21 17:12:43 +02:00
2023-10-27 14:35:41 +02:00
// Y altitude at which the diagram is currently viewed at
let viewY = 0 ;
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
2023-10-21 17:55:32 +02:00
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 ]
2023-10-21 17:55:32 +02:00
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.
2023-10-21 17:55:32 +02:00
let limit1 = ( 1 * noise . persistence * * exp )
2023-10-21 17:12:43 +02:00
let limit2
if ( ! is _absolute ) {
2023-10-21 17:55:32 +02:00
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)
2023-10-21 17:55:32 +02:00
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
2023-10-21 17:55:32 +02:00
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 {
2023-10-26 12:34:22 +02:00
console . error ( "updateAreaVars() called with wrong noise_type!" )
2023-10-21 17:55:32 +02:00
}
}
function updateAreaVars ( ) {
updateAreaVarsFor ( "heat" ) ;
updateAreaVarsFor ( "humidity" ) ;
2023-10-21 17:12:43 +02:00
// Update element
2023-10-21 23:04:38 +02:00
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-26 11:35:46 +02:00
// Current cursor position on the canvas
let canvas _cursor _x = null ;
let canvas _cursor _y = null ;
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-26 11:35:46 +02:00
// Add a biome to the biome list, does not update widgets.
// Returns a reference to the biome that was actually added.
function addBiomeRaw ( biomeDef ) {
2023-10-21 13:59:19 +02:00
biomeDef . id = lastBiomeID ;
2023-10-24 16:06:15 +02:00
biomeDef . colorIndex = lastBiomeID % CELL _COLORS . length ;
2023-10-21 13:59:19 +02:00
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 ++ ;
2023-10-26 11:35:46 +02:00
return biomeDef ;
2023-10-21 13:59:19 +02:00
}
// Add a default biome at the midpoint
2023-10-26 11:35:46 +02:00
addBiomeRaw ( { name : "default" , heat : midpoint _heat , humidity : midpoint _humidity , min _y : MIN _Y _DEFAULT , max _y : MAX _Y _DEFAULT } ) ;
// Add a new random biome to the biome list with the given biome definition.
// Then select it and update widgets
function addBiome ( biomeDef ) {
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 newBiome = addBiomeRaw ( biomeDef ) ;
let newElem = document . createElement ( "option" ) ;
newElem . id = "biome_list_element_" + newBiome . id ;
let newElemText = document . createTextNode ( newBiome . name ) ;
newElem . append ( newElemText ) ;
biomeSelector . append ( newElem ) ;
newElem . selected = "selected" ;
updateWidgetStates ( ) ;
draw ( true ) ;
}
2023-10-21 13:59:19 +02:00
2023-10-27 14:35:41 +02:00
// Get the Y value of the viewed altitude
2023-10-21 13:59:19 +02:00
function getViewY ( ) {
2023-10-27 14:35:41 +02:00
return viewY ;
2023-10-21 13:59:19 +02:00
}
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 ) {
2023-10-25 10:24:50 +02:00
return String ( 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 ) {
2023-10-21 02:18:29 +02:00
// 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 ) {
2023-10-21 02:18:29 +02:00
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. */
2023-10-25 03:39:29 +02:00
/ * R e n d e r t h e " r e s i z e c o r n e r " , a c o u p l e o f d i a g o n a l l i n e s i n t h e c o r n e r
indicating the canvas can be resized * /
function putResizeCorner ( context ) {
if ( canvas _cursor _x !== null ) {
context . beginPath ( ) ;
context . moveTo ( voronoiCanvas . width , voronoiCanvas . height - RESIZE _CORNER ) ;
context . lineTo ( voronoiCanvas . width - RESIZE _CORNER , voronoiCanvas . height ) ;
context . lineTo ( voronoiCanvas . width , voronoiCanvas . height ) ;
context . fillStyle = "#80808080" ;
context . closePath ( ) ;
context . fill ( ) ;
context . beginPath ( ) ;
context . lineWidth = 1 ;
for ( let c = RESIZE _CORNER ; c > 0 ; c -= 4 ) {
context . moveTo ( voronoiCanvas . width , voronoiCanvas . height - c ) ;
context . lineTo ( voronoiCanvas . width - c , voronoiCanvas . height ) ;
}
context . strokeStyle = "#00000080" ;
context . closePath ( ) ;
context . stroke ( ) ;
}
}
2023-10-23 08:48:00 +02:00
/* Put the name of the given point on the draw context */
2023-10-21 11:35:22 +02:00
function putPointName ( context , point ) {
2023-10-22 12:40:01 +02:00
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 ;
}
2023-10-22 12:40:01 +02:00
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" ;
2023-10-22 12:40:01 +02:00
x = x - 5 ;
2023-10-21 11:35:22 +02:00
} else {
context . textAlign = "left" ;
2023-10-22 12:40:01 +02:00
x = x + 5 ;
2023-10-21 11:35:22 +02:00
}
2023-10-22 12:40:01 +02:00
if ( y < h / 2 ) {
2023-10-21 12:08:24 +02:00
context . textBaseline = "top" ;
} else {
context . textBaseline = "alphabetic" ;
}
2023-10-22 12:40:01 +02:00
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 ) {
2023-10-22 12:40:01 +02:00
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
2023-10-22 12:40:01 +02:00
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 ( ) ;
2023-10-23 02:54:45 +02:00
// bottom left corner
2023-10-22 12:40:01 +02:00
if ( he < limit _heat _min && hu < limit _humidity _min ) {
2023-10-23 02:54:45 +02:00
context . moveTo ( limit _x _min , limit _y _min ) ;
2023-10-22 12:40:01 +02:00
context . lineTo ( limit _x _min + ARROW _SIZE _CORNER , limit _y _min ) ;
2023-10-23 02:54:45 +02:00
context . lineTo ( limit _x _min , limit _y _min - ARROW _SIZE _CORNER ) ;
2023-10-20 15:23:44 +02:00
context . closePath ( ) ;
context . fill ( ) ;
2023-10-23 02:54:45 +02:00
// bottom right corner
2023-10-22 12:40:01 +02:00
} 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 ) ;
2023-10-23 02:54:45 +02:00
context . lineTo ( limit _x _max , limit _y _min - ARROW _SIZE _CORNER ) ;
2023-10-20 15:23:44 +02:00
context . closePath ( ) ;
context . fill ( ) ;
2023-10-23 02:54:45 +02:00
// top left corner
2023-10-22 12:40:01 +02:00
} 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 ) ;
2023-10-23 02:54:45 +02:00
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
2023-10-22 12:40:01 +02:00
} 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 ) ;
2023-10-23 02:54:45 +02:00
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
2023-10-22 12:40:01 +02:00
} 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
2023-10-22 12:40:01 +02:00
} 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 ( ) ;
2023-10-23 02:54:45 +02:00
// bottom side
2023-10-22 12:40:01 +02:00
} else if ( hu < limit _humidity _min ) {
context . moveTo ( x , limit _y _min ) ;
2023-10-23 02:54:45 +02:00
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 ( ) ;
2023-10-23 02:54:45 +02:00
// top side
2023-10-22 12:40:01 +02:00
} else if ( hu > limit _humidity _max ) {
context . moveTo ( x , limit _y _max ) ;
2023-10-23 02:54:45 +02:00
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 ) ;
2023-10-22 12:40:01 +02:00
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 ) {
2023-10-22 12:40:01 +02:00
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-24 17:47:55 +02:00
2023-10-27 13:58:40 +02:00
limit _x _min = Math . max ( 0 , limit _x _min ) ;
limit _x _max = Math . min ( voronoiCanvas . width , limit _x _max ) ;
limit _y _min = Math . max ( 0 , limit _y _min ) ;
limit _y _max = Math . min ( voronoiCanvas . height , limit _y _max ) ;
2023-10-24 17:47:55 +02:00
// Calculate the "grid lines pixel ratio" to reduce the
// width of grid lines or even disable rendering them.
// A high ratio means that a LOT of grid lines would render
// on the canvas.
// This code will effectively trigger if the value range
// is very high.
let xGridLinesPerPixel = ( limit _heat _max - limit _heat _min ) / voronoiCanvas . width / GRID _STEP ;
let yGridLinesPerPixel = ( limit _humidity _max - limit _humidity _min ) / voronoiCanvas . height / GRID _STEP ;
let xWidth = GRID _WIDTH _LEVEL _3 ;
let yWidth = GRID _WIDTH _LEVEL _3 ;
if ( xGridLinesPerPixel > GRID _THRESHOLD _LEVEL _0 ) {
xWidth = null ;
} else if ( xGridLinesPerPixel > GRID _THRESHOLD _LEVEL _1 ) {
xWidth = GRID _WIDTH _LEVEL _1 ;
} else if ( xGridLinesPerPixel > GRID _THRESHOLD _LEVEL _2 ) {
xWidth = GRID _WIDTH _LEVEL _2 ;
2023-10-20 13:31:23 +02:00
}
2023-10-24 17:47:55 +02:00
if ( yGridLinesPerPixel > GRID _THRESHOLD _LEVEL _0 ) {
yWidth = null ;
} else if ( yGridLinesPerPixel > GRID _THRESHOLD _LEVEL _1 ) {
yWidth = GRID _WIDTH _LEVEL _1 ;
2023-10-27 13:58:40 +02:00
} else if ( yGridLinesPerPixel > GRID _THRESHOLD _LEVEL _2 ) {
2023-10-24 17:47:55 +02:00
yWidth = GRID _WIDTH _LEVEL _2 ;
2023-10-20 13:31:23 +02:00
}
2023-10-24 17:47:55 +02:00
context . strokeStyle = GRID _COLOR ;
2023-10-27 13:58:40 +02:00
// Fallback variable to break the loop if it draws way too many lines
let steps ;
2023-10-24 17:47:55 +02:00
if ( xWidth !== null ) {
context . lineWidth = xWidth ;
2023-10-27 13:58:40 +02:00
context . beginPath ( ) ;
let x = - xWidth * 2 ;
let [ heat , _ ] = canvasPixelCoordsToBiomeCoords ( x , 0 ) ;
heat = heat - ( heat % GRID _STEP ) ;
steps = 0 ;
while ( x < voronoiCanvas . width + xWidth * 2 ) {
[ x , _ ] = biomeCoordsToCanvasPixelCoords ( heat , 0 ) ;
2023-10-24 17:47:55 +02:00
context . moveTo ( x , limit _y _min ) ;
context . lineTo ( x , limit _y _max ) ;
2023-10-27 13:58:40 +02:00
heat += GRID _STEP ;
steps ++ ;
if ( steps > 10000 ) {
console . error ( "Over 10000 grid lines on the X axis!" ) ;
break ;
}
2023-10-24 17:47:55 +02:00
}
2023-10-27 13:58:40 +02:00
context . closePath ( ) ;
context . stroke ( ) ;
2023-10-20 13:31:23 +02:00
}
2023-10-24 17:47:55 +02:00
if ( yWidth !== null ) {
context . lineWidth = yWidth ;
2023-10-27 13:58:40 +02:00
context . beginPath ( ) ;
let y = - yWidth * 2 ;
let [ _ , humidity ] = canvasPixelCoordsToBiomeCoords ( 0 , y ) ;
humidity = humidity - ( humidity % GRID _STEP ) ;
steps = 0 ;
while ( y < voronoiCanvas . height + yWidth * 2 ) {
[ _ , y ] = biomeCoordsToCanvasPixelCoords ( 0 , humidity ) ;
2023-10-24 17:47:55 +02:00
context . moveTo ( limit _x _min , y ) ;
context . lineTo ( limit _x _max , y ) ;
2023-10-27 13:58:40 +02:00
humidity -= GRID _STEP ;
steps ++ ;
if ( steps > 10000 ) {
console . error ( "Over 10000 grid lines on the Y axis!" ) ;
break ;
}
2023-10-24 17:47:55 +02:00
}
2023-10-27 13:58:40 +02:00
context . closePath ( ) ;
context . stroke ( ) ;
2023-10-20 13:31:23 +02:00
}
}
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 ) {
2023-10-24 17:20:29 +02:00
// Size of arrows (px)
2023-10-23 02:47:00 +02:00
const AXIS _ARROW _SIZE = 8 ;
2023-10-24 17:20:29 +02:00
// Offset that arrows have from the border (px)
2023-10-24 16:36:04 +02:00
const ARROW _OFFSET = 1 ;
2023-10-24 17:20:29 +02:00
// Minimum distance (px) that axis must have from the border
const AXIS _BORDER _OFFSET = 10 ;
// Maximum distance (px) from certain borders at which the axis
// labels and ticks will be put on the other sides
const AXIS _LABEL _FLIP _OFFSET = 40 ;
2023-10-23 02:47:00 +02:00
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 ;
2023-10-24 17:20:29 +02:00
// If axis would go out of bounds, force them to
// be rendered at the side instead.
let other _side _x = false ;
let other _side _y = false ;
if ( x0 <= AXIS _BORDER _OFFSET ) {
x0 = AXIS _BORDER _OFFSET ;
} else if ( x0 >= w - AXIS _BORDER _OFFSET ) {
x0 = w - AXIS _BORDER _OFFSET ;
}
if ( y0 <= AXIS _BORDER _OFFSET ) {
y0 = AXIS _BORDER _OFFSET ;
} else if ( y0 >= h - AXIS _BORDER _OFFSET ) {
y0 = h - AXIS _BORDER _OFFSET ;
}
// Flip axis labels if coming close to certain sides
if ( x0 <= AXIS _LABEL _FLIP _OFFSET ) {
other _side _y = true ;
}
if ( y0 >= h - AXIS _LABEL _FLIP _OFFSET ) {
other _side _x = true ;
}
2023-10-23 02:47:00 +02:00
// horizontal axis
context . beginPath ( ) ;
context . moveTo ( 0 , y0 ) ;
context . lineTo ( w , y0 ) ;
2023-10-24 17:20:29 +02:00
// tick
if ( other _side _x ) {
context . moveTo ( tx , y0 - AXIS _ARROW _SIZE ) ;
context . lineTo ( tx , y0 ) ;
} else {
context . moveTo ( tx , y0 ) ;
context . lineTo ( tx , y0 + AXIS _ARROW _SIZE ) ;
}
2023-10-23 02:47:00 +02:00
// arrow
2023-10-24 16:36:04 +02:00
context . moveTo ( w - ARROW _OFFSET , y0 ) ;
context . lineTo ( w - ARROW _OFFSET - AXIS _ARROW _SIZE , y0 - AXIS _ARROW _SIZE ) ;
context . moveTo ( w - ARROW _OFFSET , y0 ) ;
context . lineTo ( w - ARROW _OFFSET - AXIS _ARROW _SIZE , y0 + AXIS _ARROW _SIZE ) ;
2023-10-23 02:47:00 +02:00
context . stroke ( ) ;
context . closePath ( ) ;
// vertical axis
context . beginPath ( ) ;
context . moveTo ( x0 , 0 ) ;
context . lineTo ( x0 , h ) ;
2023-10-24 17:20:29 +02:00
// tick
if ( other _side _y ) {
context . moveTo ( x0 + AXIS _ARROW _SIZE , ty ) ;
context . lineTo ( x0 , ty ) ;
} else {
context . moveTo ( x0 , ty ) ;
context . lineTo ( x0 - AXIS _ARROW _SIZE , ty ) ;
}
2023-10-23 02:47:00 +02:00
// arrow
2023-10-24 16:36:04 +02:00
context . moveTo ( x0 , ARROW _OFFSET ) ;
context . lineTo ( x0 - AXIS _ARROW _SIZE , ARROW _OFFSET + AXIS _ARROW _SIZE ) ;
context . moveTo ( x0 , ARROW _OFFSET ) ;
context . lineTo ( x0 + AXIS _ARROW _SIZE , ARROW _OFFSET + AXIS _ARROW _SIZE ) ;
2023-10-23 02:47:00 +02:00
context . stroke ( ) ;
context . closePath ( ) ;
2023-10-24 17:20:29 +02:00
// axis+tick labels
2023-10-23 02:47:00 +02:00
context . fillStyle = "black" ;
// heat label
context . font = "100% sans-serif" ;
2023-10-24 17:20:29 +02:00
let lx , ly , ttx , tty ;
if ( other _side _x ) {
context . textBaseline = "bottom" ;
context . textAlign = "right" ;
lx = w - AXIS _ARROW _SIZE * 2 ;
ly = y0 - 4 ;
tty = y0 - AXIS _ARROW _SIZE ;
} else {
context . textBaseline = "top" ;
context . textAlign = "right" ;
lx = w - AXIS _ARROW _SIZE * 2 ;
ly = y0 + 4 ;
tty = y0 + AXIS _ARROW _SIZE ;
}
context . fillText ( "heat" , lx , ly ) ;
2023-10-23 02:47:00 +02:00
context . textAlign = "center" ;
2023-10-24 17:20:29 +02:00
context . fillText ( Math . round ( tick _heat ) , tx , tty ) ;
2023-10-23 02:47:00 +02:00
// humidity label
context . font = "100% sans-serif" ;
context . save ( ) ;
context . rotate ( - Math . PI / 2 ) ;
2023-10-24 17:20:29 +02:00
if ( other _side _y ) {
context . textBaseline = "top" ;
context . textAlign = "right" ;
lx = - AXIS _ARROW _SIZE * 2 ;
ly = x0 + 4 ;
} else {
context . textBaseline = "bottom" ;
context . textAlign = "right" ;
lx = - AXIS _ARROW _SIZE * 2 ;
ly = x0 - 4 ;
}
context . fillText ( "humidity" , lx , ly ) ;
2023-10-23 02:47:00 +02:00
context . restore ( ) ;
2023-10-24 17:20:29 +02:00
if ( other _side _y ) {
context . textAlign = "left" ;
ttx = x0 + AXIS _ARROW _SIZE - 2 ;
} else {
context . textAlign = "right" ;
ttx = x0 - AXIS _ARROW _SIZE - 2 ;
}
2023-10-23 02:47:00 +02:00
context . textBaseline = "middle" ;
2023-10-24 17:20:29 +02:00
context . fillText ( Math . round ( tick _humidity ) , ttx , ty ) ;
2023-10-23 02:47:00 +02:00
}
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
/ * G i v e n t h e l i s t o f b i o m e p o i n t s , r e t u r n s a V o r o n o i d i a g r a m o b j e c t
( 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 ) {
2023-10-27 13:05:57 +02:00
if ( ( cachedVoronoiDiagram === null ) || 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 } ;
// ... unless a point is out of bounds,
// then we increase the bounding box size
// Calculate by how much to extend the
// bounding box for the given value.
let getBufferZone = function ( value ) {
// This essentially calculates the "order of magnitude"
// in base-2.
return 2 * * Math . floor ( Math . log2 ( Math . abs ( value ) ) ) ;
// The reason why we do this is due to floating-point arithmetic.
// If we’ d add/subtract a constant offset (like 1) from the value,
// the offset might disappear if the value is very large,
// due to floating point rounding, thus effectively adding/subtracting 0.
// This scaling makes sure we'll apply an offset that is
// "in the ballpark" of the origin value
2023-10-23 08:07:36 +02:00
}
2023-10-27 13:05:57 +02:00
for ( let p of points ) {
if ( p . heat < vbbox . xl ) {
vbbox . xl = p . heat - getBufferZone ( p . heat ) ;
} else if ( p . heat > vbbox . xr ) {
vbbox . xr = p . heat + getBufferZone ( p . heat ) ;
}
if ( p . humidity < vbbox . yt ) {
vbbox . yt = p . humidity - getBufferZone ( p . humidity ) ;
} else if ( p . humidity > vbbox . yb ) {
vbbox . yb = p . humidity + getBufferZone ( p . humidity ) ;
}
2023-10-23 08:07:36 +02:00
}
2023-10-27 13:05:57 +02:00
2023-10-20 13:31:23 +02:00
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 ) ;
}
2023-10-24 19:11:46 +02:00
try {
diagram = voronoi . compute ( sites , vbbox ) ;
2023-10-26 12:27:53 +02:00
} catch ( err ) {
diagram = null ;
2023-10-26 12:53:43 +02:00
if ( err instanceof Error ) {
console . error ( "Error when calling voronoi.compute from Javascript-Voronoi library!\n" +
"* exception name: " + err . name + "\n" +
"* exception message: " + err . message + "\n" +
"* stack:\n" + err . stack ) ;
} else {
console . error ( "Error when calling voronoi.compute from Javascript-Voronoi library!" ) ;
console . error ( err ) ;
throw err ;
}
2023-10-26 12:27:53 +02:00
} finally {
2023-10-24 19:11:46 +02:00
cachedVoronoiDiagram = diagram ;
return diagram ;
}
2023-10-20 13:31:23 +02:00
} 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" ) ;
2023-10-21 21:24:58 +02:00
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 ( ) ;
2023-10-21 21:24:58 +02:00
if ( ! context ) {
2023-10-23 18:09:23 +02:00
return false ;
2023-10-21 21:24:58 +02:00
}
2023-10-21 17:12:43 +02:00
}
2023-10-23 08:48:00 +02:00
context . fillStyle = CLEAR _COLOR ;
2023-10-22 12:40:01 +02:00
context . fillRect ( - DRAW _OFFSET , - DRAW _OFFSET , voronoiCanvas . width + DRAW _OFFSET , voronoiCanvas . height + DRAW _OFFSET ) ;
2023-10-21 21:24:58 +02:00
return true ;
2023-10-21 17:12:43 +02:00
}
2023-10-23 08:48:00 +02:00
/ * R e t u r n s a l l b i o m e p o i n t s e x c e p t t h o s e w h o s e Y l i m i t s f a l l o u t o f t h e
given y value * /
2023-10-20 22:03:15 +02:00
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 ) ;
}
}
2023-10-20 22:03:15 +02:00
return points ;
}
2023-10-21 17:12:43 +02:00
2023-10-23 08:48:00 +02:00
/ * G i v e n a b i o m e I D , r e t u r n s t h e m a t c h i n g H T M L e l e m e n t f r o m t h e
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
/ * R e t u r n s b o t h t h e I D o f s e l e c t e d b i o m e a n d t h e a s s o c i a t e d H T M L e l e m e n t f r o m
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
/ * D r a w s t h e d i a g r a m o n t h e v o r o n o i C a n v a s .
Will ( re - ) calculate the Voronoi diagram if recalculate is true ;
otherwise it may re - use a previous diagram for performance reasons . * /
function draw ( recalculate ) {
2023-10-20 22:03:15 +02:00
let context = getDrawContext ( ) ;
2023-10-22 12:40:01 +02:00
let w = voronoiCanvas . width ;
let h = voronoiCanvas . height ;
2023-10-23 08:48:00 +02:00
let y = getViewY ( ) ;
2023-10-22 12:40:01 +02:00
// shorter function name (for "convert")
let conv = biomeCoordsToCanvasPixelCoords
2023-10-21 21:24:58 +02:00
if ( ! context ) {
if ( ! voronoiCanvas . hidden ) {
voronoiCanvas . hidden = true ;
2023-10-21 21:35:20 +02:00
coordinateDisplay . hidden = true ;
altitudeDisplay . hidden = true ;
2023-10-21 23:04:38 +02:00
rangeDisplay . hidden = true ;
2023-10-21 21:24:58 +02:00
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." ;
2023-10-26 12:34:22 +02:00
console . error ( "Could not get the canvas context!" ) ;
2023-10-21 21:24:58 +02:00
}
return false ;
}
2023-10-21 17:12:43 +02:00
clear ( context ) ;
2023-10-26 00:29:30 +02:00
let showDiagramMessage = function ( context , text ) {
2023-10-21 17:12:43 +02:00
context . textAlign = "center" ;
context . fillStyle = "black" ;
context . textBaseline = "middle" ;
2023-10-26 00:29:30 +02:00
if ( voronoiCanvas . width < 300 ) {
context . font = "100% sans-serif" ;
} else if ( voronoiCanvas . width < 450 ) {
context . font = "150% sans-serif" ;
} else {
context . font = "200% sans-serif" ;
}
context . fillText ( text , voronoiCanvas . width / 2 , voronoiCanvas . height / 2 ) ;
2023-10-21 21:35:20 +02:00
updateAltitudeText ( ) ;
2023-10-26 00:29:30 +02:00
}
2023-10-27 14:57:27 +02:00
// Fail and render a special message if the value range is tiny
2023-10-26 00:29:30 +02:00
if ( ( limit _heat _max - limit _heat _min < 0.01 ) || ( limit _humidity _max - limit _humidity _min < 0.01 ) ) {
showDiagramMessage ( context , "Value range is too small." ) ;
drawError = true ;
2023-10-25 03:39:29 +02:00
putResizeCorner ( context ) ;
2023-10-21 21:24:58 +02:00
return true ;
2023-10-21 17:12:43 +02:00
}
2023-10-20 22:03:15 +02:00
2023-10-27 14:57:27 +02:00
// Fail and render a special message if the value range is huge
2023-10-27 14:35:41 +02:00
if ( ( limit _heat _max - limit _heat _min > MAX _HEAT _HUMIDITY _VALUE ) || ( limit _humidity _max - limit _humidity _min > MAX _HEAT _HUMIDITY _VALUE ) ) {
showDiagramMessage ( context , "Value range is too large." ) ;
drawError = true ;
putResizeCorner ( context ) ;
return true ;
}
2023-10-27 14:57:27 +02:00
// Fail and render a special message if value limit is out of permissible bounds
if ( ( limit _heat _max > MAX _HEAT _HUMIDITY _VALUE ) || ( limit _humidity _max > MAX _HEAT _HUMIDITY _VALUE ) ) {
showDiagramMessage ( context , "Maximum value is too large." ) ;
drawError = true ;
putResizeCorner ( context ) ;
return true ;
}
if ( ( limit _heat _min < MIN _HEAT _HUMIDITY _VALUE ) || ( limit _humidity _min < MIN _HEAT _HUMIDITY _VALUE ) ) {
showDiagramMessage ( context , "Minimum value is too small." ) ;
drawError = true ;
putResizeCorner ( context ) ;
return true ;
}
2023-10-20 22:03:15 +02:00
let points = getRenderedPoints ( y ) ;
2023-10-27 14:57:27 +02:00
// Fail and render a special message if there are no biomes
2023-10-21 17:12:43 +02:00
if ( points . length === 0 ) {
if ( biomePoints . length === 0 ) {
2023-10-26 00:29:30 +02:00
showDiagramMessage ( context , "No biomes." ) ;
2023-10-21 17:12:43 +02:00
} else {
2023-10-26 00:29:30 +02:00
showDiagramMessage ( context , "No biomes in this Y altitude." ) ;
2023-10-21 17:12:43 +02:00
}
drawError = true ;
2023-10-25 03:39:29 +02:00
putResizeCorner ( context ) ;
2023-10-21 21:24:58 +02:00
return true ;
2023-10-21 17:12:43 +02:00
}
2023-10-24 19:11:46 +02:00
2023-10-21 21:35:20 +02:00
updateAltitudeText ( ) ;
2023-10-21 17:12:43 +02:00
2023-10-24 19:11:46 +02:00
let voronoiError = function ( ) {
2023-10-26 00:29:30 +02:00
showDiagramMessage ( context , "Error in Javascript-Voronoi!" ) ;
2023-10-24 19:11:46 +02:00
drawError = true ;
2023-10-25 03:39:29 +02:00
putResizeCorner ( context ) ;
2023-10-24 19:11:46 +02:00
}
2023-10-20 17:22:48 +02:00
let diagram = getVoronoiDiagram ( points , recalculate ) ;
2023-10-20 16:31:11 +02:00
2023-10-24 19:11:46 +02:00
if ( ! diagram ) {
voronoiError ( ) ;
drawError = true ;
return true ;
}
drawError = false ;
2023-10-23 09:30:55 +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 ;
2023-10-23 09:30:55 +02:00
/ * B e f o r e w e p a i n t t h e c e l l s ,
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 ) ;
2023-10-24 16:32:30 +02:00
if ( biome . colorIndex !== null ) {
2023-10-24 16:06:15 +02:00
context . strokeStyle = CELL _COLORS [ biome . colorIndex ] ;
2023-10-24 14:39:34 +02:00
} else {
let ccol = biomeID % CELL _COLORS . length ;
context . strokeStyle = CELL _COLORS [ ccol ] ;
}
2023-10-23 09:30:55 +02:00
/ * T h e l i n e c a n b e q u i t e t h i c k b e c a u s e
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 ) ;
2023-10-24 16:32:30 +02:00
if ( biome . colorIndex !== null ) {
2023-10-24 16:06:15 +02:00
context . fillStyle = CELL _COLORS [ biome . colorIndex ] ;
2023-10-24 14:39:34 +02:00
} else {
let ccol = biomeID % CELL _COLORS . length ;
context . fillStyle = CELL _COLORS [ ccol ] ;
}
2023-10-21 13:59:19 +02:00
2023-10-23 09:30:55 +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
2023-10-24 16:06:15 +02:00
let cell = diagram . cells [ 0 ] ;
let biomeID = cell . site [ biomeIDSymbol ] ;
let biome = getBiomeByID ( biomeID ) ;
2023-10-24 16:32:30 +02:00
if ( biome . colorIndex !== null ) {
2023-10-24 16:06:15 +02:00
context . fillStyle = CELL _COLORS [ biome . colorIndex ] ;
} else {
let ccol = biomeID % CELL _COLORS . length ;
context . fillStyle = CELL _COLORS [ ccol ] ;
}
2023-10-22 12:40:01 +02:00
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 ;
2023-10-22 12:40:01 +02:00
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
2023-10-22 12:40:01 +02:00
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
}
2023-10-22 12:40:01 +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 ( ) ;
2023-10-22 12:40:01 +02:00
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
}
2023-10-24 23:17:22 +02:00
2023-10-25 03:39:29 +02:00
putResizeCorner ( context ) ;
2023-10-24 23:17:22 +02:00
2023-10-21 21:24:58 +02:00
return true ;
2023-10-20 13:31:23 +02:00
}
2023-10-23 08:48:00 +02:00
/ * C l e a r s t h e b i o m e l i s t w i d g e t a n d ( r e - a d d s ) t h e l i s t e l e m e n t s
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
/ * U p d a t e t h e s t a t u s ( l i k e d i s a b l e d o r v a l u e ) o f a l l w i d g e t s t h a t
affect the diagram , based on the internal data * /
2023-10-20 14:03:45 +02:00
function updateWidgetStates ( ) {
2023-10-24 16:06:15 +02:00
let state ;
2023-10-20 14:03:45 +02:00
if ( biomePoints . length === 0 || biomeSelector . selectedIndex === - 1 ) {
2023-10-24 16:06:15 +02:00
state = "disabled" ;
2023-10-20 14:03:45 +02:00
} else {
2023-10-24 16:06:15 +02:00
state = "" ;
}
inputHeat . disabled = state ;
inputHumidity . disabled = state ;
inputMinY . disabled = state ;
inputMaxY . disabled = state ;
inputBiomeName . disabled = state ;
2023-10-24 17:59:31 +02:00
removeBiomeButton . disabled = state ;
2023-10-24 16:06:15 +02:00
for ( let c = 0 ; c < CELL _COLORS . length ; c ++ ) {
let elem = document . getElementById ( "inputBiomeColor" + c ) ;
if ( elem ) {
elem . disabled = state ;
elem . style . borderColor = "" ;
elem . innerHTML = " " ;
}
}
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 ;
inputBiomeName . value = point . name ;
let colorIndex = point . colorIndex ;
for ( let c = 0 ; c < CELL _COLORS . length ; c ++ ) {
let elem = document . getElementById ( "inputBiomeColor" + c ) ;
if ( elem ) {
if ( c === colorIndex ) {
// Add a symbol for selected color
elem . innerHTML = "●" ; // Unicode: BLACK CIRCLE
} else {
// Blank out non-selected color
elem . innerHTML = " " ;
}
}
2023-10-20 14:18:22 +02:00
}
2023-10-20 14:03:45 +02:00
}
2023-10-21 17:55:32 +02:00
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-20 14:03:45 +02:00
}
2023-10-23 08:48:00 +02:00
/ * T o b e c a l l e d w h e n a b i o m e v a l u e l i k e h e a t w a s c h a n g e d .
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 * /
2023-10-20 14:18:22 +02:00
function onChangeBiomeValueWidget ( pointField , value ) {
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
2023-10-20 22:03:15 +02:00
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-20 22:03:15 +02:00
}
2023-10-24 14:39:34 +02:00
elem . selected = "selected" ;
2023-10-24 21:47:46 +02:00
draw ( false ) ;
2023-10-24 14:39:34 +02:00
updateWidgetStates ( ) ;
return [ true , false ] ;
2023-10-20 22:03:15 +02:00
}
}
2023-10-21 00:57:25 +02:00
return [ false , false ] ;
}
2023-10-23 08:48:00 +02:00
// Returns the distance between the two 2D points (x1, y1) and (x2, y2)
2023-10-21 00:57:25 +02:00
function getDistance ( x1 , y1 , x2 , y2 ) {
return Math . sqrt ( ( x2 - x1 ) * * 2 + ( y2 - y1 ) * * 2 ) ;
2023-10-20 22:03:15 +02:00
}
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 ) {
2023-10-21 17:55:32 +02:00
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 ) {
2023-10-21 17:55:32 +02:00
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.
2023-10-20 22:03:15 +02:00
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 ) ;
2023-10-20 22:03:15 +02:00
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
2023-10-20 22:03:15 +02:00
let mouseIsDown = false ;
2023-10-24 23:17:22 +02:00
// Whether the canvas is being resized
let resizing = false ;
// Start coordinates of canvas resize or null if not resizing
let resizing _start _pos _x = null ;
let resizing _start _pos _y = null ;
// Start size of canvas when resizing or null if not resizing
let resizing _start _size _x = null ;
let resizing _start _size _y = null ;
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.
2023-10-21 00:57:25 +02:00
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
/ * M o v e a n d u p d a t e t h e b i o m e p o i n t w h i l e t h e u s e r
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 ) {
2023-10-21 00:57:25 +02:00
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 ) ;
2023-10-21 00:57:25 +02:00
selectedPoint . heat = newHeat ;
selectedPoint . humidity = newHumidity ;
2023-10-23 08:48:00 +02:00
draw ( true ) ;
2023-10-21 00:57:25 +02:00
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 00:57:25 +02:00
}
}
}
2023-10-21 01:08:35 +02:00
}
2023-10-23 08:48:00 +02:00
/ * U p d a t e s t h e t e x t s h o w i n g t h e c u r r e n t a l t i t u d e ( Y h e i g h t )
the diagram currently applies * /
function updateAltitudeText ( ) {
2023-10-27 14:35:41 +02:00
altitudeDisplay . innerHTML = "showing diagram for altitude Y=<span class='statAltitude'>" + getViewY ( ) + "</span>" ;
2023-10-23 08:48:00 +02:00
}
/ * U p d a t e t h e t e x t t h a t s h o w s t h e b i o m e c o o r d i n a t e s
of the cursor when it ' s on the diagram * /
2023-10-21 17:12:43 +02:00
function updateCoordinateDisplay ( pixelX , pixelY ) {
2023-10-24 23:17:22 +02:00
if ( pixelX === null || pixelY === null ) {
coordinateDisplay . innerHtml = " " ;
return ;
}
2023-10-21 17:12:43 +02:00
// show coordinates
let [ heat , humidity ] = canvasPixelCoordsToBiomeCoords ( pixelX , pixelY ) ;
if ( ! drawError ) {
2023-10-21 23:04:38 +02:00
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 = " " ;
}
}
2023-10-23 08:48:00 +02:00
/ * U p d a t e s a n d c h a n g e s t h e c u r s o r t y p e o n t h e d i a g r a m
canvas depending on whether we can select , drag or do nothing
at the pointed position * /
2023-10-24 23:17:22 +02:00
function updateCanvasCursorStatus ( x , y ) {
// Show resize cursor at the bottom right corner
if ( resizing || ( x > voronoiCanvas . width - RESIZE _CORNER && y > voronoiCanvas . height - RESIZE _CORNER ) ) {
voronoiCanvas . style . cursor = "nwse-resize" ;
return
}
2023-10-21 19:26:24 +02:00
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
}
2023-10-24 23:17:22 +02:00
let nearest = getNearestPointFromCanvasPos ( x , y , POINT _SELECT _DISTANCE ) ;
2023-10-21 19:26:24 +02:00
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-24 23:17:22 +02:00
// This cursor indicates we can select 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" ;
}
}
/ * U n h i d e t h e m a i n c o n t e n t . U s e d t o d i s a b l e t h e n o s c r i p t
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 ;
/ * A l s o h i d e t h e c o n t a i n e r h o l d i n g t h e n o s c r i p t e r r o r
message to avoid spacing issues * /
noscriptContainer . hidden = true ;
}
2023-10-24 16:06:15 +02:00
function initBiomeColorSelectors ( ) {
for ( let c = 0 ; c < CELL _COLORS . length ; c ++ ) {
let button = document . createElement ( "button" ) ;
button . type = "button" ;
button . style . backgroundColor = CELL _COLORS [ c ] ;
button . style . width = "2em" ;
button . style . height = "2em" ;
button . innerHTML = " " ;
button . id = "inputBiomeColor" + c ;
button . className = "biomeColorButton" ;
biomeColorSection . append ( button ) ;
button . onclick = function ( ) {
onChangeBiomeValueWidget ( "colorIndex" , c ) ;
updateWidgetStates ( ) ;
}
}
}
2023-10-24 23:33:00 +02:00
function fitCanvasInBody ( ) {
// Get x,y position of canvas
let bodyRect = document . body . getBoundingClientRect ( ) ;
let canvasRect = voronoiCanvas . getBoundingClientRect ( ) ;
let cx = canvasRect . left - bodyRect . left ;
let cy = canvasRect . top - bodyRect . top ;
// Calculate new size
let rx = window . innerWidth - cx - CANVAS _PAGE _MARGIN _RIGHT ;
let oldWidth = voronoiCanvas . width ;
let newWidth = Math . max ( MIN _CANVAS _SIZE , rx ) ;
if ( newWidth < oldWidth ) {
// Resize
if ( voronoiCanvas . height === voronoiCanvas . width ) {
voronoiCanvas . height = newWidth ;
}
voronoiCanvas . width = newWidth ;
draw ( false ) ;
}
}
2023-10-23 08:48:00 +02:00
/***** EVENTS *****/
2023-10-24 23:33:00 +02:00
2023-10-24 23:17:22 +02:00
/* Body events */
2023-10-24 23:33:00 +02:00
window . onresize = function ( event ) {
fitCanvasInBody ( ) ;
}
2023-10-24 23:17:22 +02:00
document . body . onmousemove = function ( event ) {
if ( resizing ) {
// Get x,y position of canvas
let bodyRect = document . body . getBoundingClientRect ( ) ;
let canvasRect = voronoiCanvas . getBoundingClientRect ( ) ;
let cx = canvasRect . left - bodyRect . left ;
let cy = canvasRect . top - bodyRect . top ;
// Calculate new size
let rx = event . pageX - resizing _start _pos _x - cx ;
let ry = event . pageY - resizing _start _pos _y - cy ;
2023-10-23 08:48:00 +02:00
2023-10-24 23:17:22 +02:00
// Limit the width
2023-10-24 23:33:00 +02:00
let maxX = ( bodyRect . width - cx ) - CANVAS _PAGE _MARGIN _RIGHT ;
2023-10-24 23:17:22 +02:00
// Resize
voronoiCanvas . width = Math . min ( maxX , Math . max ( MIN _CANVAS _SIZE , resizing _start _size _x + rx ) ) ;
// Holding down Shift preserves aspect ratio
if ( event . shiftKey ) {
voronoiCanvas . height = voronoiCanvas . width ;
} else {
voronoiCanvas . height = Math . max ( MIN _CANVAS _SIZE , resizing _start _size _y + ry ) ;
}
draw ( false ) ;
return ;
}
}
document . body . onmouseup = function ( event ) {
if ( resizing ) {
resizing = false ;
updateCanvasCursorStatus ( event . offsetX , event . offsetY ) ;
}
}
document . body . onmouseleave = function ( event ) {
if ( resizing ) {
resizing = false ;
updateCanvasCursorStatus ( event . offsetX , event . offsetY ) ;
}
}
/* Canvas events */
2023-10-21 01:08:35 +02:00
voronoiCanvas . onmousemove = function ( event ) {
2023-10-24 23:17:22 +02:00
if ( resizing ) {
updateCoordinateDisplay ( event . offsetX , event . offsetY ) ;
updateCanvasCursorStatus ( event . offsetX , event . offsetY ) ;
canvas _cursor _x = event . offsetX ;
canvas _cursor _y = event . offsetY ;
draw ( false ) ;
return
}
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-24 23:17:22 +02:00
updateCanvasCursorStatus ( event . offsetX , event . offsetY ) ;
canvas _cursor _x = event . offsetX ;
canvas _cursor _y = event . offsetY ;
draw ( false ) ;
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-24 23:17:22 +02:00
updateCanvasCursorStatus ( event . offsetX , event . offsetY ) ;
canvas _cursor _x = event . offsetX ;
canvas _cursor _y = event . offsetY ;
draw ( false ) ;
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-24 23:17:22 +02:00
// Resizing the canvas
if ( event . offsetX > voronoiCanvas . width - RESIZE _CORNER && event . offsetY > voronoiCanvas . height - RESIZE _CORNER ) {
resizing _start _pos _x = event . offsetX ;
resizing _start _pos _y = event . offsetY ;
resizing _start _size _x = + this . width ;
resizing _start _size _y = + this . height ;
resizing = true ;
return ;
}
2023-10-24 19:11:46 +02:00
if ( drawError || ! showPoints ) {
2023-10-21 13:59:19 +02:00
// 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-24 23:17:22 +02:00
updateCanvasCursorStatus ( event . offsetX , event . offsetY ) ;
2023-10-21 01:08:35 +02:00
}
}
2023-10-26 11:35:46 +02:00
voronoiCanvas . ondblclick = function ( event ) {
// Add a biome at double-click position, if possible
if ( drawError || dragDropState !== 0 ) {
return ;
}
// No-op if in selection range
let nearest = getNearestPointFromCanvasPos ( event . offsetX , event . offsetY , POINT _SELECT _DISTANCE ) ;
if ( nearest !== null ) {
return ;
}
let [ he , hu ] = canvasPixelCoordsToBiomeCoords ( event . offsetX , event . offsetY ) ;
addBiome ( { name : generateBiomeName ( lastBiomeID ) , heat : he , humidity : hu , min _y : MIN _Y _DEFAULT , max _y : MAX _Y _DEFAULT } ) ;
}
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 ;
2023-10-21 00:57:25 +02:00
dragDropPointID = null ;
2023-10-24 14:39:34 +02:00
dragDropState = 0 ;
2023-10-20 22:03:15 +02:00
}
voronoiCanvas . onmouseleave = function ( ) {
2023-10-21 01:48:45 +02:00
// end drag-n-drop
2023-10-20 22:03:15 +02:00
mouseIsDown = false ;
2023-10-24 14:39:34 +02:00
dragDropStartPos = null ;
2023-10-21 00:57:25 +02:00
dragDropPointID = null ;
2023-10-24 14:39:34 +02:00
dragDropState = 0 ;
2023-10-24 23:17:22 +02:00
canvas _cursor _x = null ;
canvas _cursor _y = null ;
2023-10-21 01:48:45 +02:00
coordinateDisplay . innerHTML = " " ;
2023-10-24 23:17:22 +02:00
draw ( false ) ;
2023-10-20 22:03:15 +02:00
}
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 ( ) {
2023-10-26 11:35:46 +02:00
// Add a biome at a random position
2023-10-27 14:35:41 +02:00
let he _min = Math . max ( MIN _HEAT _HUMIDITY _VALUE , limit _heat _min ) ;
let he _max = Math . min ( MAX _HEAT _HUMIDITY _VALUE , limit _heat _max ) ;
2023-10-27 14:47:20 +02:00
let hu _min = Math . max ( MIN _HEAT _HUMIDITY _VALUE , limit _humidity _min ) ;
let hu _max = Math . min ( MAX _HEAT _HUMIDITY _VALUE , limit _humidity _max ) ;
2023-10-27 14:35:41 +02:00
let he = Math . round ( he _min + Math . random ( ) * ( he _max - he _min ) ) ;
let hu = Math . round ( hu _min + Math . random ( ) * ( hu _max - hu _min ) ) ;
2023-10-26 11:35:46 +02:00
addBiome ( { name : generateBiomeName ( lastBiomeID ) , heat : he , humidity : hu , min _y : MIN _Y _DEFAULT , max _y : MAX _Y _DEFAULT } ) ;
2023-10-23 08:48:00 +02:00
}
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 */
2023-10-27 14:35:41 +02:00
function handleBiomeNumberInput ( biomeValueName , element ) {
let val = + element . value ;
if ( element . value === "" || typeof val !== "number" ) {
return ;
}
if ( element . min !== "" && ( val < + element . min || val > + element . max ) ) {
return ;
}
onChangeBiomeValueWidget ( biomeValueName , val ) ;
}
2023-10-23 08:48:00 +02:00
inputHeat . oninput = function ( ) {
2023-10-27 14:35:41 +02:00
handleBiomeNumberInput ( "heat" , this ) ;
2023-10-23 08:48:00 +02:00
}
inputHumidity . oninput = function ( ) {
2023-10-27 14:35:41 +02:00
handleBiomeNumberInput ( "humidity" , this ) ;
2023-10-23 08:48:00 +02:00
}
inputMinY . oninput = function ( ) {
2023-10-27 14:35:41 +02:00
handleBiomeNumberInput ( "min_y" , this ) ;
2023-10-23 08:48:00 +02:00
}
inputMaxY . oninput = function ( ) {
2023-10-27 14:35:41 +02:00
handleBiomeNumberInput ( "max_y" , this ) ;
2023-10-23 08:48:00 +02:00
}
inputBiomeName . oninput = function ( ) {
onChangeBiomeValueWidget ( "name" , this . value ) ;
}
/* Diagram view settings events */
inputViewY . oninput = function ( ) {
2023-10-27 14:35:41 +02:00
let y = + this . value ;
if ( y === null ) {
return ;
}
viewY = Math . floor ( y ) ;
2023-10-25 04:13:09 +02:00
draw ( true ) ;
2023-10-23 08:48:00 +02:00
updateAltitudeText ( ) ;
}
2023-10-21 13:59:19 +02:00
inputCheckboxNames . onchange = function ( ) {
showNames = this . checked ;
2023-10-24 21:47:46 +02:00
draw ( false ) ;
2023-10-21 13:59:19 +02:00
}
inputCheckboxPoints . onchange = function ( ) {
showPoints = this . checked ;
2023-10-24 21:47:46 +02:00
draw ( false ) ;
2023-10-21 13:59:19 +02:00
}
inputCheckboxCellColors . onchange = function ( ) {
showCellColors = this . checked ;
2023-10-24 21:47:46 +02:00
draw ( false ) ;
2023-10-21 13:59:19 +02:00
}
inputCheckboxGrid . onchange = function ( ) {
showGrid = this . checked ;
2023-10-24 21:47:46 +02:00
draw ( false ) ;
2023-10-21 13:59:19 +02:00
}
2023-10-23 02:47:00 +02:00
inputCheckboxAxes . onchange = function ( ) {
showAxes = this . checked ;
2023-10-24 21:47:46 +02:00
draw ( false ) ;
2023-10-23 02:47:00 +02:00
}
2023-10-24 21:47:46 +02:00
/* Noise parameters events */
2023-10-27 14:35:41 +02:00
function updateNoiseParam ( noiseName , noiseValueName , element ) {
if ( element . value === "" ) {
return ;
}
let val = + element . value ;
noises [ noiseName ] [ noiseValueName ] = val ;
2023-10-21 17:55:32 +02:00
clear ( ) ;
updateAreaVars ( ) ;
2023-10-23 08:48:00 +02:00
draw ( true ) ;
2023-10-21 17:55:32 +02:00
}
2023-10-27 14:35:41 +02:00
inputNoiseHeatScale . oninput = function ( ) {
updateNoiseParam ( "heat" , "scale" , this ) ;
}
2023-10-21 21:00:51 +02:00
inputNoiseHeatOffset . oninput = function ( ) {
2023-10-27 14:35:41 +02:00
updateNoiseParam ( "heat" , "offset" , this ) ;
2023-10-21 17:55:32 +02:00
}
2023-10-21 21:00:51 +02:00
inputNoiseHeatPersistence . oninput = function ( ) {
2023-10-27 14:35:41 +02:00
updateNoiseParam ( "heat" , "persistence" , this ) ;
2023-10-21 17:12:43 +02:00
}
2023-10-21 21:00:51 +02:00
inputNoiseHeatOctaves . oninput = function ( ) {
2023-10-27 14:35:41 +02:00
updateNoiseParam ( "heat" , "octaves" , this ) ;
2023-10-21 17:12:43 +02:00
}
2023-10-21 21:00:51 +02:00
inputNoiseHumidityScale . oninput = function ( ) {
2023-10-27 14:35:41 +02:00
updateNoiseParam ( "humidity" , "scale" , this ) ;
2023-10-21 17:12:43 +02:00
}
2023-10-21 21:00:51 +02:00
inputNoiseHumidityOffset . oninput = function ( ) {
2023-10-27 14:35:41 +02:00
updateNoiseParam ( "humidity" , "offset" , this ) ;
2023-10-21 17:55:32 +02:00
}
2023-10-21 21:00:51 +02:00
inputNoiseHumidityPersistence . oninput = function ( ) {
2023-10-27 14:35:41 +02:00
updateNoiseParam ( "humidity" , "persistence" , this ) ;
2023-10-21 17:55:32 +02:00
}
2023-10-21 21:00:51 +02:00
inputNoiseHumidityOctaves . oninput = function ( ) {
2023-10-27 14:35:41 +02:00
updateNoiseParam ( "humidity" , "octaves" , this ) ;
2023-10-21 17:12:43 +02:00
}
inputNoiseReset . onclick = function ( ) {
2023-10-21 17:55:32 +02:00
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-25 03:24:42 +02:00
/* Export events */
inputExportLua . onclick = function ( ) {
let str = "" ;
for ( let b = 0 ; b < biomePoints . length ; b ++ ) {
let biome = biomePoints [ b ] ;
2023-10-26 00:14:46 +02:00
// escape the name for Lua
let escapedName = biome . name ;
// escape backslash
escapedName = escapedName . replace ( /\\/g , '\\\\' ) ;
// escape quotation mark
escapedName = escapedName . replace ( /"/g , '\\\"' ) ;
2023-10-25 03:24:42 +02:00
str += "minetest.register_biome({\n" ;
2023-10-26 00:14:46 +02:00
str += ` name = \" ${ escapedName } \" , \n ` ;
2023-10-25 03:24:42 +02:00
str += ` heat_point = ${ biome . heat } , \n ` ;
str += ` humidity_point = ${ biome . humidity } , \n ` ;
str += ` y_min = ${ biome . min _y } , \n ` ;
str += ` y_max = ${ biome . max _y } , \n ` ;
str += "})\n" ;
}
exportSectionText . innerText = str ;
if ( str === "" ) {
exportSectionText . hidden = true ;
exportLabel . innerText = "Export is empty." ;
} else {
exportSectionText . hidden = false ;
exportLabel . innerText = "Exported biomes (as Lua code):" ;
}
exportSectionOuter . hidden = false ;
}
inputExportJSON . onclick = function ( ) {
// Convert the biome points to a new table
let jsonPoints = [ ] ;
for ( let b = 0 ; b < biomePoints . length ; b ++ ) {
let biome = biomePoints [ b ] ;
let jsonPoint = { } ;
// Use different field names for the
// JSON export to match the Lua field
// names exactly.
jsonPoint . name = biome . name ;
jsonPoint . heat _point = biome . heat ;
jsonPoint . humidity _point = biome . humidity ;
jsonPoint . y _min = biome . min _y ;
jsonPoint . y _max = biome . max _y ;
jsonPoints . push ( jsonPoint ) ;
}
let str = JSON . stringify ( jsonPoints , undefined , 4 ) ;
exportSectionText . innerText = str ;
if ( str === "" ) {
exportSectionText . hidden = true ;
exportLabel . innerText = "Export is empty." ;
} else {
exportSectionText . hidden = false ;
exportLabel . innerText = "Exported biomes (as JSON):" ;
}
exportSectionOuter . hidden = false ;
}
2023-10-27 16:18:41 +02:00
// Assuming that hexColor is a string of the form
// "#xxxxxx" (where x is a hexadecimal digit),
// returns an object of the form { r: 0, g: 0, b: 0 }
function hexColorToRGBColor ( hexColor ) {
if ( typeof hexColor !== "string" ) {
return null ;
}
let rh = hexColor . slice ( 1 , 3 ) ;
let gh = hexColor . slice ( 3 , 5 ) ;
let bh = hexColor . slice ( 5 , 7 ) ;
if ( rh === "" || gh === "" || bh === "" ) {
return null ;
}
let r = Number ( "0x" + rh )
let g = Number ( "0x" + gh )
let b = Number ( "0x" + bh )
if ( typeof r !== "number" || typeof g !== "number" || typeof b !== "number" ) {
return null ;
}
return { r : r , g : g , b : b }
}
2023-10-26 00:50:33 +02:00
inputExportAmidstForMinetest . onclick = function ( ) {
let jsonOut = { } ;
jsonOut . name = "MiBPoV Export" ;
let jsonPoints = [ ] ;
for ( let b = 0 ; b < biomePoints . length ; b ++ ) {
let biome = biomePoints [ b ] ;
let jsonPoint = { } ;
jsonPoint . name = biome . name ;
2023-10-27 16:18:41 +02:00
let color = hexColorToRGBColor ( CELL _COLORS [ biome . colorIndex ] ) ;
if ( color !== null ) {
jsonPoint . color = color ;
} else {
jsonPoint . color = { r : 255 , g : 255 , b : 255 } ;
}
2023-10-26 00:50:33 +02:00
jsonPoint . y _min = biome . min _y ;
jsonPoint . y _max = biome . max _y ;
2023-10-27 16:18:41 +02:00
jsonPoint . heat _point = biome . heat ;
jsonPoint . humidity _point = biome . humidity ;
2023-10-26 00:50:33 +02:00
jsonPoints . push ( jsonPoint ) ;
}
jsonOut . biomeList = jsonPoints ;
2023-10-27 16:18:41 +02:00
let str = JSON . stringify ( jsonOut , undefined , 4 ) ;
2023-10-26 00:50:33 +02:00
exportSectionText . innerText = str ;
if ( str === "" ) {
exportSectionText . hidden = true ;
exportLabel . innerText = "Export is empty." ;
} else {
exportSectionText . hidden = false ;
2023-10-27 16:18:41 +02:00
exportLabel . innerText = "Exported biomes (as Amidst for Minetest biome profile):" ;
2023-10-26 00:50:33 +02:00
}
exportSectionOuter . hidden = false ;
}
2023-10-25 03:24:42 +02:00
inputExportClear . onclick = function ( ) {
exportSectionOuter . hidden = true ;
exportSectionText . innerText = "" ;
}
2023-10-25 04:12:34 +02:00
/* Import */
inputImportSubmit . onclick = function ( ) {
2023-10-25 10:19:21 +02:00
let importMessage = function ( message ) {
importResultOuter . hidden = false ;
importResultMessage . innerText = message ;
}
2023-10-25 04:12:34 +02:00
let importStr = inputImport . value ;
2023-10-25 10:19:21 +02:00
// Do the JSON parse
2023-10-25 04:12:34 +02:00
let reviver = function ( key , value ) {
if ( key === "name" && ( ( typeof value ) === "string" ) ) {
return value ;
} else if ( ( ( typeof value ) === "number" ) && (
key === "humidity_point" ||
key === "heat_point" ||
key === "y_min" ||
key === "y_max" ) ) {
return value ;
} else {
return value ;
}
}
2023-10-25 10:19:21 +02:00
let parsedJSON ;
try {
parsedJSON = JSON . parse ( importStr , reviver ) ;
2023-10-26 12:27:53 +02:00
} catch ( err ) {
if ( err . name === "SyntaxError" ) {
let details = err . message ;
if ( ! details ) {
details = "<none given>" ;
}
importMessage ( "Import failed. Not a syntactically valid JSON object. Details: " + details ) ;
} else {
importMessage ( "Import failed due to internal error of type '" + err . name + "': " + err . message ) ;
2023-10-26 12:34:22 +02:00
console . error ( "Internal error while calling JSON.parse during import!\n" +
2023-10-26 12:53:43 +02:00
"* exception name: " + err . name + "\n" +
"* exception message: " + err . message + "\n" +
"* stack:\n" + err . stack ) ;
2023-10-26 12:27:53 +02:00
}
return ;
2023-10-25 10:19:21 +02:00
}
2023-10-25 04:12:34 +02:00
if ( typeof parsedJSON !== "object" ) {
2023-10-26 12:27:53 +02:00
importMessage ( "Import failed. JSON.parse didn’ t return an object but it didn’ t throw an exception?!" ) ;
2023-10-26 12:34:22 +02:00
console . error ( "JSON.parse didn't return an object although it didn't throw an exception" ) ;
2023-10-25 04:12:34 +02:00
return ;
}
2023-10-25 10:19:21 +02:00
// Populate the temporary newPoints that MAY
// set the biomePoints if successful
2023-10-25 04:12:34 +02:00
let newPoints = [ ] ;
lastBiomeID = 0 ;
2023-10-25 10:19:21 +02:00
let fieldsToCheck = [
{ fieldName : "name" , type : "string" } ,
{ fieldName : "heat_point" , type : "number" } ,
{ fieldName : "humidity_point" , type : "number" } ,
{ fieldName : "y_min" , type : "number" } ,
{ fieldName : "y_max" , type : "number" } ,
]
2023-10-25 04:12:34 +02:00
for ( let p = 0 ; p < parsedJSON . length ; p ++ ) {
let parsedPoint = parsedJSON [ p ] ;
2023-10-25 10:19:21 +02:00
// Type checking
for ( let f = 0 ; f < fieldsToCheck . length ; f ++ ) {
let field = fieldsToCheck [ f ] . fieldName ;
let wantType = fieldsToCheck [ f ] . type ;
let gotType = typeof parsedPoint [ field ] ;
if ( gotType === "undefined" ) {
importMessage ( ` Import failed. attribute " ${ field } " of biome # ${ p } is undefined. ` )
return ;
} else if ( gotType !== wantType ) {
importMessage ( ` Import failed. attribute " ${ field } " of biome # ${ p } is of type " ${ gotType } " but " ${ wantType } " expected. ` )
return ;
}
}
2023-10-25 04:12:34 +02:00
let newPoint = {
id : lastBiomeID ,
name : parsedPoint . name ,
heat : parsedPoint . heat _point ,
humidity : parsedPoint . humidity _point ,
min _y : parsedPoint . y _min ,
max _y : parsedPoint . y _max ,
colorIndex : lastBiomeID % CELL _COLORS . length ,
} ;
lastBiomeID ++ ;
newPoints . push ( newPoint ) ;
}
2023-10-25 10:19:21 +02:00
// Replace the biomes
2023-10-25 04:12:34 +02:00
biomePoints = newPoints ;
repopulateBiomeSelector ( ) ;
updateWidgetStates ( ) ;
draw ( true ) ;
2023-10-25 10:19:21 +02:00
if ( biomePoints . length === 1 ) {
importMessage ( "Import successful. 1 biome imported." ) ;
} else {
importMessage ( ` Import successful. ${ biomePoints . length } biomes imported. ` ) ;
}
2023-10-25 04:12:34 +02:00
}
2023-10-25 03:24:42 +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-25 03:24:42 +02:00
importHeaderLink . onclick = function ( ) {
toggleConfigSectionDisplay ( this , importContainer ) ;
}
exportHeaderLink . onclick = function ( ) {
toggleConfigSectionDisplay ( this , exportContainer ) ;
}
2023-10-22 02:11:36 +02:00
2023-10-23 08:48:00 +02:00
/* Load events */
2023-10-23 01:46:24 +02:00
2023-10-24 16:06:15 +02:00
window . addEventListener ( "load" , initBiomeColorSelectors ) ;
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 ) ;
2023-10-24 23:33:00 +02:00
window . addEventListener ( "load" , fitCanvasInBody ) ;