Add clay pieces, masking, and topology editing to sculpt

Add pastel presets, piece selection and colors, mask brushes, subdivision and conservative decimation with bounded worker operations and mixed undo history.

Amp-Thread-ID: https://ampcode.com/threads/T-01a0b4b4-202c-73f1-984d-9a55c88f9c83
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-10-01 07:55:48 +00:00
co-authored by heaust
parent d930d8172f
commit d279ec4123
21 changed files with 2564 additions and 131 deletions
+144 -1
View File
@@ -23,6 +23,16 @@
<main id="viewport" tabindex="0" aria-label="Sculpting canvas">
<canvas aria-label="Your sculpt"></canvas>
<div id="cursor" hidden aria-hidden="true"><i></i></div>
<div id="empty-clay" class="canvas-message" hidden>
<h1>A fresh canvas.</h1>
<p>Add a piece of clay to begin.</p>
<button id="add-empty" class="btn">Add clay</button>
</div>
<div id="clay-progress" class="canvas-message" role="status" hidden>
<h1 id="operation-label">Updating clay…</h1>
<p>Your other pieces stay unchanged.</p>
<button id="cancel-operation" class="btn">Cancel</button>
</div>
<div id="loading" class="canvas-message" role="status">
<h1>Preparing your clay.</h1>
<p>A moment to get ready.</p>
@@ -35,6 +45,18 @@
</main>
<nav class="tool-rail" aria-label="Sculpt tools">
<button
id="pieces-toggle"
class="btn icon-button launcher"
aria-label="Clay pieces"
title="Clay pieces"
aria-expanded="false"
aria-controls="pieces-panel"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m12 3 9 5-9 5-9-5zm-9 9 9 5 9-5M3 16l9 5 9-5" />
</svg>
</button>
<div class="brushes" role="group" aria-label="Brush">
<button
class="btn icon-button"
@@ -101,6 +123,18 @@
/>
</svg>
</button>
<button
class="btn icon-button"
data-brush="mask"
aria-pressed="false"
aria-label="Mask"
title="Mask · 6"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M4 5h16v8c0 5-8 8-8 8s-8-3-8-8zM12 5v16" />
<path d="M6 8h4M6 11h4M7 14h3" />
</svg>
</button>
</div>
<div class="rail-group">
<button
@@ -253,12 +287,107 @@
value="0.45"
/>
<label class="toggle-row" for="invert"
><span>Subtract</span
><span id="invert-label">Subtract</span
><input class="input" role="switch" id="invert" type="checkbox"
/></label>
</div>
</section>
<section
id="pieces-panel"
class="panel pieces-panel"
aria-label="Clay pieces"
hidden
>
<div class="panel-heading">
<h2>Clay</h2>
<button class="btn" data-close aria-label="Close clay pieces">×</button>
</div>
<div id="piece-list" aria-label="Select a piece"></div>
<div class="piece-material">
<label for="piece-color">Color</label
><input id="piece-color" type="color" aria-label="Piece color" />
<button
id="delete-piece"
class="btn"
aria-label="Delete selected piece"
title="Delete selected piece"
>
Delete
</button>
</div>
<div class="preset-grid" aria-label="Add clay preset">
<button
class="btn"
data-preset="sphere"
aria-label="Add sphere"
title="Add sphere"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="9" />
<ellipse cx="12" cy="12" rx="4" ry="9" />
<path d="M3 12h18" />
</svg>
</button>
<button
class="btn"
data-preset="cube"
aria-label="Add cube"
title="Add cube"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m12 2 9 5v10l-9 5-9-5V7zm0 10 9-5M12 12 3 7m9 5v10" />
</svg>
</button>
<button
class="btn"
data-preset="cylinder"
aria-label="Add cylinder"
title="Add cylinder"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<ellipse cx="12" cy="5" rx="8" ry="3" />
<path d="M4 5v14c0 4 16 4 16 0V5" />
</svg>
</button>
<button
class="btn"
data-preset="cone"
aria-label="Add cone"
title="Add cone"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m4 18 8-16 8 16" />
<ellipse cx="12" cy="18" rx="8" ry="3" />
</svg>
</button>
<button
class="btn"
data-preset="torus"
aria-label="Add torus"
title="Add torus"
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<ellipse cx="12" cy="12" rx="10" ry="7" />
<ellipse cx="12" cy="10" rx="4" ry="2" />
</svg>
</button>
</div>
<details class="disclosure">
<summary>Detail &amp; mask</summary>
<div class="piece-actions">
<button id="subdivide" class="btn">Subdivide</button
><button id="decimate" class="btn">Decimate</button>
<button id="clear-mask" class="btn">Clear mask</button
><button id="invert-mask" class="btn">Invert mask</button>
</div>
<p class="hint">
Subdivide adds resolution. Decimate reduces detail and preserves
masked areas.
</p>
</details>
</section>
<span id="modified" class="modified-label" hidden>Unsaved</span>
<aside
id="inspector"
@@ -456,6 +585,20 @@
Palm rejection is on. Turn it off in Settings to draw with a
finger or move with two fingers.
</dd>
<dt>Build with clay</dt>
<dd>
Open Clay at the top of the toolbar. Add a preset, select its row,
then change its color or delete it. Each new piece gets a pastel
color.
</dd>
<dt>Protect and refine</dt>
<dd>
Mask paints protected areas; use Erase mask in brush settings to
remove it. Clay → Detail &amp; mask contains Clear, Invert,
Subdivide and Decimate. Subdivide adds resolution without
smoothing. Decimate preserves masked areas and may reduce less
than half.
</dd>
<dt>Keep your work</dt>
<dd>
Download your project before leaving. Open it here to keep