Skip to content

Draw dice with the rounded edges the solver collides them with - #377

Open
drehtuer wants to merge 2 commits into
rendering/translucent-dicefrom
rendering/rounded-dice
Open

drehtuer wants to merge 2 commits into
rendering/translucent-dicefrom
rendering/rounded-dice

Conversation

@drehtuer

@drehtuer drehtuer commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Step 4 on feature/realistic-rendering: rounded edges and corners on the dice.

The physics die was never sharp. JoltWorld gives Jolt a convex radius of 3 % of size_mm (0.48 mm on a 16 mm die). Jolt clamps that by its corner-error limit (the d4 gets 0.25 mm) and by the die's thinnest width. So the sharp picture stood up to 0.5 mm outside the colliding die: into the felt on a corner contact, and into each other where corners touched.

This draws exactly the solver's rounding (decision 91):

  • The drawn radius repeats Jolt's limits over the same faces, so the drawn surface is the colliding surface: the gap is 0 by construction.
  • Physics is untouched: the 0.03 share moved unchanged into simulation/api/HullMargin, which JoltWorld reads, so no golden, fairness or harness figure can move.
  • RoundedEdges builds the mesh: flat faces shrunk on their own planes, edge cylinders (≤ 30° a segment), sphere-patch corners, smooth normals on the curves. All eight catalogue shapes are covered.
  • Flat-part UVs equal the old mapping, so numbers and artwork sit exactly where they did; artwork now runs round the bends. The d4's corner numerals reach 0.19 mm onto the bend.
  • DieMesh.of(shape, rounding = 0.0) reproduces the old mesh exactly.
  • Triangles, sharp → rounded: d6 12 → 204, d20 20 → 260, coin 92 → 1,052. That is 100d6 at ~20k triangles, still to be measured with tools/harness.sh --rendered.

On the Pixel 10a:

  • the full :render:filament device suite passes: 39 tests, 0 failures, including PrintedNumbersDeviceTest;
  • in the gallery, edges and corners read softer, and dice on a face sit exactly where they did.

Tests: RoundedEdgesTest and HullMarginTest cover face planes kept, rounding 0 = old mesh, UVs, outward unit normals, a closed mesh, a zero gap and the counts. Function coverage is 93.2 %, branch 73.1 %.

Docs: docs/physics-and-rendering.md ("Rounded edges", with the "Dice bodies" bullet corrected), decision 91.

Based on #376.

🤖 Generated with Claude Code

drehtuer and others added 2 commits October 4, 2026 17:30
The solver never rolled a sharp die: Jolt is handed the sharp hull and a
convex radius of 3 % of the die's size, pulls every face plane in by it and
grows the result back out, so what meets the felt has 0.48 mm fillets on
every edge and corner (0.25 mm on a d4, where Jolt's 0.5 mm error limit
bites). The picture was the sharp solid, which stood outside the colliding
die at every corner - up to half a millimetre into the felt.

RoundedEdges repeats the same construction over the mesh's faces with the
same radius: flat faces stay on their own planes (a die on a face is drawn
exactly where it was), edges become cylinder strips and corners sphere
patches with a normal per corner, and the radius goes through Jolt's own
two limits rather than being chosen for the picture. The drawn surface is
the colliding one, so there is no gap left to bound.

The share moves from JoltWorld to simulation/api's HullMargin unchanged in
value, because render/filament cannot see the Jolt module and two copies of
the number would come apart. No physics behaviour changes.

Texture coordinates stay a function of a point's place on its face's
plane, so the flat part samples its cell exactly as before; the bends
continue their face's cell rather than going blank. Labels are still sized
against the sharp face, so the tray and the face designer agree. The d4's
corner numbers reach 0.19 mm onto the start of the bend, painted there,
not clipped. DieMesh.of(shape, 0.0) is the sharp mesh, surface for surface.

Decision 91.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rendered harness on the Pixel 10a, 20 rolls of 100d6: GPU p99 14.8 ms
against 13.7 ms before, 57.4 fps against 57.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@drehtuer

drehtuer commented Oct 4, 2026

Copy link
Copy Markdown
Owner Author

Rendered harness on the Pixel 10a, 20 rolls of 100d6 with rounded dice (3,864 frames): GPU p50/p99 6.5 / 14.8 ms against 6.2 / 13.7 ms before rounding, work p99 25.5 ms against 24.3 ms, 57.4 fps against 57.3. That is about 1 ms of GPU at the capacity limit, and the GPU bar of 16.6 ms is still met. The work p99 was already over 16.6 ms at 100 dice before this change (the physics steps; see "Performance"). Recorded in the docs in 4c4… (latest commit).

@sonarqubecloud

sonarqubecloud Bot commented Oct 4, 2026

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant