mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-04-04 22:47:23 +02:00
feat: relief three.js renderer
This commit is contained in:
parent
7a49098425
commit
7481a2843e
19 changed files with 828 additions and 120 deletions
|
|
@ -180,7 +180,10 @@ async function getMapURL(
|
|||
fullMap = false
|
||||
} = {}
|
||||
) {
|
||||
// Temporarily inject <use> elements so the clone includes relief icon data
|
||||
if (typeof prepareReliefForSave === "function") prepareReliefForSave();
|
||||
const cloneEl = byId("map").cloneNode(true); // clone svg
|
||||
if (typeof restoreReliefAfterSave === "function") restoreReliefAfterSave();
|
||||
cloneEl.id = "fantasyMap";
|
||||
document.body.appendChild(cloneEl);
|
||||
const clone = d3.select(cloneEl);
|
||||
|
|
@ -286,13 +289,13 @@ async function getMapURL(
|
|||
}
|
||||
}
|
||||
|
||||
// add relief icons
|
||||
// add relief icons (from <use> elements – canvas <image> is excluded)
|
||||
if (cloneEl.getElementById("terrain")) {
|
||||
const uniqueElements = new Set();
|
||||
const terrainNodes = cloneEl.getElementById("terrain").childNodes;
|
||||
for (let i = 0; i < terrainNodes.length; i++) {
|
||||
const href = terrainNodes[i].getAttribute("href") || terrainNodes[i].getAttribute("xlink:href");
|
||||
uniqueElements.add(href);
|
||||
const terrainUses = cloneEl.getElementById("terrain").querySelectorAll("use");
|
||||
for (let i = 0; i < terrainUses.length; i++) {
|
||||
const href = terrainUses[i].getAttribute("href") || terrainUses[i].getAttribute("xlink:href");
|
||||
if (href && href.startsWith("#")) uniqueElements.add(href);
|
||||
}
|
||||
|
||||
const defsRelief = svgDefs.getElementById("defs-relief");
|
||||
|
|
@ -424,7 +427,8 @@ async function getMapURL(
|
|||
|
||||
// remove hidden g elements and g elements without children to make downloaded svg smaller in size
|
||||
function removeUnusedElements(clone) {
|
||||
if (!terrain.selectAll("use").size()) clone.select("#defs-relief")?.remove();
|
||||
// Check the clone (not the live terrain) so canvas-mode maps export correctly
|
||||
if (!clone.select("#terrain use").size()) clone.select("#defs-relief")?.remove();
|
||||
|
||||
for (let empty = 1; empty; ) {
|
||||
empty = 0;
|
||||
|
|
@ -583,31 +587,31 @@ function saveGeoJsonZones() {
|
|||
// Handles multiple disconnected components and holes properly
|
||||
function getZonePolygonCoordinates(zoneCells) {
|
||||
const cellsInZone = new Set(zoneCells);
|
||||
const ofSameType = (cellId) => cellsInZone.has(cellId);
|
||||
const ofDifferentType = (cellId) => !cellsInZone.has(cellId);
|
||||
|
||||
const ofSameType = cellId => cellsInZone.has(cellId);
|
||||
const ofDifferentType = cellId => !cellsInZone.has(cellId);
|
||||
|
||||
const checkedCells = new Set();
|
||||
const rings = []; // Array of LinearRings (each ring is an array of coordinates)
|
||||
|
||||
|
||||
// Find all boundary components by tracing each connected region
|
||||
for (const cellId of zoneCells) {
|
||||
if (checkedCells.has(cellId)) continue;
|
||||
|
||||
|
||||
// Check if this cell is on the boundary (has a neighbor outside the zone)
|
||||
const neighbors = cells.c[cellId];
|
||||
const onBorder = neighbors.some(ofDifferentType);
|
||||
if (!onBorder) continue;
|
||||
|
||||
|
||||
// Check if this is an inner lake (hole) - skip if so
|
||||
const feature = pack.features[cells.f[cellId]];
|
||||
if (feature.type === "lake" && feature.shoreline) {
|
||||
if (feature.shoreline.every(ofSameType)) continue;
|
||||
}
|
||||
|
||||
|
||||
// Find a starting vertex that's on the boundary
|
||||
const cellVertices = cells.v[cellId];
|
||||
let startingVertex = null;
|
||||
|
||||
|
||||
for (const vertexId of cellVertices) {
|
||||
const vertexCells = vertices.c[vertexId];
|
||||
if (vertexCells.some(ofDifferentType)) {
|
||||
|
|
@ -615,38 +619,38 @@ function saveGeoJsonZones() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (startingVertex === null) continue;
|
||||
|
||||
|
||||
// Use connectVertices to trace the boundary (reusing existing logic)
|
||||
const vertexChain = connectVertices({
|
||||
vertices,
|
||||
startingVertex,
|
||||
ofSameType,
|
||||
addToChecked: (cellId) => checkedCells.add(cellId),
|
||||
closeRing: false, // We'll close it manually after converting to coordinates
|
||||
addToChecked: cellId => checkedCells.add(cellId),
|
||||
closeRing: false // We'll close it manually after converting to coordinates
|
||||
});
|
||||
|
||||
|
||||
if (vertexChain.length < 3) continue;
|
||||
|
||||
|
||||
// Convert vertex chain to coordinates
|
||||
const coordinates = [];
|
||||
for (const vertexId of vertexChain) {
|
||||
const [x, y] = vertices.p[vertexId];
|
||||
coordinates.push(getCoordinates(x, y, 4));
|
||||
}
|
||||
|
||||
|
||||
// Close the ring (first coordinate = last coordinate)
|
||||
if (coordinates.length > 0) {
|
||||
coordinates.push(coordinates[0]);
|
||||
}
|
||||
|
||||
|
||||
// Only add ring if it has at least 4 positions (minimum for valid LinearRing)
|
||||
if (coordinates.length >= 4) {
|
||||
rings.push(coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return rings;
|
||||
}
|
||||
|
||||
|
|
@ -656,10 +660,10 @@ function saveGeoJsonZones() {
|
|||
if (zone.hidden || !zone.cells || zone.cells.length === 0) return;
|
||||
|
||||
const rings = getZonePolygonCoordinates(zone.cells);
|
||||
|
||||
|
||||
// Skip if no valid rings were generated
|
||||
if (rings.length === 0) return;
|
||||
|
||||
|
||||
const properties = {
|
||||
id: zone.i,
|
||||
name: zone.name,
|
||||
|
|
@ -667,7 +671,7 @@ function saveGeoJsonZones() {
|
|||
color: zone.color,
|
||||
cells: zone.cells
|
||||
};
|
||||
|
||||
|
||||
// If there's only one ring, use Polygon geometry
|
||||
if (rings.length === 1) {
|
||||
const feature = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue