auto-git:
[add] src/commands/create-custom-material-command.ts
This commit is contained in:
205
src/commands/create-custom-material-command.ts
Normal file
205
src/commands/create-custom-material-command.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
import type { EditorSelection } from "../core/selection";
|
||||
import type { ToolMode } from "../core/tool-mode";
|
||||
import { updateBrush, type WhiteboxFaceId } from "../document/brushes";
|
||||
import { getBrushFaceIds } from "../geometry/whitebox-topology";
|
||||
import {
|
||||
cloneMaterialDef,
|
||||
type CustomMaterialDef
|
||||
} from "../materials/starter-material-library";
|
||||
|
||||
import {
|
||||
cloneSelectionForCommand,
|
||||
getBoxBrushOrThrow,
|
||||
replaceBrush,
|
||||
replaceBoxBrushFace,
|
||||
setSingleBrushFaceSelection,
|
||||
setSingleBrushSelection
|
||||
} from "./brush-command-helpers";
|
||||
import type { EditorCommand } from "./command";
|
||||
|
||||
type CreateCustomMaterialApplyTarget =
|
||||
| { kind: "brush"; brushId: string }
|
||||
| { kind: "face"; brushId: string; faceId: WhiteboxFaceId };
|
||||
|
||||
interface CreateCustomMaterialCommandOptions {
|
||||
material: CustomMaterialDef;
|
||||
applyTo?: CreateCustomMaterialApplyTarget;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export function createCreateCustomMaterialCommand(
|
||||
options: CreateCustomMaterialCommandOptions
|
||||
): EditorCommand {
|
||||
const nextMaterial = cloneMaterialDef(options.material) as CustomMaterialDef;
|
||||
let previousSelection: EditorSelection | null = null;
|
||||
let previousToolMode: ToolMode | null = null;
|
||||
let previousMaterialIds: Record<WhiteboxFaceId, string | null> | null = null;
|
||||
|
||||
return {
|
||||
id: createOpaqueId("command"),
|
||||
label: options.label ?? "Create custom material",
|
||||
execute(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
|
||||
if (nextMaterial.kind !== "custom") {
|
||||
throw new Error("Only custom materials can be created by this command.");
|
||||
}
|
||||
|
||||
if (currentDocument.materials[nextMaterial.id] !== undefined) {
|
||||
throw new Error(`Material ${nextMaterial.id} already exists.`);
|
||||
}
|
||||
|
||||
if (previousSelection === null) {
|
||||
previousSelection = cloneSelectionForCommand(context.getSelection());
|
||||
}
|
||||
|
||||
if (previousToolMode === null) {
|
||||
previousToolMode = context.getToolMode();
|
||||
}
|
||||
|
||||
let nextDocument = {
|
||||
...currentDocument,
|
||||
materials: {
|
||||
...currentDocument.materials,
|
||||
[nextMaterial.id]: cloneMaterialDef(nextMaterial)
|
||||
}
|
||||
};
|
||||
|
||||
if (options.applyTo?.kind === "brush") {
|
||||
const currentBrush = getBoxBrushOrThrow(
|
||||
currentDocument,
|
||||
options.applyTo.brushId
|
||||
);
|
||||
|
||||
if (previousMaterialIds === null) {
|
||||
previousMaterialIds = Object.fromEntries(
|
||||
getBrushFaceIds(currentBrush).map((faceId) => [
|
||||
faceId,
|
||||
currentBrush.faces[faceId].materialId
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
nextDocument = replaceBrush(
|
||||
nextDocument,
|
||||
updateBrush(currentBrush, {
|
||||
faces: Object.fromEntries(
|
||||
getBrushFaceIds(currentBrush).map((faceId) => [
|
||||
faceId,
|
||||
{
|
||||
...currentBrush.faces[faceId],
|
||||
materialId: nextMaterial.id
|
||||
}
|
||||
])
|
||||
) as typeof currentBrush.faces
|
||||
})
|
||||
);
|
||||
context.setSelection(setSingleBrushSelection(options.applyTo.brushId));
|
||||
} else if (options.applyTo?.kind === "face") {
|
||||
const currentBrush = getBoxBrushOrThrow(
|
||||
currentDocument,
|
||||
options.applyTo.brushId
|
||||
);
|
||||
const currentFace = currentBrush.faces[options.applyTo.faceId];
|
||||
|
||||
if (currentFace === undefined) {
|
||||
throw new Error(
|
||||
`Whitebox solid ${options.applyTo.brushId} does not contain face ${options.applyTo.faceId}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (previousMaterialIds === null) {
|
||||
previousMaterialIds = {
|
||||
[options.applyTo.faceId]: currentFace.materialId
|
||||
} as Record<WhiteboxFaceId, string | null>;
|
||||
}
|
||||
|
||||
nextDocument = replaceBoxBrushFace(
|
||||
nextDocument,
|
||||
options.applyTo.brushId,
|
||||
options.applyTo.faceId,
|
||||
{
|
||||
...currentFace,
|
||||
materialId: nextMaterial.id
|
||||
}
|
||||
);
|
||||
context.setSelection(
|
||||
setSingleBrushFaceSelection(
|
||||
options.applyTo.brushId,
|
||||
options.applyTo.faceId
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
context.setDocument(nextDocument);
|
||||
context.setToolMode("select");
|
||||
},
|
||||
undo(context) {
|
||||
const currentDocument = context.getDocument();
|
||||
const nextMaterials = {
|
||||
...currentDocument.materials
|
||||
};
|
||||
|
||||
delete nextMaterials[nextMaterial.id];
|
||||
|
||||
let nextDocument = {
|
||||
...currentDocument,
|
||||
materials: nextMaterials
|
||||
};
|
||||
|
||||
if (options.applyTo?.kind === "brush" && previousMaterialIds !== null) {
|
||||
const currentBrush = getBoxBrushOrThrow(
|
||||
currentDocument,
|
||||
options.applyTo.brushId
|
||||
);
|
||||
|
||||
nextDocument = replaceBrush(
|
||||
nextDocument,
|
||||
updateBrush(currentBrush, {
|
||||
faces: Object.fromEntries(
|
||||
getBrushFaceIds(currentBrush).map((faceId) => [
|
||||
faceId,
|
||||
{
|
||||
...currentBrush.faces[faceId],
|
||||
materialId: previousMaterialIds?.[faceId] ?? null
|
||||
}
|
||||
])
|
||||
) as typeof currentBrush.faces
|
||||
})
|
||||
);
|
||||
} else if (
|
||||
options.applyTo?.kind === "face" &&
|
||||
previousMaterialIds !== null
|
||||
) {
|
||||
const currentBrush = getBoxBrushOrThrow(
|
||||
currentDocument,
|
||||
options.applyTo.brushId
|
||||
);
|
||||
const currentFace = currentBrush.faces[options.applyTo.faceId];
|
||||
|
||||
if (currentFace !== undefined) {
|
||||
nextDocument = replaceBoxBrushFace(
|
||||
nextDocument,
|
||||
options.applyTo.brushId,
|
||||
options.applyTo.faceId,
|
||||
{
|
||||
...currentFace,
|
||||
materialId: previousMaterialIds[options.applyTo.faceId] ?? null
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
context.setDocument(nextDocument);
|
||||
|
||||
if (previousSelection !== null) {
|
||||
context.setSelection(previousSelection);
|
||||
}
|
||||
|
||||
if (previousToolMode !== null) {
|
||||
context.setToolMode(previousToolMode);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user