feat: make Glow follow door opening state (#20)
Issue: #20 User-Visible: yes
@@ -1259,10 +1259,11 @@ its configured space.
|
||||
`_lightBarriers` collects everything opaque: the wall bodies exactly as the
|
||||
plan draws them (`wallBodiesGeometry`, real thickness, mitred junctions),
|
||||
every independent body (partition, column, draft), and the bare outline of
|
||||
any edge that carries no thickness. It cuts out the exceptions: doorways
|
||||
and gates — cut through the masonry, so an opening is a real gap
|
||||
between two jamb faces and a beam is narrowed by the returns of a thick wall
|
||||
— plus dashed zero-thickness walls. Solid zero-thickness walls instead add
|
||||
any edge that carries no thickness. It cuts out the exceptions: interior
|
||||
doorways and gates according to their resolved live opening amount — a
|
||||
closed bound opening keeps the masonry, while a positional cover cuts a
|
||||
centre-aligned fraction between the jambs — and saved passages, which remain
|
||||
fully open, plus dashed zero-thickness walls. Solid zero-thickness walls instead add
|
||||
their exact axes as zero-area barriers. Windows stay
|
||||
solid, so an indoor lamp never washes the street; the light's masonry is cut
|
||||
by passages only and therefore differs on purpose from the drawn one. So does
|
||||
@@ -1289,10 +1290,12 @@ its configured space.
|
||||
open-zone graph and no shadow mask left in the light path.
|
||||
|
||||
Barriers are cached per space by a fingerprint of their complete geometry
|
||||
(every body point, both wall endpoints and scale inputs), never by
|
||||
`_cfgEpoch`: the epoch lags behind geometry edited in place, and a stale
|
||||
barrier set is invisible — the plan simply keeps lighting through a wall that
|
||||
now exists. The same fingerprint keys the per-source region cache. A cached per-
|
||||
(every body point, both wall endpoints and scale inputs) plus a sorted
|
||||
signature of bound interior door/gate opening amounts, never by `_cfgEpoch`:
|
||||
the epoch lags behind geometry edited in place, and a stale barrier set is
|
||||
invisible — the plan simply keeps lighting through a wall or closed door that
|
||||
now exists. Unrelated HA updates retain the same signature and reuse the
|
||||
barrier set. The combined fingerprint keys the per-source region cache. A cached per-
|
||||
`Document` raster probe verifies actual SVG screen pixels rather than trusting
|
||||
CSS syntax support; pending/unsupported/error/timeout states render with
|
||||
deterministic normal blending and a successful probe requests one update.
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
first frame was also the last one
|
||||
([#358](https://github.com/Matysh/houseplan-card/issues/358)).
|
||||
|
||||
- Glow now follows the live state of bound interior doors and gates: a closed
|
||||
opening blocks light, an open one passes it, and a positional cover exposes
|
||||
the matching fraction of the aperture. Passages remain open, windows and
|
||||
exterior openings remain opaque, and unrelated Home Assistant updates reuse
|
||||
the existing light geometry ([#20](https://github.com/Matysh/houseplan-card/issues/20)).
|
||||
|
||||
- Tapping a device on a freshly opened page works again without first
|
||||
visiting any editor screen. A wall switch controlling virtual light sources
|
||||
(and every other tap-to-toggle device) silently ignored the tap on a cold
|
||||
|
||||
@@ -14,6 +14,13 @@
|
||||
загрузила, — первый кадр оказывался последним
|
||||
([#358](https://github.com/Matysh/houseplan-card/issues/358)).
|
||||
|
||||
- Glow теперь учитывает текущее состояние привязанных внутренних дверей и
|
||||
ворот: закрытый проём останавливает свет, открытый пропускает его, а cover с
|
||||
позицией открывает соответствующую долю прохода. Открытые проходы остаются
|
||||
прозрачными, окна и наружные проёмы — непрозрачными; посторонние обновления
|
||||
Home Assistant переиспользуют готовую геометрию света
|
||||
([#20](https://github.com/Matysh/houseplan-card/issues/20)).
|
||||
|
||||
- Нажатие на устройство на только что открытой странице снова работает без
|
||||
предварительного захода в какой-либо редактор. Выключатель, управляющий
|
||||
виртуальными источниками света (и любое другое устройство с тап-переключением),
|
||||
|
||||
@@ -20,7 +20,8 @@ all. None of those states is expressible now.
|
||||
## What stops light, and what does not
|
||||
|
||||
`_lightBarriers()` (`src/houseplan-card.ts`) builds the barrier set once per
|
||||
plan geometry and shares it between every lamp in the space.
|
||||
plan geometry and relevant opening-state signature, then shares it between
|
||||
every lamp in the space.
|
||||
|
||||
**Opaque**
|
||||
|
||||
@@ -34,8 +35,11 @@ plan geometry and shares it between every lamp in the space.
|
||||
|
||||
**Transparent**
|
||||
|
||||
- doorways, gates and saved `passage` openings — cut through the masonry, so an opening is a real
|
||||
gap between two jamb faces and a thick wall's returns narrow the beam;
|
||||
- doorways and gates to floor on both sides, but only by their resolved opening
|
||||
amount. An unbound opening is fully transparent, a bound closed one is opaque,
|
||||
and a positional cover cuts a centre-aligned fraction of the full aperture;
|
||||
- saved `passage` openings — always cut through the masonry, so an opening is a
|
||||
real gap between two jamb faces and a thick wall's returns narrow the beam;
|
||||
- every zero-thickness wall when its space uses the **Dashed** style.
|
||||
|
||||
The setting is intentionally one semantic switch, not just paint. Missing or
|
||||
@@ -52,7 +56,12 @@ input is only a v8 read projection and migrates to ordinary `cm:0` atoms.
|
||||
sides are floor; otherwise a front door glows halfway — up to the centreline,
|
||||
where the room polygon ends — and the plan shows a lit doorway to nowhere.
|
||||
|
||||
The classifier is an explicit `door | gate | passage` allowlist. Any unknown
|
||||
The classifier is an explicit `door | gate | passage` allowlist. Door and gate
|
||||
state is resolved by the same `openingAmount()` used for their visible symbol:
|
||||
binary contacts yield zero or full aperture, finite `current_position` yields
|
||||
`clamp(position / 100)`, and `invert` mirrors a known amount. Missing,
|
||||
disabled, `unknown` or `unavailable` contacts preserve the static-plan fallback
|
||||
of a fully open door/gate rather than manufacturing a closure. Any unknown
|
||||
future opening type remains opaque until its physical semantics are reviewed;
|
||||
it never inherits transparency merely by not being a window.
|
||||
|
||||
@@ -129,11 +138,13 @@ pass over the layer costs a fifth of one blurred mask per source.
|
||||
|
||||
## Caching
|
||||
|
||||
Barriers are keyed by a fingerprint of their own geometry, never by
|
||||
`_cfgEpoch`: the epoch lags behind geometry edited in place, and a stale barrier
|
||||
set is invisible — the plan simply keeps lighting through a wall that now
|
||||
exists. The same fingerprint, plus position and radius, keys the per-source
|
||||
region cache (`_glowClipCache`).
|
||||
Barriers are keyed by their geometry fingerprint plus a compact, sorted
|
||||
signature of bound interior door/gate opening amounts, never by `_cfgEpoch`:
|
||||
the epoch lags behind geometry edited in place, and a stale barrier set is
|
||||
invisible — the plan simply keeps lighting through a wall or closed door that
|
||||
now exists. Unrelated HA updates preserve the signature and hit the bounded
|
||||
barrier cache. The combined fingerprint, plus source position and radius, keys
|
||||
the per-source region cache (`_glowClipCache`).
|
||||
|
||||
The masonry boolean receives room walls after passage cuts plus the cached
|
||||
joined independent body set. Its outer/hole rings are the authoritative
|
||||
@@ -216,6 +227,9 @@ The complete UI and runtime truth table lives in
|
||||
exterior opening masonry, wall bodies, partitions and columns are fail-dark,
|
||||
while a real interior passage remains a valid source position; the rendered
|
||||
Glow path is pinned to that shared guard.
|
||||
- `test/logic.test.mjs` and `test/physical-geometry.test.mjs` — the shared
|
||||
opening amount, stable state signature and centre-preserving partial cut for
|
||||
contour and independent walls.
|
||||
- `demo/smoke_glow.mjs` — pixels on a rendered plan: the aperture itself is lit,
|
||||
the floor behind a door is lit, the visible beam is no wider than twice the
|
||||
opening, there is no light behind a column while the floor beside it is lit,
|
||||
|
||||
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 343 KiB After Width: | Height: | Size: 336 KiB |
|
Before Width: | Height: | Size: 334 KiB After Width: | Height: | Size: 327 KiB |
|
Before Width: | Height: | Size: 319 KiB After Width: | Height: | Size: 289 KiB |
|
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 291 KiB |
|
Before Width: | Height: | Size: 296 KiB After Width: | Height: | Size: 290 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 134 KiB |
@@ -3,7 +3,7 @@
|
||||
"fixture": "synthetic-only",
|
||||
"chromium": "151.0.7922.34",
|
||||
"oxipng": null,
|
||||
"sourceFingerprint": "2bb218477956888a84706e4a8a76248b823d70dc26fc80bcc5272c30e4556a38",
|
||||
"sourceFingerprint": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
|
||||
"captureScriptSha256": "fe76c8e2e27501e9a377d7aa15dd6cba6a018d55c07deb602dbee097f0a0a5ba",
|
||||
"command": "npm run build && node demo/docs/capture.mjs",
|
||||
"scenarios": {
|
||||
@@ -15,8 +15,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "2bb218477956888a84706e4a8a76248b823d70dc26fc80bcc5272c30e4556a38",
|
||||
"imageSha256": "78d328498bd17a9ee1ec44b389d244b61fca9ac52c9aad6631aa85c53233fec0"
|
||||
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
|
||||
"imageSha256": "36223106c073f07d8cc3ecf8eaab37192ebb2687daba65c5c21047d0b7890de0"
|
||||
},
|
||||
"view-touch": {
|
||||
"file": "02-view-touch.png",
|
||||
@@ -26,8 +26,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "2bb218477956888a84706e4a8a76248b823d70dc26fc80bcc5272c30e4556a38",
|
||||
"imageSha256": "3be239a5a494f6776540a9c0049694ee6f9d2ba927c8bcb4b26ee5a69a228a7d"
|
||||
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
|
||||
"imageSha256": "a4aa3af48578ee41594a9b1a6e98b4d39f3324a949a9fd9e63d5854576d5db85"
|
||||
},
|
||||
"space-create": {
|
||||
"file": "03-space-create.png",
|
||||
@@ -37,8 +37,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "2bb218477956888a84706e4a8a76248b823d70dc26fc80bcc5272c30e4556a38",
|
||||
"imageSha256": "a55b463055a565892ca139014e314a8f63fb5301c87fff2e48d7e69210fe9b6c"
|
||||
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
|
||||
"imageSha256": "0dc024726327f60f4a9ceaf3044381691f81f1090af81d1812e870f22d9343ba"
|
||||
},
|
||||
"room-contour-close": {
|
||||
"file": "04-room-contour-close.png",
|
||||
@@ -48,8 +48,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "2bb218477956888a84706e4a8a76248b823d70dc26fc80bcc5272c30e4556a38",
|
||||
"imageSha256": "dfad9a27371cfa397a3fd8dce7005f42d24649743f5322105ee894067d3a9f16"
|
||||
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
|
||||
"imageSha256": "44cfaa95bd51e2cd628400e28db0ad8b2f0cd904385845bd402494f3f5c0d93c"
|
||||
},
|
||||
"plan-context-tray": {
|
||||
"file": "05-plan-context-tray.png",
|
||||
@@ -59,8 +59,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "2bb218477956888a84706e4a8a76248b823d70dc26fc80bcc5272c30e4556a38",
|
||||
"imageSha256": "b75f348c632296da5c2fc142d53df613f757e1516acac315be50f5b4557729cc"
|
||||
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
|
||||
"imageSha256": "e0662429b423fb74151b583dcc2c8635b001b637d03bbf7a6b16aec46399c3f8"
|
||||
},
|
||||
"device-editor": {
|
||||
"file": "06-device-editor.png",
|
||||
@@ -70,8 +70,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "2bb218477956888a84706e4a8a76248b823d70dc26fc80bcc5272c30e4556a38",
|
||||
"imageSha256": "6b9cd281450e7782cff292f11efc58d227bedd0df86afc3be987feb2edec9dc3"
|
||||
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
|
||||
"imageSha256": "d0ffd31ce80bfde21ab75da356a5fc1af38246f2b301030880320620c228d89d"
|
||||
},
|
||||
"device-display-preview": {
|
||||
"file": "06-device-display-preview.png",
|
||||
@@ -81,8 +81,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "2bb218477956888a84706e4a8a76248b823d70dc26fc80bcc5272c30e4556a38",
|
||||
"imageSha256": "6f628abe6b89d7c4b352e6d73ac0b126526f6fdba5927bb774c5b884e484ca19"
|
||||
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
|
||||
"imageSha256": "2cdabae1f89c3286e4fac0ce30f757ee1690b707ab8a5488748b7cd420626160"
|
||||
},
|
||||
"background-editor": {
|
||||
"file": "07-background-editor.png",
|
||||
@@ -92,8 +92,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "2bb218477956888a84706e4a8a76248b823d70dc26fc80bcc5272c30e4556a38",
|
||||
"imageSha256": "a1d1a905f1410f73231d2ea5d1dfbd8ae79c2fe8b0a79cff87ebb977ac7a3103"
|
||||
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
|
||||
"imageSha256": "30147bb00a90eea7136b4cee30995f6e6a9217b5132f3e8d3ad7471413b1af8a"
|
||||
},
|
||||
"room-card": {
|
||||
"file": "08-room-card.png",
|
||||
@@ -103,8 +103,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "2bb218477956888a84706e4a8a76248b823d70dc26fc80bcc5272c30e4556a38",
|
||||
"imageSha256": "48a5685039a5e0de2a28190d857b193c86e2e63606e3c7a8c40fc9d2b190da4e"
|
||||
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
|
||||
"imageSha256": "029a3e69ec647a8a370d99e6bb7f9225833c526739076022f6b52ba54bff30ea"
|
||||
},
|
||||
"device-info": {
|
||||
"file": "09-device-info.png",
|
||||
@@ -114,8 +114,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "2bb218477956888a84706e4a8a76248b823d70dc26fc80bcc5272c30e4556a38",
|
||||
"imageSha256": "29a342811aaedaebc54ba337792d60cb454a7cf4c3063b210f6c4bafe0a4d87b"
|
||||
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
|
||||
"imageSha256": "dd492f53150b7149085daada5cce9eeae9bde9e7ea1d86679a54b3041f72f517"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||