mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-21 19:41:23 +01:00
feat: routes editor - split route
This commit is contained in:
parent
cd45c3a01b
commit
41354a857e
5 changed files with 154 additions and 55 deletions
|
|
@ -2937,6 +2937,11 @@
|
||||||
data-tip="Create a new route selecting route cells"
|
data-tip="Create a new route selecting route cells"
|
||||||
class="icon-map-pin"
|
class="icon-map-pin"
|
||||||
></button>
|
></button>
|
||||||
|
<button
|
||||||
|
id="routeSplit"
|
||||||
|
data-tip="Click on a control point to split the route there"
|
||||||
|
class="icon-unlink"
|
||||||
|
></button>
|
||||||
<button
|
<button
|
||||||
id="routeElevationProfile"
|
id="routeElevationProfile"
|
||||||
data-tip="Show the elevation profile for the route"
|
data-tip="Show the elevation profile for the route"
|
||||||
|
|
@ -8120,6 +8125,7 @@
|
||||||
<script src="utils/stringUtils.js"></script>
|
<script src="utils/stringUtils.js"></script>
|
||||||
<script src="utils/languageUtils.js"></script>
|
<script src="utils/languageUtils.js"></script>
|
||||||
<script src="utils/unitUtils.js?v=1.87.00"></script>
|
<script src="utils/unitUtils.js?v=1.87.00"></script>
|
||||||
|
<script defer src="utils/debugUtils.js"></script>
|
||||||
|
|
||||||
<script src="modules/voronoi.js"></script>
|
<script src="modules/voronoi.js"></script>
|
||||||
<script src="config/heightmap-templates.js"></script>
|
<script src="config/heightmap-templates.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ function editRoute(id) {
|
||||||
|
|
||||||
// add listeners
|
// add listeners
|
||||||
byId("routeCreateSelectingCells").on("click", showCreationDialog);
|
byId("routeCreateSelectingCells").on("click", showCreationDialog);
|
||||||
|
byId("routeSplit").on("click", toggleSplitRoute);
|
||||||
byId("routeElevationProfile").on("click", showRouteElevationProfile);
|
byId("routeElevationProfile").on("click", showRouteElevationProfile);
|
||||||
byId("routeLegend").on("click", editRouteLegend);
|
byId("routeLegend").on("click", editRouteLegend);
|
||||||
byId("routeRemove").on("click", removeRoute);
|
byId("routeRemove").on("click", removeRoute);
|
||||||
|
|
@ -84,7 +85,7 @@ function editRoute(id) {
|
||||||
.attr("cy", d => d[1])
|
.attr("cy", d => d[1])
|
||||||
.attr("r", 0.6)
|
.attr("r", 0.6)
|
||||||
.call(d3.drag().on("start", dragControlPoint))
|
.call(d3.drag().on("start", dragControlPoint))
|
||||||
.on("click", removeControlPoint);
|
.on("click", handleControlPointClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawCells() {
|
function drawCells() {
|
||||||
|
|
@ -170,30 +171,76 @@ function editRoute(id) {
|
||||||
redrawRoute();
|
redrawRoute();
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeControlPoint() {
|
function handleControlPointClick() {
|
||||||
const route = getRoute();
|
const controlPoint = d3.select(this);
|
||||||
|
const isSplitMode = byId("routeSplit").classList.contains("pressed");
|
||||||
|
isSplitMode ? splitRoute(controlPoint) : removeControlPoint(controlPoint);
|
||||||
|
|
||||||
if (!route.points) route.points = debug.selectAll("#controlPoints > *").data();
|
function splitRoute(controlPoint) {
|
||||||
const [x, y] = d3.select(this).datum();
|
const allPoints = debug.selectAll("#controlPoints > *").data();
|
||||||
const cellId = findCell(x, y);
|
const pointIndex = allPoints.indexOf(controlPoint.datum());
|
||||||
const routeAllCells = route.points.map(([x, y]) => findCell(x, y));
|
|
||||||
|
|
||||||
const isOnlyPointInCell = routeAllCells.filter(cell => cell === cellId).length === 1;
|
const oldRoutePoints = allPoints.slice(0, pointIndex + 1);
|
||||||
if (isOnlyPointInCell) {
|
const newRoutePoints = allPoints.slice(pointIndex);
|
||||||
const index = route.cells.indexOf(cellId);
|
|
||||||
const prev = route.cells[index - 1];
|
// update old route
|
||||||
const next = route.cells[index + 1];
|
const oldRoute = getRoute();
|
||||||
if (prev) removeConnection(prev, cellId);
|
oldRoute.points = oldRoutePoints;
|
||||||
if (next) removeConnection(cellId, next);
|
oldRoute.cells = unique(oldRoute.points.map(([x, y]) => findCell(x, y)));
|
||||||
if (prev && next) addConnection(prev, next, route.i);
|
drawControlPoints(oldRoute.points);
|
||||||
|
drawCells();
|
||||||
|
redrawRoute();
|
||||||
|
|
||||||
|
// create new route
|
||||||
|
const newRoute = {
|
||||||
|
...oldRoute,
|
||||||
|
i: Math.max(...pack.routes.map(route => route.i)) + 1,
|
||||||
|
cells: unique(newRoutePoints.map(([x, y]) => findCell(x, y))),
|
||||||
|
points: newRoutePoints
|
||||||
|
};
|
||||||
|
pack.routes.push(newRoute);
|
||||||
|
|
||||||
|
for (let i = 0; i < newRoute.cells.length; i++) {
|
||||||
|
const cellId = newRoute.cells[i];
|
||||||
|
const nextCellId = newRoute.cells[i + 1];
|
||||||
|
if (nextCellId) addConnection(cellId, nextCellId, newRoute.i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineGen = d3.line();
|
||||||
|
lineGen.curve(ROUTE_CURVES[newRoute.group] || ROUTE_CURVES.default);
|
||||||
|
routes
|
||||||
|
.select("#" + newRoute.group)
|
||||||
|
.append("path")
|
||||||
|
.attr("d", round(lineGen(Routes.getPoints(newRoute, newRoutePoints)), 1))
|
||||||
|
.attr("id", "route" + newRoute.i);
|
||||||
|
|
||||||
|
byId("routeSplit").classList.remove("pressed");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.remove();
|
function removeControlPoint(controlPoint) {
|
||||||
route.points = debug.selectAll("#controlPoints > *").data();
|
const route = getRoute();
|
||||||
route.cells = unique(route.points.map(([x, y]) => findCell(x, y)));
|
|
||||||
|
|
||||||
drawCells();
|
if (!route.points) route.points = debug.selectAll("#controlPoints > *").data();
|
||||||
redrawRoute();
|
const cellId = findCell(...controlPoint.datum());
|
||||||
|
const routeAllCells = route.points.map(([x, y]) => findCell(x, y));
|
||||||
|
|
||||||
|
const isOnlyPointInCell = routeAllCells.filter(cell => cell === cellId).length === 1;
|
||||||
|
if (isOnlyPointInCell) {
|
||||||
|
const index = route.cells.indexOf(cellId);
|
||||||
|
const prev = route.cells[index - 1];
|
||||||
|
const next = route.cells[index + 1];
|
||||||
|
if (prev) removeConnection(prev, cellId);
|
||||||
|
if (next) removeConnection(cellId, next);
|
||||||
|
if (prev && next) addConnection(prev, next, route.i);
|
||||||
|
}
|
||||||
|
|
||||||
|
controlPoint.remove();
|
||||||
|
route.points = debug.selectAll("#controlPoints > *").data();
|
||||||
|
route.cells = unique(route.points.map(([x, y]) => findCell(x, y)));
|
||||||
|
|
||||||
|
drawCells();
|
||||||
|
redrawRoute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showCreationDialog() {
|
function showCreationDialog() {
|
||||||
|
|
@ -201,6 +248,10 @@ function editRoute(id) {
|
||||||
createRoute(route.group);
|
createRoute(route.group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleSplitRoute() {
|
||||||
|
this.classList.toggle("pressed");
|
||||||
|
}
|
||||||
|
|
||||||
function removeConnection(from, to) {
|
function removeConnection(from, to) {
|
||||||
const routes = pack.cells.routes;
|
const routes = pack.cells.routes;
|
||||||
if (routes[from]) delete routes[from][to];
|
if (routes[from]) delete routes[from][to];
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,27 @@ function toHEX(rgb) {
|
||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const C_12 = [
|
||||||
|
"#dababf",
|
||||||
|
"#fb8072",
|
||||||
|
"#80b1d3",
|
||||||
|
"#fdb462",
|
||||||
|
"#b3de69",
|
||||||
|
"#fccde5",
|
||||||
|
"#c6b9c1",
|
||||||
|
"#bc80bd",
|
||||||
|
"#ccebc5",
|
||||||
|
"#ffed6f",
|
||||||
|
"#8dd3c7",
|
||||||
|
"#eb8de7"
|
||||||
|
];
|
||||||
|
const scaleRainbow = d3.scaleSequential(d3.interpolateRainbow);
|
||||||
|
|
||||||
// return array of standard shuffled colors
|
// return array of standard shuffled colors
|
||||||
function getColors(number) {
|
function getColors(number) {
|
||||||
const c12 = ["#dababf", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#c6b9c1", "#bc80bd", "#ccebc5", "#ffed6f", "#8dd3c7", "#eb8de7"];
|
const colors = d3.shuffle(
|
||||||
const cRB = d3.scaleSequential(d3.interpolateRainbow);
|
d3.range(number).map(i => (i < 12 ? C_12[i] : d3.color(scaleRainbow((i - 12) / (number - 12))).hex()))
|
||||||
const colors = d3.shuffle(d3.range(number).map(i => (i < 12 ? c12[i] : d3.color(cRB((i - 12) / (number - 12))).hex())));
|
);
|
||||||
return colors;
|
return colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
58
utils/debugUtils.js
Normal file
58
utils/debugUtils.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
"use strict";
|
||||||
|
// FMG utils used for debugging
|
||||||
|
|
||||||
|
function drawCellsValue(data) {
|
||||||
|
debug.selectAll("text").remove();
|
||||||
|
debug
|
||||||
|
.selectAll("text")
|
||||||
|
.data(data)
|
||||||
|
.enter()
|
||||||
|
.append("text")
|
||||||
|
.attr("x", (d, i) => pack.cells.p[i][0])
|
||||||
|
.attr("y", (d, i) => pack.cells.p[i][1])
|
||||||
|
.text(d => d);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawPolygons(data) {
|
||||||
|
const max = d3.max(data);
|
||||||
|
const min = d3.min(data);
|
||||||
|
const scheme = getColorScheme(terrs.select("#landHeights").attr("scheme"));
|
||||||
|
|
||||||
|
data = data.map(d => 1 - normalize(d, min, max));
|
||||||
|
|
||||||
|
debug.selectAll("polygon").remove();
|
||||||
|
debug
|
||||||
|
.selectAll("polygon")
|
||||||
|
.data(data)
|
||||||
|
.enter()
|
||||||
|
.append("polygon")
|
||||||
|
.attr("points", (d, i) => getGridPolygon(i))
|
||||||
|
.attr("fill", d => scheme(d))
|
||||||
|
.attr("stroke", d => scheme(d));
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawRouteConnections() {
|
||||||
|
debug.select("#connections").remove();
|
||||||
|
const routes = debug.append("g").attr("id", "connections").attr("stroke-width", 0.4);
|
||||||
|
|
||||||
|
const points = pack.cells.p;
|
||||||
|
const links = pack.cells.routes;
|
||||||
|
|
||||||
|
for (const from in links) {
|
||||||
|
for (const to in links[from]) {
|
||||||
|
const [x1, y1] = points[from];
|
||||||
|
const [x3, y3] = points[to];
|
||||||
|
const [x2, y2] = [(x1 + x3) / 2, (y1 + y3) / 2];
|
||||||
|
const routeId = links[from][to];
|
||||||
|
|
||||||
|
routes
|
||||||
|
.append("line")
|
||||||
|
.attr("x1", x1)
|
||||||
|
.attr("y1", y1)
|
||||||
|
.attr("x2", x2)
|
||||||
|
.attr("y2", y2)
|
||||||
|
.attr("data-id", routeId)
|
||||||
|
.attr("stroke", C_12[routeId % 12]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -312,38 +312,6 @@ void (function addFindAll() {
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// helper function non-used for the generation
|
|
||||||
function drawCellsValue(data) {
|
|
||||||
debug.selectAll("text").remove();
|
|
||||||
debug
|
|
||||||
.selectAll("text")
|
|
||||||
.data(data)
|
|
||||||
.enter()
|
|
||||||
.append("text")
|
|
||||||
.attr("x", (d, i) => pack.cells.p[i][0])
|
|
||||||
.attr("y", (d, i) => pack.cells.p[i][1])
|
|
||||||
.text(d => d);
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function non-used for the main generation
|
|
||||||
function drawPolygons(data) {
|
|
||||||
const max = d3.max(data);
|
|
||||||
const min = d3.min(data);
|
|
||||||
const scheme = getColorScheme(terrs.select("#landHeights").attr("scheme"));
|
|
||||||
|
|
||||||
data = data.map(d => 1 - normalize(d, min, max));
|
|
||||||
|
|
||||||
debug.selectAll("polygon").remove();
|
|
||||||
debug
|
|
||||||
.selectAll("polygon")
|
|
||||||
.data(data)
|
|
||||||
.enter()
|
|
||||||
.append("polygon")
|
|
||||||
.attr("points", (d, i) => getGridPolygon(i))
|
|
||||||
.attr("fill", d => scheme(d))
|
|
||||||
.attr("stroke", d => scheme(d));
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw raster heightmap preview (not used in main generation)
|
// draw raster heightmap preview (not used in main generation)
|
||||||
function drawHeights({heights, width, height, scheme, renderOcean}) {
|
function drawHeights({heights, width, height, scheme, renderOcean}) {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue