Skip to content

render: translucent materials, always-on AO, adaptive tessellation, silhouette outline#175

Merged
ecto merged 5 commits intomainfrom
claude/fix-vcad-rendering-PDsBu
May 5, 2026
Merged

render: translucent materials, always-on AO, adaptive tessellation, silhouette outline#175
ecto merged 5 commits intomainfrom
claude/fix-vcad-rendering-PDsBu

Conversation

@ecto
Copy link
Copy Markdown
Owner

@ecto ecto commented May 5, 2026

Summary

Four targeted rendering fixes after auditing why a vcad scene doesn't yet
look like a polished commercial CAD viewer (translucent enclosure over a
PCB, soft AO during motion, smooth curves, subtle silhouettes). Each
change is its own commit so they can be reverted independently.

Commits, in order

  1. feat(render): translucent materials via MeshPhysicalMaterial
    Adds optional transmission / ior / thickness / attenuationDistance
    / attenuationColor / clearcoat / clearcoatRoughness to
    MaterialPreset and MaterialDef. SceneMesh now emits
    <meshPhysicalMaterial> when transmission > 0, otherwise the cheaper
    <meshStandardMaterial>. Existing glass / glass-tinted presets gain
    transmission and IOR; new presets: Acrylic (Clear) and
    Polycarbonate (Frosted). The biggest visible gap — translucent
    housings rendering as opaque polished plastic — is closed.

  2. fix(render): keep AO running during camera motion at reduced samples
    The post-processing pass was gated on !isCameraMoving, so AO and
    vignette vanished the moment the user started orbiting. The scene went
    visibly flat during interaction, then snapped back at rest. Now the
    EffectComposer stays mounted and aoSamples drops 6→3 (denoise 4→1)
    while moving.

  3. feat(tessellate): chord and angular tolerance for adaptive segmentation
    Adds optional chord_tolerance and angular_tolerance fields to
    TessellationParams plus a circle_segments_for_radius() helper.
    Cylindrical, conical, spherical, and toroidal face tessellators now
    call it so a 1mm fillet doesn't get the same 32 segments as a 100mm
    cylinder. Defaults stay None — existing callers see no behavior
    change. Opt-in entry: TessellationParams::from_tolerances(chord, ang).

  4. feat(render): silhouette outline post-process around every part
    Wraps the rendered scene in <Selection> / <Select> from
    @react-three/postprocessing and adds an Outline pass for the dark
    contour polished CAD viewers all use. Color tracks the theme so the
    contour stays subtle in dark mode. The three branched
    EffectComposers (AO+vignette / AO only / vignette only) collapse
    into one composer with conditionally-mounted children. New
    SceneSettings.postProcessing.silhouette flag, defaults on.

  5. fix(render): build EffectComposer children as a typed array
    Followup to commit 4: EffectComposer types its children strictly
    (JSX.Element | JSX.Element[]), so the inlined cond && <Effect/>
    shorthand was rejected. Build the array up-front instead.

Test plan

  • cargo test -p vcad-kernel-tessellate --lib — all 14 pass
  • cargo test -p vcad-kernel --lib — all 47 pass
  • cargo clippy -p vcad-kernel-tessellate -p vcad-kernel -- -D warnings — clean
  • npm run build -w @vcad/ir — clean
  • npm test -w @vcad/ir — 34/34 pass
  • npm test -w @vcad/app — 15/15 pass
  • VCAD_WASM_SKIP=1 npm run build -w @vcad/app — clean (vite + tsc -b)
  • Visual smoke test: open a scene with the glass material and confirm
    it actually transmits light through the housing
  • Visual smoke test: orbit the viewport and confirm AO doesn't pop in/out
  • Visual smoke test: confirm the dark silhouette appears around parts
    in both light and dark themes

What this does NOT do

The original audit also flagged: contact / PCSS shadows, ray-traced
transmission, per-component PCB rendering. Those are bigger projects
and intentionally out of scope here.

https://claude.ai/code/session_01HsoFZhtUKiU22qRZp4SbUR


Generated by Claude Code

claude added 5 commits May 5, 2026 17:47
Adds optional transmission/ior/thickness/attenuation/clearcoat fields to
MaterialPreset and MaterialDef. SceneMesh swaps to meshPhysicalMaterial
when transmission > 0 so glass enclosures and acrylic housings render
with refraction instead of looking like opaque polished plastic.

New presets: Acrylic (Clear), Polycarbonate (Frosted). Existing Glass and
Tinted Glass presets gain transmission=1.0 / 0.95.

The .loon vcode format keeps its positional M-line — the new fields
survive in the JSON .vcad format only for now.
The post-processing pass was gated on !isCameraMoving, so AO and vignette
vanished the moment the user started orbiting. The scene went visibly
flat during interaction, then snapped back to "good" at rest.

Now the EffectComposer stays mounted across motion and we drop the AO
sample count from 6 → 3 (and denoise 4 → 1) while moving. Same depth
cues during orbit, no framerate cliff.
Adds optional `chord_tolerance` and `angular_tolerance` fields to
TessellationParams plus a `circle_segments_for_radius()` helper that
raises the segment count so curvature error stays below the configured
sag. Cylindrical, conical, spherical, and toroidal face tessellators
now use this so a 1mm fillet doesn't get the same 32 segments as a
100mm cylinder.

Defaults stay `None`, so existing callers see no behavior change. New
constructor `TessellationParams::from_tolerances(chord, angular)` is
the opt-in path for downstream consumers.
Adds a screen-space outline pass that draws a subtle dark contour
around every rendered part — the touch polished CAD viewers use to
keep parts legible against busy backgrounds.

Implementation: <Selection>/<Select> from @react-three/postprocessing
wraps the rendered scene meshes; a single Outline effect picks up the
selection and runs once per frame. Shadow color tracks the theme so
the contour stays subtle in dark mode.

The three branched EffectComposers (AO+vignette / AO only / vignette
only) collapse into one composer with conditionally-mounted children,
which is also what made room for the new Outline child without an
8-way truth table.

New SceneSettings.postProcessing.silhouette flag (defaults on); turn
off via doc settings if a project doesn't want the contour.
EffectComposer types its children as `JSX.Element | JSX.Element[]`,
which rejects the inlined `cond && <Effect/>` shorthand because that
expression is `false | Element`. Build the array up-front so disabled
effects drop out cleanly.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mecheval Ready Ready Preview, Comment May 5, 2026 6:03pm
vcad Ready Ready Preview, Comment May 5, 2026 6:03pm
vcad-docs Ready Ready Preview, Comment May 5, 2026 6:03pm
vcad-mcp Ready Ready Preview, Comment May 5, 2026 6:03pm

Request Review

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.

2 participants