Skip to content

perf(web): device models download at a third of their size - #12

Merged
Kaloyanes merged 1 commit into
Kaloyanes:feat/device-models-by-typefrom
DominikScholz:feat/device-models-meshopt
Oct 30, 2026
Merged

Kaloyanes merged 1 commit into
Kaloyanes:feat/device-models-by-typefrom
DominikScholz:feat/device-models-meshopt

Conversation

@DominikScholz

Copy link
Copy Markdown

This PR targets pingdotgg#26884's branch. Its description lists "the models folder grows to 38 MB" as a tradeoff, and this takes that off the table before it reaches main. The 18 device models go from 39.9 MB to 12.0 MB (22.3 to 8.3 MB gzipped). It also fixes the iPhone 16 and 16 Plus camera glass flickering.

Compression. Geometry was almost all of the size, so the GLBs now use EXT_meshopt_compression, and modelScene.ts registers three's MeshoptDecoder. Quantization runs on one scene-wide 16-bit grid. Apple's assets stack coincident layers (glass over a base finish), and per-mesh grids round those layers apart until they z-fight. Screen meshes get quantized too, so the loader bakes their node transform back into float geometry, because the screen checks, UVs and touch projection work in model space. It's all the stock CLI: gltf-transform weld, then meshopt --quantization-volume scene --quantize-position 16.

Hidden parts. 478 parts never change a pixel. I rendered every model from 26 directions with a logarithmic depth buffer and hid each node in turn; only nodes that changed nothing are removed. They're listed per model as hiddenNodes in sources.json. Parts that are only nearly hidden stay. The Duo keeps everything, because its hinge pose changes what's visible.

Glass fix. Most models' glass is an exact copy of the layer beneath it, which draws cleanly. On the iPhone 16 and 16 Plus the blue glass shell crosses the white base, partly in front and partly behind, so WebGL draws a flickering patchwork even without compression. surfaceOffsets pushes that shell 0.0003 units outward (about 0.1 px at normal size).

Both new sources.json fields are applied by convert-device-model.py, and docs/internals/devices.md records the constraints. Simplification is ruled out there: even a 1e-5 error changed rim highlights and put a seam in the Duo's inner displays.

Evidence

Rendered with the 3D viewer's renderer settings, lights and near/far.

iPhone 16 and 16 Plus camera glass, before and after

Original vs shipped, front and back:

Original vs shipped models

Checks:

  • All 18 shipped models load through loadDeviceModel and build their phone or Duo scene with its screen checks (temporary browser test, hardware WebGL).
  • Close-up renders against the originals: compression alone averages 0.06/255 per pixel. Removing hidden nodes changes no pixel by construction.
  • Blender 5.2.2 run of the updated converter on the iPhone 16 source (checksum matched): 97 meshes become 64, exactly the 33 recorded hidden nodes, and the glass sits in front of the base on all 1,591 coincident triangles.
  • vp test run packages/client-runtime/src/device: 74 passed. The new case feeds a quantized screen through the load step, which the screen checks reject without it.

Not checked: a live simulator in the app. This was done on Linux without an iOS simulator, so the device models were only exercised through the runtime code paths above.

Created with Claude Opus 5.5 in Claude Code (T3 Code).

The 18 device GLBs were 39.9 MB (22.3 MB gzipped), almost all
uncompressed geometry, and the iPhone 16 and 16 Plus camera glass
z-fought with the layer beneath it.

- Compress geometry with EXT_meshopt_compression on one scene-wide
  16-bit grid. Per-mesh grids round Apple's coincident layers apart and
  z-fight. The loader registers three's MeshoptDecoder and bakes display
  meshes back into float model-space geometry, which the screen checks,
  UVs and touch projection need.
- Drop 478 parts that never change a pixel (sources.json hiddenNodes).
- Push the iPhone 16 and 16 Plus glass shells 0.0003 units outward
  (sources.json surfaceOffsets) so they stop crossing the base layer.

The converter applies both new fields; docs/internals/devices.md records
the pipeline constraints. Models now total 12.0 MB (8.3 MB gzipped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL labels Oct 30, 2026
@Kaloyanes
Kaloyanes merged commit 1d97c2a into Kaloyanes:feat/device-models-by-type Oct 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants