mudlet-map-renderer 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +520 -0
  2. package/dist/AreaMapRenderer.d.ts +125 -0
  3. package/dist/CullingManager.d.ts +15 -0
  4. package/dist/ExitRenderer.d.ts +109 -0
  5. package/dist/InteractionHandler.d.ts +47 -0
  6. package/dist/MapGraph.d.ts +28 -0
  7. package/dist/MapReader-mU_4JWv_.js +224 -0
  8. package/dist/MapReader-mU_4JWv_.js.map +1 -0
  9. package/dist/MapState.d.ts +110 -0
  10. package/dist/PathData.d.ts +18 -0
  11. package/dist/PathFinder.d.ts +12 -0
  12. package/dist/ScenePipeline.d.ts +226 -0
  13. package/dist/SvgTypes.d.ts +21 -0
  14. package/dist/TypedEventEmitter.d.ts +17 -0
  15. package/dist/binary/BinaryMapReader.d.ts +36 -0
  16. package/dist/binary/index.d.ts +2 -0
  17. package/dist/binary.mjs +34 -0
  18. package/dist/binary.mjs.map +1 -0
  19. package/dist/camera/Camera.d.ts +106 -0
  20. package/dist/coord/CoordFn.d.ts +13 -0
  21. package/dist/directions.d.ts +25 -0
  22. package/dist/draw/DrawCommand.d.ts +104 -0
  23. package/dist/draw/DrawCommandBuilder.d.ts +19 -0
  24. package/dist/export/CanvasExporter.d.ts +71 -0
  25. package/dist/export/Exporter.d.ts +58 -0
  26. package/dist/export/PngExporter.d.ts +23 -0
  27. package/dist/export/SvgExporter.d.ts +19 -0
  28. package/dist/export/canvasToBytes.d.ts +19 -0
  29. package/dist/export/clipSceneToViewport.d.ts +48 -0
  30. package/dist/export/flushSceneShapes.d.ts +21 -0
  31. package/dist/export/sceneBounds.d.ts +24 -0
  32. package/dist/hit/HitTester.d.ts +101 -0
  33. package/dist/index.d.ts +61 -0
  34. package/dist/index.mjs +6656 -0
  35. package/dist/index.mjs.map +1 -0
  36. package/dist/lens/ExplorationLens.d.ts +29 -0
  37. package/dist/lens/RoomLens.d.ts +47 -0
  38. package/dist/lens/composeLenses.d.ts +31 -0
  39. package/dist/lens/index.d.ts +5 -0
  40. package/dist/overlay/AmbientLightOverlay.d.ts +32 -0
  41. package/dist/overlay/LiveEffect.d.ts +33 -0
  42. package/dist/overlay/SceneOverlay.d.ts +68 -0
  43. package/dist/reader/Area.d.ts +63 -0
  44. package/dist/reader/Exit.d.ts +15 -0
  45. package/dist/reader/MapReader.d.ts +34 -0
  46. package/dist/reader/Plane.d.ts +33 -0
  47. package/dist/render/CanvasRenderer.d.ts +17 -0
  48. package/dist/render/RecordingLayer.d.ts +158 -0
  49. package/dist/render/SvgRenderer.d.ts +7 -0
  50. package/dist/render/shapeToRecording.d.ts +3 -0
  51. package/dist/rendering/KonvaRenderBackend.d.ts +122 -0
  52. package/dist/rendering/MapRenderer.d.ts +213 -0
  53. package/dist/rendering/SceneManager.d.ts +54 -0
  54. package/dist/scene/AmbientLightStyle.d.ts +29 -0
  55. package/dist/scene/GridStyle.d.ts +21 -0
  56. package/dist/scene/InnerExitStyle.d.ts +24 -0
  57. package/dist/scene/OverlayStyle.d.ts +47 -0
  58. package/dist/scene/RoomStyle.d.ts +32 -0
  59. package/dist/scene/Shape.d.ts +151 -0
  60. package/dist/scene/SpecialExitStyle.d.ts +37 -0
  61. package/dist/scene/StubStyle.d.ts +15 -0
  62. package/dist/scene/elements/ExitLayout.d.ts +22 -0
  63. package/dist/scene/elements/GridLayout.d.ts +24 -0
  64. package/dist/scene/elements/LabelLayout.d.ts +11 -0
  65. package/dist/scene/elements/OverlayLayout.d.ts +14 -0
  66. package/dist/scene/elements/RoomLayout.d.ts +19 -0
  67. package/dist/scene/elements/SpecialExitLayout.d.ts +19 -0
  68. package/dist/scene/elements/StubLayout.d.ts +19 -0
  69. package/dist/style/Style.d.ts +60 -0
  70. package/dist/style/applyStyle.d.ts +3 -0
  71. package/dist/style/index.d.ts +21 -0
  72. package/dist/style/shape/BlueprintStyle.d.ts +11 -0
  73. package/dist/style/shape/ConstructionStyle.d.ts +17 -0
  74. package/dist/style/shape/IsometricStyle.d.ts +32 -0
  75. package/dist/style/shape/NeonStyle.d.ts +17 -0
  76. package/dist/style/shape/ParchmentStyle.d.ts +13 -0
  77. package/dist/style/shape/SciFiStyle.d.ts +16 -0
  78. package/dist/style/shape/SketchyStyle.d.ts +22 -0
  79. package/dist/style/shape/index.d.ts +25 -0
  80. package/dist/style/shape/paintMap.d.ts +25 -0
  81. package/dist/style/shape/wobble.d.ts +24 -0
  82. package/{src/types/MapData.ts → dist/types/MapData.d.ts} +70 -90
  83. package/dist/types/Settings.d.ts +206 -0
  84. package/dist/utils/color.d.ts +12 -0
  85. package/dist/utils/textMeasure.d.ts +38 -0
  86. package/package.json +67 -37
  87. package/demo/colors.json +0 -1
  88. package/demo/index.html +0 -102
  89. package/demo/main.ts +0 -389
  90. package/demo/mapExport.json +0 -1
  91. package/demo/tsconfig.json +0 -11
  92. package/src/ExitRenderer.ts +0 -275
  93. package/src/PathFinder.ts +0 -93
  94. package/src/PathRenderer.ts +0 -129
  95. package/src/Renderer.ts +0 -374
  96. package/src/directions.ts +0 -66
  97. package/src/index.ts +0 -4
  98. package/src/reader/Area.ts +0 -96
  99. package/src/reader/Exit.ts +0 -36
  100. package/src/reader/ExplorationArea.ts +0 -132
  101. package/src/reader/MapReader.ts +0 -123
  102. package/src/reader/Plane.ts +0 -42
  103. package/tsconfig.json +0 -17
  104. package/vercel.json +0 -5
  105. package/vite.config.ts +0 -29
  106. package/vite.demo.config.ts +0 -15
