Skip to content

feat(3d): project a grid onto a curved surface, and a dot_map globe - #406

Merged
LeadcodeDev merged 2 commits into
mainfrom
feat/3d-surface-and-globe
Sep 28, 2026
Merged

LeadcodeDev merged 2 commits into
mainfrom
feat/3d-surface-and-globe

Conversation

@LeadcodeDev

Copy link
Copy Markdown
Owner

Closes #387. Refs #388.

dot_map as a globe

projection: "orthographic" turns the flat map into a sphere. The visibility test is the part a clip-path: circle can never do — it has no notion of a sphere underneath, so it cuts a disc out of a flat map and shows the far hemisphere straight through.

  • far-hemisphere culling from cos_c
  • rotate.lng / rotate.lat to aim it, with a linear sweep
  • limb_shading darkening towards the edge
  • great-circle arcs by 3D slerp, not linear lat/lng interpolation — which would not follow a great circle. Each sample is re-projected and visibility-tested on its own, so an arc crossing the horizon is cut into visible segments rather than drawn through the planet, and one entirely on the far side paints nothing.

The world dot grid switches from fixed pixel spacing to fixed angular spacing at orthographic scale, so screen spacing converges towards the limb by construction rather than by a fudge.

layout-surface on a container

The grid is laid out flat by taffy, then each cell is projected onto a cylinder or sphere with one perspective shared by the container — the shared vanishing point that per-node transform cannot give. It reuses css_perspective_m44 and the pre_concat idiom apply_plane_camera established in #400, applied per child instead of per plane.

layout_pass.rs was not touched, and that is the right answer rather than an omission: like transform and perspective, this is purely paint-time. taffy_bridge.rs never reads those either.

cylinder has no arc_y field at all — compiler- and schema-enforced, rather than a shared struct with a field that is silently inert. That is the lesson from #339, #349 and #364 applied at the API level.

Depth ordering is a stated limitation, not a silence. Children still paint in declaration order. For the intended use — a patch well under 180°, a dome of cards where every cell faces the camera — that is fine. A wide arc, or enough rotate_y to swing a cell past the limb, can make it paint over a nearer one. #93 owns depth sorting generally and is untouched; the rule file and the type's own docs say so.

A false-positive test, caught and fixed

dot_spacing_converges_towards_the_limb passed under the stub at first: float noise made edge_gap 13.888888888888868 and centre_gap 13.888888888888888, so a naive < was satisfied by rounding alone. Requiring a real margin (edge < centre * 0.9) made it report centre=13.889px, edge=13.889px and fail correctly.

Worth recording as a general point: a bare < between two floats that are mathematically equal is not a regression guard.

Verification

18 new tests, each proven red by stubbing the projection:

assertion failed: orthographic_project(0.0, 170.0, 0.0, 0.0).is_none()
an arc entirely on the far hemisphere must be culled, not drawn through the globe
depth must recede monotonically away from the centre: mid=0.0 edge=0.0
a projected grid's cells must not all be the same width: centre_gap=90.00px, edge_gap=90.00px

Backward compatibility pinned both ways: a dot_map with no projection field and a container with no layout-surface render byte-identically.

cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace (1740) all clean. An end-to-end scenario mixing both features validates and renders.

One thing to know for later

Both SurfaceAngle and the globe's RotationAngle are small self-contained Fixed | Animated{from,to} types evaluated locally from the frame time — not wired into the shared keyframe pipeline, because animator.rs was owned elsewhere while this was built. If a general hook lands there, these two ad hoc mini-animations are the first candidates to be unified into it.

Written comment-free, per the codebase-wide rule from #345.

…p globe

Both halves of #387 — a dome of flat content and a real globe — need the
same thing a per-node transform/perspective can't give: one shared vanishing
point (or one shared hemisphere test) for a whole set of children, not a
private one per node.

style.layout-surface (cylinder|sphere) projects a container's direct
children onto a curved surface at paint time only — taffy still lays out
the grid flat, so nothing about layout itself changes. Each child gets a
translate3d computed from its normalized offset from the container's
center, run through the same css_perspective_m44/pre_concat machinery
apply_plane_camera already uses for the camera's own 3D tilt (#400), just
applied per child instead of per plane. Depth ordering is left at
declaration/z-index order — a wide enough arc or turntable rotation can
make a far cell paint over a near one, which #93 owns generally and isn't
fixed here.

dot_map gains projection: "orthographic", the closed-form lat/lng-to-screen
projection plus the far-hemisphere visibility test a clip-path circle can't
express (it just crops a flat map, it never curves). rotate.lng/lat aim the
globe, limb_shading darkens towards the edge, and arcs are great-circle
paths slerped in 3D and lifted off the surface, culled the same way as the
dots so they don't get drawn straight through the globe. The historical flat
mapping is untouched code, gated behind the projection's default, and pinned
byte-identical by test.

Both features are proven with pixel renders, not just the underlying
geometry: a projected grid's columns are shown to differ in width where a
flat grid's don't, and an orthographic globe's dot spacing is shown to
converge towards the limb where the flat map's doesn't.
@LeadcodeDev LeadcodeDev added the enhancement New feature or request label Sep 28, 2026
@LeadcodeDev LeadcodeDev self-assigned this Sep 28, 2026
@LeadcodeDev
LeadcodeDev merged commit c9831e7 into main Sep 28, 2026
4 checks passed
@LeadcodeDev
LeadcodeDev deleted the feat/3d-surface-and-globe branch September 29, 2026 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Project flat content in 3D: children on a curved surface, and an orthographic dot_map globe

1 participant