"use strict"; const MIN_Y_DEFAULT = -31000 const MAX_Y_DEFAULT = 31000 const LIMIT_MIN = -37.5 const LIMIT_MAX = 137.5 const DRAW_MIN = LIMIT_MIN - 10 const DRAW_MAX = LIMIT_MAX + 10 const GRID_STEP = 10 const MIDPOINT = 50 // Symbol for storing the biome ID in site objects // for the Voronoi script const biomeIDSymbol = Symbol("Biome ID"); let lastBiomeID = 0; let biomePoints = []; function addBiome(biomeDef) { biomeDef.id = lastBiomeID; biomePoints.push(biomeDef); lastBiomeID++; } // 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}) function getViewY() { if (!inputViewY) { return 0; } return inputViewY.value; } const pointColor = "#913636"; const pointColorSelected = "#e19696"; const edgeColor = "#0f2c2e"; const gridColor = "#00000040"; const axisColor = "#000000"; const clearColor = "#ecddba"; const cellColors = [ "#64988e", "#3d7085", "#345644", "#6b7f5c", "#868750", "#a7822c", "#a06e38", "#ad5f52", "#692f11", "#89542f", "#796e63", "#a17d5e", "#5a3f20", "#836299", ]; // returns the biome point by its given ID // or null if it couldn't be found function getBiomeByID(id) { for(let b=0; b LIMIT_MAX || y < LIMIT_MIN || y > LIMIT_MAX) { return; } if (x > MIDPOINT) { context.textAlign = "right"; x = x-2; } else { context.textAlign = "left"; x = x+2; } if (y < MIDPOINT) { context.textBaseline = "top"; } else { context.textBaseline = "alphabetic"; } context.font = "35% sans-serif" context.fillText(point.name, x, y); } function putPoint(context, point) { const ARROW_SIZE_SIDE = 2.25; const ARROW_SIZE_CORNER = 2.5; 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) { 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); 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); 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); 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); 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); 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); 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); 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); context.closePath(); context.fill(); } // Point is in bounds: Draw a dot } else { context.beginPath(); context.moveTo(0, 0); context.arc(x, y, 2, 0, Math.PI * 2); context.closePath(); context.fill(); } }; function putGrid(context) { context.lineWidth = 0.5; context.strokeStyle = gridColor; for (let x=0; x<=LIMIT_MAX; x+=GRID_STEP) { context.beginPath(); context.moveTo(x, LIMIT_MIN); context.lineTo(x, LIMIT_MAX); context.stroke(); } for (let x=-GRID_STEP; x>=LIMIT_MIN; x-=GRID_STEP) { context.beginPath(); context.moveTo(x, LIMIT_MIN); context.lineTo(x, LIMIT_MAX); context.stroke(); } for (let y=0; y<=LIMIT_MAX; y+=GRID_STEP) { context.beginPath(); context.moveTo(LIMIT_MIN, y); context.lineTo(LIMIT_MAX, y); context.stroke(); } for (let y=-GRID_STEP; y>=LIMIT_MIN; y-=GRID_STEP) { context.beginPath(); context.moveTo(LIMIT_MIN, y); context.lineTo(LIMIT_MAX, y); context.stroke(); } } function putAxes(context) { context.lineWidth = 0.75; // heat axis (horizontal) context.beginPath(); context.moveTo(LIMIT_MIN,0) context.lineTo(LIMIT_MAX,0) context.closePath(); context.stroke(); // humidity axis () context.beginPath(); context.moveTo(0,LIMIT_MIN) context.lineTo(0,LIMIT_MAX) context.closePath(); context.stroke(); } // Cache diagram object for performance boost 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 sites = [] for (let p of points) { sites.push(biomePointToVoronoiPoint(p)); } let voronoi = new Voronoi(); let diagram = null; if (cachedVoronoiDiagram && recalculate) { diagram = cachedVoronoiDiagram; // This should improve performance voronoi.recycle(diagram); } diagram = voronoi.compute(sites, vbbox); cachedVoronoiDiagram = diagram; return diagram; } else { return cachedVoronoiDiagram; } } function getDrawContext() { let canvas = document.getElementById("voronoiCanvas"); // TODO: Check for getContext support of browser return canvas.getContext("2d"); } function drawInit() { let context = getDrawContext(); context.scale(voronoiCanvas.width/(LIMIT_MAX-LIMIT_MIN), voronoiCanvas.height/(LIMIT_MAX-LIMIT_MIN)); context.translate(-LIMIT_MIN, -LIMIT_MIN); } function getRenderedPoints(y) { let points = []; for (let p=0; p= point.min_y && y <= point.max_y) { points.push(point); } } return points; } function draw(y, recalculate) { let context = getDrawContext(); // Clear draw area context.fillStyle = clearColor; context.fillRect(DRAW_MIN, DRAW_MIN, DRAW_MAX-DRAW_MIN, DRAW_MAX-DRAW_MIN); let points = getRenderedPoints(y); let diagram = getVoronoiDiagram(points, recalculate); // Render cell colors let colors = cellColors; for (let c=0; c 0) { //putAxes(context); putGrid(context); } // Render Voronoi cell edges context.lineWidth = 1; for (let e=0; e 0) { let newIndex = firstIndex-1; if (newIndex < 0) { newIndex = 0; } biomeSelector.options[newIndex].selected = "selected"; } draw(getViewY(), true); updateWidgetStates(); } function selectPoint(point) { for (let elem of biomeSelector.options) { let strID = elem.id; let elemID = null; if (strID.startsWith("biome_list_element_")) { let slice = strID.slice(19); if (slice) { elemID = +slice; } } if (elemID !== null) { if (point.id === elemID) { if (elem.selected) { console.log("Already selected!"); return [true, true]; } elem.selected = "selected"; draw(getViewY(), true); updateWidgetStates(); return [true, false]; } } } return [false, false]; } 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); 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; return [pixelX, pixelY]; } function getNearestPointFromCanvasPos(x, y, maxDist) { let nearestPoint = null; let nearestDist = null; let points = getRenderedPoints(getViewY()); for (let i=0; i