# Brush Authoring

Brushes are persisted blockout geometry stored as `ActorKind::Brush + BrushDesc`.

## MVP Scope

- `BrushDesc::default()` creates an additive cube brush.
- Each face stores a stable ID, plane, vertices, optional material/texture refs, UV offset/scale/rotation, and smoothing group.
- Hydration builds generated mesh children from active brush components and strips those children before scene save.
- Brush actors validate separately from primitives and imported/static mesh actors.
- The Actor Inspector Add Component shelf can add a **Brush** component. The Brush card exposes kind, face count, shadow flags, and cube reset.
- The Brush card runs shared validation and reports invalid faces inline. Fatal geometry errors prevent hydration; warnings call out authoring issues that can still render. **Reset Cube Brush** is the MVP repair path.
- Scene save runs the same fatal brush validation and blocks writing unrecoverable invalid brush geometry.
- **Window → Brush Diagnostics** lists all brush actors, surfaces validation counts/messages, can select the affected brush, and provides an undoable **Reset Cube** repair for invalid brushes.
- Draw Brush mode (`B`, toolbar pencil, or command `brush.draw`) creates additive prism brushes from snapped floor points. While active, the viewport shows phase-specific quick hints. In outline phase, LMB places points, Backspace removes the last point, and Enter locks the outline for height editing. In height phase, mouse up/down adjusts brush height, Enter or LMB commits through undo history, and Backspace returns to outline editing. Esc/right-click cancels without scene mutation. Simple concave outlines are decomposed into multiple convex brush parts with one undo entry, and the preview shows the generated part boundaries. Adjacent duplicate clicks and a closing duplicate point are merged before commit. Invalid outlines, including self-intersections, preview in red, report the reason in the status/operator text, log the failed Enter attempt, and leave the scene unchanged.

## Element Modes

With a brush actor selected, `1`/`2`/`3`/`4` enter vertex, edge, face, and clip modes. Element modes show viewport overlays for the selected brush, route LMB to element picking, support Shift+LMB multi-select, and display a brush edit banner with the active mode, selected element count, and available controls. Actor transform gizmos are replaced by a brush element transform gizmo at the selected element pivot while a brush element mode is active. `Esc` returns to object mode.

Vertex, edge, and face selections are transformed through the standard viewport gizmo. `W`, `E`, and `R` choose translate, rotate, and scale for the selected brush elements. The editor transforms matching duplicated corner vertices across all brush faces so simple cube/prism brushes stay welded, recomputes face planes, validates the result, and commits one undoable brush edit when the gizmo releases. Invalid gizmo results are rejected with operator status text and reverted to the pre-edit brush.

Clip mode is a bounds-based MVP for cuboid/prism blockout. Select one face in Clip mode to preview the half-brush result along that face's dominant normal axis; the magenta wireframe shows the pending trimmed bounds. **Enter** commits the clip through history and **Esc** returns to Object mode.

When one or more faces are selected in Face mode, the Brush inspector shows selected-face controls for UV offset, UV scale, UV rotation in degrees, material ref, and texture ref. Material and texture rows use Asset Browser-aware picker controls with clear, locate, browse, and compatible drag/drop assignment. These fields edit the persisted `BrushFaceDesc` data through undoable brush updates and update hydrated brush mesh UVs. Face refs cache loadable source paths when available; hydration groups faces by material/texture path and spawns separate generated mesh children for those batches. Non-finite UV offset/scale/rotation values are reported as brush diagnostics and sanitized during hydration so invalid authoring data does not emit NaN mesh UVs.

## Boolean Operations

The command palette exposes `brush.subtract`, `brush.intersect`, and `brush.merge_convex` for selected brush actors. The current implementation is a conservative bounds-based MVP intended for cuboid/prism blockout brushes:

- **Intersect** replaces the primary selected brush with the overlapping bounds of all selected brushes and deletes the other selected brushes.
- **Convex Merge** replaces the primary selected brush with the bounding cuboid union and deletes the other selected brushes.
- **Subtract** clips the primary selected brush to the largest remaining axis-aligned slab after subtracting the second selected brush; the cutter remains in the scene.

These operations validate selected brushes before previewing results and validate the generated result before any scene mutation. A cyan wireframe preview shows the pending bounds result; **Enter** commits and **Esc** cancels. Failed operations report status text and leave the scene untouched. Successful operations commit through history but are not full arbitrary-face CSG yet. Full convex clipping, multi-output subtraction, and material-preserving face matching remain future work.

## Current Limits

- Only convex authored faces are stored and hydrated by the mesh builder. Draw Brush can accept a simple concave floor outline by decomposing it into multiple convex brush actors.
- Brush diagnostics currently cover face validity, plane normals, inverted face normals, finite vertices, duplicate vertices, degenerate area, open/non-manifold edges, and UV scale warnings.
- Subtractive brush markers are stored but not automatically evaluated.
- Arbitrary plane clipping, split-into-two output, and arbitrary-face CSG remain future roadmap work.
- Imported source-material subasset refs persist for future material-preserving CSG, but only face refs with loadable source paths can drive brush material hydration today.
