@gtanczyk/diorama 0.0.0-stage → 0.2.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 +202 -0
- package/README.md +157 -2
- package/app/bin/diorama.mjs +13 -0
- package/app/src/actions/budget.ts +160 -0
- package/app/src/actions/build.ts +158 -0
- package/app/src/actions/cast.ts +267 -0
- package/app/src/actions/depth.ts +95 -0
- package/app/src/actions/extend.ts +36 -0
- package/app/src/actions/faces.ts +160 -0
- package/app/src/actions/feedback.ts +87 -0
- package/app/src/actions/fix.ts +382 -0
- package/app/src/actions/history.ts +93 -0
- package/app/src/actions/idea-preview.ts +65 -0
- package/app/src/actions/idea-review.ts +102 -0
- package/app/src/actions/idea-sketch.ts +172 -0
- package/app/src/actions/index.ts +32 -0
- package/app/src/actions/items.ts +94 -0
- package/app/src/actions/kids.ts +215 -0
- package/app/src/actions/language.ts +52 -0
- package/app/src/actions/models.ts +64 -0
- package/app/src/actions/motion.ts +119 -0
- package/app/src/actions/pictures.ts +49 -0
- package/app/src/actions/reactions.ts +36 -0
- package/app/src/actions/registry.ts +101 -0
- package/app/src/actions/share.ts +32 -0
- package/app/src/actions/sound.ts +73 -0
- package/app/src/actions/start.ts +154 -0
- package/app/src/actions/step-work.ts +150 -0
- package/app/src/actions/story.ts +101 -0
- package/app/src/actions/studio.ts +133 -0
- package/app/src/actions/style.ts +72 -0
- package/app/src/actions/video.ts +46 -0
- package/app/src/actions/view.ts +109 -0
- package/app/src/actions/weather.ts +38 -0
- package/app/src/actions/world.ts +290 -0
- package/app/src/commands/action.ts +39 -0
- package/app/src/commands/backdrop.ts +188 -0
- package/app/src/commands/browsers.ts +21 -0
- package/app/src/commands/build.ts +503 -0
- package/app/src/commands/cast.ts +161 -0
- package/app/src/commands/clip.ts +431 -0
- package/app/src/commands/derive.ts +167 -0
- package/app/src/commands/doctor.ts +74 -0
- package/app/src/commands/explore.ts +123 -0
- package/app/src/commands/export.ts +158 -0
- package/app/src/commands/extend.ts +75 -0
- package/app/src/commands/faces.ts +80 -0
- package/app/src/commands/icons.ts +69 -0
- package/app/src/commands/index.ts +37 -0
- package/app/src/commands/language.ts +87 -0
- package/app/src/commands/layout.ts +31 -0
- package/app/src/commands/masks.ts +117 -0
- package/app/src/commands/mcp.ts +52 -0
- package/app/src/commands/model.ts +284 -0
- package/app/src/commands/paint.ts +182 -0
- package/app/src/commands/plates.ts +97 -0
- package/app/src/commands/play.ts +195 -0
- package/app/src/commands/playthrough.ts +111 -0
- package/app/src/commands/publish.ts +78 -0
- package/app/src/commands/reactions.ts +40 -0
- package/app/src/commands/reel.ts +46 -0
- package/app/src/commands/setup.ts +85 -0
- package/app/src/commands/sound.ts +100 -0
- package/app/src/commands/sprites.ts +232 -0
- package/app/src/commands/story.ts +37 -0
- package/app/src/commands/studio.ts +65 -0
- package/app/src/commands/style.ts +87 -0
- package/app/src/commands/verify.ts +45 -0
- package/app/src/commands/voice-retry.ts +14 -0
- package/app/src/commands/voices.ts +145 -0
- package/app/src/commands/weather.ts +52 -0
- package/app/src/commands/world.ts +104 -0
- package/app/src/film/clip.ts +386 -0
- package/app/src/film/reel.ts +126 -0
- package/app/src/film/screencast.ts +123 -0
- package/app/src/image/mask.ts +29 -0
- package/app/src/image/overlay.ts +38 -0
- package/app/src/image/raster.ts +224 -0
- package/app/src/main.ts +28 -0
- package/app/src/meta/choice.ts +27 -0
- package/app/src/meta/http.ts +99 -0
- package/app/src/meta/memo.ts +105 -0
- package/app/src/meta/muse.ts +70 -0
- package/app/src/meta/openrouter.ts +93 -0
- package/app/src/meta/prices.ts +25 -0
- package/app/src/meta/sam.ts +83 -0
- package/app/src/meta/schema.ts +63 -0
- package/app/src/meta/spark.ts +220 -0
- package/app/src/meta/step.ts +14 -0
- package/app/src/meta/voice-file.ts +78 -0
- package/app/src/meta/voice.ts +103 -0
- package/app/src/model/animation.ts +38 -0
- package/app/src/model/blender/relax.py +284 -0
- package/app/src/model/blender.ts +115 -0
- package/app/src/model/build.ts +50 -0
- package/app/src/model/face.ts +21 -0
- package/app/src/model/fit.ts +181 -0
- package/app/src/model/glb.ts +241 -0
- package/app/src/model/plan.ts +151 -0
- package/app/src/model/quality.ts +48 -0
- package/app/src/model/space.ts +168 -0
- package/app/src/model/sprites.ts +293 -0
- package/app/src/model/views.ts +163 -0
- package/app/src/play/creature.ts +43 -0
- package/app/src/play/depth-occlusion.ts +150 -0
- package/app/src/play/exit-outline.ts +48 -0
- package/app/src/play/face3d.ts +34 -0
- package/app/src/play/focus.ts +78 -0
- package/app/src/play/geometry.ts +272 -0
- package/app/src/play/gestures.ts +23 -0
- package/app/src/play/glow.ts +187 -0
- package/app/src/play/lipsync.ts +69 -0
- package/app/src/play/living.ts +83 -0
- package/app/src/play/outline-path.ts +130 -0
- package/app/src/play/parallax.ts +200 -0
- package/app/src/play/path.ts +262 -0
- package/app/src/play/preview.ts +71 -0
- package/app/src/play/reactions.ts +92 -0
- package/app/src/play/rig.ts +172 -0
- package/app/src/play/shadow.ts +253 -0
- package/app/src/play/sound.ts +51 -0
- package/app/src/play/weather.ts +58 -0
- package/app/src/qa/explore.ts +136 -0
- package/app/src/qa/probe.ts +139 -0
- package/app/src/qa/route.ts +118 -0
- package/app/src/registry.ts +23 -0
- package/app/src/scene/animation-source.ts +36 -0
- package/app/src/scene/assets.ts +37 -0
- package/app/src/scene/autofix.ts +133 -0
- package/app/src/scene/backdrop.ts +326 -0
- package/app/src/scene/blockin.ts +189 -0
- package/app/src/scene/brief.ts +74 -0
- package/app/src/scene/carry.ts +177 -0
- package/app/src/scene/cast-fixes.ts +33 -0
- package/app/src/scene/cast.ts +199 -0
- package/app/src/scene/corrections.ts +164 -0
- package/app/src/scene/critic.ts +169 -0
- package/app/src/scene/depthmap.ts +149 -0
- package/app/src/scene/derive.ts +475 -0
- package/app/src/scene/diagnose.ts +132 -0
- package/app/src/scene/face-calibration.ts +65 -0
- package/app/src/scene/face-registration.ts +21 -0
- package/app/src/scene/face-work.ts +71 -0
- package/app/src/scene/faces-data.ts +48 -0
- package/app/src/scene/faces-settings.ts +20 -0
- package/app/src/scene/faces.ts +435 -0
- package/app/src/scene/glows.ts +153 -0
- package/app/src/scene/held.ts +109 -0
- package/app/src/scene/icons.ts +185 -0
- package/app/src/scene/layers.ts +822 -0
- package/app/src/scene/layout.ts +216 -0
- package/app/src/scene/locate.ts +118 -0
- package/app/src/scene/maskcheck.ts +43 -0
- package/app/src/scene/masks.ts +392 -0
- package/app/src/scene/occluders.ts +123 -0
- package/app/src/scene/occlusion-check.ts +138 -0
- package/app/src/scene/paint-layout.ts +135 -0
- package/app/src/scene/paint.ts +81 -0
- package/app/src/scene/photo.ts +123 -0
- package/app/src/scene/plates.ts +256 -0
- package/app/src/scene/playbook.ts +121 -0
- package/app/src/scene/preview.ts +23 -0
- package/app/src/scene/reactions.ts +118 -0
- package/app/src/scene/restyle.ts +53 -0
- package/app/src/scene/rig-check.ts +68 -0
- package/app/src/scene/rig.ts +379 -0
- package/app/src/scene/scale.ts +148 -0
- package/app/src/scene/shadows.ts +215 -0
- package/app/src/scene/stairs.ts +183 -0
- package/app/src/scene/status.ts +32 -0
- package/app/src/scene/verify.ts +156 -0
- package/app/src/scene/walk.ts +326 -0
- package/app/src/scene/warp.ts +419 -0
- package/app/src/scene/weather.ts +118 -0
- package/app/src/scene/words.ts +65 -0
- package/app/src/secrets.ts +219 -0
- package/app/src/setup/checks.ts +110 -0
- package/app/src/sound/cost.ts +74 -0
- package/app/src/sound/data.ts +89 -0
- package/app/src/sound/fixes.ts +23 -0
- package/app/src/sound/issues.ts +21 -0
- package/app/src/sound/language.ts +11 -0
- package/app/src/sound/lipsync-make.ts +98 -0
- package/app/src/sound/lipsync.ts +24 -0
- package/app/src/sound/make.ts +229 -0
- package/app/src/sound/musicgen.py +20 -0
- package/app/src/sound/pcm.ts +280 -0
- package/app/src/sound/plan.ts +154 -0
- package/app/src/sound/retry.ts +43 -0
- package/app/src/sound/settings.ts +28 -0
- package/app/src/sound/sources.ts +81 -0
- package/app/src/sound/spoken.ts +70 -0
- package/app/src/sound/voices.ts +579 -0
- package/app/src/speech/transcribe.ts +38 -0
- package/app/src/speech/types.ts +18 -0
- package/app/src/story/check.ts +230 -0
- package/app/src/story/design.ts +108 -0
- package/app/src/story/engine.ts +233 -0
- package/app/src/story/fixes.ts +54 -0
- package/app/src/story/npc-guidance.ts +7 -0
- package/app/src/story/phrases.ts +4 -0
- package/app/src/story/schema.ts +92 -0
- package/app/src/studio/agent.ts +555 -0
- package/app/src/studio/discovery.ts +28 -0
- package/app/src/studio/events.ts +48 -0
- package/app/src/studio/history.ts +124 -0
- package/app/src/studio/jobs.ts +291 -0
- package/app/src/studio/locks.ts +33 -0
- package/app/src/studio/mcp.ts +86 -0
- package/app/src/studio/operation.ts +18 -0
- package/app/src/studio/repair.ts +31 -0
- package/app/src/studio/server.ts +423 -0
- package/app/src/studio/trouble.ts +48 -0
- package/app/src/world/design.ts +72 -0
- package/app/src/world/drawings.ts +198 -0
- package/app/src/world/extend-stage.ts +109 -0
- package/app/src/world/extend.ts +123 -0
- package/app/src/world/extension-progress.ts +10 -0
- package/app/src/world/files.ts +62 -0
- package/app/src/world/idea-review.ts +32 -0
- package/app/src/world/language.ts +116 -0
- package/app/src/world/photo-limits.ts +2 -0
- package/app/src/world/pool.ts +41 -0
- package/app/src/world/schedule.ts +64 -0
- package/app/src/world/schema.ts +74 -0
- package/app/src/world/sketch.ts +81 -0
- package/app/src/world/style-plan.ts +90 -0
- package/app/src/world/style-reference.ts +16 -0
- package/app/src/world/styled.ts +51 -0
- package/app/src/world/told.ts +105 -0
- package/app/src/world/video-limits.ts +3 -0
- package/app/src/world/video.ts +98 -0
- package/app/studio/activity.tsx +124 -0
- package/app/studio/agent.tsx +384 -0
- package/app/studio/api.ts +111 -0
- package/app/studio/atelier.tsx +234 -0
- package/app/studio/audio-missing.tsx +25 -0
- package/app/studio/audio-setup.tsx +33 -0
- package/app/studio/blender-setup.tsx +30 -0
- package/app/studio/build.tsx +679 -0
- package/app/studio/camera.tsx +59 -0
- package/app/studio/cast-work.tsx +207 -0
- package/app/studio/cast.tsx +178 -0
- package/app/studio/concept-edit.tsx +47 -0
- package/app/studio/creator-idea.tsx +46 -0
- package/app/studio/custom-look.tsx +36 -0
- package/app/studio/faces-setup.tsx +28 -0
- package/app/studio/favicon.svg +11 -0
- package/app/studio/figure.tsx +314 -0
- package/app/studio/fonts/OFL.txt +93 -0
- package/app/studio/fonts/fonts.css +7 -0
- package/app/studio/fonts/fraunces-italic-latin-ext.woff2 +0 -0
- package/app/studio/fonts/fraunces-italic-latin.woff2 +0 -0
- package/app/studio/fonts/fraunces-normal-latin-ext.woff2 +0 -0
- package/app/studio/fonts/fraunces-normal-latin.woff2 +0 -0
- package/app/studio/fonts/instrument-sans-normal-latin-ext.woff2 +0 -0
- package/app/studio/fonts/instrument-sans-normal-latin.woff2 +0 -0
- package/app/studio/icons.tsx +42 -0
- package/app/studio/idea-card.tsx +42 -0
- package/app/studio/idea-review.tsx +181 -0
- package/app/studio/idea-storyboard.tsx +134 -0
- package/app/studio/index.html +15 -0
- package/app/studio/job-words.ts +33 -0
- package/app/studio/lightbox.tsx +49 -0
- package/app/studio/main.tsx +1329 -0
- package/app/studio/nav.ts +14 -0
- package/app/studio/place.tsx +264 -0
- package/app/studio/review-sketch.tsx +47 -0
- package/app/studio/say-it.tsx +49 -0
- package/app/studio/step-work.tsx +134 -0
- package/app/studio/story.tsx +394 -0
- package/app/studio/style-menu.tsx +81 -0
- package/app/studio/style.css +1390 -0
- package/app/studio/sure.tsx +54 -0
- package/app/studio/tip.tsx +74 -0
- package/app/studio/tsconfig.json +5 -0
- package/app/studio/video.tsx +73 -0
- package/app/studio/world.tsx +188 -0
- package/app/tsconfig.json +13 -0
- package/app/web/audio.ts +315 -0
- package/app/web/face.ts +131 -0
- package/app/web/face3d.ts +35 -0
- package/app/web/glows.ts +387 -0
- package/app/web/hero3d.ts +728 -0
- package/app/web/index.html +118 -0
- package/app/web/living.ts +404 -0
- package/app/web/main.ts +3769 -0
- package/app/web/pad.ts +174 -0
- package/app/web/reactions.ts +79 -0
- package/app/web/sheet.ts +36 -0
- package/app/web/synth.ts +140 -0
- package/app/web/tsconfig.json +5 -0
- package/app/web/ui.ts +263 -0
- package/app/web/weather.ts +306 -0
- package/app/web/words.ts +12 -0
- package/docs/characters.md +58 -0
- package/docs/npc-motion.md +69 -0
- package/docs/scenes.md +75 -0
- package/docs/setup.md +60 -0
- package/docs/sound.md +23 -0
- package/docs/studio.md +62 -0
- package/package.json +70 -4
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// The walk grid: which 8 px cells the hero can stand in, which floor each belongs to, and whether
|
|
2
|
+
// every part of the floor can be reached from the start through a corridor at least 2 cells wide.
|
|
3
|
+
import type { Mask } from '../image/mask.ts';
|
|
4
|
+
|
|
5
|
+
export const CELL = 8;
|
|
6
|
+
|
|
7
|
+
export class Grid {
|
|
8
|
+
readonly open: Uint8Array;
|
|
9
|
+
/** Floor index per cell, -1 for none. */
|
|
10
|
+
readonly zone: Int8Array;
|
|
11
|
+
|
|
12
|
+
constructor(readonly cols: number, readonly rows: number) {
|
|
13
|
+
this.open = new Uint8Array(cols * rows);
|
|
14
|
+
this.zone = new Int8Array(cols * rows).fill(-1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
at(c: number, r: number) {
|
|
18
|
+
return r * this.cols + c;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
inside(c: number, r: number) {
|
|
22
|
+
return c >= 0 && r >= 0 && c < this.cols && r < this.rows;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
centre(i: number): [number, number] {
|
|
26
|
+
return [((i % this.cols) + 0.5) * CELL, (Math.floor(i / this.cols) + 0.5) * CELL];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
cellOf(x: number, y: number): number {
|
|
30
|
+
const c = Math.min(this.cols - 1, Math.max(0, Math.floor(x / CELL)));
|
|
31
|
+
const r = Math.min(this.rows - 1, Math.max(0, Math.floor(y / CELL)));
|
|
32
|
+
return this.at(c, r);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
count(cells = this.open) {
|
|
36
|
+
return cells.reduce((n, v) => n + v, 0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** geometry.json rows: '#' walkable, '.' not; and the floor digit per cell. */
|
|
40
|
+
rowsOut(): { walk: string[]; zone: string[] } {
|
|
41
|
+
const walk: string[] = [];
|
|
42
|
+
const zone: string[] = [];
|
|
43
|
+
for (let r = 0; r < this.rows; r++) {
|
|
44
|
+
let w = '';
|
|
45
|
+
let z = '';
|
|
46
|
+
for (let c = 0; c < this.cols; c++) {
|
|
47
|
+
const i = this.at(c, r);
|
|
48
|
+
w += this.open[i] ? '#' : '.';
|
|
49
|
+
z += this.zone[i] >= 0 ? String(this.zone[i]) : '.';
|
|
50
|
+
}
|
|
51
|
+
walk.push(w);
|
|
52
|
+
zone.push(z);
|
|
53
|
+
}
|
|
54
|
+
return { walk, zone };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Each of 4 neighbours inside the grid. */
|
|
58
|
+
neighbours(i: number): number[] {
|
|
59
|
+
const c = i % this.cols;
|
|
60
|
+
const out: number[] = [];
|
|
61
|
+
if (c > 0) out.push(i - 1);
|
|
62
|
+
if (c < this.cols - 1) out.push(i + 1);
|
|
63
|
+
if (i >= this.cols) out.push(i - this.cols);
|
|
64
|
+
if (i < this.cols * (this.rows - 1)) out.push(i + this.cols);
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A cell is walkable when at least half its pixels are; its floor is the most common floor
|
|
71
|
+
* among them. `floorOf` gives a pixel's floor index, or -1.
|
|
72
|
+
*/
|
|
73
|
+
export function gridFromPixels(walk: Mask, floorOf: (pixel: number) => number, floors: number): Grid {
|
|
74
|
+
const g = new Grid(Math.ceil(walk.width / CELL), Math.ceil(walk.height / CELL));
|
|
75
|
+
const votes = new Int32Array(floors);
|
|
76
|
+
for (let r = 0; r < g.rows; r++) {
|
|
77
|
+
for (let c = 0; c < g.cols; c++) {
|
|
78
|
+
votes.fill(0);
|
|
79
|
+
let on = 0;
|
|
80
|
+
let all = 0;
|
|
81
|
+
for (let y = r * CELL; y < Math.min(walk.height, (r + 1) * CELL); y++) {
|
|
82
|
+
for (let x = c * CELL; x < Math.min(walk.width, (c + 1) * CELL); x++) {
|
|
83
|
+
const p = y * walk.width + x;
|
|
84
|
+
all++;
|
|
85
|
+
if (!walk.bits[p]) continue;
|
|
86
|
+
on++;
|
|
87
|
+
const f = floorOf(p);
|
|
88
|
+
if (f >= 0) votes[f]++;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const i = g.at(c, r);
|
|
92
|
+
g.open[i] = on * 2 >= all ? 1 : 0;
|
|
93
|
+
let best = -1;
|
|
94
|
+
votes.forEach((v, f) => {
|
|
95
|
+
if (v > 0 && (best < 0 || v > votes[best])) best = f;
|
|
96
|
+
});
|
|
97
|
+
g.zone[i] = best;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
fillZones(g);
|
|
101
|
+
return g;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Every walkable cell takes a floor: one with none takes its nearest floored neighbour's. */
|
|
105
|
+
export function fillZones(g: Grid) {
|
|
106
|
+
const queue: number[] = [];
|
|
107
|
+
for (let i = 0; i < g.open.length; i++) {
|
|
108
|
+
if (!g.open[i]) g.zone[i] = -1;
|
|
109
|
+
else if (g.zone[i] >= 0) queue.push(i);
|
|
110
|
+
}
|
|
111
|
+
for (let q = 0; q < queue.length; q++) {
|
|
112
|
+
for (const n of g.neighbours(queue[q])) {
|
|
113
|
+
if (g.open[n] && g.zone[n] < 0) {
|
|
114
|
+
g.zone[n] = g.zone[queue[q]];
|
|
115
|
+
queue.push(n);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Open cells that are part of a fully open 2x2 block: where the hero fits. */
|
|
122
|
+
export function roomy(g: Grid): Uint8Array {
|
|
123
|
+
const out = new Uint8Array(g.open.length);
|
|
124
|
+
for (let r = 0; r + 1 < g.rows; r++) {
|
|
125
|
+
for (let c = 0; c + 1 < g.cols; c++) {
|
|
126
|
+
const i = g.at(c, r);
|
|
127
|
+
if (g.open[i] && g.open[i + 1] && g.open[i + g.cols] && g.open[i + g.cols + 1]) {
|
|
128
|
+
out[i] = out[i + 1] = out[i + g.cols] = out[i + g.cols + 1] = 1;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Cells reachable from `from` over roomy cells. */
|
|
136
|
+
export function reach(g: Grid, from: number, cells = roomy(g)): Uint8Array {
|
|
137
|
+
const seen = new Uint8Array(g.open.length);
|
|
138
|
+
if (!cells[from]) return seen;
|
|
139
|
+
const queue = [from];
|
|
140
|
+
seen[from] = 1;
|
|
141
|
+
for (let q = 0; q < queue.length; q++) {
|
|
142
|
+
for (const n of g.neighbours(queue[q])) {
|
|
143
|
+
if (cells[n] && !seen[n]) {
|
|
144
|
+
seen[n] = 1;
|
|
145
|
+
queue.push(n);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return seen;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Roomy islands as cell lists, largest first. */
|
|
153
|
+
export function islands(g: Grid, cells = roomy(g)): number[][] {
|
|
154
|
+
const seen = new Uint8Array(g.open.length);
|
|
155
|
+
const out: number[][] = [];
|
|
156
|
+
for (let i = 0; i < cells.length; i++) {
|
|
157
|
+
if (!cells[i] || seen[i]) continue;
|
|
158
|
+
const island = [i];
|
|
159
|
+
seen[i] = 1;
|
|
160
|
+
for (let q = 0; q < island.length; q++) {
|
|
161
|
+
for (const n of g.neighbours(island[q])) {
|
|
162
|
+
if (cells[n] && !seen[n]) {
|
|
163
|
+
seen[n] = 1;
|
|
164
|
+
island.push(n);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
out.push(island);
|
|
169
|
+
}
|
|
170
|
+
return out.sort((a, b) => b.length - a.length);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** The roomy cell nearest (x, y), searching outward ring by ring; undefined on an empty grid. */
|
|
174
|
+
export function nearestRoomy(g: Grid, x: number, y: number, cells = roomy(g)): number | undefined {
|
|
175
|
+
const c0 = Math.floor(x / CELL);
|
|
176
|
+
const r0 = Math.floor(y / CELL);
|
|
177
|
+
for (let d = 0; d < Math.max(g.cols, g.rows); d++) {
|
|
178
|
+
let best: number | undefined;
|
|
179
|
+
let bestDist = Infinity;
|
|
180
|
+
for (let r = r0 - d; r <= r0 + d; r++) {
|
|
181
|
+
for (let c = c0 - d; c <= c0 + d; c++) {
|
|
182
|
+
if (Math.max(Math.abs(c - c0), Math.abs(r - r0)) !== d || !g.inside(c, r) || !cells[g.at(c, r)]) continue;
|
|
183
|
+
const dist = (c - c0) ** 2 + (r - r0) ** 2;
|
|
184
|
+
if (dist < bestDist) [best, bestDist] = [g.at(c, r), dist];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (best !== undefined) return best;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
class Heap {
|
|
192
|
+
private items: [number, number][] = [];
|
|
193
|
+
get size() {
|
|
194
|
+
return this.items.length;
|
|
195
|
+
}
|
|
196
|
+
push(cost: number, value: number) {
|
|
197
|
+
const a = this.items;
|
|
198
|
+
a.push([cost, value]);
|
|
199
|
+
for (let i = a.length - 1; i > 0; ) {
|
|
200
|
+
const p = (i - 1) >> 1;
|
|
201
|
+
if (a[p][0] <= a[i][0]) break;
|
|
202
|
+
[a[p], a[i]] = [a[i], a[p]];
|
|
203
|
+
i = p;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
pop(): [number, number] {
|
|
207
|
+
const a = this.items;
|
|
208
|
+
const top = a[0];
|
|
209
|
+
const last = a.pop()!;
|
|
210
|
+
if (a.length) {
|
|
211
|
+
a[0] = last;
|
|
212
|
+
for (let i = 0; ; ) {
|
|
213
|
+
const l = 2 * i + 1;
|
|
214
|
+
const r = l + 1;
|
|
215
|
+
let m = i;
|
|
216
|
+
if (l < a.length && a[l][0] < a[m][0]) m = l;
|
|
217
|
+
if (r < a.length && a[r][0] < a[m][0]) m = r;
|
|
218
|
+
if (m === i) break;
|
|
219
|
+
[a[m], a[i]] = [a[i], a[m]];
|
|
220
|
+
i = m;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return top;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export type Connected = {
|
|
228
|
+
/** Cells opened to join islands to the start. */
|
|
229
|
+
reopened: number;
|
|
230
|
+
/** Islands too small to matter that were closed instead. */
|
|
231
|
+
dropped: number;
|
|
232
|
+
/** Islands of at least minIsland cells that no allowed path reaches. */
|
|
233
|
+
stranded: number[][];
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Joins every island of at least `minIsland` roomy cells to the start, opening the cheapest path
|
|
238
|
+
* of closed cells (2 cells wide); `cost(i)` is the price of opening cell i, Infinity where it must
|
|
239
|
+
* stay closed. Smaller unreachable islands are closed: they are specks, not places to walk.
|
|
240
|
+
*/
|
|
241
|
+
export function connect(g: Grid, start: number, cost: (i: number) => number, minIsland = 20): Connected {
|
|
242
|
+
const result: Connected = { reopened: 0, dropped: 0, stranded: [] };
|
|
243
|
+
const given = new Set<number>();
|
|
244
|
+
// A path along open cells too narrow to walk (a one-cell fringe) can't always be widened: when opening it reached
|
|
245
|
+
// nothing new, the same island is joined again by a path that opens every cell it takes.
|
|
246
|
+
let last = { island: -1, reached: -1 };
|
|
247
|
+
let strict = false;
|
|
248
|
+
for (let round = 0; round < 32; round++) {
|
|
249
|
+
const cells = roomy(g);
|
|
250
|
+
const reached = reach(g, start, cells);
|
|
251
|
+
const away = islands(g, cells).filter((island) => !reached[island[0]] && island.length >= minIsland && !given.has(island[0]));
|
|
252
|
+
if (away.length === 0) break;
|
|
253
|
+
const count = reached.reduce((a, b) => a + b, 0);
|
|
254
|
+
if (away[0][0] === last.island && count === last.reached) {
|
|
255
|
+
if (strict) {
|
|
256
|
+
result.stranded.push(away[0]);
|
|
257
|
+
given.add(away[0][0]);
|
|
258
|
+
strict = false;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
strict = true;
|
|
262
|
+
} else strict = false;
|
|
263
|
+
last = { island: away[0][0], reached: count };
|
|
264
|
+
const target = new Uint8Array(g.open.length);
|
|
265
|
+
away[0].forEach((i) => (target[i] = 1));
|
|
266
|
+
|
|
267
|
+
const dist = new Float64Array(g.open.length).fill(Infinity);
|
|
268
|
+
const prev = new Int32Array(g.open.length).fill(-1);
|
|
269
|
+
const heap = new Heap();
|
|
270
|
+
reached.forEach((v, i) => {
|
|
271
|
+
if (v) {
|
|
272
|
+
dist[i] = 0;
|
|
273
|
+
heap.push(0, i);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
let hit = -1;
|
|
277
|
+
while (heap.size) {
|
|
278
|
+
const [d, i] = heap.pop();
|
|
279
|
+
if (d > dist[i]) continue;
|
|
280
|
+
if (target[i]) {
|
|
281
|
+
hit = i;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
for (const n of g.neighbours(i)) {
|
|
285
|
+
const step = g.open[n] && (!strict || cells[n]) ? 0.01 : g.open[n] ? Math.max(0.01, cost(n)) : cost(n);
|
|
286
|
+
if (!Number.isFinite(step) || d + step >= dist[n]) continue;
|
|
287
|
+
dist[n] = d + step;
|
|
288
|
+
prev[n] = i;
|
|
289
|
+
heap.push(dist[n], n);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (hit < 0) {
|
|
293
|
+
result.stranded.push(away[0]);
|
|
294
|
+
given.add(away[0][0]);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
// Open the path and its right and lower neighbours, so it is two cells wide.
|
|
298
|
+
for (let i = hit; i >= 0; i = prev[i]) {
|
|
299
|
+
const atRightEdge = i % g.cols === g.cols - 1;
|
|
300
|
+
for (const n of atRightEdge ? [i, i + g.cols] : [i, i + 1, i + g.cols, i + g.cols + 1]) {
|
|
301
|
+
if (n < g.open.length && !g.open[n] && Number.isFinite(cost(n))) {
|
|
302
|
+
g.open[n] = 1;
|
|
303
|
+
result.reopened++;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
fillZones(g);
|
|
308
|
+
}
|
|
309
|
+
// Whatever is still cut off goes. Thin fringes touching the reached floor stay, but a fringe is
|
|
310
|
+
// never a way into another patch: the hero needs two cells to pass.
|
|
311
|
+
const finalCells = roomy(g);
|
|
312
|
+
const keep = reach(g, start, finalCells);
|
|
313
|
+
for (const island of result.stranded) island.forEach((i) => (keep[i] = 1));
|
|
314
|
+
const fringe = [...keep.keys()].filter((i) => keep[i]);
|
|
315
|
+
for (const i of fringe) {
|
|
316
|
+
for (const n of g.neighbours(i)) if (g.open[n] && !finalCells[n]) keep[n] = 1;
|
|
317
|
+
}
|
|
318
|
+
for (let i = 0; i < g.open.length; i++) {
|
|
319
|
+
if (g.open[i] && !keep[i]) {
|
|
320
|
+
g.open[i] = 0;
|
|
321
|
+
g.zone[i] = -1;
|
|
322
|
+
result.dropped++;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return result;
|
|
326
|
+
}
|