diff --git a/src/viewport-three/viewport-transient-state.ts b/src/viewport-three/viewport-transient-state.ts index 6f5cabde..5f89a76e 100644 --- a/src/viewport-three/viewport-transient-state.ts +++ b/src/viewport-three/viewport-transient-state.ts @@ -1,4 +1,6 @@ import type { Vec3 } from "../core/vector"; +import type { ToolMode } from "../core/tool-mode"; +import type { EntityKind } from "../entities/entity-instances"; import type { ViewportPanelId } from "./viewport-layout"; export interface BoxCreateViewportToolPreview { @@ -7,7 +9,25 @@ export interface BoxCreateViewportToolPreview { center: Vec3 | null; } -export type ViewportToolPreview = BoxCreateViewportToolPreview | { kind: "none" }; +export type ViewportPlacementPreviewTarget = + | { + kind: "entity"; + entityKind: EntityKind; + audioAssetId: string | null; + } + | { + kind: "model-instance"; + assetId: string; + }; + +export interface PlacementViewportToolPreview { + kind: "placement"; + sourcePanelId: ViewportPanelId; + target: ViewportPlacementPreviewTarget; + center: Vec3 | null; +} + +export type ViewportToolPreview = BoxCreateViewportToolPreview | PlacementViewportToolPreview | { kind: "none" }; export interface ViewportTransientState { toolPreview: ViewportToolPreview; @@ -26,6 +46,25 @@ export function cloneViewportToolPreview(toolPreview: ViewportToolPreview): View return toolPreview; } + if (toolPreview.kind === "placement") { + return { + kind: "placement", + sourcePanelId: toolPreview.sourcePanelId, + target: + toolPreview.target.kind === "entity" + ? { + kind: "entity", + entityKind: toolPreview.target.entityKind, + audioAssetId: toolPreview.target.audioAssetId + } + : { + kind: "model-instance", + assetId: toolPreview.target.assetId + }, + center: toolPreview.center === null ? null : { ...toolPreview.center } + }; + } + return { kind: "box-create", sourcePanelId: toolPreview.sourcePanelId, @@ -42,6 +81,32 @@ export function areViewportToolPreviewsEqual(left: ViewportToolPreview, right: V return true; } + if (left.kind === "placement" && right.kind === "placement") { + if (left.sourcePanelId !== right.sourcePanelId) { + return false; + } + + if (left.target.kind !== right.target.kind) { + return false; + } + + if (left.target.kind === "entity" && right.target.kind === "entity") { + if (left.target.entityKind !== right.target.entityKind || left.target.audioAssetId !== right.target.audioAssetId) { + return false; + } + } + + if (left.target.kind === "model-instance" && right.target.kind === "model-instance" && left.target.assetId !== right.target.assetId) { + return false; + } + + if (left.center === null || right.center === null) { + return left.center === right.center; + } + + return left.center.x === right.center.x && left.center.y === right.center.y && left.center.z === right.center.z; + } + if (left.sourcePanelId !== right.sourcePanelId) { return false; } @@ -52,3 +117,19 @@ export function areViewportToolPreviewsEqual(left: ViewportToolPreview, right: V return left.center.x === right.center.x && left.center.y === right.center.y && left.center.z === right.center.z; } + +export function isViewportToolPreviewCompatible(toolMode: ToolMode, toolPreview: ViewportToolPreview): boolean { + if (toolPreview.kind === "none") { + return true; + } + + if (toolMode === "box-create") { + return toolPreview.kind === "box-create"; + } + + if (toolMode === "place") { + return toolPreview.kind === "placement"; + } + + return false; +}