Skip to content

viewer: nothing passes through a roof - #1018

Open
8Maverik8 wants to merge 1 commit into
pascalorg:mainfrom
8Maverik8:roof-cuts-everything-below
Open

8Maverik8 wants to merge 1 commit into
pascalorg:mainfrom
8Maverik8:roof-cuts-everything-below

Conversation

@8Maverik8

@8Maverik8 8Maverik8 commented Oct 8, 2026 •

Copy link
Copy Markdown

What does this PR do?

Walls, posts and other meshes ignore the roof today: a full-storey wall under a low roof (eaves below the storey height) pokes through the roof and its overhang, and so do posts, furniture or a pipe placed under it. This PR makes a roof cut everything in its building at its underside (the bottom of the deck):

  • Walls get one extra CSG subtraction of a heightfield solid above the roof underside (wall-roof-fit.ts), kept separate from the opening union, so the wall top follows the slope with a closed, flat-shaded top. Walls never grow towards a roof.
  • Every other mesh of a level — posts, furniture, fences, pipes, ceilings — is clipped geometrically against convex prisms above the underside (roof-clip.ts), whatever its material or loader. The cut face sits against the deck underside and is not visible. Roofs and the nodes they host (chimneys, skylights) are left alone; skinned and instanced meshes are skipped.
  • Columns under a roof reach its underside, measured from their slab lift (getFloorStackedPosition), so posts of a terrace or porch meet the roof.

The underside planes come from the same faces the roof renders: the getVol closure in getRoofSegmentBrushes is extracted as getRoofSegmentVolumeFaces, and getRoofSegmentUndersidePolygons returns the deck-bottom faces clipped by the segment trim (roof-underside.ts). Only 3D geometry changes: plans, stored heights, the spatial grid and the roof elevation that follows wall tops keep reading the storey height. A roof edit rebuilds only the walls whose roof cover changed; a mesh is re-clipped only when its geometry, placement or the building's roofs change.

How to test

  1. bun test packages/viewer/src/systems/wall/wall-roof-fit.test.ts packages/viewer/src/systems/roof/roof-clip.test.ts — walls cut by a low roof (full-height, partition and half walls), walls outside a roof untouched, roof edit marks only affected walls, posts reach the underside up or down and from a slab, mesh clipping keeps material groups.
  2. bun dev, draw a room with full-height walls, add a shed roof whose eave is below the storey height: the walls follow the roof slope instead of poking through.
  3. Put a column and an item under the roof: the column reaches the roof underside; anything taller than the underside is cut at it.

Screenshots / screen recording

Before/after screenshots will be added in a comment (tested on a real house plan with a split-level shed roof).

Checklist

  • I've tested this locally with bun dev (tested in our fork's editor on a production house plan; on this branch: unit tests and tsc for viewer)
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

🤖 Generated with Claude Code


Note

Medium Risk
Touches core wall CSG and a per-frame mesh clip over the scene graph; incorrect patches or transforms could mis-cut geometry or add frame cost, though invalidation and tests limit blast radius.

Overview
Building content no longer pierces roofs: the viewer derives roof underside patches from the same segment volume data used for roof brushes, then uses them in three paths.

Walls under a roof get an extra CSG subtraction (wall-roof-fit) so the extruded top follows the sloped underside without changing stored storey height; roof edits mark only walls whose cover signature changed.

Other level meshes (furniture, ceilings, etc.) are clipped each frame in runRoofClipFrame against convex regions above those patches; roofs, wall meshes, and skinned/instanced geometry are skipped.

Columns override rendered height via resolveColumnRoofHeight (from slab lift through getFloorStackedPosition) so posts meet the underside; taller geometry can still be trimmed by the mesh clip pass.

getRoofSegmentVolumeFaces / getRoofSegmentUndersidePolygons centralize underside planes; tests cover wall fitting, column height, and clipGeometryByRegions.

Reviewed by Cursor Bugbot for commit eadf6de. Bugbot is set up for automated code reviews on this repo. Configure here.

A roof now cuts everything in its building at its underside (the bottom of
the deck):

- walls get one extra CSG subtraction of a heightfield solid above the roof
  underside, separate from the opening union, so their top follows the slope;
- every other mesh of a level (posts, furniture, fences, pipes, ceilings) is
  clipped geometrically against convex prisms above the underside, whatever
  its material; roofs and the nodes they host are left alone;
- a column under a roof reaches its underside, measured from its slab lift.

The underside planes come from the same faces the roof renders
(`getRoofSegmentVolumeFaces`, extracted from the `getVol` closure, and
`getRoofSegmentUndersidePolygons`, honouring segment trim). A roof edit only
rebuilds the walls whose roof cover changed.

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

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit eadf6de. Configure here.

const level = elevations.get(levelId)
if (!levelObject || !level) continue
const patches = patchesByBuilding.get(level.buildingId)
if (!patches?.length) continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clipped meshes never restore

High Severity

runRoofClipFrame returns as soon as no building has underside patches, and skips a level when that building’s patch list is empty. Those paths never walk existing meshes, so a deleted or emptied roof leaves furniture, pipes, fences and ceilings permanently cut until remount.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eadf6de. Configure here.

const regions = buildingRegions.map((region) =>
region.map((plane) => plane.clone().applyMatrix4(toLocal)),
)
clipped = clipGeometryByRegions(source, regions)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lower roofs clip storeys above

High Severity

Mesh clipping treats every roof in the building as an unbounded upward prism and, once any patch AABB hits, subtracts all of them. A mesh that sits entirely above a lower roof (room over a porch, split-level wing, garage with a storey above) is removed inside that roof’s outline instead of being left alone. Walls already skip roofs below their base via capPatchAt; this path does not.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eadf6de. Configure here.

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