v1.1.23 — Direct-mesh rendering path
Opt-in direct-mesh render path: ViewportBody.directMesh(id:positions:normals:indices:color:…) builds a body from de-interleaved position/normal arrays, uploaded as two GPU buffers (position @ buffer 0, normal @ buffer 2) instead of one interleaved stride-6 buffer.
Why
The interleaved path walks every vertex to repack position+normal before upload — an extra pass plus an extra in-memory copy. A CAD kernel's triangulation (OCCT Poly_Triangulation) already stores those arrays flat and contiguous, so that repack is pure overhead. Removing it is a load-time and peak-memory win that scales with mesh size.
It also skips NormalSmoothing, which is correct for OCCT-sourced geometry: those per-vertex normals are already analytically accurate. (Re-deriving them from face normals caused the striations fixed in v1.1.22 — this is complementary.)
Opt-in and source-compatible
Nothing changes unless you call directMesh(...). Interleaved bodies keep the existing path, including autoSmoothNormals. Both kinds coexist in one scene. New API: meshPositions, meshNormals, usesDirectMesh; init gains defaulted meshPositions:/meshNormals:.
Render-pass parity
All passes route direct bodies in both ViewportRenderer and OffscreenRenderer: shaded (opaque + transparent), shadow casting, SSAO/silhouette depth prepass, overlay layer, GPU pick.
Limitations (by design)
autoSmoothNormalsand PN-triangle tessellation (renderingQuality = .enhanced) do not apply to direct bodies — their normals are used verbatim.- A direct body carries no separate B-Rep corner-vertex / per-segment edge pick indices (
verticesholds the mesh vertices, for bbox /fit(to:)/ CPU raycast). Face display, GPU pick, CPU raycast and edge display all work. - Only consumers holding flat contiguous
[Float]arrays benefit. If your mesh stores[SIMD3<Float>], you would have to flatten anyway (SIMD3 is 16-byte padded) — stay on the interleaved path.
Fixed
SceneRaycastcrash on direct-mesh bodies — the narrowphase readvertexData(empty on direct bodies) indexed byindices, trapping on any raycast against one. Now readsverticeswhenvertexDatais empty.
Verification
170 tests (7 new differential/pipeline/raycast tests). Verified beyond the headless suite: macOS under Metal API + GPU Validation with SSAO/silhouettes/shadows/overlay/picking enabled (zero errors), and GPU pick readback confirmed on an iPhone 15 Pro. New Examples/DirectMeshVerify on-device rig.
Full changelog: docs/CHANGELOG.md · PR: #83