Add methods for creating and updating brush previews in ViewportHost

This commit is contained in:
2026-04-05 01:57:31 +02:00
parent 30f4a24571
commit 0845ac436c

View File

@@ -1732,6 +1732,296 @@ export class ViewportHost {
};
}
private createTargetPreviewBrush(session: ActiveTransformSession): BoxBrush | null {
if (
session.target.kind !== "brush" &&
session.target.kind !== "brushFace" &&
session.target.kind !== "brushEdge" &&
session.target.kind !== "brushVertex"
) {
return null;
}
const currentBrush = this.currentDocument?.brushes[session.target.brushId];
if (currentBrush === undefined || currentBrush.kind !== "box") {
return null;
}
return {
...currentBrush,
center: {
...session.target.initialCenter
},
rotationDegrees: {
...session.target.initialRotationDegrees
},
size: {
...session.target.initialSize
}
};
}
private createBrushPreviewFromExtents(
brush: BoxBrush,
extents: { min: Vec3; max: Vec3 },
rotationDegrees?: Vec3
): { kind: "brush"; center: Vec3; rotationDegrees: Vec3; size: Vec3 } {
const localCenter = {
x: (extents.min.x + extents.max.x) * 0.5,
y: (extents.min.y + extents.max.y) * 0.5,
z: (extents.min.z + extents.max.z) * 0.5
};
const centerOffset = transformBoxBrushLocalPointToWorld(
{
...brush,
center: { x: 0, y: 0, z: 0 },
rotationDegrees: rotationDegrees ?? brush.rotationDegrees
},
localCenter
);
const nextSize = {
x: this.snapWhiteboxSizeValue(extents.max.x - extents.min.x),
y: this.snapWhiteboxSizeValue(extents.max.y - extents.min.y),
z: this.snapWhiteboxSizeValue(extents.max.z - extents.min.z)
};
return {
kind: "brush",
center: {
x: this.snapWhiteboxPositionValue(brush.center.x + centerOffset.x),
y: this.snapWhiteboxPositionValue(brush.center.y + centerOffset.y),
z: this.snapWhiteboxPositionValue(brush.center.z + centerOffset.z)
},
rotationDegrees: {
...(rotationDegrees ?? brush.rotationDegrees)
},
size: nextSize
};
}
private getInitialBrushLocalExtents(brush: BoxBrush): { min: Vec3; max: Vec3 } {
return {
min: {
x: -brush.size.x * 0.5,
y: -brush.size.y * 0.5,
z: -brush.size.z * 0.5
},
max: {
x: brush.size.x * 0.5,
y: brush.size.y * 0.5,
z: brush.size.z * 0.5
}
};
}
private buildComponentTranslatedBrushPreview(
session: ActiveTransformSession,
origin: { x: number; y: number },
current: { x: number; y: number },
axisConstraint: TransformAxis | null
) {
const initialBrush = this.createTargetPreviewBrush(session);
if (initialBrush === null) {
throw new Error("Cannot build a component translation preview without a box brush target.");
}
const initialPivot = this.getTransformPivotPosition({
...session,
preview: {
kind: "brush",
center: { ...initialBrush.center },
rotationDegrees: { ...initialBrush.rotationDegrees },
size: { ...initialBrush.size }
}
});
let worldDelta = {
x: 0,
y: 0,
z: 0
};
if (axisConstraint === null) {
const plane = this.getTransformPlaneForPivot(initialPivot);
const startIntersection = this.getPointerPlaneIntersection(origin.x, origin.y, plane);
const currentIntersection = this.getPointerPlaneIntersection(current.x, current.y, plane);
if (startIntersection !== null && currentIntersection !== null) {
const delta = currentIntersection.sub(startIntersection);
worldDelta = {
x: delta.x,
y: delta.y,
z: delta.z
};
}
} else {
const axisDelta = this.getAxisMovementDistance(axisConstraint, initialPivot, origin, current);
worldDelta = this.setAxisComponent(worldDelta, axisConstraint, axisDelta);
}
const localDelta = transformBoxBrushWorldVectorToLocal(initialBrush, worldDelta);
const extents = this.getInitialBrushLocalExtents(initialBrush);
if (session.target.kind === "brushFace") {
const meta = getBoxBrushFaceTransformMeta(session.target.faceId);
for (const axis of ["x", "y", "z"] as const) {
const axisDelta = localDelta[axis];
if (axis === meta.axis) {
if (meta.sign > 0) {
extents.max[axis] += axisDelta;
} else {
extents.min[axis] += axisDelta;
}
} else {
extents.min[axis] += axisDelta;
extents.max[axis] += axisDelta;
}
}
} else if (session.target.kind === "brushEdge") {
const meta = getBoxBrushEdgeTransformMeta(session.target.edgeId);
for (const axis of ["x", "y", "z"] as const) {
const axisDelta = localDelta[axis];
const axisSign = meta.signs[axis];
if (axisSign === null) {
extents.min[axis] += axisDelta;
extents.max[axis] += axisDelta;
continue;
}
if (axisSign > 0) {
extents.max[axis] += axisDelta;
} else {
extents.min[axis] += axisDelta;
}
}
} else if (session.target.kind === "brushVertex") {
const signs = getBoxBrushVertexSigns(session.target.vertexId);
for (const axis of ["x", "y", "z"] as const) {
if (signs[axis] > 0) {
extents.max[axis] += localDelta[axis];
} else {
extents.min[axis] += localDelta[axis];
}
}
}
return this.createBrushPreviewFromExtents(initialBrush, extents);
}
private buildComponentRotatedBrushPreview(
session: ActiveTransformSession,
origin: { x: number; y: number },
current: { x: number; y: number },
axisConstraint: TransformAxis | null
) {
const initialBrush = this.createTargetPreviewBrush(session);
if (initialBrush === null) {
throw new Error("Cannot build a component rotation preview without a box brush target.");
}
const effectiveAxis = axisConstraint ?? this.getEffectiveRotationAxis(session);
const pointerDeltaDegrees = (current.x - origin.x - (current.y - origin.y)) * 0.5;
const pivot = this.getTransformPivotPosition({
...session,
preview: {
kind: "brush",
center: { ...initialBrush.center },
rotationDegrees: { ...initialBrush.rotationDegrees },
size: { ...initialBrush.size }
}
});
const axisVector = this.axisVector(effectiveAxis).normalize();
const centerVector = new Vector3(initialBrush.center.x, initialBrush.center.y, initialBrush.center.z);
const pivotVector = new Vector3(pivot.x, pivot.y, pivot.z);
const nextCenter = centerVector
.sub(pivotVector)
.applyAxisAngle(axisVector, (pointerDeltaDegrees * Math.PI) / 180)
.add(pivotVector);
const nextRotationDegrees = {
...initialBrush.rotationDegrees
};
nextRotationDegrees[effectiveAxis] = this.normalizeDegrees(nextRotationDegrees[effectiveAxis] + pointerDeltaDegrees);
return {
kind: "brush" as const,
center: {
x: this.snapWhiteboxPositionValue(nextCenter.x),
y: this.snapWhiteboxPositionValue(nextCenter.y),
z: this.snapWhiteboxPositionValue(nextCenter.z)
},
rotationDegrees: nextRotationDegrees,
size: {
...initialBrush.size
}
};
}
private buildComponentScaledBrushPreview(
session: ActiveTransformSession,
origin: { x: number; y: number },
current: { x: number; y: number },
axisConstraint: TransformAxis | null
) {
const initialBrush = this.createTargetPreviewBrush(session);
if (initialBrush === null) {
throw new Error("Cannot build a component scale preview without a box brush target.");
}
const extents = this.getInitialBrushLocalExtents(initialBrush);
if (session.target.kind === "brushFace") {
const meta = getBoxBrushFaceTransformMeta(session.target.faceId);
const scaleFactor =
1 +
this.getAxisMovementDistance(
axisConstraint ?? meta.axis,
this.getTransformPivotPosition(session),
origin,
current
) *
0.45;
if (meta.sign > 0) {
extents.max[meta.axis] = extents.min[meta.axis] + (extents.max[meta.axis] - extents.min[meta.axis]) * scaleFactor;
} else {
extents.min[meta.axis] = extents.max[meta.axis] - (extents.max[meta.axis] - extents.min[meta.axis]) * scaleFactor;
}
} else if (session.target.kind === "brushEdge") {
const meta = getBoxBrushEdgeTransformMeta(session.target.edgeId);
const affectedAxes = (["x", "y", "z"] as const).filter(
(axis) => meta.signs[axis] !== null && (axisConstraint === null || axisConstraint === axis)
);
const pivot = this.getTransformPivotPosition(session);
for (const axis of affectedAxes) {
const sign = meta.signs[axis];
if (sign === null) {
continue;
}
const scaleFactor = 1 + this.getAxisMovementDistance(axis, pivot, origin, current) * 0.45;
if (sign > 0) {
extents.max[axis] = extents.min[axis] + (extents.max[axis] - extents.min[axis]) * scaleFactor;
} else {
extents.min[axis] = extents.max[axis] - (extents.max[axis] - extents.min[axis]) * scaleFactor;
}
}
}
return this.createBrushPreviewFromExtents(initialBrush, extents);
}
private applyBrushRenderObjectTransform(brushId: string, center: Vec3, rotationDegrees: Vec3, size: Vec3) {
const renderObjects = this.brushRenderObjects.get(brushId);
const brush = this.currentDocument?.brushes[brushId];