Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
7481a28
feat: relief three.js renderer
Azgaar Mar 9, 2026
62e27a7
fix: replace preloadTextures with loadTexture in SVG edit mode
Azgaar Mar 9, 2026
8d092e2
fix: set color space to SRGB for loaded textures
Azgaar Mar 9, 2026
cbed9af
fix: update .gitignore to include additional directories and files
Azgaar Mar 9, 2026
bf22c5e
Refactor relief rendering and generation logic
Azgaar Mar 10, 2026
4515232
fix: update relief icon hover transition and click selection logic
Azgaar Mar 10, 2026
dc06f3d
fix: streamline texture loading and preload logic in WebGL renderer
Azgaar Mar 10, 2026
ab7baf8
feat: update relief rendering logic and version to 1.114.0
Azgaar Mar 10, 2026
828534e
fix: clean up drawWebGl and drawSvg functions for improved readability
Azgaar Mar 10, 2026
ffe350b
fix: update type definitions and improve renderer initialization in d…
Azgaar Mar 10, 2026
3c7b6ff
Merge branch 'master' into relief-webgl-renderer
Azgaar Mar 11, 2026
fae0bd1
fix: improve comments for clarity and update type imports for consist…
Azgaar Mar 11, 2026
94da8fa
fix: no foright object
Azgaar Mar 11, 2026
b6484a7
fix: clean up .gitignore by removing unnecessary entries
Azgaar Mar 11, 2026
3047aef
bmad-init
Azgaar Mar 12, 2026
dc212ee
brainstorm
Azgaar Mar 12, 2026
21d76c4
feat: add architecture and product requirements documents for WebGL l…
Azgaar Mar 12, 2026
610d6ae
feat: update architecture document with detailed project context and …
Azgaar Mar 12, 2026
1503d18
feat: enhance WebGL layer framework with improved registration API an…
Azgaar Mar 12, 2026
6b7029a
Add sprint status and implementation readiness documents for Fantasy-…
Azgaar Mar 12, 2026
42b92d9
feat: update sprint status to reflect in-progress development for Web…
Azgaar Mar 12, 2026
769ef9e
feat: implement WebGL2 layer framework with core functionalities incl…
Azgaar Mar 12, 2026
8c78fe2
Refactor code structure for improved readability and maintainability
Azgaar Mar 12, 2026
30f7437
feat: Refactor draw-relief-icons.ts to integrate with WebGL2LayerFram…
Azgaar Mar 12, 2026
a285d45
feat: refactor draw-relief-icons renderer to utilize WebGL2LayerFrame…
Azgaar Mar 12, 2026
1c1d97b
feat: Update sprint status and complete Epic 2 tasks
Azgaar Mar 12, 2026
2dae325
feat: Update sprint status to mark retrospectives as done for Epic 1 …
Azgaar Mar 12, 2026
8560c13
feat: Enhance WebGL2LayerFramework initialization and improve global …
Azgaar Mar 12, 2026
d1d31da
feat: Improve camera synchronization and add null check for camera in…
Azgaar Mar 12, 2026
9e00d69
refactor: replace webgl-layer-framework with webgl-layer module
Azgaar Mar 12, 2026
744dbb1
feat: Optimize WebGL2LayerClass initialization and improve camera syn…
Azgaar Mar 12, 2026
ae6d105
refactor: Remove debug logging from WebGL2LayerClass initialization a…
Azgaar Mar 12, 2026
dc6ff78
refactor: Simplify texture loading and disposal in draw-relief-icons …
Azgaar Mar 12, 2026
70e3eea
feat: Introduce TextureAtlasLayer for efficient texture management an…
Azgaar Mar 12, 2026
c7793d2
refactor: Remove lastDrawnIcons tracking to simplify terrainLayer dra…
Azgaar Mar 12, 2026
b17b417
refactor: Update RELIEF_SYMBOLS structure and utilize RELIEF_ATLASES …
Azgaar Mar 12, 2026
256f360
refactor: Update relief icon handling and streamline texture atlas in…
Azgaar Mar 12, 2026
df18f69
feat: Add architecture decision and product requirements documents fo…
Azgaar Mar 12, 2026
125403b
feat: Implement Story 1.6 - Move Shared Defs Resources to the Dedicat…
Azgaar Mar 12, 2026
4255788
feat: Introduce SceneModule for managing map and WebGL canvas integra…
Azgaar Mar 13, 2026
52708e5
feat: Update SceneModule to manage camera state and viewport, refacto…
Azgaar Mar 13, 2026
f928f9d
feat: Implement compatibility bridge for legacy single-SVG callers
Azgaar Mar 13, 2026
73d6d66
feat: Implement RuntimeDefsModule for managing shared runtime definit…
Azgaar Mar 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@
/dist
/coverage
/playwright-report
/test-results
/test-results
/_bmad
/_bmad-output
/.DS_Store
/.github/agents
/.github/prompts
67 changes: 66 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@
"vitest": "^4.0.18"
},
"dependencies": {
"@types/three": "^0.183.1",
"alea": "^1.0.1",
"d3": "^7.9.0",
"delaunator": "^5.0.1",
"polylabel": "^2.0.1"
"polylabel": "^2.0.1",
"three": "^0.183.2"
Comment thread
Azgaar marked this conversation as resolved.
Outdated
},
"engines": {
"node": ">=24.0.0"
Expand Down
Binary file added public/images/relief/colored.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/relief/gray.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/relief/simple.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 1 addition & 3 deletions public/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ t,
pointer-events: none;
}

