Add geometry support to BoxBrush and related functions

This commit is contained in:
2026-04-05 02:21:22 +02:00
parent c25aa3f32b
commit c02acc32af

View File

@@ -83,6 +83,12 @@ export interface BrushFace {
export type BoxBrushFaces = Record<BoxFaceId, BrushFace>;
export type BoxBrushGeometryVertices = Record<BoxVertexId, Vec3>;
export interface BoxBrushGeometry {
vertices: BoxBrushGeometryVertices;
}
export interface BoxBrush {
id: string;
kind: "box";
@@ -90,6 +96,7 @@ export interface BoxBrush {
center: Vec3;
rotationDegrees: Vec3;
size: Vec3;
geometry: BoxBrushGeometry;
faces: BoxBrushFaces;
layerId?: string;
groupId?: string;
@@ -139,6 +146,81 @@ function cloneBrushFace(face: BrushFace): BrushFace {
};
}
function cloneBoxBrushGeometryVertex(vertex: Vec3): Vec3 {
return {
x: vertex.x,
y: vertex.y,
z: vertex.z
};
}
export function cloneBoxBrushGeometry(geometry: BoxBrushGeometry): BoxBrushGeometry {
return {
vertices: {
negX_negY_negZ: cloneBoxBrushGeometryVertex(geometry.vertices.negX_negY_negZ),
posX_negY_negZ: cloneBoxBrushGeometryVertex(geometry.vertices.posX_negY_negZ),
negX_posY_negZ: cloneBoxBrushGeometryVertex(geometry.vertices.negX_posY_negZ),
posX_posY_negZ: cloneBoxBrushGeometryVertex(geometry.vertices.posX_posY_negZ),
negX_negY_posZ: cloneBoxBrushGeometryVertex(geometry.vertices.negX_negY_posZ),
posX_negY_posZ: cloneBoxBrushGeometryVertex(geometry.vertices.posX_negY_posZ),
negX_posY_posZ: cloneBoxBrushGeometryVertex(geometry.vertices.negX_posY_posZ),
posX_posY_posZ: cloneBoxBrushGeometryVertex(geometry.vertices.posX_posY_posZ)
}
};
}
export function getBoxBrushGeometryLocalBounds(geometry: BoxBrushGeometry): { min: Vec3; max: Vec3 } {
const vertices = Object.values(geometry.vertices);
const firstVertex = vertices[0];
const min = { ...firstVertex };
const max = { ...firstVertex };
for (const vertex of vertices.slice(1)) {
min.x = Math.min(min.x, vertex.x);
min.y = Math.min(min.y, vertex.y);
min.z = Math.min(min.z, vertex.z);
max.x = Math.max(max.x, vertex.x);
max.y = Math.max(max.y, vertex.y);
max.z = Math.max(max.z, vertex.z);
}
return {
min,
max
};
}
export function deriveBoxBrushSizeFromGeometry(geometry: BoxBrushGeometry): Vec3 {
const bounds = getBoxBrushGeometryLocalBounds(geometry);
return {
x: bounds.max.x - bounds.min.x,
y: bounds.max.y - bounds.min.y,
z: bounds.max.z - bounds.min.z
};
}
export function createDefaultBoxBrushGeometry(size: Vec3 = DEFAULT_BOX_BRUSH_SIZE): BoxBrushGeometry {
const halfSize = {
x: size.x * 0.5,
y: size.y * 0.5,
z: size.z * 0.5
};
return {
vertices: {
negX_negY_negZ: { x: -halfSize.x, y: -halfSize.y, z: -halfSize.z },
posX_negY_negZ: { x: halfSize.x, y: -halfSize.y, z: -halfSize.z },
negX_posY_negZ: { x: -halfSize.x, y: halfSize.y, z: -halfSize.z },
posX_posY_negZ: { x: halfSize.x, y: halfSize.y, z: -halfSize.z },
negX_negY_posZ: { x: -halfSize.x, y: -halfSize.y, z: halfSize.z },
posX_negY_posZ: { x: halfSize.x, y: -halfSize.y, z: halfSize.z },
negX_posY_posZ: { x: -halfSize.x, y: halfSize.y, z: halfSize.z },
posX_posY_posZ: { x: halfSize.x, y: halfSize.y, z: halfSize.z }
}
};
}
export function isBoxFaceId(value: unknown): value is BoxFaceId {
return typeof value === "string" && BOX_FACE_IDS.some((faceId) => faceId === value);
}
@@ -230,11 +312,13 @@ export function createDefaultBoxBrushFaces(): BoxBrushFaces {
}
export function createBoxBrush(
overrides: Partial<Pick<BoxBrush, "id" | "name" | "center" | "rotationDegrees" | "size" | "faces" | "layerId" | "groupId">> = {}
overrides: Partial<Pick<BoxBrush, "id" | "name" | "center" | "rotationDegrees" | "size" | "geometry" | "faces" | "layerId" | "groupId">> = {}
): BoxBrush {
const center = cloneVec3(overrides.center ?? DEFAULT_BOX_BRUSH_CENTER);
const rotationDegrees = cloneVec3(overrides.rotationDegrees ?? DEFAULT_BOX_BRUSH_ROTATION_DEGREES);
const size = cloneVec3(overrides.size ?? DEFAULT_BOX_BRUSH_SIZE);
const fallbackSize = cloneVec3(overrides.size ?? DEFAULT_BOX_BRUSH_SIZE);
const geometry = overrides.geometry === undefined ? createDefaultBoxBrushGeometry(fallbackSize) : cloneBoxBrushGeometry(overrides.geometry);
const size = deriveBoxBrushSizeFromGeometry(geometry);
if (!hasPositiveBoxSize(size)) {
throw new Error("Box brush size must remain positive on every axis.");
@@ -247,6 +331,7 @@ export function createBoxBrush(
center,
rotationDegrees,
size,
geometry,
faces: overrides.faces === undefined ? createDefaultBoxBrushFaces() : cloneBoxBrushFaces(overrides.faces),
layerId: overrides.layerId,
groupId: overrides.groupId