@rpgjs/map-builder 0.0.0-stage → 5.0.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/LICENSE +19 -0
- package/README.md +341 -2
- package/dist/authority/authority.d.ts +118 -0
- package/dist/authority/authority.d.ts.map +1 -0
- package/dist/authority/authority.js +289 -0
- package/dist/authority/authority.js.map +1 -0
- package/dist/authority/index.d.ts +3 -0
- package/dist/authority/index.d.ts.map +1 -0
- package/dist/authority/index.js +3 -0
- package/dist/authority/store.d.ts +36 -0
- package/dist/authority/store.d.ts.map +1 -0
- package/dist/authority/store.js +35 -0
- package/dist/authority/store.js.map +1 -0
- package/dist/client/index.d.ts +72 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +200 -0
- package/dist/client/index.js.map +1 -0
- package/dist/embed/client.d.ts +85 -0
- package/dist/embed/client.d.ts.map +1 -0
- package/dist/embed/client.js +255 -0
- package/dist/embed/client.js.map +1 -0
- package/dist/embed/host.d.ts +112 -0
- package/dist/embed/host.d.ts.map +1 -0
- package/dist/embed/host.js +278 -0
- package/dist/embed/host.js.map +1 -0
- package/dist/embed/index.d.ts +4 -0
- package/dist/embed/index.d.ts.map +1 -0
- package/dist/embed/index.js +4 -0
- package/dist/embed/protocol.d.ts +137 -0
- package/dist/embed/protocol.d.ts.map +1 -0
- package/dist/embed/protocol.js +161 -0
- package/dist/embed/protocol.js.map +1 -0
- package/dist/features/index.d.ts +66 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +157 -0
- package/dist/features/index.js.map +1 -0
- package/dist/features/skin.d.ts +9 -0
- package/dist/features/skin.d.ts.map +1 -0
- package/dist/features/skin.js +43 -0
- package/dist/features/skin.js.map +1 -0
- package/dist/features/terrain.d.ts +66 -0
- package/dist/features/terrain.d.ts.map +1 -0
- package/dist/features/terrain.js +291 -0
- package/dist/features/terrain.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/interaction/builtin-intents.d.ts +9 -0
- package/dist/interaction/builtin-intents.d.ts.map +1 -0
- package/dist/interaction/builtin-intents.js +61 -0
- package/dist/interaction/builtin-intents.js.map +1 -0
- package/dist/interaction/catalog.d.ts +24 -0
- package/dist/interaction/catalog.d.ts.map +1 -0
- package/dist/interaction/catalog.js +20 -0
- package/dist/interaction/catalog.js.map +1 -0
- package/dist/interaction/controller.d.ts +137 -0
- package/dist/interaction/controller.d.ts.map +1 -0
- package/dist/interaction/controller.js +215 -0
- package/dist/interaction/controller.js.map +1 -0
- package/dist/interaction/index.d.ts +14 -0
- package/dist/interaction/index.d.ts.map +1 -0
- package/dist/interaction/index.js +13 -0
- package/dist/interaction/input.d.ts +26 -0
- package/dist/interaction/input.d.ts.map +1 -0
- package/dist/interaction/input.js +33 -0
- package/dist/interaction/input.js.map +1 -0
- package/dist/interaction/intents.d.ts +80 -0
- package/dist/interaction/intents.d.ts.map +1 -0
- package/dist/interaction/keyboard.d.ts +26 -0
- package/dist/interaction/keyboard.d.ts.map +1 -0
- package/dist/interaction/keyboard.js +32 -0
- package/dist/interaction/keyboard.js.map +1 -0
- package/dist/interaction/picking.d.ts +33 -0
- package/dist/interaction/picking.d.ts.map +1 -0
- package/dist/interaction/picking.js +35 -0
- package/dist/interaction/picking.js.map +1 -0
- package/dist/interaction/pipeline.d.ts +45 -0
- package/dist/interaction/pipeline.d.ts.map +1 -0
- package/dist/interaction/pipeline.js +26 -0
- package/dist/interaction/pipeline.js.map +1 -0
- package/dist/interaction/placement.d.ts +76 -0
- package/dist/interaction/placement.d.ts.map +1 -0
- package/dist/interaction/placement.js +238 -0
- package/dist/interaction/placement.js.map +1 -0
- package/dist/interaction/selection-tool.d.ts +29 -0
- package/dist/interaction/selection-tool.d.ts.map +1 -0
- package/dist/interaction/selection-tool.js +190 -0
- package/dist/interaction/selection-tool.js.map +1 -0
- package/dist/interaction/steps.d.ts +62 -0
- package/dist/interaction/steps.d.ts.map +1 -0
- package/dist/interaction/steps.js +126 -0
- package/dist/interaction/steps.js.map +1 -0
- package/dist/interaction/store.d.ts +10 -0
- package/dist/interaction/store.d.ts.map +1 -0
- package/dist/interaction/store.js +28 -0
- package/dist/interaction/store.js.map +1 -0
- package/dist/interaction/tools.d.ts +26 -0
- package/dist/interaction/tools.d.ts.map +1 -0
- package/dist/interaction/tools.js +41 -0
- package/dist/interaction/tools.js.map +1 -0
- package/dist/model/assets.d.ts +36 -0
- package/dist/model/assets.d.ts.map +1 -0
- package/dist/model/assets.js +18 -0
- package/dist/model/assets.js.map +1 -0
- package/dist/model/committer.d.ts +66 -0
- package/dist/model/committer.d.ts.map +1 -0
- package/dist/model/committer.js +38 -0
- package/dist/model/committer.js.map +1 -0
- package/dist/model/core-patches.d.ts +2 -0
- package/dist/model/core-patches.d.ts.map +1 -0
- package/dist/model/core-patches.js +193 -0
- package/dist/model/core-patches.js.map +1 -0
- package/dist/model/define-map.d.ts +52 -0
- package/dist/model/define-map.d.ts.map +1 -0
- package/dist/model/define-map.js +66 -0
- package/dist/model/define-map.js.map +1 -0
- package/dist/model/definition.d.ts +57 -0
- package/dist/model/definition.d.ts.map +1 -0
- package/dist/model/definition.js +6 -0
- package/dist/model/definition.js.map +1 -0
- package/dist/model/errors.d.ts +21 -0
- package/dist/model/errors.d.ts.map +1 -0
- package/dist/model/errors.js +14 -0
- package/dist/model/errors.js.map +1 -0
- package/dist/model/history.d.ts +43 -0
- package/dist/model/history.d.ts.map +1 -0
- package/dist/model/history.js +141 -0
- package/dist/model/history.js.map +1 -0
- package/dist/model/index.d.ts +14 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +14 -0
- package/dist/model/json.d.ts +15 -0
- package/dist/model/json.d.ts.map +1 -0
- package/dist/model/json.js +35 -0
- package/dist/model/json.js.map +1 -0
- package/dist/model/model.d.ts +68 -0
- package/dist/model/model.d.ts.map +1 -0
- package/dist/model/model.js +170 -0
- package/dist/model/model.js.map +1 -0
- package/dist/model/parse.d.ts +24 -0
- package/dist/model/parse.d.ts.map +1 -0
- package/dist/model/parse.js +165 -0
- package/dist/model/parse.js.map +1 -0
- package/dist/model/patches.d.ts +100 -0
- package/dist/model/patches.d.ts.map +1 -0
- package/dist/model/patches.js +21 -0
- package/dist/model/patches.js.map +1 -0
- package/dist/model/result.d.ts +22 -0
- package/dist/model/result.d.ts.map +1 -0
- package/dist/model/result.js +10 -0
- package/dist/model/result.js.map +1 -0
- package/dist/model/source.d.ts +52 -0
- package/dist/model/source.d.ts.map +1 -0
- package/dist/model/source.js +75 -0
- package/dist/model/source.js.map +1 -0
- package/dist/protocol/index.d.ts +92 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +106 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/remote/committer.d.ts +43 -0
- package/dist/remote/committer.d.ts.map +1 -0
- package/dist/remote/committer.js +206 -0
- package/dist/remote/committer.js.map +1 -0
- package/dist/remote/index.d.ts +4 -0
- package/dist/remote/index.d.ts.map +1 -0
- package/dist/remote/index.js +4 -0
- package/dist/remote/join.d.ts +30 -0
- package/dist/remote/join.d.ts.map +1 -0
- package/dist/remote/join.js +36 -0
- package/dist/remote/join.js.map +1 -0
- package/dist/remote/memory-transport.d.ts +20 -0
- package/dist/remote/memory-transport.d.ts.map +1 -0
- package/dist/remote/memory-transport.js +48 -0
- package/dist/remote/memory-transport.js.map +1 -0
- package/dist/renderer/effects.d.ts +52 -0
- package/dist/renderer/effects.d.ts.map +1 -0
- package/dist/renderer/effects.js +111 -0
- package/dist/renderer/effects.js.map +1 -0
- package/dist/renderer/feature.d.ts +79 -0
- package/dist/renderer/feature.d.ts.map +1 -0
- package/dist/renderer/feature.js +50 -0
- package/dist/renderer/feature.js.map +1 -0
- package/dist/renderer/geometry.d.ts +59 -0
- package/dist/renderer/geometry.d.ts.map +1 -0
- package/dist/renderer/geometry.js +114 -0
- package/dist/renderer/geometry.js.map +1 -0
- package/dist/renderer/index.d.ts +7 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +7 -0
- package/dist/renderer/scene.d.ts +77 -0
- package/dist/renderer/scene.d.ts.map +1 -0
- package/dist/renderer/scene.js +125 -0
- package/dist/renderer/scene.js.map +1 -0
- package/dist/renderer/textures.d.ts +20 -0
- package/dist/renderer/textures.d.ts.map +1 -0
- package/dist/renderer/textures.js +61 -0
- package/dist/renderer/textures.js.map +1 -0
- package/dist/renderer/view.d.ts +139 -0
- package/dist/renderer/view.d.ts.map +1 -0
- package/dist/renderer/view.js +349 -0
- package/dist/renderer/view.js.map +1 -0
- package/dist/rpgjs/shared.d.ts +41 -0
- package/dist/rpgjs/shared.d.ts.map +1 -0
- package/dist/rpgjs/shared.js +77 -0
- package/dist/rpgjs/shared.js.map +1 -0
- package/dist/server/index.d.ts +64 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +205 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/storage.d.ts +16 -0
- package/dist/server/storage.d.ts.map +1 -0
- package/dist/server/storage.js +52 -0
- package/dist/server/storage.js.map +1 -0
- package/dist/skins/default.d.ts +27 -0
- package/dist/skins/default.d.ts.map +1 -0
- package/dist/skins/default.js +144 -0
- package/dist/skins/default.js.map +1 -0
- package/dist/skins/frames.d.ts +19 -0
- package/dist/skins/frames.d.ts.map +1 -0
- package/dist/skins/frames.js +49 -0
- package/dist/skins/frames.js.map +1 -0
- package/dist/skins/game.d.ts +21 -0
- package/dist/skins/game.d.ts.map +1 -0
- package/dist/skins/game.js +21 -0
- package/dist/skins/game.js.map +1 -0
- package/dist/skins/index.d.ts +5 -0
- package/dist/skins/index.d.ts.map +1 -0
- package/dist/skins/index.js +5 -0
- package/dist/skins/types.d.ts +46 -0
- package/dist/skins/types.d.ts.map +1 -0
- package/dist/skins/types.js +12 -0
- package/dist/skins/types.js.map +1 -0
- package/dist/terrain/data.d.ts +10 -0
- package/dist/terrain/data.d.ts.map +1 -0
- package/dist/terrain/data.js +83 -0
- package/dist/terrain/data.js.map +1 -0
- package/dist/terrain/define.d.ts +72 -0
- package/dist/terrain/define.d.ts.map +1 -0
- package/dist/terrain/define.js +91 -0
- package/dist/terrain/define.js.map +1 -0
- package/dist/terrain/editing.d.ts +54 -0
- package/dist/terrain/editing.d.ts.map +1 -0
- package/dist/terrain/editing.js +169 -0
- package/dist/terrain/editing.js.map +1 -0
- package/dist/terrain/index.d.ts +10 -0
- package/dist/terrain/index.d.ts.map +1 -0
- package/dist/terrain/index.js +10 -0
- package/dist/terrain/kernel.d.ts +57 -0
- package/dist/terrain/kernel.d.ts.map +1 -0
- package/dist/terrain/kernel.js +198 -0
- package/dist/terrain/kernel.js.map +1 -0
- package/dist/terrain/mask.d.ts +37 -0
- package/dist/terrain/mask.d.ts.map +1 -0
- package/dist/terrain/mask.js +244 -0
- package/dist/terrain/mask.js.map +1 -0
- package/dist/terrain/patches.d.ts +32 -0
- package/dist/terrain/patches.d.ts.map +1 -0
- package/dist/terrain/patches.js +135 -0
- package/dist/terrain/patches.js.map +1 -0
- package/dist/terrain/rle.d.ts +4 -0
- package/dist/terrain/rle.d.ts.map +1 -0
- package/dist/terrain/rle.js +68 -0
- package/dist/terrain/rle.js.map +1 -0
- package/dist/terrain/scheduler.d.ts +46 -0
- package/dist/terrain/scheduler.d.ts.map +1 -0
- package/dist/terrain/scheduler.js +148 -0
- package/dist/terrain/scheduler.js.map +1 -0
- package/dist/terrain/types.d.ts +110 -0
- package/dist/terrain/types.d.ts.map +1 -0
- package/dist/terrain/types.js +11 -0
- package/dist/terrain/types.js.map +1 -0
- package/dist/terrain-render/backend.d.ts +66 -0
- package/dist/terrain-render/backend.d.ts.map +1 -0
- package/dist/terrain-render/backend.js +96 -0
- package/dist/terrain-render/backend.js.map +1 -0
- package/dist/terrain-render/host.d.ts +21 -0
- package/dist/terrain-render/host.d.ts.map +1 -0
- package/dist/terrain-render/host.js +62 -0
- package/dist/terrain-render/host.js.map +1 -0
- package/dist/terrain-render/index.d.ts +3 -0
- package/dist/terrain-render/index.d.ts.map +1 -0
- package/dist/terrain-render/index.js +3 -0
- package/dist/terrain-render/worker.d.ts +2 -0
- package/dist/terrain-render/worker.d.ts.map +1 -0
- package/dist/terrain-render/worker.js +34 -0
- package/dist/terrain-render/worker.js.map +1 -0
- package/package.json +124 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effects.d.ts","sourceRoot":"","sources":["../../src/renderer/effects.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY;IACrE,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mCAAmC;IACnC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;CACjE;AAKD,uGAAuG;AACvG,eAAO,MAAM,WAAW,EAAE,gBAOzB,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,WAAW,EAAE,gBAMzB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAA8C,CAAC;AAErH,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,WAAW;AACb,4DAA4D;GAC1D,gBAAgB,GAChB,gBAAgB,GAChB,gBAAgB,CAAC;AAErB,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,MAAM,IAAI,IAAI,CAAC;IACf,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,mBAAmB;IAClC,qCAAqC;IACrC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IACnC,mGAAmG;IACnG,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAAC;IAC5D,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC3D,qGAAqG;IACrG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,YAAY,CAAC;IAC3E,sGAAsG;IACtG,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,2DAA2D;IAC3D,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,sDAAsD;IACtD,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAYD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAgF7E"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
//#region src/renderer/effects.ts
|
|
2
|
+
var number = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
3
|
+
/** Left-right oscillation that fades out. Params: `intensity` (pixels), `frequency` (oscillations). */
|
|
4
|
+
var shakeEffect = {
|
|
5
|
+
duration: 300,
|
|
6
|
+
update(progress, params, output) {
|
|
7
|
+
const intensity = number(params.intensity, 3);
|
|
8
|
+
const frequency = number(params.frequency, 6);
|
|
9
|
+
output.offsetX += Math.sin(progress * frequency * Math.PI * 2) * intensity * (1 - progress);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
/** Fades out and back in. Params: `strength` (0 to 1, how transparent it gets). */
|
|
13
|
+
var blinkEffect = {
|
|
14
|
+
duration: 400,
|
|
15
|
+
update(progress, params, output) {
|
|
16
|
+
const strength = Math.min(1, Math.max(0, number(params.strength, .6)));
|
|
17
|
+
output.alpha *= 1 - strength * Math.sin(progress * Math.PI);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var builtInEffects = {
|
|
21
|
+
shake: shakeEffect,
|
|
22
|
+
blink: blinkEffect
|
|
23
|
+
};
|
|
24
|
+
function createEffectRunner(options) {
|
|
25
|
+
const definitions = new Map(Object.entries(builtInEffects));
|
|
26
|
+
let running = [];
|
|
27
|
+
let nextId = 1;
|
|
28
|
+
const finish = (effect, status) => {
|
|
29
|
+
running = running.filter((entry) => entry !== effect);
|
|
30
|
+
effect.settle(status);
|
|
31
|
+
};
|
|
32
|
+
const flush = (targetKey) => {
|
|
33
|
+
const active = running.filter((entry) => entry.targetKey === targetKey);
|
|
34
|
+
if (active.length === 0) {
|
|
35
|
+
options.apply(targetKey, null);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const output = {
|
|
39
|
+
offsetX: 0,
|
|
40
|
+
offsetY: 0,
|
|
41
|
+
alpha: 1
|
|
42
|
+
};
|
|
43
|
+
for (const effect of active) effect.definition.update(Math.min(1, effect.elapsed / effect.duration), effect.params, output);
|
|
44
|
+
options.apply(targetKey, output);
|
|
45
|
+
};
|
|
46
|
+
const ended = (status) => ({
|
|
47
|
+
id: 0,
|
|
48
|
+
cancel: () => void 0,
|
|
49
|
+
finished: Promise.resolve(status)
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
register(name, definition) {
|
|
53
|
+
definitions.set(name, definition);
|
|
54
|
+
},
|
|
55
|
+
play(targetKey, name, params = {}) {
|
|
56
|
+
const definition = definitions.get(name);
|
|
57
|
+
if (!definition) {
|
|
58
|
+
options.onWarning?.(`Unknown effect "${name}"`);
|
|
59
|
+
return ended("unknown-effect");
|
|
60
|
+
}
|
|
61
|
+
if (!options.exists(targetKey)) return ended("target-missing");
|
|
62
|
+
const duration = Math.max(1, number(params.duration, definition.duration));
|
|
63
|
+
let settle;
|
|
64
|
+
const finished = new Promise((resolve) => settle = resolve);
|
|
65
|
+
const effect = {
|
|
66
|
+
id: nextId++,
|
|
67
|
+
targetKey,
|
|
68
|
+
definition,
|
|
69
|
+
params,
|
|
70
|
+
duration,
|
|
71
|
+
elapsed: 0,
|
|
72
|
+
settle
|
|
73
|
+
};
|
|
74
|
+
running.push(effect);
|
|
75
|
+
flush(targetKey);
|
|
76
|
+
return {
|
|
77
|
+
id: effect.id,
|
|
78
|
+
cancel: () => {
|
|
79
|
+
if (!running.includes(effect)) return;
|
|
80
|
+
finish(effect, "cancelled");
|
|
81
|
+
flush(targetKey);
|
|
82
|
+
},
|
|
83
|
+
finished
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
tick(deltaMs) {
|
|
87
|
+
if (running.length === 0) return;
|
|
88
|
+
const targets = new Set(running.map((effect) => effect.targetKey));
|
|
89
|
+
for (const effect of running) effect.elapsed += deltaMs;
|
|
90
|
+
for (const effect of [...running]) if (effect.elapsed >= effect.duration) finish(effect, "completed");
|
|
91
|
+
for (const targetKey of targets) flush(targetKey);
|
|
92
|
+
},
|
|
93
|
+
clear(targetKey) {
|
|
94
|
+
const cancelled = running.filter((effect) => targetKey === void 0 || effect.targetKey === targetKey);
|
|
95
|
+
const targets = new Set(cancelled.map((effect) => effect.targetKey));
|
|
96
|
+
for (const effect of cancelled) finish(effect, "cancelled");
|
|
97
|
+
for (const key of targets) flush(key);
|
|
98
|
+
},
|
|
99
|
+
targetRemoved(targetKey) {
|
|
100
|
+
const removed = running.filter((effect) => effect.targetKey === targetKey);
|
|
101
|
+
for (const effect of removed) finish(effect, "target-removed");
|
|
102
|
+
},
|
|
103
|
+
get running() {
|
|
104
|
+
return running.length;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
export { blinkEffect, builtInEffects, createEffectRunner, shakeEffect };
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=effects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effects.js","names":[],"sources":["../../src/renderer/effects.ts"],"sourcesContent":["/** What an effect writes for one frame. Effects combine: offsets add up, alphas multiply. */\nexport interface EffectOutput {\n offsetX: number;\n offsetY: number;\n alpha: number;\n}\n\nexport type EffectParams = Readonly<Record<string, unknown>>;\n\n/**\n * A short-lived visual change: an element shaking, blinking. It never goes\n * through the model, so it is not persisted, undoable, nor replayed to late joiners.\n */\nexport interface EffectDefinition<P extends EffectParams = EffectParams> {\n /** Default duration in milliseconds. A `duration` param overrides it. */\n readonly duration: number;\n /** `progress` goes from 0 to 1. */\n update(progress: number, params: P, output: EffectOutput): void;\n}\n\nconst number = (value: unknown, fallback: number): number =>\n typeof value === \"number\" && Number.isFinite(value) ? value : fallback;\n\n/** Left-right oscillation that fades out. Params: `intensity` (pixels), `frequency` (oscillations). */\nexport const shakeEffect: EffectDefinition = {\n duration: 300,\n update(progress, params, output) {\n const intensity = number(params.intensity, 3);\n const frequency = number(params.frequency, 6);\n output.offsetX += Math.sin(progress * frequency * Math.PI * 2) * intensity * (1 - progress);\n },\n};\n\n/** Fades out and back in. Params: `strength` (0 to 1, how transparent it gets). */\nexport const blinkEffect: EffectDefinition = {\n duration: 400,\n update(progress, params, output) {\n const strength = Math.min(1, Math.max(0, number(params.strength, 0.6)));\n output.alpha *= 1 - strength * Math.sin(progress * Math.PI);\n },\n};\n\nexport const builtInEffects: Readonly<Record<string, EffectDefinition>> = { shake: shakeEffect, blink: blinkEffect };\n\nexport type EffectStatus =\n | \"completed\"\n | \"cancelled\"\n /** The element disappeared while the effect was running. */\n | \"target-removed\"\n | \"unknown-effect\"\n | \"target-missing\";\n\nexport interface EffectHandle {\n readonly id: number;\n /** Stops the effect and restores the target. No effect if it already ended. */\n cancel(): void;\n readonly finished: Promise<EffectStatus>;\n}\n\nexport interface EffectRunnerOptions {\n /** Whether a target still exists. */\n exists(targetKey: string): boolean;\n /** Receives the combined result for a target each frame, and `null` once its last effect ended. */\n apply(targetKey: string, output: EffectOutput | null): void;\n onWarning?(message: string): void;\n}\n\nexport interface EffectRunner {\n register(name: string, definition: EffectDefinition): void;\n /** Starts an effect. It never throws: an unknown effect or target ends immediately with a status. */\n play(targetKey: string, name: string, params?: EffectParams): EffectHandle;\n /** Advances every running effect by `deltaMs`. Time only moves here, so effects are deterministic. */\n tick(deltaMs: number): void;\n /** Cancels every effect on a target, or on all targets. */\n clear(targetKey?: string): void;\n /** Called when a target is removed from the scene. */\n targetRemoved(targetKey: string): void;\n readonly running: number;\n}\n\ninterface Running {\n readonly id: number;\n readonly targetKey: string;\n readonly definition: EffectDefinition;\n readonly params: EffectParams;\n readonly duration: number;\n elapsed: number;\n readonly settle: (status: EffectStatus) => void;\n}\n\nexport function createEffectRunner(options: EffectRunnerOptions): EffectRunner {\n const definitions = new Map<string, EffectDefinition>(Object.entries(builtInEffects));\n let running: Running[] = [];\n let nextId = 1;\n\n const finish = (effect: Running, status: EffectStatus): void => {\n running = running.filter((entry) => entry !== effect);\n effect.settle(status);\n };\n\n const flush = (targetKey: string): void => {\n const active = running.filter((entry) => entry.targetKey === targetKey);\n if (active.length === 0) {\n options.apply(targetKey, null);\n return;\n }\n const output: EffectOutput = { offsetX: 0, offsetY: 0, alpha: 1 };\n for (const effect of active) {\n effect.definition.update(Math.min(1, effect.elapsed / effect.duration), effect.params, output);\n }\n options.apply(targetKey, output);\n };\n\n const ended = (status: EffectStatus): EffectHandle => ({\n id: 0,\n cancel: () => undefined,\n finished: Promise.resolve(status),\n });\n\n return {\n register(name, definition) {\n definitions.set(name, definition);\n },\n play(targetKey, name, params = {}) {\n const definition = definitions.get(name);\n if (!definition) {\n options.onWarning?.(`Unknown effect \"${name}\"`);\n return ended(\"unknown-effect\");\n }\n if (!options.exists(targetKey)) return ended(\"target-missing\");\n\n const duration = Math.max(1, number(params.duration, definition.duration));\n let settle!: (status: EffectStatus) => void;\n const finished = new Promise<EffectStatus>((resolve) => (settle = resolve));\n const effect: Running = { id: nextId++, targetKey, definition, params, duration, elapsed: 0, settle };\n running.push(effect);\n flush(targetKey);\n return {\n id: effect.id,\n cancel: () => {\n if (!running.includes(effect)) return;\n finish(effect, \"cancelled\");\n flush(targetKey);\n },\n finished,\n };\n },\n tick(deltaMs) {\n if (running.length === 0) return;\n const targets = new Set(running.map((effect) => effect.targetKey));\n for (const effect of running) effect.elapsed += deltaMs;\n for (const effect of [...running]) {\n if (effect.elapsed >= effect.duration) finish(effect, \"completed\");\n }\n for (const targetKey of targets) flush(targetKey);\n },\n clear(targetKey) {\n const cancelled = running.filter((effect) => targetKey === undefined || effect.targetKey === targetKey);\n const targets = new Set(cancelled.map((effect) => effect.targetKey));\n for (const effect of cancelled) finish(effect, \"cancelled\");\n for (const key of targets) flush(key);\n },\n targetRemoved(targetKey) {\n const removed = running.filter((effect) => effect.targetKey === targetKey);\n for (const effect of removed) finish(effect, \"target-removed\");\n },\n get running() {\n return running.length;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Container } from 'pixi.js';
|
|
2
|
+
import { AssetResolver } from '../model/assets';
|
|
3
|
+
import { MapState } from '../model/definition';
|
|
4
|
+
import { MapPatch } from '../model/patches';
|
|
5
|
+
import { ApplyResult, MaybePromise, Unsubscribe } from '../model/result';
|
|
6
|
+
import { InteractionController, ModelApi } from '../interaction/controller';
|
|
7
|
+
import { TextureStore } from './textures';
|
|
8
|
+
import { Rect } from './geometry';
|
|
9
|
+
import { StageName, SceneNode, DescribeContext } from './scene';
|
|
10
|
+
/** Version of the contract between the renderer and a feature. */
|
|
11
|
+
export declare const MAP_FEATURE_API_VERSION: 1;
|
|
12
|
+
/** How a feature contributes to the scene. */
|
|
13
|
+
export interface FeatureRender {
|
|
14
|
+
readonly stage: StageName;
|
|
15
|
+
/** Every node of the feature for `state`. */
|
|
16
|
+
describeAll(state: MapState, context: DescribeContext): readonly SceneNode[];
|
|
17
|
+
/** The node with this id, or `undefined` when there is none (removed, not drawable yet). */
|
|
18
|
+
describeOne(state: MapState, id: string, context: DescribeContext): SceneNode | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Ids of the nodes a patch may have changed. `"all"` re-describes the whole
|
|
21
|
+
* feature (for patches the feature does not understand), `"none"` ignores the patch.
|
|
22
|
+
*/
|
|
23
|
+
affected(patch: MapPatch): readonly string[] | "all" | "none";
|
|
24
|
+
}
|
|
25
|
+
/** What a feature can reach when the view is created: the model, the interaction layer, the scene. */
|
|
26
|
+
export interface FeatureHost<R extends MaybePromise<ApplyResult> = MaybePromise<ApplyResult>> {
|
|
27
|
+
readonly model: ModelApi<R>;
|
|
28
|
+
readonly assets: AssetResolver;
|
|
29
|
+
readonly interaction: InteractionController<R>;
|
|
30
|
+
readonly stages: Readonly<Record<StageName, Container>>;
|
|
31
|
+
readonly textures: TextureStore;
|
|
32
|
+
/** What is drawn right now. */
|
|
33
|
+
describe(): readonly SceneNode[];
|
|
34
|
+
/** Called every frame with the time elapsed, in milliseconds. */
|
|
35
|
+
onTick(listener: (deltaMs: number) => void): Unsubscribe;
|
|
36
|
+
/** What is looked at, in world pixels (`null`: no preference). Called now with the current one, then on every change. */
|
|
37
|
+
onViewport(listener: (viewport: Rect | null) => void): Unsubscribe;
|
|
38
|
+
}
|
|
39
|
+
export interface FeatureInstance<Api extends object = Record<never, never>> {
|
|
40
|
+
/** Exposed on the view under the id of the feature: `view.placement`. */
|
|
41
|
+
readonly api?: Api;
|
|
42
|
+
/** Called when the view is destroyed. */
|
|
43
|
+
dispose?(): void;
|
|
44
|
+
}
|
|
45
|
+
export interface MapFeature<Id extends string = string, Api extends object = Record<never, never>> {
|
|
46
|
+
readonly id: Id;
|
|
47
|
+
readonly apiVersion: typeof MAP_FEATURE_API_VERSION;
|
|
48
|
+
/** Ids of the features this one cannot work without. */
|
|
49
|
+
readonly requires: readonly string[];
|
|
50
|
+
/** What the feature draws. */
|
|
51
|
+
readonly render?: FeatureRender;
|
|
52
|
+
/**
|
|
53
|
+
* What the feature does besides drawing: registers intents and tools, mounts skins.
|
|
54
|
+
* Runs once, when the view is created.
|
|
55
|
+
*/
|
|
56
|
+
readonly setup?: (host: FeatureHost) => FeatureInstance<Api>;
|
|
57
|
+
}
|
|
58
|
+
/** Names the view already uses: a feature cannot take them as its id. */
|
|
59
|
+
export declare const RESERVED_FEATURE_IDS: readonly string[];
|
|
60
|
+
type UnionToIntersection<U> = (U extends unknown ? (value: U) => void : never) extends (value: infer I) => void ? I : never;
|
|
61
|
+
/** The APIs features add to the view, keyed by feature id: `view.placement`, `view.selection`. */
|
|
62
|
+
export type FeatureApis<F extends readonly MapFeature<string, object>[]> = UnionToIntersection<F[number] extends infer Feature ? Feature extends MapFeature<infer Id, infer Api> ? keyof Api extends never ? never : {
|
|
63
|
+
readonly [K in Id]: Api;
|
|
64
|
+
} : never : never>;
|
|
65
|
+
export declare class MapFeatureError extends Error {
|
|
66
|
+
constructor(message: string);
|
|
67
|
+
}
|
|
68
|
+
export interface MapFeatureDefinition<Id extends string = string, Api extends object = Record<never, never>> {
|
|
69
|
+
readonly id: Id;
|
|
70
|
+
readonly requires?: readonly string[];
|
|
71
|
+
readonly render?: FeatureRender;
|
|
72
|
+
readonly setup?: (host: FeatureHost) => FeatureInstance<Api>;
|
|
73
|
+
}
|
|
74
|
+
/** Declares a feature. Built-in and third-party features use the same contract. */
|
|
75
|
+
export declare function defineMapFeature<const Id extends string, Api extends object = Record<never, never>>(definition: MapFeatureDefinition<Id, Api>): MapFeature<Id, Api>;
|
|
76
|
+
/** Checks ids and dependencies. Fails at startup, with a message that says what to add. */
|
|
77
|
+
export declare function validateFeatures(features: readonly MapFeature<string, object>[]): void;
|
|
78
|
+
export {};
|
|
79
|
+
//# sourceMappingURL=feature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../../src/renderer/feature.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/renderer/feature.ts
|
|
2
|
+
/** Version of the contract between the renderer and a feature. */
|
|
3
|
+
var MAP_FEATURE_API_VERSION = 1;
|
|
4
|
+
/** Names the view already uses: a feature cannot take them as its id. */
|
|
5
|
+
var RESERVED_FEATURE_IDS = [
|
|
6
|
+
"root",
|
|
7
|
+
"stages",
|
|
8
|
+
"effects",
|
|
9
|
+
"setViewport",
|
|
10
|
+
"tick",
|
|
11
|
+
"mount",
|
|
12
|
+
"whenIdle",
|
|
13
|
+
"describe",
|
|
14
|
+
"destroy",
|
|
15
|
+
"interaction",
|
|
16
|
+
"submit",
|
|
17
|
+
"textures"
|
|
18
|
+
];
|
|
19
|
+
var MapFeatureError = class extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "MapFeatureError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/** Declares a feature. Built-in and third-party features use the same contract. */
|
|
26
|
+
function defineMapFeature(definition) {
|
|
27
|
+
if (typeof definition.id !== "string" || definition.id.length === 0) throw new MapFeatureError("A feature needs a non-empty id");
|
|
28
|
+
if (RESERVED_FEATURE_IDS.includes(definition.id)) throw new MapFeatureError(`"${definition.id}" is already a property of the view and cannot be the id of a feature`);
|
|
29
|
+
return Object.freeze({
|
|
30
|
+
id: definition.id,
|
|
31
|
+
apiVersion: 1,
|
|
32
|
+
requires: Object.freeze([...definition.requires ?? []]),
|
|
33
|
+
...definition.render ? { render: definition.render } : {},
|
|
34
|
+
...definition.setup ? { setup: definition.setup } : {}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Checks ids and dependencies. Fails at startup, with a message that says what to add. */
|
|
38
|
+
function validateFeatures(features) {
|
|
39
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40
|
+
for (const feature of features) {
|
|
41
|
+
if (feature.apiVersion !== 1) throw new MapFeatureError(`Feature "${feature.id}" was built for feature API version ${String(feature.apiVersion)}, this renderer supports version 1`);
|
|
42
|
+
if (seen.has(feature.id)) throw new MapFeatureError(`Feature "${feature.id}" is listed twice`);
|
|
43
|
+
seen.add(feature.id);
|
|
44
|
+
}
|
|
45
|
+
for (const feature of features) for (const required of feature.requires) if (!seen.has(required)) throw new MapFeatureError(`Feature "${feature.id}" requires "${required}", which is not in the features list`);
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { MAP_FEATURE_API_VERSION, MapFeatureError, RESERVED_FEATURE_IDS, defineMapFeature, validateFeatures };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=feature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature.js","names":[],"sources":["../../src/renderer/feature.ts"],"sourcesContent":["import type { Container } from \"pixi.js\";\nimport type { AssetResolver } from \"../model/assets\";\nimport type { MapState } from \"../model/definition\";\nimport type { MapPatch } from \"../model/patches\";\nimport type { ApplyResult, MaybePromise, Unsubscribe } from \"../model/result\";\nimport type { InteractionController, ModelApi } from \"../interaction/controller\";\nimport type { TextureStore } from \"./textures\";\nimport type { Rect } from \"./geometry\";\nimport type { StageName, SceneNode, DescribeContext } from \"./scene\";\n\n/** Version of the contract between the renderer and a feature. */\nexport const MAP_FEATURE_API_VERSION = 1 as const;\n\n/** How a feature contributes to the scene. */\nexport interface FeatureRender {\n readonly stage: StageName;\n /** Every node of the feature for `state`. */\n describeAll(state: MapState, context: DescribeContext): readonly SceneNode[];\n /** The node with this id, or `undefined` when there is none (removed, not drawable yet). */\n describeOne(state: MapState, id: string, context: DescribeContext): SceneNode | undefined;\n /**\n * Ids of the nodes a patch may have changed. `\"all\"` re-describes the whole\n * feature (for patches the feature does not understand), `\"none\"` ignores the patch.\n */\n affected(patch: MapPatch): readonly string[] | \"all\" | \"none\";\n}\n\n/** What a feature can reach when the view is created: the model, the interaction layer, the scene. */\nexport interface FeatureHost<R extends MaybePromise<ApplyResult> = MaybePromise<ApplyResult>> {\n readonly model: ModelApi<R>;\n readonly assets: AssetResolver;\n readonly interaction: InteractionController<R>;\n readonly stages: Readonly<Record<StageName, Container>>;\n readonly textures: TextureStore;\n /** What is drawn right now. */\n describe(): readonly SceneNode[];\n /** Called every frame with the time elapsed, in milliseconds. */\n onTick(listener: (deltaMs: number) => void): Unsubscribe;\n /** What is looked at, in world pixels (`null`: no preference). Called now with the current one, then on every change. */\n onViewport(listener: (viewport: Rect | null) => void): Unsubscribe;\n}\n\nexport interface FeatureInstance<Api extends object = Record<never, never>> {\n /** Exposed on the view under the id of the feature: `view.placement`. */\n readonly api?: Api;\n /** Called when the view is destroyed. */\n dispose?(): void;\n}\n\nexport interface MapFeature<Id extends string = string, Api extends object = Record<never, never>> {\n readonly id: Id;\n readonly apiVersion: typeof MAP_FEATURE_API_VERSION;\n /** Ids of the features this one cannot work without. */\n readonly requires: readonly string[];\n /** What the feature draws. */\n readonly render?: FeatureRender;\n /**\n * What the feature does besides drawing: registers intents and tools, mounts skins.\n * Runs once, when the view is created.\n */\n readonly setup?: (host: FeatureHost) => FeatureInstance<Api>;\n}\n\n/** Names the view already uses: a feature cannot take them as its id. */\nexport const RESERVED_FEATURE_IDS: readonly string[] = [\n \"root\",\n \"stages\",\n \"effects\",\n \"setViewport\",\n \"tick\",\n \"mount\",\n \"whenIdle\",\n \"describe\",\n \"destroy\",\n \"interaction\",\n \"submit\",\n \"textures\",\n];\n\ntype UnionToIntersection<U> = (U extends unknown ? (value: U) => void : never) extends (value: infer I) => void ? I : never;\n\n/** The APIs features add to the view, keyed by feature id: `view.placement`, `view.selection`. */\nexport type FeatureApis<F extends readonly MapFeature<string, object>[]> = UnionToIntersection<\n F[number] extends infer Feature\n ? Feature extends MapFeature<infer Id, infer Api>\n ? keyof Api extends never\n ? never\n : { readonly [K in Id]: Api }\n : never\n : never\n>;\n\nexport class MapFeatureError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"MapFeatureError\";\n }\n}\n\nexport interface MapFeatureDefinition<Id extends string = string, Api extends object = Record<never, never>> {\n readonly id: Id;\n readonly requires?: readonly string[];\n readonly render?: FeatureRender;\n readonly setup?: (host: FeatureHost) => FeatureInstance<Api>;\n}\n\n/** Declares a feature. Built-in and third-party features use the same contract. */\nexport function defineMapFeature<const Id extends string, Api extends object = Record<never, never>>(\n definition: MapFeatureDefinition<Id, Api>,\n): MapFeature<Id, Api> {\n if (typeof definition.id !== \"string\" || definition.id.length === 0) {\n throw new MapFeatureError(\"A feature needs a non-empty id\");\n }\n if (RESERVED_FEATURE_IDS.includes(definition.id)) {\n throw new MapFeatureError(`\"${definition.id}\" is already a property of the view and cannot be the id of a feature`);\n }\n return Object.freeze({\n id: definition.id,\n apiVersion: MAP_FEATURE_API_VERSION,\n requires: Object.freeze([...(definition.requires ?? [])]),\n ...(definition.render ? { render: definition.render } : {}),\n ...(definition.setup ? { setup: definition.setup } : {}),\n });\n}\n\n/** Checks ids and dependencies. Fails at startup, with a message that says what to add. */\nexport function validateFeatures(features: readonly MapFeature<string, object>[]): void {\n const seen = new Set<string>();\n for (const feature of features) {\n if (feature.apiVersion !== MAP_FEATURE_API_VERSION) {\n throw new MapFeatureError(\n `Feature \"${feature.id}\" was built for feature API version ${String(feature.apiVersion)}, this renderer supports version ${MAP_FEATURE_API_VERSION}`,\n );\n }\n if (seen.has(feature.id)) throw new MapFeatureError(`Feature \"${feature.id}\" is listed twice`);\n seen.add(feature.id);\n }\n for (const feature of features) {\n for (const required of feature.requires) {\n if (!seen.has(required)) {\n throw new MapFeatureError(`Feature \"${feature.id}\" requires \"${required}\", which is not in the features list`);\n }\n }\n }\n}\n"],"mappings":";;AAWA,IAAa,0BAA0B;;AAqDvC,IAAa,uBAA0C;CACrD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAeA,IAAa,kBAAb,cAAqC,MAAM;CACzC,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;;AAUA,SAAgB,iBACd,YACqB;CACrB,IAAI,OAAO,WAAW,OAAO,YAAY,WAAW,GAAG,WAAW,GAChE,MAAM,IAAI,gBAAgB,gCAAgC;CAE5D,IAAI,qBAAqB,SAAS,WAAW,EAAE,GAC7C,MAAM,IAAI,gBAAgB,IAAI,WAAW,GAAG,sEAAsE;CAEpH,OAAO,OAAO,OAAO;EACnB,IAAI,WAAW;EACf,YAAA;EACA,UAAU,OAAO,OAAO,CAAC,GAAI,WAAW,YAAY,CAAC,CAAE,CAAC;EACxD,GAAI,WAAW,SAAS,EAAE,QAAQ,WAAW,OAAO,IAAI,CAAC;EACzD,GAAI,WAAW,QAAQ,EAAE,OAAO,WAAW,MAAM,IAAI,CAAC;CACxD,CAAC;AACH;;AAGA,SAAgB,iBAAiB,UAAuD;CACtF,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,QAAQ,eAAA,GACV,MAAM,IAAI,gBACR,YAAY,QAAQ,GAAG,sCAAsC,OAAO,QAAQ,UAAU,EAAE,mCAC1F;EAEF,IAAI,KAAK,IAAI,QAAQ,EAAE,GAAG,MAAM,IAAI,gBAAgB,YAAY,QAAQ,GAAG,kBAAkB;EAC7F,KAAK,IAAI,QAAQ,EAAE;CACrB;CACA,KAAK,MAAM,WAAW,UACpB,KAAK,MAAM,YAAY,QAAQ,UAC7B,IAAI,CAAC,KAAK,IAAI,QAAQ,GACpB,MAAM,IAAI,gBAAgB,YAAY,QAAQ,GAAG,cAAc,SAAS,qCAAqC;AAIrH"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { MapDepth, MapScale } from '../model/definition';
|
|
2
|
+
export interface Rect {
|
|
3
|
+
readonly x: number;
|
|
4
|
+
readonly y: number;
|
|
5
|
+
readonly width: number;
|
|
6
|
+
readonly height: number;
|
|
7
|
+
}
|
|
8
|
+
export type Frame = readonly [x: number, y: number, width: number, height: number];
|
|
9
|
+
export declare function normalizeScale(scale: MapScale | undefined): {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
};
|
|
13
|
+
export interface GeometryInput {
|
|
14
|
+
/** World position of the top-left corner. */
|
|
15
|
+
readonly x: number;
|
|
16
|
+
readonly y: number;
|
|
17
|
+
readonly scale?: MapScale;
|
|
18
|
+
readonly depth?: MapDepth;
|
|
19
|
+
/** Part of the source image to show. */
|
|
20
|
+
readonly frame?: Frame;
|
|
21
|
+
/**
|
|
22
|
+
* Size of the whole source image, when `frame` is not given. Without it, `size`
|
|
23
|
+
* is taken as the size of the image too.
|
|
24
|
+
*/
|
|
25
|
+
readonly sourceSize?: readonly [number, number];
|
|
26
|
+
/** Size at which the asset is drawn before `scale`. Defaults to the size of the frame. */
|
|
27
|
+
readonly size?: readonly [number, number];
|
|
28
|
+
/** `[x, y, width, height]` collision box, in pixels of the source image. */
|
|
29
|
+
readonly hitbox?: Frame;
|
|
30
|
+
}
|
|
31
|
+
export interface ElementGeometry {
|
|
32
|
+
/** Where the element is drawn, after scale. */
|
|
33
|
+
readonly draw: Rect;
|
|
34
|
+
/** Collision box in source pixels, clamped to the source, with the ratio that maps it to the world. */
|
|
35
|
+
readonly hitbox?: Rect & {
|
|
36
|
+
readonly scaleX: number;
|
|
37
|
+
readonly scaleY: number;
|
|
38
|
+
};
|
|
39
|
+
/** Elements with a higher key are drawn in front. */
|
|
40
|
+
readonly sortKey: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Where an element is drawn and in which order, with the rules of the Studio
|
|
44
|
+
* renderer:
|
|
45
|
+
*
|
|
46
|
+
* - sizes are rounded and at least 1 pixel;
|
|
47
|
+
* - the hitbox is clamped to the source image and scaled with the drawing;
|
|
48
|
+
* - the sort key is the bottom of the hitbox (or of the drawing without one),
|
|
49
|
+
* plus `depth.z`. `depth.anchorY` replaces the bottom with an explicit offset.
|
|
50
|
+
*
|
|
51
|
+
* Returns `undefined` when the size of the source is unknown: no `frame`, no
|
|
52
|
+
* `sourceSize` and no `size`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveElementGeometry(input: GeometryInput): ElementGeometry | undefined;
|
|
55
|
+
export declare function unionBounds(rects: readonly Rect[]): Rect;
|
|
56
|
+
/** True when the two rectangles overlap or touch. */
|
|
57
|
+
export declare function intersects(a: Rect, b: Rect): boolean;
|
|
58
|
+
export declare function grow(rect: Rect, margin: number): Rect;
|
|
59
|
+
//# sourceMappingURL=geometry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../../src/renderer/geometry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE9D,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAMnF,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAOpF;AAED,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC;IAC1B,wCAAwC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,0FAA0F;IAC1F,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,uGAAuG;IACvG,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,GAAG;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9E,qDAAqD;IACrD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,aAAa,GAAG,eAAe,GAAG,SAAS,CA0CxF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,IAAI,CAaxD;AAED,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,OAAO,CAEpD;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAErD"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//#region src/renderer/geometry.ts
|
|
2
|
+
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
3
|
+
/** Pixel sizes are whole numbers, and at least 1. */
|
|
4
|
+
var toSize = (value) => Math.max(1, Math.round(value));
|
|
5
|
+
function normalizeScale(scale) {
|
|
6
|
+
if (typeof scale === "number") return scale > 0 && Number.isFinite(scale) ? {
|
|
7
|
+
x: scale,
|
|
8
|
+
y: scale
|
|
9
|
+
} : {
|
|
10
|
+
x: 1,
|
|
11
|
+
y: 1
|
|
12
|
+
};
|
|
13
|
+
if (Array.isArray(scale)) {
|
|
14
|
+
const [x, y] = scale;
|
|
15
|
+
return {
|
|
16
|
+
x: x > 0 && Number.isFinite(x) ? x : 1,
|
|
17
|
+
y: y > 0 && Number.isFinite(y) ? y : 1
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
x: 1,
|
|
22
|
+
y: 1
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Where an element is drawn and in which order, with the rules of the Studio
|
|
27
|
+
* renderer:
|
|
28
|
+
*
|
|
29
|
+
* - sizes are rounded and at least 1 pixel;
|
|
30
|
+
* - the hitbox is clamped to the source image and scaled with the drawing;
|
|
31
|
+
* - the sort key is the bottom of the hitbox (or of the drawing without one),
|
|
32
|
+
* plus `depth.z`. `depth.anchorY` replaces the bottom with an explicit offset.
|
|
33
|
+
*
|
|
34
|
+
* Returns `undefined` when the size of the source is unknown: no `frame`, no
|
|
35
|
+
* `sourceSize` and no `size`.
|
|
36
|
+
*/
|
|
37
|
+
function resolveElementGeometry(input) {
|
|
38
|
+
const source = input.frame ? [input.frame[2], input.frame[3]] : input.sourceSize ?? input.size;
|
|
39
|
+
if (!source) return void 0;
|
|
40
|
+
const [sourceWidth, sourceHeight] = source;
|
|
41
|
+
const scale = normalizeScale(input.scale);
|
|
42
|
+
const [baseWidth, baseHeight] = input.size ?? source;
|
|
43
|
+
const draw = {
|
|
44
|
+
x: input.x,
|
|
45
|
+
y: input.y,
|
|
46
|
+
width: toSize(toSize(baseWidth) * scale.x),
|
|
47
|
+
height: toSize(toSize(baseHeight) * scale.y)
|
|
48
|
+
};
|
|
49
|
+
const z = input.depth?.z ?? 0;
|
|
50
|
+
let hitbox;
|
|
51
|
+
if (input.hitbox) {
|
|
52
|
+
const boundsWidth = Math.max(1, sourceWidth);
|
|
53
|
+
const boundsHeight = Math.max(1, sourceHeight);
|
|
54
|
+
const [rawX, rawY, rawWidth, rawHeight] = input.hitbox;
|
|
55
|
+
const x = clamp(rawX, 0, Math.max(0, boundsWidth - 1));
|
|
56
|
+
const y = clamp(rawY, 0, Math.max(0, boundsHeight - 1));
|
|
57
|
+
const right = clamp(rawX + toSize(rawWidth), x + 1, boundsWidth);
|
|
58
|
+
const bottom = clamp(rawY + toSize(rawHeight), y + 1, boundsHeight);
|
|
59
|
+
hitbox = {
|
|
60
|
+
x,
|
|
61
|
+
y,
|
|
62
|
+
width: right - x,
|
|
63
|
+
height: bottom - y,
|
|
64
|
+
scaleX: draw.width / boundsWidth,
|
|
65
|
+
scaleY: draw.height / boundsHeight
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const anchor = input.depth?.anchorY !== void 0 ? input.depth.anchorY : hitbox ? (hitbox.y + hitbox.height) * hitbox.scaleY : draw.height;
|
|
69
|
+
return {
|
|
70
|
+
draw,
|
|
71
|
+
...hitbox ? { hitbox } : {},
|
|
72
|
+
sortKey: Math.round(draw.y + anchor + z)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function unionBounds(rects) {
|
|
76
|
+
if (rects.length === 0) return {
|
|
77
|
+
x: 0,
|
|
78
|
+
y: 0,
|
|
79
|
+
width: 0,
|
|
80
|
+
height: 0
|
|
81
|
+
};
|
|
82
|
+
let left = Infinity;
|
|
83
|
+
let top = Infinity;
|
|
84
|
+
let right = -Infinity;
|
|
85
|
+
let bottom = -Infinity;
|
|
86
|
+
for (const rect of rects) {
|
|
87
|
+
left = Math.min(left, rect.x);
|
|
88
|
+
top = Math.min(top, rect.y);
|
|
89
|
+
right = Math.max(right, rect.x + rect.width);
|
|
90
|
+
bottom = Math.max(bottom, rect.y + rect.height);
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
x: left,
|
|
94
|
+
y: top,
|
|
95
|
+
width: right - left,
|
|
96
|
+
height: bottom - top
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** True when the two rectangles overlap or touch. */
|
|
100
|
+
function intersects(a, b) {
|
|
101
|
+
return a.x <= b.x + b.width && b.x <= a.x + a.width && a.y <= b.y + b.height && b.y <= a.y + a.height;
|
|
102
|
+
}
|
|
103
|
+
function grow(rect, margin) {
|
|
104
|
+
return {
|
|
105
|
+
x: rect.x - margin,
|
|
106
|
+
y: rect.y - margin,
|
|
107
|
+
width: rect.width + margin * 2,
|
|
108
|
+
height: rect.height + margin * 2
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { grow, intersects, normalizeScale, resolveElementGeometry, unionBounds };
|
|
113
|
+
|
|
114
|
+
//# sourceMappingURL=geometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry.js","names":[],"sources":["../../src/renderer/geometry.ts"],"sourcesContent":["import type { MapDepth, MapScale } from \"../model/definition\";\n\nexport interface Rect {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\nexport type Frame = readonly [x: number, y: number, width: number, height: number];\n\nconst clamp = (value: number, min: number, max: number): number => Math.max(min, Math.min(max, value));\n/** Pixel sizes are whole numbers, and at least 1. */\nconst toSize = (value: number): number => Math.max(1, Math.round(value));\n\nexport function normalizeScale(scale: MapScale | undefined): { x: number; y: number } {\n if (typeof scale === \"number\") return scale > 0 && Number.isFinite(scale) ? { x: scale, y: scale } : { x: 1, y: 1 };\n if (Array.isArray(scale)) {\n const [x, y] = scale as readonly [number, number];\n return { x: x > 0 && Number.isFinite(x) ? x : 1, y: y > 0 && Number.isFinite(y) ? y : 1 };\n }\n return { x: 1, y: 1 };\n}\n\nexport interface GeometryInput {\n /** World position of the top-left corner. */\n readonly x: number;\n readonly y: number;\n readonly scale?: MapScale;\n readonly depth?: MapDepth;\n /** Part of the source image to show. */\n readonly frame?: Frame;\n /**\n * Size of the whole source image, when `frame` is not given. Without it, `size`\n * is taken as the size of the image too.\n */\n readonly sourceSize?: readonly [number, number];\n /** Size at which the asset is drawn before `scale`. Defaults to the size of the frame. */\n readonly size?: readonly [number, number];\n /** `[x, y, width, height]` collision box, in pixels of the source image. */\n readonly hitbox?: Frame;\n}\n\nexport interface ElementGeometry {\n /** Where the element is drawn, after scale. */\n readonly draw: Rect;\n /** Collision box in source pixels, clamped to the source, with the ratio that maps it to the world. */\n readonly hitbox?: Rect & { readonly scaleX: number; readonly scaleY: number };\n /** Elements with a higher key are drawn in front. */\n readonly sortKey: number;\n}\n\n/**\n * Where an element is drawn and in which order, with the rules of the Studio\n * renderer:\n *\n * - sizes are rounded and at least 1 pixel;\n * - the hitbox is clamped to the source image and scaled with the drawing;\n * - the sort key is the bottom of the hitbox (or of the drawing without one),\n * plus `depth.z`. `depth.anchorY` replaces the bottom with an explicit offset.\n *\n * Returns `undefined` when the size of the source is unknown: no `frame`, no\n * `sourceSize` and no `size`.\n */\nexport function resolveElementGeometry(input: GeometryInput): ElementGeometry | undefined {\n const source = input.frame ? ([input.frame[2], input.frame[3]] as const) : (input.sourceSize ?? input.size);\n if (!source) return undefined;\n const [sourceWidth, sourceHeight] = source;\n const scale = normalizeScale(input.scale);\n\n const [baseWidth, baseHeight] = input.size ?? source;\n const draw: Rect = {\n x: input.x,\n y: input.y,\n width: toSize(toSize(baseWidth) * scale.x),\n height: toSize(toSize(baseHeight) * scale.y),\n };\n const z = input.depth?.z ?? 0;\n\n let hitbox: ElementGeometry[\"hitbox\"];\n if (input.hitbox) {\n const boundsWidth = Math.max(1, sourceWidth);\n const boundsHeight = Math.max(1, sourceHeight);\n const [rawX, rawY, rawWidth, rawHeight] = input.hitbox;\n const x = clamp(rawX, 0, Math.max(0, boundsWidth - 1));\n const y = clamp(rawY, 0, Math.max(0, boundsHeight - 1));\n const right = clamp(rawX + toSize(rawWidth), x + 1, boundsWidth);\n const bottom = clamp(rawY + toSize(rawHeight), y + 1, boundsHeight);\n hitbox = {\n x,\n y,\n width: right - x,\n height: bottom - y,\n scaleX: draw.width / boundsWidth,\n scaleY: draw.height / boundsHeight,\n };\n }\n\n const anchor =\n input.depth?.anchorY !== undefined\n ? input.depth.anchorY\n : hitbox\n ? (hitbox.y + hitbox.height) * hitbox.scaleY\n : draw.height;\n\n return { draw, ...(hitbox ? { hitbox } : {}), sortKey: Math.round(draw.y + anchor + z) };\n}\n\nexport function unionBounds(rects: readonly Rect[]): Rect {\n if (rects.length === 0) return { x: 0, y: 0, width: 0, height: 0 };\n let left = Infinity;\n let top = Infinity;\n let right = -Infinity;\n let bottom = -Infinity;\n for (const rect of rects) {\n left = Math.min(left, rect.x);\n top = Math.min(top, rect.y);\n right = Math.max(right, rect.x + rect.width);\n bottom = Math.max(bottom, rect.y + rect.height);\n }\n return { x: left, y: top, width: right - left, height: bottom - top };\n}\n\n/** True when the two rectangles overlap or touch. */\nexport function intersects(a: Rect, b: Rect): boolean {\n return a.x <= b.x + b.width && b.x <= a.x + a.width && a.y <= b.y + b.height && b.y <= a.y + a.height;\n}\n\nexport function grow(rect: Rect, margin: number): Rect {\n return { x: rect.x - margin, y: rect.y - margin, width: rect.width + margin * 2, height: rect.height + margin * 2 };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/renderer/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,QAAQ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { grow, intersects, normalizeScale, resolveElementGeometry, unionBounds } from "./geometry.js";
|
|
2
|
+
import { STAGE_ORDER, describeElement, describeEvent } from "./scene.js";
|
|
3
|
+
import { blinkEffect, builtInEffects, createEffectRunner, shakeEffect } from "./effects.js";
|
|
4
|
+
import { MAP_FEATURE_API_VERSION, MapFeatureError, RESERVED_FEATURE_IDS, defineMapFeature, validateFeatures } from "./feature.js";
|
|
5
|
+
import { createTextureStore, pixiTextureProvider } from "./textures.js";
|
|
6
|
+
import { attachMapView, createMapView } from "./view.js";
|
|
7
|
+
export { MAP_FEATURE_API_VERSION, MapFeatureError, RESERVED_FEATURE_IDS, STAGE_ORDER, attachMapView, blinkEffect, builtInEffects, createEffectRunner, createMapView, createTextureStore, defineMapFeature, describeElement, describeEvent, grow, intersects, normalizeScale, pixiTextureProvider, resolveElementGeometry, shakeEffect, unionBounds, validateFeatures };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { AssetDescriptor, AssetResolver } from '../model/assets';
|
|
2
|
+
import { MapElement, MapEvent } from '../model/definition';
|
|
3
|
+
import { Frame, Rect } from './geometry';
|
|
4
|
+
export type StageName = "ground" | "objects" | "overlay" | "lighting" | "post";
|
|
5
|
+
/** Draw order, back to front. Only `objects` is filled by the built-in features. */
|
|
6
|
+
export declare const STAGE_ORDER: readonly StageName[];
|
|
7
|
+
/** One image drawn at a world position. */
|
|
8
|
+
export interface ScenePart {
|
|
9
|
+
readonly key: string;
|
|
10
|
+
readonly x: number;
|
|
11
|
+
readonly y: number;
|
|
12
|
+
readonly width: number;
|
|
13
|
+
readonly height: number;
|
|
14
|
+
readonly src: string;
|
|
15
|
+
readonly frame?: Frame;
|
|
16
|
+
}
|
|
17
|
+
/** What to draw for one element or event. Plain data: it can be compared, logged and snapshotted. */
|
|
18
|
+
export interface SceneNode {
|
|
19
|
+
/** Unique in the scene: `<feature>/<id>`. */
|
|
20
|
+
readonly key: string;
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly type: "element" | "event";
|
|
23
|
+
readonly kind: string;
|
|
24
|
+
readonly stage: StageName;
|
|
25
|
+
/** Nodes with a higher key are drawn in front. */
|
|
26
|
+
readonly sortKey: number;
|
|
27
|
+
readonly bounds: Rect;
|
|
28
|
+
readonly parts: readonly ScenePart[];
|
|
29
|
+
/** Collision box in world coordinates. */
|
|
30
|
+
readonly hitbox?: Rect;
|
|
31
|
+
}
|
|
32
|
+
/** A part given by a hook, relative to the element origin. */
|
|
33
|
+
export interface SpritePartSpec {
|
|
34
|
+
readonly key?: string;
|
|
35
|
+
readonly x: number;
|
|
36
|
+
readonly y: number;
|
|
37
|
+
readonly width: number;
|
|
38
|
+
readonly height: number;
|
|
39
|
+
readonly src?: string;
|
|
40
|
+
readonly frame?: Frame;
|
|
41
|
+
}
|
|
42
|
+
/** What a hook can change about how an element is drawn. */
|
|
43
|
+
export interface ElementSpriteSpec {
|
|
44
|
+
/** Replaces the single image by these parts (a repeated body, a nine-slice…). */
|
|
45
|
+
readonly parts?: readonly SpritePartSpec[];
|
|
46
|
+
readonly src?: string;
|
|
47
|
+
readonly frame?: Frame;
|
|
48
|
+
readonly size?: readonly [number, number];
|
|
49
|
+
readonly hitbox?: Frame;
|
|
50
|
+
}
|
|
51
|
+
export interface MapViewHooks {
|
|
52
|
+
/** Changes how an element is drawn. Studio uses it for drawing rules and element sizes. */
|
|
53
|
+
resolveElementSprite?(element: MapElement, context: {
|
|
54
|
+
readonly descriptor: AssetDescriptor | undefined;
|
|
55
|
+
}): ElementSpriteSpec | undefined;
|
|
56
|
+
/** Last chance to adjust a node, e.g. its sort key or its parts. */
|
|
57
|
+
onBeforeRenderElement?(node: SceneNode, element: MapElement): SceneNode | void;
|
|
58
|
+
/** An element or an event refers to an asset nobody knows. */
|
|
59
|
+
onMissingAsset?(kind: string, owner: {
|
|
60
|
+
readonly type: "element" | "event";
|
|
61
|
+
readonly id: string;
|
|
62
|
+
}): void;
|
|
63
|
+
}
|
|
64
|
+
export interface DescribeContext {
|
|
65
|
+
readonly assets: AssetResolver;
|
|
66
|
+
readonly hooks?: MapViewHooks;
|
|
67
|
+
/** Size of a loaded image, for assets that do not declare one. */
|
|
68
|
+
readonly measure?: (src: string) => readonly [number, number] | undefined;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* What to draw for an element, or `undefined` when nothing can be drawn yet: the
|
|
72
|
+
* asset is unknown, or its size is not known until its image has loaded.
|
|
73
|
+
*/
|
|
74
|
+
export declare function describeElement(element: MapElement, context: DescribeContext): SceneNode | undefined;
|
|
75
|
+
/** What to draw for an event: its graphic, when it has one that is a known asset. */
|
|
76
|
+
export declare function describeEvent(event: MapEvent, context: DescribeContext): SceneNode | undefined;
|
|
77
|
+
//# sourceMappingURL=scene.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene.d.ts","sourceRoot":"","sources":["../../src/renderer/scene.ts"],"names":[],"mappings":"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"}
|