#terrain,
#burgIcons {
cursor: pointer;
}
Expand Down Expand Up @@ -2090,13 +2089,12 @@ svg.button {
background-color: #e7e6e4;
border: 1px solid #a9a9a9;
cursor: pointer;
transition: all 0.2s;
}

#reliefIconsDiv svg:hover {
border-color: #5c5c5c;
background-color: #eef6fb;
transition: all 0.3s ease-out 3s;
transform: scale(2);
}

#reliefIconsDiv svg.pressed {
Expand Down
4 changes: 3 additions & 1 deletion public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,8 @@ function handleZoom(isScaleChanged, isPositionChanged) {
fitScaleBar(scaleBar, svgWidth, svgHeight);
}

if (layerIsOn("toggleRelief")) rerenderReliefIcons();

// zoom image converter overlay
if (customization === 1) {
const canvas = byId("canvas");
Expand Down Expand Up @@ -1234,7 +1236,7 @@ function showStatistics() {
INFO && console.info(stats);

// Dispatch event for test automation and external integrations
window.dispatchEvent(new CustomEvent('map:generated', { detail: { seed, mapId } }));
window.dispatchEvent(new CustomEvent("map:generated", {detail: {seed, mapId}}));
}

const regenerateMap = debounce(async function (options) {
Expand Down
56 changes: 30 additions & 26 deletions public/modules/io/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -583,70 +587,70 @@ 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)) {
startingVertex = vertexId;
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;
}

Expand All @@ -656,18 +660,18 @@ 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,
type: zone.type,
color: zone.color,
cells: zone.cells
};

// If there's only one ring, use Polygon geometry
if (rings.length === 1) {
const feature = {
Expand Down
7 changes: 6 additions & 1 deletion public/modules/io/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,12 @@ async function parseLoadedData(data, mapVersion) {
if (hasChildren(coordinates)) turnOn("toggleCoordinates");
if (isVisible(compass) && hasChild(compass, "use")) turnOn("toggleCompass");
if (hasChildren(rivers)) turnOn("toggleRivers");
if (isVisible(terrain) && hasChildren(terrain)) turnOn("toggleRelief");
if (isVisible(terrain) && hasChildren(terrain)) {
turnOn("toggleRelief");
}
// Migrate any legacy SVG <use> elements to canvas rendering
// (runs regardless of visibility to handle maps loaded with relief layer off)
if (typeof migrateReliefFromSvg === "function") migrateReliefFromSvg();
Comment thread
Azgaar marked this conversation as resolved.
Outdated
if (hasChildren(relig)) turnOn("toggleReligions");
if (hasChildren(cults)) turnOn("toggleCultures");
if (hasChildren(statesBody)) turnOn("toggleStates");
Expand Down
Loading
Loading