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:
@@ -2,6 +2,7 @@ import { Sculpt, StrokePath, pressureValue } from "./engine.js";
|
||||
import { sphere, updateNormals } from "./mesh.js";
|
||||
import { cameraBasis, add, mul, sub } from "./math.js";
|
||||
import { Mailbox } from "./mailbox.js";
|
||||
import { hydrate } from "./document.js";
|
||||
|
||||
// Sole owner of brush-time geometry work. Exported for deterministic tests;
|
||||
// the browser entry below only supplies worker rAF and postMessage.
|
||||
@@ -30,12 +31,14 @@ export class StrokeProcessor {
|
||||
moved.push(v);
|
||||
}
|
||||
e.mesh.positions.set(data.positions);
|
||||
if (data.masks) e.mesh.masks.set(data.masks);
|
||||
if (data.activeId) e.activeId = data.activeId;
|
||||
if (moved.length) {
|
||||
const { faces } = updateNormals(e.mesh, moved);
|
||||
e.bvh.refit(faces);
|
||||
}
|
||||
e.dirty.clear();
|
||||
e.begin(data.settings);
|
||||
e.begin({ ...data.settings, activeId: data.activeId });
|
||||
const basis = cameraBasis(data.camera.yaw, data.camera.pitch);
|
||||
this.job = {
|
||||
...data,
|
||||
@@ -57,7 +60,14 @@ export class StrokeProcessor {
|
||||
add(this.world(sample), mul(basis.forward, 24)),
|
||||
mul(basis.forward, -1),
|
||||
);
|
||||
if (!hit) this.path.break();
|
||||
const piece = e.mesh.pieces.find((p) => p.id === e.activeId);
|
||||
if (
|
||||
!hit ||
|
||||
!piece ||
|
||||
hit.face * 3 < piece.indexStart ||
|
||||
hit.face * 3 >= piece.indexStart + piece.indexCount
|
||||
)
|
||||
this.path.break();
|
||||
else e.dab(hit, sample.pressure);
|
||||
},
|
||||
);
|
||||
@@ -95,6 +105,24 @@ export class StrokeProcessor {
|
||||
const e = this.engine;
|
||||
if (!this.job || this.outstanding || !e.dirty.size) return;
|
||||
const rows = Uint32Array.from(e.dirty);
|
||||
if (this.job.settings.brush === "mask") {
|
||||
const masks = Float32Array.from(rows, (v) => e.mesh.masks[v]);
|
||||
const touched = Uint32Array.from(e.stroke.touched);
|
||||
e.dirty.clear();
|
||||
this.outstanding = ++this.packet;
|
||||
this.send(
|
||||
{
|
||||
type: "mask",
|
||||
id: this.job.id,
|
||||
packet: this.packet,
|
||||
masks,
|
||||
rows,
|
||||
touched,
|
||||
},
|
||||
[masks.buffer, rows.buffer, touched.buffer],
|
||||
);
|
||||
return;
|
||||
}
|
||||
const vertices = new Float32Array(rows.length * 8);
|
||||
for (let row = 0; row < rows.length; row++) {
|
||||
const i = rows[row] * 3,
|
||||
@@ -212,7 +240,7 @@ if (typeof self !== "undefined" && typeof document === "undefined") {
|
||||
"Sculpt worker requires worker requestAnimationFrame support",
|
||||
);
|
||||
processor = new StrokeProcessor(
|
||||
sphere(data.level),
|
||||
data.mesh ? hydrate(data.mesh) : sphere(data.level),
|
||||
new Mailbox(data.row),
|
||||
(message, transfer = []) => self.postMessage(message, transfer),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user