Commit cac9da3
authored
feat(tv): per-preset FPV player scale + camera handle fix + retro/pixel polish (#64)
* feat(iframe): <poly-iframe> element + /television demo
Adds a new public element across all three renderers for placing live
iframes in 3D space, plus a five-TV demo page.
- packages/polycss: PolyIframeElement (vanilla custom element). Mounts an
iframe wrapper inside the parent <poly-scene>'s .polycss-scene so the
CSS camera transform composes naturally with surrounding meshes. Same
position/rotation/scale conventions as <poly-mesh> post-parity (world
units, world-axis order, rotation conjugation rotateY(-rx) rotateX(-ry)
rotateZ(-rz)). Width/height in world units; iframe centred at the
wrapper's local origin so rotation/scale pivot at the visible centre.
- packages/react: <PolyIframe> mirror with the same props.
- packages/vue: <PolyIframe> mirror with the same props.
- 14/8/7 tests added per package; transform math + attribute forwarding
pinned. AGENTS.md updated with the new element + naming entry.
Bug fixes shipped alongside:
- PolyPerspectiveCameraElement / PolyOrthographicCameraElement runtime
attribute updates now mutate the camera handle in place and re-apply
the scene transform. Before, the elements either recreated the handle
(orphaning the scene's pointer) or updated but never called
applyCamera, so rot-x/rot-y/zoom changes were ignored at runtime.
- DocsHeader hides the floating search dock on /television (matches
/gallery, /builder, /wordart).
/television demo:
- Vertical-rail TV picker (5 sets, hand-drawn SVG art per tile). ?tv=id
query param + popstate sync so individual TVs are linkable.
- Per-TV polygon indices identify which face(s) are the screen; the page
derives the iframe's world-unit position, rotation, width, and height
from those polygons' vertices at load time (area-weighted normal, bbox
in the in-plane right/up basis, small lift along the normal so the
iframe doesn't z-fight with the screen polygon itself). Per-TV rotY +
zoom remain hand-tuned because GLB authoring orientations differ.
- Retro Stack uses multiple screen entries — one <poly-iframe> per CRT
is mounted dynamically and each is placed independently.
* feat(tv): rename /television → /tv, add header nav + floor + lighting
- Rename /television route + public assets to /tv (shorter, link to it
from the docs header next to WordArt).
- Query param renamed to ?model=<id> (was ?tv=<id> — reads better
alongside /tv).
- Brighter scene lighting (directional 4.5→6.5, ambient 0.55→0.9) plus
a ground <poly-plane> below each TV with cast-shadow on the mesh, so
the bottom of every TV sits visibly on the floor.
Element changes to support the floor:
- PolyShapeElement now forwards `cast-shadow` / `receive-shadow` to
scene.add (mirrors <poly-mesh>) and `exclude-from-auto-center` so
ground planes don't skew the scene's auto-center calculation.
- PolyShapeElement gains an attributeChangedCallback so transform attrs
(position/scale/rotation/shadow) propagate after mount — the floor is
re-positioned per TV from the script side once the mesh has loaded.
- PolyPlaneElement: new `offset` attribute, defaulting to 0 so the
plane is centered at the element's local origin. The underlying
planePolygons helper defaulted to size*2 because it was authored as
a transform-control drag handle.
Camera-element runtime fix:
- PolyPerspectiveCameraElement / PolyOrthographicCameraElement
attributeChangedCallback now correctly locates the scene as a
descendant (`this.querySelector('poly-scene')`) rather than an
ancestor. The previous walk-up version always found null and
applyCamera was never called, so runtime rot-x/rot-y/zoom changes
were no-ops in vanilla.
Tuning:
- TV mesh gets `auto-center` so its bbox center IS mesh-local (0,0,0).
- Floor's per-preset Z is derived from the mesh's minZ on load.
- Per-TV cam.rotY/zoom dialed in by hand for each set (pixel TV at
rotY 245 / zoom 10 for a 3/4 view that doesn't crop).
* tv: add polygon 216 to retro stack's first screen + TODO for shadows
- Retro stack screen 1: append poly 216 to the polyIndices array (user
identified it as part of that CRT's bezel strip; was missing).
- Floor shadow attempt: receiver-shadow on the floor + scene shadow
config wired up cleanly, but the projected shadow SVG lands at wrong
scene coords for our auto-centered TV + excludeFromAutoCenter floor
combination. Vanilla dropped the legacy ground-shadow fallback for
three.js parity, so a caster with no receiver draws nothing. Leaving
the floor as a non-receiver for now with a TODO; lighting + floor
geometry still ship.
* fix(tv): bump shadow.lift to 1 world unit so TV shadows clear the floor
The receiver-shadow SVG sits at the floor plane plus `shadow.lift`
(default 0.05 world units = 2.5 CSS px). On a big 400-world-unit floor
viewed at a normal-angle camera (rotX 70), that razor-thin gap z-fought
with the floor itself and the shadow only showed from straight overhead.
A 1 world unit (50 CSS px) lift puts the shadow clearly in front at
every angle the camera reaches.
* fix(tv): drop poly 211 from retro stack's first screen
The first CRT's actual screen polygons are 212–216 — 211 is an adjacent
bezel face the iframe-placement code was averaging into the plane,
shifting the iframe off the screen. Restricting to 212–216 lands the
iframe on the visible glass.
* tv: per-screen `lift` override; recess retro-stack iframes into bezels
placementFromPolygons now takes an optional liftOverride; the TvScreen
config exposes it as `lift`. The retro stack's CRT screens already
model the front glass surface, so a small negative lift (−0.6 world
units) seats the iframe inside the bezel instead of in front of it.
The default lift formula (0.2 + 0.01 * max(w,h)) still applies for
every other TV — those polygons mark the screen plane itself, so the
iframe sitting slightly in front of them reads as the picture surface.
* tv: shift retro-stack screen polys from 206–210 to 207–211
* tv(retro-stack): each CRT plays a different YouTube video
* tv(retro-stack): swap NCtzkaL2t_Y → UqyT8IEBkvY @0:35
* tv: add receive-shadow to the TV mesh for self-shadowing
* tv: revert receive-shadow on TV mesh (caused atlas-poly positioning artifacts)
* tv: re-enable receive-shadow on the TV mesh + bump shadow opacity
The 'floating quad' I attributed to receive-shadow earlier turned out to
be the monitor.glb's back panel authored at an offset position — present
even without receive-shadow.
Self-shadows on textured receivers are computed correctly but the
texturedReceiver code reduces opacity to mimic three.js's 'darken to
ambient-only' (`effOp = opacity * (1 - (ambient/total)^(1/2.4))`).
With our bright light (dir 6.5, ambient 0.9) that pushed shadow alpha
to ~0.32, which read as nearly-invisible 'white-ish' on textured TVs.
Bumping the input opacity to 0.95 lands the effective alpha at ~0.55
and the self-shadows now read as actually dark.
* tv: shadow tweaks — lower lift back to 0.1, drop ambient so shadows read dark
- Lift 1 → 0.1 world units. The bigger value was detaching shadows
visibly from their receivers; 0.1 (5 CSS px) still clears z-fighting
but reads as flush with the floor / bezel.
- Ambient intensity 0.9 → 0.4. The texture-receiver code caps shadow
alpha at `opacity * (1 - (ambient/total)^(1/2.4))` to mimic three.js's
'darken to ambient-only' lighting model. With ambient=0.9 the cap
pushed effective shadow alpha to ~0.32 — visibly faint / 'white-ish'
on the TV's own faces. Lower ambient = darker possible shadows; 0.4
hits the polycss default and lets self-shadows read as properly dark.
* tv: floor color to gallery's #4a505a
* tv: Orbit/FPV camera-mode pill + wider zoom range
- Bottom-centre toggle switches between <poly-orbit-controls> and
<poly-first-person-controls>. FPV mounts with WASD/jump/look and a
hint label appears on the right side of the pill while active.
- Orbit zoom range widened to [1, 200] so users can zoom out far past
each TV's default zoom.
* tv: disable self-shadow + match gallery's FPV camera feel
- Drop receive-shadow from the TV mesh: the texture-receiver darkening
reads as 'white-ish' (capped at ambient brightness) and visible
self-shadows weren't worth the cost.
- FPV mode now matches the gallery's defaults: move-speed 30,
jump-velocity 25, gravity 60, eye-height 6, look-sensitivity 0.15.
Also switches camera perspective to 2000 while FPV is active (the
same FPV_PERSPECTIVE the gallery and builder use) for a wider FOV;
restores to 32000 when going back to Orbit.
* tv: FPV spawns BEHIND the TV looking at it (matches gallery's useFpvSpawn)
Entering FPV used to drop the camera at scene origin (where the TV
sits, since the scene is auto-centered) — so the player was INSIDE the
TV with a face full of bezel.
Now matching gallery's spawn behaviour:
- Save + drop scene's auto-center attribute on entry, restore on exit.
- Compute TV bbox from the loaded mesh handle, derive a spawn target
one mesh-span behind the TV along the current rotY look direction,
set it on the camera so the controls' initializeOriginFromTarget
uses that as the seed.
- Remove target on exit so orbit returns to its scene-origin pivot.
* fix(camera): keep handle identity when <poly-perspective-camera> perspective changes
* tv: per-preset FPV player scale + auto-pitch + re-spawn on mesh switch1 parent fe37afa commit cac9da3
3 files changed
Lines changed: 269 additions & 7 deletions
File tree
- packages/polycss/src/elements
- website/src/pages
Lines changed: 75 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
Lines changed: 5 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
103 | 103 | | |
104 | 104 | | |
105 | 105 | | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
111 | 111 | | |
112 | 112 | | |
113 | 113 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
83 | 87 | | |
84 | 88 | | |
85 | 89 | | |
| |||
88 | 92 | | |
89 | 93 | | |
90 | 94 | | |
| 95 | + | |
91 | 96 | | |
92 | 97 | | |
93 | 98 | | |
| |||
187 | 192 | | |
188 | 193 | | |
189 | 194 | | |
190 | | - | |
191 | | - | |
| 195 | + | |
| 196 | + | |
192 | 197 | | |
193 | 198 | | |
194 | 199 | | |
| |||
208 | 213 | | |
209 | 214 | | |
210 | 215 | | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
211 | 221 | | |
212 | 222 | | |
213 | 223 | | |
| |||
231 | 241 | | |
232 | 242 | | |
233 | 243 | | |
| 244 | + | |
234 | 245 | | |
235 | 246 | | |
236 | 247 | | |
| |||
461 | 472 | | |
462 | 473 | | |
463 | 474 | | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
464 | 607 | | |
465 | 608 | | |
466 | 609 | | |
| |||
545 | 688 | | |
546 | 689 | | |
547 | 690 | | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
548 | 696 | | |
549 | 697 | | |
550 | 698 | | |
| |||
717 | 865 | | |
718 | 866 | | |
719 | 867 | | |
| 868 | + | |
| 869 | + | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
| 875 | + | |
| 876 | + | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
| 882 | + | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
720 | 907 | | |
721 | 908 | | |
722 | 909 | | |
| |||
0 commit comments