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.
- package/README.md +520 -0
- package/dist/AreaMapRenderer.d.ts +125 -0
- package/dist/CullingManager.d.ts +15 -0
- package/dist/ExitRenderer.d.ts +109 -0
- package/dist/InteractionHandler.d.ts +47 -0
- package/dist/MapGraph.d.ts +28 -0
- package/dist/MapReader-mU_4JWv_.js +224 -0
- package/dist/MapReader-mU_4JWv_.js.map +1 -0
- package/dist/MapState.d.ts +110 -0
- package/dist/PathData.d.ts +18 -0
- package/dist/PathFinder.d.ts +12 -0
- package/dist/ScenePipeline.d.ts +226 -0
- package/dist/SvgTypes.d.ts +21 -0
- package/dist/TypedEventEmitter.d.ts +17 -0
- package/dist/binary/BinaryMapReader.d.ts +36 -0
- package/dist/binary/index.d.ts +2 -0
- package/dist/binary.mjs +34 -0
- package/dist/binary.mjs.map +1 -0
- package/dist/camera/Camera.d.ts +106 -0
- package/dist/coord/CoordFn.d.ts +13 -0
- package/dist/directions.d.ts +25 -0
- package/dist/draw/DrawCommand.d.ts +104 -0
- package/dist/draw/DrawCommandBuilder.d.ts +19 -0
- package/dist/export/CanvasExporter.d.ts +71 -0
- package/dist/export/Exporter.d.ts +58 -0
- package/dist/export/PngExporter.d.ts +23 -0
- package/dist/export/SvgExporter.d.ts +19 -0
- package/dist/export/canvasToBytes.d.ts +19 -0
- package/dist/export/clipSceneToViewport.d.ts +48 -0
- package/dist/export/flushSceneShapes.d.ts +21 -0
- package/dist/export/sceneBounds.d.ts +24 -0
- package/dist/hit/HitTester.d.ts +101 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.mjs +6656 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lens/ExplorationLens.d.ts +29 -0
- package/dist/lens/RoomLens.d.ts +47 -0
- package/dist/lens/composeLenses.d.ts +31 -0
- package/dist/lens/index.d.ts +5 -0
- package/dist/overlay/AmbientLightOverlay.d.ts +32 -0
- package/dist/overlay/LiveEffect.d.ts +33 -0
- package/dist/overlay/SceneOverlay.d.ts +68 -0
- package/dist/reader/Area.d.ts +63 -0
- package/dist/reader/Exit.d.ts +15 -0
- package/dist/reader/MapReader.d.ts +34 -0
- package/dist/reader/Plane.d.ts +33 -0
- package/dist/render/CanvasRenderer.d.ts +17 -0
- package/dist/render/RecordingLayer.d.ts +158 -0
- package/dist/render/SvgRenderer.d.ts +7 -0
- package/dist/render/shapeToRecording.d.ts +3 -0
- package/dist/rendering/KonvaRenderBackend.d.ts +122 -0
- package/dist/rendering/MapRenderer.d.ts +213 -0
- package/dist/rendering/SceneManager.d.ts +54 -0
- package/dist/scene/AmbientLightStyle.d.ts +29 -0
- package/dist/scene/GridStyle.d.ts +21 -0
- package/dist/scene/InnerExitStyle.d.ts +24 -0
- package/dist/scene/OverlayStyle.d.ts +47 -0
- package/dist/scene/RoomStyle.d.ts +32 -0
- package/dist/scene/Shape.d.ts +151 -0
- package/dist/scene/SpecialExitStyle.d.ts +37 -0
- package/dist/scene/StubStyle.d.ts +15 -0
- package/dist/scene/elements/ExitLayout.d.ts +22 -0
- package/dist/scene/elements/GridLayout.d.ts +24 -0
- package/dist/scene/elements/LabelLayout.d.ts +11 -0
- package/dist/scene/elements/OverlayLayout.d.ts +14 -0
- package/dist/scene/elements/RoomLayout.d.ts +19 -0
- package/dist/scene/elements/SpecialExitLayout.d.ts +19 -0
- package/dist/scene/elements/StubLayout.d.ts +19 -0
- package/dist/style/Style.d.ts +60 -0
- package/dist/style/applyStyle.d.ts +3 -0
- package/dist/style/index.d.ts +21 -0
- package/dist/style/shape/BlueprintStyle.d.ts +11 -0
- package/dist/style/shape/ConstructionStyle.d.ts +17 -0
- package/dist/style/shape/IsometricStyle.d.ts +32 -0
- package/dist/style/shape/NeonStyle.d.ts +17 -0
- package/dist/style/shape/ParchmentStyle.d.ts +13 -0
- package/dist/style/shape/SciFiStyle.d.ts +16 -0
- package/dist/style/shape/SketchyStyle.d.ts +22 -0
- package/dist/style/shape/index.d.ts +25 -0
- package/dist/style/shape/paintMap.d.ts +25 -0
- package/dist/style/shape/wobble.d.ts +24 -0
- package/{src/types/MapData.ts → dist/types/MapData.d.ts} +70 -90
- package/dist/types/Settings.d.ts +206 -0
- package/dist/utils/color.d.ts +12 -0
- package/dist/utils/textMeasure.d.ts +38 -0
- package/package.json +67 -37
- package/demo/colors.json +0 -1
- package/demo/index.html +0 -102
- package/demo/main.ts +0 -389
- package/demo/mapExport.json +0 -1
- package/demo/tsconfig.json +0 -11
- package/src/ExitRenderer.ts +0 -275
- package/src/PathFinder.ts +0 -93
- package/src/PathRenderer.ts +0 -129
- package/src/Renderer.ts +0 -374
- package/src/directions.ts +0 -66
- package/src/index.ts +0 -4
- package/src/reader/Area.ts +0 -96
- package/src/reader/Exit.ts +0 -36
- package/src/reader/ExplorationArea.ts +0 -132
- package/src/reader/MapReader.ts +0 -123
- package/src/reader/Plane.ts +0 -42
- package/tsconfig.json +0 -17
- package/vercel.json +0 -5
- package/vite.config.ts +0 -29
- 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
|
+

|
|
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
|
+
}
|