Add support for brush face, edge, and vertex transform targets in transform session

This commit is contained in:
2026-04-05 01:55:52 +02:00
parent f6ed103be3
commit 924b0c93fe

View File

@@ -2,6 +2,14 @@ import { createOpaqueId } from "./ids";
import type { EditorSelection } from "./selection";
import type { WhiteboxSelectionMode } from "./whitebox-selection-mode";
import type { Vec3 } from "./vector";
import {
BOX_EDGE_LABELS,
BOX_FACE_LABELS,
BOX_VERTEX_LABELS,
type BoxEdgeId,
type BoxFaceId,
type BoxVertexId
} from "../document/brushes";
import type { SceneDocument } from "../document/scene-document";
import {
cloneEntityInstance,
@@ -40,6 +48,33 @@ export interface BrushTransformTarget {
initialSize: Vec3;
}
export interface BrushFaceTransformTarget {
kind: "brushFace";
brushId: string;
faceId: BoxFaceId;
initialCenter: Vec3;
initialRotationDegrees: Vec3;
initialSize: Vec3;
}
export interface BrushEdgeTransformTarget {
kind: "brushEdge";
brushId: string;
edgeId: BoxEdgeId;
initialCenter: Vec3;
initialRotationDegrees: Vec3;
initialSize: Vec3;
}
export interface BrushVertexTransformTarget {
kind: "brushVertex";
brushId: string;
vertexId: BoxVertexId;
initialCenter: Vec3;
initialRotationDegrees: Vec3;
initialSize: Vec3;
}
export interface ModelInstanceTransformTarget {
kind: "modelInstance";
modelInstanceId: string;
@@ -57,7 +92,13 @@ export interface EntityTransformTarget {
initialRotation: EntityTransformRotationState;
}
export type TransformTarget = BrushTransformTarget | ModelInstanceTransformTarget | EntityTransformTarget;
export type TransformTarget =
| BrushTransformTarget
| BrushFaceTransformTarget
| BrushEdgeTransformTarget
| BrushVertexTransformTarget
| ModelInstanceTransformTarget
| EntityTransformTarget;
export interface BrushTransformPreview {
kind: "brush";
@@ -161,6 +202,33 @@ export function cloneTransformTarget(target: TransformTarget): TransformTarget {
initialRotationDegrees: cloneVec3(target.initialRotationDegrees),
initialSize: cloneVec3(target.initialSize)
};
case "brushFace":
return {
kind: "brushFace",
brushId: target.brushId,
faceId: target.faceId,
initialCenter: cloneVec3(target.initialCenter),
initialRotationDegrees: cloneVec3(target.initialRotationDegrees),
initialSize: cloneVec3(target.initialSize)
};
case "brushEdge":
return {
kind: "brushEdge",
brushId: target.brushId,
edgeId: target.edgeId,
initialCenter: cloneVec3(target.initialCenter),
initialRotationDegrees: cloneVec3(target.initialRotationDegrees),
initialSize: cloneVec3(target.initialSize)
};
case "brushVertex":
return {
kind: "brushVertex",
brushId: target.brushId,
vertexId: target.vertexId,
initialCenter: cloneVec3(target.initialCenter),
initialRotationDegrees: cloneVec3(target.initialRotationDegrees),
initialSize: cloneVec3(target.initialSize)
};
case "modelInstance":
return {
kind: "modelInstance",
@@ -257,6 +325,33 @@ function areTransformTargetsEqual(left: TransformTarget, right: TransformTarget)
areVec3Equal(left.initialRotationDegrees, right.initialRotationDegrees) &&
areVec3Equal(left.initialSize, right.initialSize)
);
case "brushFace":
return (
right.kind === "brushFace" &&
left.brushId === right.brushId &&
left.faceId === right.faceId &&
areVec3Equal(left.initialCenter, right.initialCenter) &&
areVec3Equal(left.initialRotationDegrees, right.initialRotationDegrees) &&
areVec3Equal(left.initialSize, right.initialSize)
);
case "brushEdge":
return (
right.kind === "brushEdge" &&
left.brushId === right.brushId &&
left.edgeId === right.edgeId &&
areVec3Equal(left.initialCenter, right.initialCenter) &&
areVec3Equal(left.initialRotationDegrees, right.initialRotationDegrees) &&
areVec3Equal(left.initialSize, right.initialSize)
);
case "brushVertex":
return (
right.kind === "brushVertex" &&
left.brushId === right.brushId &&
left.vertexId === right.vertexId &&
areVec3Equal(left.initialCenter, right.initialCenter) &&
areVec3Equal(left.initialRotationDegrees, right.initialRotationDegrees) &&
areVec3Equal(left.initialSize, right.initialSize)
);
case "modelInstance":
return (
right.kind === "modelInstance" &&
@@ -324,6 +419,9 @@ export function createTransformSession(options: {
export function createTransformPreviewFromTarget(target: TransformTarget): TransformPreview {
switch (target.kind) {
case "brush":
case "brushFace":
case "brushEdge":
case "brushVertex":
return {
kind: "brush",
center: cloneVec3(target.initialCenter),
@@ -349,6 +447,9 @@ export function createTransformPreviewFromTarget(target: TransformTarget): Trans
export function doesTransformSessionChangeTarget(session: ActiveTransformSession): boolean {
switch (session.target.kind) {
case "brush":
case "brushFace":
case "brushEdge":
case "brushVertex":
return (
session.preview.kind === "brush" &&
(!areVec3Equal(session.preview.center, session.target.initialCenter) ||
@@ -390,6 +491,12 @@ export function getTransformTargetLabel(target: TransformTarget): string {
switch (target.kind) {
case "brush":
return "Whitebox Box";
case "brushFace":
return `Whitebox Face (${BOX_FACE_LABELS[target.faceId]})`;
case "brushEdge":
return `Whitebox Edge (${BOX_EDGE_LABELS[target.edgeId]})`;
case "brushVertex":
return `Whitebox Vertex (${BOX_VERTEX_LABELS[target.vertexId]})`;
case "modelInstance":
return getModelInstanceKindLabel();
case "entity":
@@ -400,7 +507,11 @@ export function getTransformTargetLabel(target: TransformTarget): string {
export function getSupportedTransformOperations(target: TransformTarget): TransformOperation[] {
switch (target.kind) {
case "brush":
case "brushFace":
case "brushEdge":
return ["translate", "rotate", "scale"];
case "brushVertex":
return ["translate"];
case "modelInstance":
return ["translate", "rotate", "scale"];
case "entity":
@@ -417,12 +528,54 @@ export function supportsTransformAxisConstraint(session: ActiveTransformSession,
case "translate":
return true;
case "scale":
return session.target.kind === "modelInstance" || session.target.kind === "brush";
if (session.target.kind === "modelInstance" || session.target.kind === "brush" || session.target.kind === "brushVertex") {
return session.target.kind !== "brushVertex";
}
if (session.target.kind === "brushFace") {
const normalAxis = session.target.faceId === "posX" || session.target.faceId === "negX" ? "x" : session.target.faceId === "posY" || session.target.faceId === "negY" ? "y" : "z";
return axis === normalAxis;
}
if (session.target.kind === "brushEdge") {
if (session.target.edgeId.startsWith("edgeX_")) {
return axis !== "x";
}
if (session.target.edgeId.startsWith("edgeY_")) {
return axis !== "y";
}
return axis !== "z";
}
return false;
case "rotate":
if (session.target.kind === "entity" && session.target.initialRotation.kind === "yaw") {
return axis === "y";
}
if (session.target.kind === "brushFace") {
const normalAxis = session.target.faceId === "posX" || session.target.faceId === "negX" ? "x" : session.target.faceId === "posY" || session.target.faceId === "negY" ? "y" : "z";
return axis === normalAxis;
}
if (session.target.kind === "brushEdge") {
if (session.target.edgeId.startsWith("edgeX_")) {
return axis === "x";
}
if (session.target.edgeId.startsWith("edgeY_")) {
return axis === "y";
}
return axis === "z";
}
if (session.target.kind === "brushVertex") {
return false;
}
return true;
}
}
@@ -472,6 +625,66 @@ function createBrushTransformTarget(document: SceneDocument, brushId: string): T
};
}
function createBrushFaceTransformTarget(document: SceneDocument, brushId: string, faceId: BoxFaceId): TransformTargetResolution {
const brushResolution = createBrushTransformTarget(document, brushId);
if (brushResolution.target === null || brushResolution.target.kind !== "brush") {
return brushResolution;
}
return {
target: {
kind: "brushFace",
brushId,
faceId,
initialCenter: cloneVec3(brushResolution.target.initialCenter),
initialRotationDegrees: cloneVec3(brushResolution.target.initialRotationDegrees),
initialSize: cloneVec3(brushResolution.target.initialSize)
},
message: null
};
}
function createBrushEdgeTransformTarget(document: SceneDocument, brushId: string, edgeId: BoxEdgeId): TransformTargetResolution {
const brushResolution = createBrushTransformTarget(document, brushId);
if (brushResolution.target === null || brushResolution.target.kind !== "brush") {
return brushResolution;
}
return {
target: {
kind: "brushEdge",
brushId,
edgeId,
initialCenter: cloneVec3(brushResolution.target.initialCenter),
initialRotationDegrees: cloneVec3(brushResolution.target.initialRotationDegrees),
initialSize: cloneVec3(brushResolution.target.initialSize)
},
message: null
};
}
function createBrushVertexTransformTarget(document: SceneDocument, brushId: string, vertexId: BoxVertexId): TransformTargetResolution {
const brushResolution = createBrushTransformTarget(document, brushId);
if (brushResolution.target === null || brushResolution.target.kind !== "brush") {
return brushResolution;
}
return {
target: {
kind: "brushVertex",
brushId,
vertexId,
initialCenter: cloneVec3(brushResolution.target.initialCenter),
initialRotationDegrees: cloneVec3(brushResolution.target.initialRotationDegrees),
initialSize: cloneVec3(brushResolution.target.initialSize)
},
message: null
};
}
function createEntityTransformTarget(document: SceneDocument, entityId: string): TransformTargetResolution {
const entity = document.entities[entityId];
@@ -533,20 +746,32 @@ export function resolveTransformTarget(
message: "Select a single brush, entity, or model instance before transforming it."
};
case "brushFace":
return {
target: null,
message: "Face selection is component-only in this slice. Switch to Object mode to transform the whole whitebox box."
};
if (whiteboxSelectionMode !== "face") {
return {
target: null,
message: "Switch to Face mode to transform a selected whitebox face."
};
}
return createBrushFaceTransformTarget(document, selection.brushId, selection.faceId);
case "brushEdge":
return {
target: null,
message: "Edge transforms land in the next whitebox slice. Switch to Object mode to transform the whole whitebox box."
};
if (whiteboxSelectionMode !== "edge") {
return {
target: null,
message: "Switch to Edge mode to transform a selected whitebox edge."
};
}
return createBrushEdgeTransformTarget(document, selection.brushId, selection.edgeId);
case "brushVertex":
return {
target: null,
message: "Vertex transforms land in the next whitebox slice. Switch to Object mode to transform the whole whitebox box."
};
if (whiteboxSelectionMode !== "vertex") {
return {
target: null,
message: "Switch to Vertex mode to transform a selected whitebox vertex."
};
}
return createBrushVertexTransformTarget(document, selection.brushId, selection.vertexId);
case "brushes":
if (whiteboxSelectionMode !== "object") {
return {