diff --git a/src/app/editor-store.ts b/src/app/editor-store.ts index 7a2799d1..4ea667f3 100644 --- a/src/app/editor-store.ts +++ b/src/app/editor-store.ts @@ -1,7 +1,12 @@ import { CommandHistory } from "../commands/command-history"; import type { CommandContext, EditorCommand } from "../commands/command"; -import { areEditorSelectionsEqual, type EditorSelection } from "../core/selection"; +import { + areEditorSelectionsEqual, + normalizeSelectionForWhiteboxSelectionMode, + type EditorSelection +} from "../core/selection"; import type { ToolMode } from "../core/tool-mode"; +import { type WhiteboxSelectionMode } from "../core/whitebox-selection-mode"; import { areTransformSessionsEqual, cloneTransformSession, @@ -45,6 +50,7 @@ import { export interface EditorStoreState { document: SceneDocument; selection: EditorSelection; + whiteboxSelectionMode: WhiteboxSelectionMode; toolMode: ToolMode; viewportLayoutMode: ViewportLayoutMode; activeViewportPanelId: ViewportPanelId; @@ -72,6 +78,7 @@ export type EditorDraftLoadResult = LoadSceneDocumentDraftResult; export class EditorStore { private document: SceneDocument; private selection: EditorSelection = { kind: "none" }; + private whiteboxSelectionMode: WhiteboxSelectionMode = "object"; private toolMode: ToolMode = "select"; private viewportLayoutMode: ViewportLayoutMode; private activeViewportPanelId: ViewportPanelId; @@ -344,6 +351,23 @@ export class EditorStore { this.emit(); } + setWhiteboxSelectionMode(mode: WhiteboxSelectionMode) { + if (this.whiteboxSelectionMode === mode) { + return; + } + + if (this.viewportTransientState.transformSession.kind !== "none") { + this.viewportTransientState = { + ...this.viewportTransientState, + transformSession: createInactiveTransformSession() + }; + } + + this.whiteboxSelectionMode = mode; + this.selection = normalizeSelectionForWhiteboxSelectionMode(this.selection, mode); + this.emit(); + } + executeCommand(command: EditorCommand) { if (this.viewportTransientState.transformSession.kind !== "none") { this.viewportTransientState = { @@ -412,6 +436,7 @@ export class EditorStore { replaceDocument(document: SceneDocument, resetHistory = true) { this.document = document; this.selection = { kind: "none" }; + this.whiteboxSelectionMode = "object"; this.toolMode = "select"; this.previousEditingToolMode = "select"; this.viewportTransientState = createDefaultViewportTransientState(); @@ -499,6 +524,7 @@ export class EditorStore { return { document: this.document, selection: this.selection, + whiteboxSelectionMode: this.whiteboxSelectionMode, toolMode: this.toolMode, viewportLayoutMode: this.viewportLayoutMode, activeViewportPanelId: this.activeViewportPanelId, diff --git a/src/core/selection.ts b/src/core/selection.ts index 524ddd97..aba52ede 100644 --- a/src/core/selection.ts +++ b/src/core/selection.ts @@ -1,9 +1,12 @@ -import type { BoxFaceId } from "../document/brushes"; +import type { WhiteboxSelectionMode } from "./whitebox-selection-mode"; +import type { BoxEdgeId, BoxFaceId, BoxVertexId } from "../document/brushes"; export type EditorSelection = | { kind: "none" } | { kind: "brushes"; ids: string[] } | { kind: "brushFace"; brushId: string; faceId: BoxFaceId } + | { kind: "brushEdge"; brushId: string; edgeId: BoxEdgeId } + | { kind: "brushVertex"; brushId: string; vertexId: BoxVertexId } | { kind: "entities"; ids: string[] } | { kind: "modelInstances"; ids: string[] }; @@ -22,6 +25,22 @@ export function cloneEditorSelection(selection: EditorSelection): EditorSelectio }; } + if (selection.kind === "brushEdge") { + return { + kind: "brushEdge", + brushId: selection.brushId, + edgeId: selection.edgeId + }; + } + + if (selection.kind === "brushVertex") { + return { + kind: "brushVertex", + brushId: selection.brushId, + vertexId: selection.vertexId + }; + } + return { kind: selection.kind, ids: [...selection.ids] @@ -38,6 +57,10 @@ export function areEditorSelectionsEqual(left: EditorSelection, right: EditorSel return true; case "brushFace": return right.kind === "brushFace" && left.brushId === right.brushId && left.faceId === right.faceId; + case "brushEdge": + return right.kind === "brushEdge" && left.brushId === right.brushId && left.edgeId === right.edgeId; + case "brushVertex": + return right.kind === "brushVertex" && left.brushId === right.brushId && left.vertexId === right.vertexId; case "brushes": case "entities": case "modelInstances": @@ -46,7 +69,7 @@ export function areEditorSelectionsEqual(left: EditorSelection, right: EditorSel } export function getSingleSelectedBrushId(selection: EditorSelection): string | null { - if (selection.kind === "brushFace") { + if (selection.kind === "brushFace" || selection.kind === "brushEdge" || selection.kind === "brushVertex") { return selection.brushId; } @@ -65,6 +88,22 @@ export function getSelectedBrushFaceId(selection: EditorSelection): BoxFaceId | return selection.faceId; } +export function getSelectedBrushEdgeId(selection: EditorSelection): BoxEdgeId | null { + if (selection.kind !== "brushEdge") { + return null; + } + + return selection.edgeId; +} + +export function getSelectedBrushVertexId(selection: EditorSelection): BoxVertexId | null { + if (selection.kind !== "brushVertex") { + return null; + } + + return selection.vertexId; +} + export function getSingleSelectedEntityId(selection: EditorSelection): string | null { if (selection.kind !== "entities" || selection.ids.length !== 1) { return null; @@ -84,7 +123,8 @@ export function getSingleSelectedModelInstanceId(selection: EditorSelection): st export function isBrushSelected(selection: EditorSelection, brushId: string): boolean { return ( (selection.kind === "brushes" && selection.ids.includes(brushId)) || - (selection.kind === "brushFace" && selection.brushId === brushId) + ((selection.kind === "brushFace" || selection.kind === "brushEdge" || selection.kind === "brushVertex") && + selection.brushId === brushId) ); } @@ -92,6 +132,42 @@ export function isBrushFaceSelected(selection: EditorSelection, brushId: string, return selection.kind === "brushFace" && selection.brushId === brushId && selection.faceId === faceId; } +export function isBrushEdgeSelected(selection: EditorSelection, brushId: string, edgeId: BoxEdgeId): boolean { + return selection.kind === "brushEdge" && selection.brushId === brushId && selection.edgeId === edgeId; +} + +export function isBrushVertexSelected(selection: EditorSelection, brushId: string, vertexId: BoxVertexId): boolean { + return selection.kind === "brushVertex" && selection.brushId === brushId && selection.vertexId === vertexId; +} + export function isModelInstanceSelected(selection: EditorSelection, modelInstanceId: string): boolean { return selection.kind === "modelInstances" && selection.ids.includes(modelInstanceId); } + +export function normalizeSelectionForWhiteboxSelectionMode(selection: EditorSelection, mode: WhiteboxSelectionMode): EditorSelection { + switch (selection.kind) { + case "brushFace": + return mode === "face" + ? selection + : { + kind: "brushes", + ids: [selection.brushId] + }; + case "brushEdge": + return mode === "edge" + ? selection + : { + kind: "brushes", + ids: [selection.brushId] + }; + case "brushVertex": + return mode === "vertex" + ? selection + : { + kind: "brushes", + ids: [selection.brushId] + }; + default: + return selection; + } +}