diff --git a/index.html b/index.html index eb703795..c4280dd9 100644 --- a/index.html +++ b/index.html @@ -7645,7 +7645,6 @@ - diff --git a/src/layers/renderers/drawRoutes.ts b/src/layers/renderers/drawRoutes.ts index d7308bea..2c9751f4 100644 --- a/src/layers/renderers/drawRoutes.ts +++ b/src/layers/renderers/drawRoutes.ts @@ -2,11 +2,17 @@ import * as d3 from "d3"; import {round} from "utils/stringUtils"; +const lineGenTypeMap: {[key in IRoute["type"]]: d3.CurveFactory | d3.CurveFactoryLineOnly} = { + road: d3.curveCatmullRom.alpha(0.1), + trail: d3.curveCatmullRom.alpha(0.1), + sea: d3.curveBundle.beta(1) +}; + export function drawRoutes() { routes.selectAll("path").remove(); const {cells, burgs} = pack; - const lineGen = d3.line().curve(d3.curveCatmullRom.alpha(0.1)); + const lineGen = d3.line(); const SHARP_ANGLE = 135; const VERY_SHARP_ANGLE = 115; @@ -15,8 +21,10 @@ export function drawRoutes() { const routePaths: Dict = {}; for (const {i, type, cells} of pack.routes) { - straightenPathAngles(cells); // mutates points + if (type !== "sea") straightenPathAngles(cells); // mutates points const pathPoints = cells.map(cellId => points[cellId]); + + lineGen.curve(lineGenTypeMap[type]); const path = round(lineGen(pathPoints)!, 1); if (!routePaths[type]) routePaths[type] = []; diff --git a/src/layers/toggles.ts b/src/layers/toggles.ts index 6c97ef9e..27ab35fc 100644 --- a/src/layers/toggles.ts +++ b/src/layers/toggles.ts @@ -375,14 +375,11 @@ function toggleRivers(event?: MouseEvent) { function toggleRoutes(event?: MouseEvent) { if (!layerIsOn("toggleRoutes")) { turnLayerButtonOn("toggleRoutes"); - $("#routes").fadeIn(); + renderLayer("routes"); if (isCtrlPressed(event)) editStyle("routes"); } else { - if (isCtrlPressed(event)) { - editStyle("routes"); - return; - } - $("#routes").fadeOut(); + if (isCtrlPressed(event)) return editStyle("routes"); + routes.selectAll("path").remove(); turnLayerButtonOff("toggleRoutes"); } } diff --git a/src/modules/dynamic/export-json.js b/src/modules/dynamic/export-json.js index 215ba2dc..a71647cf 100644 --- a/src/modules/dynamic/export-json.js +++ b/src/modules/dynamic/export-json.js @@ -76,7 +76,7 @@ function getGridCellsDataJson() { const gridCells = getGridCellsData(); const exportData = {info, gridCells}; - TIME && console.log("getGridCellsDataJson"); + TIME && console.timeEnd("getGridCellsDataJson"); return JSON.stringify(exportData); } diff --git a/src/modules/io/cloud.js b/src/modules/io/cloud.js index 18da11a2..f68704f0 100644 --- a/src/modules/io/cloud.js +++ b/src/modules/io/cloud.js @@ -59,7 +59,7 @@ window.Cloud = (function () { async save(fileName, contents) { const resp = await this.call("filesUpload", {path: "/" + fileName, contents}); - DEBUG && console.log("Dropbox response:", resp); + DEBUG && console.info("Dropbox response:", resp); return true; }, @@ -103,7 +103,7 @@ window.Cloud = (function () { // Callback function for auth window async setDropBoxToken(token) { - DEBUG && console.log("Access token:", token); + DEBUG && console.info("Access token:", token); setToken(this.name, token); await this.connect(token); this.authWindow.close(); @@ -130,7 +130,7 @@ window.Cloud = (function () { allow_download: true }; const resp = await this.call("sharingCreateSharedLinkWithSettings", {path, settings}); - DEBUG && console.log("Dropbox link object:", resp.result); + DEBUG && console.info("Dropbox link object:", resp.result); return resp.result.url; } }; diff --git a/src/modules/io/load.js b/src/modules/io/load.js index eff5ce21..af0f973f 100644 --- a/src/modules/io/load.js +++ b/src/modules/io/load.js @@ -75,7 +75,7 @@ export function quickLoad() { async function loadFromDropbox() { const mapPath = document.getElementById("loadFromDropboxSelect")?.value; - DEBUG && console.log("Loading map from Dropbox:", mapPath); + DEBUG && console.info("Loading map from Dropbox:", mapPath); const blob = await Cloud.providers.dropbox.load(mapPath); uploadMap(blob); } diff --git a/src/modules/routes-generator.js b/src/modules/routes-generator.js deleted file mode 100644 index 22132637..00000000 --- a/src/modules/routes-generator.js +++ /dev/null @@ -1,287 +0,0 @@ -import * as d3 from "d3"; -import FlatQueue from "flatqueue"; - -import {TIME} from "config/logging"; -import {findCell} from "utils/graphUtils"; -import {last} from "utils/arrayUtils"; -import {round} from "utils/stringUtils"; - -window.Routes = (function () { - const getRoads = function () { - TIME && console.time("generateMainRoads"); - const cells = pack.cells; - const burgs = pack.burgs.filter(b => b.i && !b.removed); - const capitals = burgs.filter(b => b.capital).sort((a, b) => a.population - b.population); - - if (capitals.length < 2) return []; // not enough capitals to build main roads - const paths = []; // array to store path segments - - for (const b of capitals) { - const connect = capitals.filter(c => c.feature === b.feature && c !== b); - for (const t of connect) { - const [from, exit] = findLandPath(b.cell, t.cell, true); - const segments = restorePath(b.cell, exit, "main", from); - segments.forEach(s => paths.push(s)); - } - } - - cells.i.forEach(i => (cells.s[i] += cells.road[i] / 2)); // add roads to suitability score - TIME && console.timeEnd("generateMainRoads"); - return paths; - }; - - const getTrails = function () { - TIME && console.time("generateTrails"); - const cells = pack.cells; - const burgs = pack.burgs.filter(b => b.i && !b.removed); - - if (burgs.length < 2) return []; // not enough burgs to build trails - - let paths = []; // array to store path segments - for (const f of pack.features.filter(f => f.land)) { - const isle = burgs.filter(b => b.feature === f.i); // burgs on island - if (isle.length < 2) continue; - - isle.forEach(function (b, i) { - let path = []; - if (!i) { - // build trail from the first burg on island - // to the farthest one on the same island or the closest road - const farthest = d3.scan( - isle, - (a, c) => (c.y - b.y) ** 2 + (c.x - b.x) ** 2 - ((a.y - b.y) ** 2 + (a.x - b.x) ** 2) - ); - const to = isle[farthest].cell; - if (cells.road[to]) return; - const [from, exit] = findLandPath(b.cell, to, true); - path = restorePath(b.cell, exit, "small", from); - } else { - // build trail from all other burgs to the closest road on the same island - if (cells.road[b.cell]) return; - const [from, exit] = findLandPath(b.cell, null, true); - if (exit === null) return; - path = restorePath(b.cell, exit, "small", from); - } - if (path) paths = paths.concat(path); - }); - } - - TIME && console.timeEnd("generateTrails"); - return paths; - }; - - const getSearoutes = function () { - TIME && console.time("generateSearoutes"); - const {cells, burgs, features} = pack; - const allPorts = burgs.filter(b => b.port > 0 && !b.removed); - - if (!allPorts.length) return []; - - const bodies = new Set(allPorts.map(b => b.port)); // water features with ports - let paths = []; // array to store path segments - const connected = []; // store cell id of connected burgs - - bodies.forEach(f => { - const ports = allPorts.filter(b => b.port === f); // all ports on the same feature - if (!ports.length) return; - - if (features[f]?.border) addOverseaRoute(f, ports[0]); - - // get inner-map routes - for (let s = 0; s < ports.length; s++) { - const source = ports[s].cell; - if (connected[source]) continue; - - for (let t = s + 1; t < ports.length; t++) { - const target = ports[t].cell; - if (connected[target]) continue; - - const [from, exit, passable] = findOceanPath(target, source, true); - if (!passable) continue; - - const path = restorePath(target, exit, "ocean", from); - paths = paths.concat(path); - - connected[source] = 1; - connected[target] = 1; - } - } - }); - - function addOverseaRoute(f, port) { - const {x, y, cell: source} = port; - const dist = p => Math.abs(p[0] - x) + Math.abs(p[1] - y); - const [x1, y1] = [ - [0, y], - [x, 0], - [graphWidth, y], - [x, graphHeight] - ].sort((a, b) => dist(a) - dist(b))[0]; - const target = findCell(x1, y1); - - if (cells.f[target] === f && cells.h[target] < 20) { - const [from, exit, passable] = findOceanPath(target, source, true); - - if (passable) { - const path = restorePath(target, exit, "ocean", from); - paths = paths.concat(path); - last(path).push([x1, y1]); - } - } - } - - TIME && console.timeEnd("generateSearoutes"); - return paths; - }; - - const lineGen = d3.line().curve(d3.curveBasis); - - const draw = function (main, small, water) { - TIME && console.time("drawRoutes"); - const {cells, burgs} = pack; - const {burg, p} = cells; - - const getBurgCoords = b => [burgs[b].x, burgs[b].y]; - const getPathPoints = cells => cells.map(i => (Array.isArray(i) ? i : burg[i] ? getBurgCoords(burg[i]) : p[i])); - const getPath = segment => round(lineGen(getPathPoints(segment)), 1); - const getPathsHTML = (paths, type) => - paths.map((path, i) => ``).join(""); - - lineGen.curve(d3.curveCatmullRom.alpha(0.1)); - roads.html(getPathsHTML(main, "road")); - trails.html(getPathsHTML(small, "trail")); - - lineGen.curve(d3.curveBundle.beta(1)); - searoutes.html(getPathsHTML(water, "searoute")); - - TIME && console.timeEnd("drawRoutes"); - }; - - const regenerate = function () { - routes.selectAll("path").remove(); - pack.cells.road = new Uint16Array(pack.cells.i.length); - pack.cells.crossroad = new Uint16Array(pack.cells.i.length); - const main = getRoads(); - const small = getTrails(); - const water = getSearoutes(); - draw(main, small, water); - }; - - return {getRoads, getTrails, getSearoutes, draw, regenerate}; - - // Find a land path to a specific cell (exit), to a closest road (toRoad), or to all reachable cells (null, null) - function findLandPath(start, exit = null, toRoad = null) { - const cells = pack.cells; - const queue = new FlatQueue(); - const cost = []; - const from = []; - queue.push(start, 0); - - while (queue.length) { - const priority = queue.peekValue(); - const next = queue.pop(); - - if (toRoad && cells.road[next]) return [from, next]; - - for (const neibCellId of cells.c[next]) { - if (cells.h[neibCellId] < 20) continue; // ignore water cells - const stateChangeCost = cells.state && cells.state[neibCellId] !== cells.state[next] ? 400 : 0; // trails tend to lay within the same state - const habitability = biomesData.habitability[cells.biome[neibCellId]]; - if (!habitability) continue; // avoid inhabitable cells (eg. lava, glacier) - const habitedCost = habitability ? Math.max(100 - habitability, 0) : 400; // routes tend to lay within populated areas - const heightChangeCost = Math.abs(cells.h[neibCellId] - cells.h[next]) * 10; // routes tend to avoid elevation changes - const heightCost = cells.h[neibCellId] > 80 ? cells.h[neibCellId] : 0; // routes tend to avoid mountainous areas - const cellCoast = 10 + stateChangeCost + habitedCost + heightChangeCost + heightCost; - const totalCost = priority + (cells.road[neibCellId] || cells.burg[neibCellId] ? cellCoast / 3 : cellCoast); - - if (from[neibCellId] || totalCost >= cost[neibCellId]) continue; - from[neibCellId] = next; - if (neibCellId === exit) return [from, exit]; - cost[neibCellId] = totalCost; - queue.push(neibCellId, totalCost); - } - } - return [from, exit]; - } - - function restorePath(start, end, type, from) { - const cells = pack.cells; - const path = []; // to store all segments; - let segment = [], - current = end, - prev = end; - const score = type === "main" ? 5 : 1; // to increase road score at cell - - if (type === "ocean" || !cells.road[prev]) segment.push(end); - if (!cells.road[prev]) cells.road[prev] = score; - - for (let i = 0, limit = 1000; i < limit; i++) { - if (!from[current]) break; - current = from[current]; - - if (cells.road[current]) { - if (segment.length) { - segment.push(current); - path.push(segment); - if (segment[0] !== end) { - cells.road[segment[0]] += score; - cells.crossroad[segment[0]] += score; - } - if (current !== start) { - cells.road[current] += score; - cells.crossroad[current] += score; - } - } - segment = []; - prev = current; - } else { - if (prev) segment.push(prev); - prev = null; - segment.push(current); - } - - cells.road[current] += score; - if (current === start) break; - } - - if (segment.length > 1) path.push(segment); - return path; - } - - // find water paths - function findOceanPath(start, exit = null, toRoute = null) { - const cells = pack.cells; - const temp = grid.cells.temp; - - const queue = new FlatQueue(); - const cost = []; - const from = []; - queue.push(start, 0); - - while (queue.length) { - const priority = queue.peekValue(); - const next = queue.pop(); - - if (toRoute && next !== start && cells.road[next]) return [from, next, true]; - - for (const neibCellId of cells.c[next]) { - if (neibCellId === exit) { - from[neibCellId] = next; - return [from, exit, true]; - } - - if (cells.h[neibCellId] >= 20) continue; // ignore land cells - if (temp[cells.g[neibCellId]] <= -5) continue; // ignore cells with temp <= -5 - - const dist2 = - (cells.p[neibCellId][1] - cells.p[next][1]) ** 2 + (cells.p[neibCellId][0] - cells.p[next][0]) ** 2; - const totalCost = priority + (cells.road[neibCellId] ? 1 + dist2 / 2 : dist2 + (cells.t[neibCellId] ? 1 : 100)); - - if (from[neibCellId] || totalCost >= cost[neibCellId]) continue; - (from[neibCellId] = next), (cost[neibCellId] = totalCost); - queue.push(neibCellId, totalCost); - } - } - return [from, exit, false]; - } -})(); diff --git a/src/modules/submap.js b/src/modules/submap.js index 850f07c0..b8cff97e 100644 --- a/src/modules/submap.js +++ b/src/modules/submap.js @@ -33,7 +33,7 @@ window.Submap = (function () { const projection = options.projection; const inverse = options.inverse; - const stage = s => INFO && console.log("SUBMAP:", s); + const stage = s => INFO && console.info("SUBMAP:", s); const timeStart = performance.now(); Zoom.invoke(); @@ -41,7 +41,7 @@ window.Submap = (function () { seed = parentMap.seed; Math.random = aleaPRNG(seed); INFO && console.group("SubMap with seed: " + seed); - DEBUG && console.log("Using Options:", options); + DEBUG && console.info("Using Options:", options); // create new grid applyMapSize(); @@ -396,7 +396,7 @@ window.Submap = (function () { b.removed = true; return; } - DEBUG && console.log(`Moving ${b.name} from ${cityCell} to ${newCell} near ${neighbor}.`); + DEBUG && console.info(`Moving ${b.name} from ${cityCell} to ${newCell} near ${neighbor}.`); [b.x, b.y] = b.port ? getCommonEdgePoint(newCell, neighbor) : cells.p[newCell]; // TODO: to be reworked if (b.port) b.port = cells.f[neighbor]; // copy feature number b.cell = newCell; diff --git a/src/modules/ui/style.js b/src/modules/ui/style.js index 454daea2..7fd6b22a 100644 --- a/src/modules/ui/style.js +++ b/src/modules/ui/style.js @@ -788,7 +788,7 @@ function textureProvideURL() { } function fetchTextureURL(url) { - INFO && console.log("Provided URL is", url); + INFO && console.info("Provided URL is", url); const img = new Image(); img.onload = function () { const canvas = document.getElementById("texturePreview"); diff --git a/src/scripts/generation/pack/generateRoutes.ts b/src/scripts/generation/pack/generateRoutes.ts index 10896ea3..94ca3adc 100644 --- a/src/scripts/generation/pack/generateRoutes.ts +++ b/src/scripts/generation/pack/generateRoutes.ts @@ -18,7 +18,6 @@ export function generateRoutes(burgs: TBurgs, temp: Int8Array, cells: TCellsData const seaRoutes = generateSeaRoutes(); const routes = combineRoutes(); - console.log(routes); return {cellRoutes, routes}; function sortBurgsByFeature(burgs: TBurgs) { @@ -103,7 +102,6 @@ export function generateRoutes(burgs: TBurgs, temp: Int8Array, cells: TCellsData const exit = featurePorts[toId].cell; const segments = findPathSegments({isWater: true, cellRoutes, connections, start, exit}); - for (const segment of segments) { addConnections(segment, ROUTES.MAIN_ROAD); mainRoads.push({feature: Number(key), cells: segment}); @@ -137,7 +135,7 @@ export function generateRoutes(burgs: TBurgs, temp: Int8Array, cells: TCellsData start: number; exit: number; }): number[][] { - const from = findPath(isWater, cellRoutes, temp, cells, start, exit); + const from = findPath(isWater, cellRoutes, temp, cells, start, exit, connections); if (!from) return []; const pathCells = restorePath(start, exit, from); @@ -170,7 +168,8 @@ function findPath( temp: Int8Array, cells: TCellsData, start: number, - exit: number + exit: number, + connections: Map ) { const from: number[] = []; const cost: number[] = []; @@ -193,9 +192,9 @@ function findPath( const distanceCost = dist2(cells.p[next], cells.p[neibCellId]); const habitabilityModifier = 1 + Math.max(100 - habitability, 0) / 1000; // [1, 1.1]; - const heightModifier = 1 + Math.max(cells.h[neibCellId] - ELEVATION.HILLS, 0) / 500; // [1, 1.1]; - const roadModifier = cellRoutes[neibCellId] ? 0.5 : 1; - const burgModifier = cells.burg[neibCellId] ? 0.5 : 1; + const heightModifier = 1 + Math.max(cells.h[neibCellId] - ELEVATION.HILLS, 0) / 50; // [1, 2]; + const roadModifier = cellRoutes[neibCellId] ? 1 : 2; + const burgModifier = cells.burg[neibCellId] ? 1 : 2; const cellsCost = distanceCost * habitabilityModifier * heightModifier * roadModifier * burgModifier; const totalCost = priority + cellsCost; @@ -231,9 +230,11 @@ function findPath( const distanceCost = dist2(cells.p[next], cells.p[neibCellId]); const typeModifier = Math.abs(cells.t[neibCellId]); // 1 for coastline, 2 for deep ocean, 3 for deeper ocean - const routeModifier = cellRoutes[neibCellId] ? 0.5 : 1; + const routeModifier = cellRoutes[neibCellId] ? 1 : 2; + const connectionModifier = + connections.has(`${next}-${neibCellId}`) || connections.has(`${neibCellId}-${next}`) ? 1 : 3; - const cellsCost = distanceCost * typeModifier * routeModifier; + const cellsCost = distanceCost * typeModifier * routeModifier * connectionModifier; const totalCost = priority + cellsCost; if (from[neibCellId] || totalCost >= cost[neibCellId]) continue; diff --git a/src/scripts/statistics.ts b/src/scripts/statistics.ts index ed7d706e..201919ba 100644 --- a/src/scripts/statistics.ts +++ b/src/scripts/statistics.ts @@ -26,5 +26,5 @@ export function showStatistics() { mapId = Date.now(); // unique map id is it's creation date number mapHistory.push({seed, width: graphWidth, height: graphHeight, template: heightmap, created: mapId}); - INFO && console.log(stats); + INFO && console.info(stats); }