package/README.md ADDED
@@ -0,0 +1,520 @@
1
+ # mudlet-map-renderer
2
+
3
+ A rendering library for [Mudlet](https://www.mudlet.org/) map data. Takes Mudlet's JSON map format and renders interactive, zoomable maps using Konva, with SVG and PNG export support.
4
+
5
+ ![Nuln area rendered with default settings](docs/screenshot.png)
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ yarn add mudlet-map-renderer
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```ts
16
+ import { MudletMapReader } from 'mudlet-map-binary-reader';
17
+ import { MapRenderer, MapReader, createSettings } from 'mudlet-map-renderer';
18
+
19
+ // Read a Mudlet binary map file and export renderer-compatible data
20
+ const map = MudletMapReader.read('map.dat');
21
+ const { mapData, colors } = MudletMapReader.export(map);
22
+
23
+ const mapReader = new MapReader(mapData, colors);
24
+
25
+ // Create an interactive renderer attached to a DOM element
26
+ const container = document.getElementById('map') as HTMLDivElement;
27
+ const renderer = new MapRenderer(mapReader, createSettings(), container);
28
+
29
+ // Display an area (area ID, z-level)
30
+ renderer.drawArea(42, 0);
31
+
32
+ // Show the player position (centers the viewport on the room)
33
+ renderer.setPosition(1234);
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ### Settings
39
+
40
+ Create a settings object with `createSettings()` and customize before passing to the renderer:
41
+
42
+ ```ts
43
+ import { createSettings } from 'mudlet-map-renderer';
44
+
45
+ const settings = createSettings();
46
+ settings.roomSize = 0.8;
47
+ settings.roomShape = 'circle'; // 'rectangle' | 'circle' | 'roundedRectangle'
48
+ settings.backgroundColor = '#1a1a2e';
49
+ settings.lineColor = 'rgb(200, 220, 255)';
50
+ settings.gridEnabled = true;
51
+ settings.emboss = true;
52
+ settings.areaName = true;
53
+
54
+ // Player position marker
55
+ settings.playerMarker.strokeColor = '#ff6600';
56
+ settings.playerMarker.fillColor = '#ff6600';
57
+ settings.playerMarker.fillAlpha = 0.3;
58
+ settings.playerMarker.matchRoomShape = true;
59
+
60
+ const renderer = new MapRenderer(mapReader, settings, container);
61
+ ```
62
+
63
+ Settings are a shared mutable object. To change settings after the renderer is already running, modify the properties and call `refresh()`:
64
+
65
+ ```ts
66
+ // Change appearance at runtime
67
+ renderer.settings.roomShape = 'circle';
68
+ renderer.settings.emboss = true;
69
+ renderer.refresh();
70
+
71
+ // Background color has its own update method (CSS-only, no scene rebuild)
72
+ renderer.settings.backgroundColor = '#1a1a2e';
73
+ renderer.updateBackground();
74
+ ```
75
+
76
+ ### Navigation
77
+
78
+ ```ts
79
+ // Switch to a different area and z-level
80
+ renderer.drawArea(areaId, zIndex);
81
+
82
+ // Move player position (auto-switches area if needed)
83
+ renderer.setPosition(roomId);
84
+
85
+ // Move player without centering the viewport
86
+ renderer.setPosition(roomId, false);
87
+
88
+ // Update the marker without triggering area switch logic
89
+ renderer.updatePositionMarker(roomId);
90
+
91
+ // Center viewport on a room (with animation)
92
+ renderer.centerOn(roomId);
93
+
94
+ // Center instantly (no animation)
95
+ renderer.centerOn(roomId, true);
96
+
97
+ // Remove the position marker
98
+ renderer.clearPosition();
99
+ ```
100
+
101
+ ### Highlights and paths
102
+
103
+ ```ts
104
+ // Highlight rooms with a color
105
+ renderer.renderHighlight(roomId, '#ff0000');
106
+ renderer.renderHighlight(otherRoomId, 'rgba(0, 255, 0, 0.5)');
107
+
108
+ // Remove a specific highlight
109
+ renderer.removeHighlight(roomId);
110
+
111
+ // Check if a room is highlighted
112
+ if (renderer.hasHighlight(roomId)) { /* ... */ }
113
+
114
+ // Clear all highlights
115
+ renderer.clearHighlights();
116
+
117
+ // Render a path through a list of room IDs
118
+ renderer.renderPath([101, 102, 103, 104], '#66E64D');
119
+
120
+ // Clear all paths
121
+ renderer.clearPaths();
122
+ ```
123
+
124
+ ### Viewport control
125
+
126
+ ```ts
127
+ // Zoom
128
+ renderer.setZoom(1.5);
129
+ renderer.zoomToCenter(2.0);
130
+ console.log(renderer.getZoom());
131
+
132
+ // Fit the entire area in view
133
+ renderer.fitArea();
134
+
135
+ // Get current viewport bounds (in map coordinates)
136
+ const bounds = renderer.getViewportBounds();
137
+ // { minX, maxX, minY, maxY }
138
+
139
+ // Control resize behavior
140
+ renderer.centerOnResize = true; // re-center on window resize
141
+ renderer.minZoom = 0.1;
142
+ ```
143
+
144
+ ### Events
145
+
146
+ ```ts
147
+ renderer.on('roomclick', ({ roomId, position }) => {
148
+ console.log(`Clicked room ${roomId} at (${position.x}, ${position.y})`);
149
+ });
150
+
151
+ renderer.on('roomcontextmenu', ({ roomId, position }) => {
152
+ // Right-click or long-press on a room
153
+ });
154
+
155
+ renderer.on('areaexitclick', ({ targetRoomId, position }) => {
156
+ // Clicked an area exit — navigate to the target room's area
157
+ renderer.setPosition(targetRoomId);
158
+ });
159
+
160
+ renderer.on('mapclick', () => {
161
+ // Clicked on empty space
162
+ });
163
+
164
+ renderer.on('zoom', ({ zoom }) => {
165
+ console.log(`Zoom level: ${zoom}`);
166
+ });
167
+
168
+ renderer.on('pan', (bounds) => {
169
+ // Viewport moved
170
+ });
171
+
172
+ // Remove a listener
173
+ const handler = ({ roomId }) => { /* ... */ };
174
+ renderer.on('roomclick', handler);
175
+ renderer.off('roomclick', handler);
176
+ ```
177
+
178
+ ### Culling
179
+
180
+ For large maps, spatial culling hides off-screen rooms for better performance:
181
+
182
+ ```ts
183
+ // "indexed" (default) — bucket-based spatial index, best for large maps
184
+ // "basic" — simple bounds check per room
185
+ // "none" — render everything (useful for debugging)
186
+ renderer.setCullingMode('indexed');
187
+ ```
188
+
189
+ ### Styles
190
+
191
+ A `Style` is a target-agnostic visual transformer. One style drives the
192
+ interactive canvas *and* every exporter — set it once and it applies to SVG,
193
+ PNG, and anything else you export.
194
+
195
+ ```ts
196
+ import {
197
+ compose, identityStyle,
198
+ Parchment, Blueprint, Neon, Sketchy, Isometric,
199
+ } from 'mudlet-map-renderer';
200
+
201
+ // Single style
202
+ renderer.setStyle(Parchment);
203
+
204
+ // Compose a chain (left → right = inner → outer)
205
+ renderer.setStyle(compose(
206
+ Parchment,
207
+ Sketchy({ jitter: 0.012, color: '#4a3728' }),
208
+ Isometric({ rotation: 30, depth: 0.18 }),
209
+ ));
210
+
211
+ // Clear the current style
212
+ renderer.setStyle(identityStyle);
213
+ // or
214
+ renderer.clearStyle();
215
+ ```
216
+
217
+ Built-in styles:
218
+
219
+ | Style | Effect |
220
+ |---|---|
221
+ | `Parchment` | Warm sepia / aged-paper palette |
222
+ | `Blueprint` | White lines on deep blue |
223
+ | `Neon` | Glowing neon outlines on dark background |
224
+ | `Sketchy({ jitter, color })` | Hand-drawn pencil wobble |
225
+ | `Isometric({ rotation?, depth? })` | 2:1 iso projection with optional cubes |
226
+
227
+ Custom styles extend `BaseStyle<Inner>` and override only the draw calls they
228
+ transform — see the built-ins for examples.
229
+
230
+ ### Export
231
+
232
+ Exporters are plug-ins: a new output format is a new `Exporter<T>` class,
233
+ not a new `MapRenderer` method.
234
+
235
+ ```ts
236
+ import {
237
+ SvgExporter, PngExporter, PngBlobExporter, CanvasExporter,
238
+ } from 'mudlet-map-renderer';
239
+
240
+ // SVG (string)
241
+ const svg = renderer.export(new SvgExporter());
242
+ const svgCentered = renderer.export(new SvgExporter({ roomId: 1234, padding: 5 }));
243
+ const svgTyped = renderer.export(new SvgExporter({
244
+ overlays: {
245
+ position: { roomId: 1234 },
246
+ highlights: [{ roomId: 100, color: '#ff0000' }],
247
+ paths: [{ locations: [101, 102, 103], color: '#00ff00' }],
248
+ },
249
+ }));
250
+
251
+ // PNG data URL
252
+ const pngUrl = renderer.export(new PngExporter({ pixelRatio: 2 }));
253
+
254
+ // PNG as Blob
255
+ const blob = await renderer.export(new PngBlobExporter({ pixelRatio: 2 }));
256
+
257
+ // Headless PNG bytes at a specific size (portable — works in browser + Node)
258
+ const png = renderer.export(new PngBytesExporter({
259
+ width: 1920,
260
+ height: 1080,
261
+ roomId: 1234,
262
+ padding: 5,
263
+ }));
264
+ // fs.writeFileSync('out.png', png!); // Node
265
+ // new Blob([png!], { type: 'image/png' }); // Browser
266
+
267
+ // Canvas handle (if you need to draw more on it, attach to DOM, etc.)
268
+ const canvas = renderer.export(new CanvasExporter({
269
+ width: 1920,
270
+ height: 1080,
271
+ }));
272
+ ```
273
+
274
+ Style + export compose: the style currently applied with `setStyle` is passed
275
+ to every exporter, so the SVG, the PNG, and the on-screen canvas stay in sync.
276
+
277
+ Writing a new exporter — e.g. PDF — means shipping a class that implements
278
+ `Exporter<Uint8Array>`. No changes to `MapRenderer`.
279
+
280
+ ### Headless rendering (no DOM)
281
+
282
+ For server-side or offscreen rendering (Node.js), install [`canvas`](https://www.npmjs.com/package/canvas) alongside this package and import Konva's canvas backend once at startup. `canvas` is a peer dependency — it isn't shipped transitively because browsers don't need it.
283
+
284
+ ```bash
285
+ yarn add canvas
286
+ ```
287
+
288
+ ```ts
289
+ import "konva/canvas-backend"; // must run before creating a MapRenderer
290
+ import { MapRenderer, createSettings, SvgExporter, PngBytesExporter } from "mudlet-map-renderer";
291
+
292
+ // Omit the container argument for headless rendering.
293
+ const renderer = new MapRenderer(mapReader, createSettings());
294
+
295
+ renderer.drawArea(42, 0);
296
+ renderer.state.positionRoomId = 1234; // mark player position without auto-centering
297
+
298
+ const svg = renderer.export(new SvgExporter({ padding: 5 }));
299
+ const png = renderer.export(new PngBytesExporter({ width: 1920, height: 1080 }));
300
+ ```
301
+
302
+ SVG export doesn't actually hit Konva, so if you only need SVG you can skip both the `canvas` install and the `konva/canvas-backend` import. PNG / canvas exporters and anything that builds a Konva stage do require them.
303
+
304
+ ### Overlays
305
+
306
+ Two kinds, picked by what you need:
307
+
308
+ - **`SceneOverlay`** — target-agnostic, appears in every output (interactive
309
+ canvas + every exporter). Use for static scene content (badges, annotations).
310
+ - **`LiveEffect`** — interactive canvas only; receives a Konva layer and
311
+ viewport updates so it can run its own animation loop. Skipped by exporters.
312
+
313
+ ```ts
314
+ import type { SceneOverlay, LiveEffect } from 'mudlet-map-renderer';
315
+
316
+ // Scene overlay — emits target-agnostic Shapes; appears in every output
317
+ class BadgeOverlay implements SceneOverlay {
318
+ render(state, bounds) {
319
+ return { type: 'circle', cx: 5, cy: 5, radius: 0.4, paint: { fill: '#ff0' } };
320
+ }
321
+ }
322
+
323
+ renderer.addSceneOverlay('badge', new BadgeOverlay());
324
+ renderer.removeSceneOverlay('badge');
325
+
326
+ // Live effect — gets a Konva layer for animation; interactive canvas only
327
+ class Pulse implements LiveEffect {
328
+ attach(layer) { /* add Konva shapes */ }
329
+ updateViewport(bounds, scale) { /* react to pan/zoom */ }
330
+ destroy() { /* cleanup */ }
331
+ }
332
+
333
+ renderer.addLiveEffect('pulse', new Pulse());
334
+ renderer.removeLiveEffect('pulse');
335
+ ```
336
+
337
+ ### Cleanup
338
+
339
+ Call `destroy()` when you're done with a renderer to release all resources — DOM event listeners, Konva stages, and internal subscriptions:
340
+
341
+ ```ts
342
+ const renderer = new MapRenderer(mapReader, settings, container);
343
+
344
+ // ... use the renderer ...
345
+
346
+ // Tear down completely
347
+ renderer.destroy();
348
+ ```
349
+
350
+ This is important in SPAs and frameworks like React where components mount and unmount. Without calling `destroy()`, event listeners on `window` and the container element will leak.
351
+
352
+ #### React example
353
+
354
+ ```tsx
355
+ import { useEffect, useRef } from 'react';
356
+ import { MapRenderer, MapReader, createSettings } from 'mudlet-map-renderer';
357
+
358
+ function MudletMap({ mapData, envData, areaId, roomId }) {
359
+ const containerRef = useRef<HTMLDivElement>(null);
360
+
361
+ useEffect(() => {
362
+ if (!containerRef.current) return;
363
+
364
+ const mapReader = new MapReader(mapData, envData);
365
+ const renderer = new MapRenderer(mapReader, createSettings(), containerRef.current);
366
+
367
+ renderer.drawArea(areaId, 0);
368
+ if (roomId) renderer.setPosition(roomId);
369
+
370
+ return () => {
371
+ renderer.destroy();
372
+ };
373
+ }, [mapData, envData, areaId, roomId]);
374
+
375
+ return <div ref={containerRef} style={{ width: '100%', height: '100%' }} />;
376
+ }
377
+ ```
378
+
379
+ ## Pathfinding
380
+
381
+ ```ts
382
+ import { MapReader, PathFinder, MapGraph, computePathData } from 'mudlet-map-renderer';
383
+
384
+ const mapReader = new MapReader(mapData, envData);
385
+ const pathFinder = new PathFinder(mapReader);
386
+
387
+ // Find shortest path between two rooms (Dijkstra or A*)
388
+ const roomIds = pathFinder.findPath(startRoomId, endRoomId, 'astar');
389
+
390
+ if (roomIds) {
391
+ // Render the path on the map
392
+ renderer.renderPath(roomIds, '#66E64D');
393
+
394
+ // Or compute detailed path geometry for custom rendering
395
+ const pathData = computePathData(mapReader, renderer.settings, roomIds, '#66E64D');
396
+ }
397
+ ```
398
+
399
+ ## Exploration mode
400
+
401
+ Show only rooms the player has visited (fog of war):
402
+
403
+ ```ts
404
+ const mapReader = new MapReader(mapData, envData);
405
+
406
+ // Enable exploration with a set of visited room IDs
407
+ const visitedRooms = mapReader.decorateWithExploration(new Set([100, 101, 102, 200]));
408
+
409
+ // Add newly visited rooms as the player explores
410
+ visitedRooms.add(103);
411
+
412
+ // Areas automatically filter to only show visited rooms
413
+ const renderer = new MapRenderer(mapReader, createSettings(), container);
414
+ renderer.drawArea(areaId, 0);
415
+
416
+ // Disable exploration mode (show all rooms again)
417
+ mapReader.clearExplorationDecoration();
418
+ renderer.refresh();
419
+ ```
420
+
421
+ ## Map data format
422
+
423
+ The renderer expects data produced by [mudlet-map-binary-reader](https://github.com/Delwing/node-mudlet-map-binary-reader), which reads Mudlet's binary `map.dat` format and exports renderer-compatible structures:
424
+
425
+ ```ts
426
+ import { MudletMapReader } from 'mudlet-map-binary-reader';
427
+
428
+ const map = MudletMapReader.read('map.dat');
429
+ const { mapData, colors } = MudletMapReader.export(map);
430
+ // mapData: MapData.Map (Area[])
431
+ // colors: MapData.Env[]
432
+ ```
433
+
434
+ `MapReader` accepts these two values directly:
435
+
436
+ ```ts
437
+ const mapReader = new MapReader(mapData, colors);
438
+ ```
439
+
440
+ The binary reader can also write exported data to files for static use:
441
+
442
+ ```ts
443
+ MudletMapReader.export(map, 'output'); // writes .js files
444
+ MudletMapReader.exportJson(map, 'map.json');
445
+ ```
446
+
447
+ ## API reference
448
+
449
+ ### `MapRenderer`
450
+
451
+ | Method | Description |
452
+ |--------|-------------|
453
+ | `drawArea(id, zIndex)` | Display an area at a z-level |
454
+ | `setPosition(roomId, center?)` | Set player position (auto-switches area) |
455
+ | `updatePositionMarker(roomId)` | Update marker without area switch |
456
+ | `clearPosition()` | Remove position marker |
457
+ | `centerOn(roomId, instant?)` | Center viewport on a room |
458
+ | `renderHighlight(roomId, color)` | Highlight a room |
459
+ | `removeHighlight(roomId)` | Remove a highlight |
460
+ | `clearHighlights()` | Clear all highlights |
461
+ | `renderPath(roomIds, color?)` | Draw a path |
462
+ | `clearPaths()` | Clear all paths |
463
+ | `setZoom(zoom)` | Set zoom level |
464
+ | `zoomToCenter(zoom)` | Zoom keeping center fixed |
465
+ | `fitArea()` | Fit the full area in view |
466
+ | `setStyle(style)` | Apply a visual style (interactive + exporters) |
467
+ | `clearStyle()` | Remove the current style |
468
+ | `export(exporter)` | Run an `Exporter<T>` and return its output |
469
+ | `addSceneOverlay(id, overlay)` | Add a target-agnostic overlay |
470
+ | `removeSceneOverlay(id)` | Remove a scene overlay |
471
+ | `addLiveEffect(id, effect)` | Add an interactive-only animated effect |
472
+ | `removeLiveEffect(id)` | Remove a live effect |
473
+ | `refresh()` | Force a full re-render |
474
+ | `on(event, handler)` | Subscribe to an event |
475
+ | `off(event, handler)` | Unsubscribe from an event |
476
+ | `setCullingMode(mode)` | Set culling strategy |
477
+ | `destroy()` | Release all resources |
478
+
479
+ ### Exporters
480
+
481
+ | Exporter | Output |
482
+ |---|---|
483
+ | `SvgExporter({ roomId?, padding?, overlays? })` | `string` — SVG document |
484
+ | `PngExporter({ pixelRatio? })` | `string` — PNG data URL (current viewport) |
485
+ | `PngBlobExporter({ pixelRatio? })` | `Promise<Blob>` — PNG Blob (browser only) |
486
+ | `PngBytesExporter({ width, height, roomId?, padding?, overlays?, mimeType?, quality? })` | `Uint8Array` — headless PNG/JPEG bytes; portable (browser + Node) |
487
+ | `CanvasExporter({ width, height, roomId?, padding?, overlays? })` | `ExportCanvas` — canvas handle; reframes to fit |
488
+
489
+ ### Styles
490
+
491
+ | Style | Constructor / Usage |
492
+ |---|---|
493
+ | `Parchment` | `setStyle(Parchment)` |
494
+ | `Blueprint` | `setStyle(Blueprint)` |
495
+ | `Neon` | `setStyle(Neon)` |
496
+ | `Sketchy(opts)` | `setStyle(Sketchy({ jitter, color }))` |
497
+ | `Isometric(opts)` | `setStyle(Isometric({ rotation?, depth? }))` |
498
+ | `compose(...)` | Chain multiple styles into one |
499
+ | `identityStyle` | Pass-through; equivalent to `clearStyle()` |
500
+
501
+ ### `PathFinder`
502
+
503
+ | Method | Description |
504
+ |--------|-------------|
505
+ | `findPath(from, to, algorithm?)` | Find shortest path (`'dijkstra'` or `'astar'`) |
506
+
507
+ ### `MapReader`
508
+
509
+ | Method | Description |
510
+ |--------|-------------|
511
+ | `getArea(areaId)` | Get an area by ID |
512
+ | `getAreas()` | Get all areas |
513
+ | `getRoom(roomId)` | Get a room by ID |
514
+ | `getRooms()` | Get all rooms |
515
+ | `decorateWithExploration(visited?)` | Enable fog of war |
516
+ | `clearExplorationDecoration()` | Disable fog of war |
517
+
518
+ ## License
519
+
520
+ MIT
@@ -0,0 +1,125 @@
1
+ import { IMapReader } from './reader/MapReader';
2
+ import { PlanarDirection } from './directions';
3
+ type AreaConnection = {
4
+ fromAreaId: number;
5
+ toAreaId: number;
6
+ fromRoomId: number;
7
+ toRoomId: number;
8
+ direction: PlanarDirection | null;
9
+ fromRoomPosition: {
10
+ x: number;
11
+ y: number;
12
+ };
13
+ toRoomPosition: {
14
+ x: number;
15
+ y: number;
16
+ };
17
+ };
18
+ type AreaNode = {
19
+ areaId: number;
20
+ name: string;
21
+ x: number;
22
+ y: number;
23
+ width: number;
24
+ height: number;
25
+ connections: AreaConnection[];
26
+ roomCount: number;
27
+ realCenterX: number;
28
+ realCenterY: number;
29
+ };
30
+ type ConnectionGroup = {
31
+ fromAreaId: number;
32
+ toAreaId: number;
33
+ connections: AreaConnection[];
34
+ primaryDirection: PlanarDirection | null;
35
+ averageOffset: {
36
+ x: number;
37
+ y: number;
38
+ };
39
+ };
40
+ export type AreaDomainInfo = {
41
+ isIshtar: boolean;
42
+ isEmpire: boolean;
43
+ };
44
+ export type DomainFilter = "ishtar" | "empire" | "interdomain" | "all";
45
+ export type AreaMapSettings = {
46
+ areaWidth: number;
47
+ areaHeight: number;
48
+ areaSpacing: number;
49
+ fontSize: number;
50
+ connectionLineWidth: number;
51
+ areaFillColor: string;
52
+ areaStrokeColor: string;
53
+ textColor: string;
54
+ connectionColor: string;
55
+ highlightColor: string;
56
+ };
57
+ export declare function createAreaMapSettings(): AreaMapSettings;
58
+ export declare class AreaMapRenderer {
59
+ private readonly stage;
60
+ private readonly backgroundLayer;
61
+ private readonly areaLayer;
62
+ private readonly connectionLayer;
63
+ private readonly mapReader;
64
+ private readonly settings;
65
+ private areaNodes;
66
+ private connectionGroups;
67
+ private currentZoom;
68
+ private highlightedArea?;
69
+ private domainInfo;
70
+ private domainFilter;
71
+ private backgroundImage?;
72
+ private backgroundConfig?;
73
+ private dotsMode;
74
+ constructor(container: HTMLDivElement, mapReader: IMapReader, settings?: AreaMapSettings);
75
+ private initResize;
76
+ private initScaling;
77
+ setZoom(zoom: number): boolean;
78
+ getZoom(): number;
79
+ setDomainInfo(domainInfo: Record<number, AreaDomainInfo>): void;
80
+ setDomainFilter(filter: DomainFilter): void;
81
+ getDomainFilter(): DomainFilter;
82
+ setBackgroundImage(config: {
83
+ url: string;
84
+ x: number;
85
+ y: number;
86
+ width: number;
87
+ height: number;
88
+ opacity?: number;
89
+ }): void;
90
+ clearBackgroundImage(): void;
91
+ redrawBackground(): void;
92
+ setDotsMode(enabled: boolean): void;
93
+ getDotsMode(): boolean;
94
+ redraw(): void;
95
+ private isAreaInDomain;
96
+ private areAreasInSameDomain;
97
+ render(): void;
98
+ private drawBackground;
99
+ private analyzeConnections;
100
+ private getLockedDirections;
101
+ private createConnection;
102
+ private addConnection;
103
+ private createConnectionGroup;
104
+ private parseDirection;
105
+ private toPlanarDirection;
106
+ private layoutAreas;
107
+ private findConnectedComponents;
108
+ private forceDirectedLayout;
109
+ private getDirectionOffset;
110
+ private applyClusterForces;
111
+ private applyForces;
112
+ private closestPointOnSegment;
113
+ private edgesIntersect;
114
+ private drawConnections;
115
+ private getEdgePoint;
116
+ private drawAreas;
117
+ private centerView;
118
+ highlightArea(areaId: number | undefined): void;
119
+ centerOnArea(areaId: number): void;
120
+ private emitAreaClickEvent;
121
+ getAreaNode(areaId: number): AreaNode | undefined;
122
+ getConnectionGroups(): ConnectionGroup[];
123
+ destroy(): void;
124
+ }
125
+ export {};
@@ -0,0 +1,15 @@
1
+ import { Settings } from './types/Settings';
2
+ import { CoordFn } from './coord/CoordFn';
3
+ export declare class CullingManager {
4
+ private readonly settings;
5
+ private readonly onCullingNeeded;
6
+ private cullingScheduled;
7
+ private coordinateTransform;
8
+ constructor(settings: Settings, onCullingNeeded: () => void);
9
+ setCoordinateTransform(fn: CoordFn): void;
10
+ getCoordinateTransform(): CoordFn;
11
+ /** Schedule a cull pass on the next animation frame (no-op if already scheduled). */
12
+ scheduleCulling(): void;
13
+ /** Run a cull pass immediately (used when mode changes). */
14
+ updateCulling(): void;
15
+ }