Releases: SecondMouseAU/OCCTSwiftViewport
v1.1.22 — NormalSmoothing striations fix (#81)
NormalSmoothing "brushed" striations fix (closes #81)
Fixes the fine periodic "brushed" striations that appeared on highly anisotropic CAD meshes — most visibly on threadedShaft helical flanks. This is the root cause of OCCTSwift#255 (it's a viewport shading bug, not an OCCT geometry bug).
NormalSmoothing.smoothNormals was discarding the per-vertex normals it was handed and recomputing each from an area-weighted face-normal average. On long thin triangles running along a sweep that average is directionally biased and renders as a sandpaper-like ripple.
Fix
Assign the area-weighted average of the original per-vertex normals within each crease group (snapshotted before the in-place write). Face normals are still used for crease detection, so hard edges stay crisp.
- Smooth B-rep meshes → OCCT's analytic normals are reproduced (verified byte-identical to a raw-normal render of an M10×1.5 thread, 728K triangles) → striations gone.
- Flat STL (vertex normal == face normal) → reduces to the old face-normal average → backward-compatible.
Tests
New smoothInputNormalsPreserved (the #81 regression) + flatInputMatchesFaceNormalAverage (backward-compat); updated the crease test to seed realistic per-face normals. 163 tests pass.
Full Changelog: v1.1.21...v1.1.22
v1.1.21 — Unlit display mode (#77)
Unlit / flat-colour display mode (closes #77)
New DisplayMode.unlit draws each body in its constant base colour with no lighting, ambient, shadows, fresnel, curvature, or tone mapping — so vivid, colour-coded diagnostic / debug renders stay faithful and distinguishable.
Previously OffscreenRenderer .shaded ran every body through the full PBR path (hemisphere ambient + fresnel rim + curvature; ACES tone map on the interactive post-pass), which desaturated and hue-shifted saturated colours — a bright magenta read as green-ish, breaking region colour-coding (e.g. OCCTReconstruct's diagnostic renders).
How it works
shaded_fragmentearly-returnsbodyUniforms.colorwhen the newUniforms.unlitflag is set (clip-plane discard + selection tint still apply).- Both
OffscreenRendererandViewportRendererset the flag from theirdisplayMode;.unlitroutes through the shaded pipeline viashowsSurfaces == true. The interactive SSAO / ACES tone-map post-pass is skipped for.unlit. Uniformsgains auint unlitfield (Swift↔Metal in sync; repacked the existing clip-pad tail, stride unchanged).
Verification
New UnlitColorTests: a magenta panel renders faithfully in .unlit (R≈255, G≈38, B≈217) and measurably more saturated than .shaded. 161 tests pass.
Full Changelog: v1.1.20...v1.1.21
v1.1.20 — Tap-to-measure (#68)
Tap-to-measure (issue #68)
ViewportController.measurementMode (.distance / .angle / .radius) now actually drives interaction. While a mode is active, taps on geometry accumulate world-space surface points and commit a ViewportMeasurement (rendered by the existing MeasurementOverlay). Previously the property was published but consumed by nothing.
Added
- Point order per mode:
.distance→ start, end (2 taps);.angle→ armA, vertex, armB (3 taps);.radius→ center, edge (2 taps). ViewportBody.worldHitPoint(ray:triangleIndex:)— reconstructs the picked surface point in world space, respecting the body'stransform.- Controller:
pendingMeasurementPoints,addMeasurementPoint(_:),handleMeasurementPick(...),cancelPendingMeasurement(),clearMeasurements(),static pointCount(for:). Mode change clears pending; face picks only; selection stream untouched. MeasurementModeis nowEquatable.
Tests
- New
MeasurementModeTests(14). 160 tests total, all green.
Additive and source-compatible.
🤖 Generated with Claude Code
v1.1.16 — Cube drag direction fix
Follow-up to v1.1.15: dragging the navigation cube now orbits the camera the intended way (the cube is a camera proxy, so it orbits around the model — opposite sign to the viewport's grab-the-model drag). 142 tests.
v1.1.15 — Cube drag-to-orbit + edge/corner targets
Follow-up to #60: the navigation cube is now drag-to-orbit (grab-and-spin); a non-moving press still snaps to the tapped face/edge/corner. Faces draw a 3x3 grid so edge/corner zones are discoverable, and the cube is larger (96pt) so they're reachable. Classifier round-trip verified under the iso rotation. 142 tests. Lives here (shared viewport widget) for ecosystem commonality.
v1.1.14 — Non-pickable bodies
Closes #63: ViewportBody.isPickable (default true) excludes a body from the GPU pick buffer while still drawing it, so always-on-top reference/overlay bodies (datum/ground planes) stop stealing face/edge/vertex picks. Gated in every pick sub-pass; pick IDs stay stable. PickabilityTests; 141 tests; no behaviour change by default.
v1.1.13 — View cube position
Closes #62: the view-cube overlay now honours ViewportConfiguration.viewCubePosition (topLeading/topTrailing/bottomLeading/bottomTrailing) instead of being hardcoded bottom-trailing — so hosts can move it clear of e.g. an iPhone bottom sheet. Default unchanged. 138 tests.
v1.1.12 — Interactive navigation cube
Closes #60: NavigationCubeView — a Shapr3D/Fusion-style cube tracking the camera with clickable faces/edges/corners that snap to plan/elevation/iso views (goToRegion). Pure unit-tested NavigationCube hit-test (ray→cube→3x3-per-face → 26 regions); robust iOS+macOS. NavigationCubeTests; 136 tests; rendering verified on Vision Pro sim.
v1.1.11 — Scene-adaptive clip planes (z-fighting fix)
Closes #57: near/far clip planes are now derived per frame from camera distance + scene radius (CameraState.clipPlanes), keeping far/near ~1e3 at any model scale instead of the fixed 0.01/10000 (1e6) range that caused z-fighting on large/real-scale models. Both renderers. ClipPlaneTests; 131 tests; verified on Vision Pro sim. No API change.
v1.1.10 — Headless per-body transforms
Closes #55: OffscreenRenderer now applies body.transform (shaded/transparent/point/shadow passes + transformed shadow-frustum bounds), so transformed/instanced/assembled bodies render correctly headlessly. Matches ViewportRenderer. OffscreenTransformTests; 126 tests.