/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /** * Live SVG preview for the Split tool. Mounted by `ToolOverlays` * while `activeTool === 'split'`. Branches by element type: * * Wall / beam / column / member (single-click): * Perpendicular guide line through the projected cut point, * "distance / length" readout. State driven by * `splitHoverPoint` / `splitHoverAxisDirection` / * `splitHoverDistance` / `splitHoverLength`. * * Slab / roof / plate / space (two-click): * Outline the polygon footprint with a faint purple stroke. * Before the first click: hint chip says "click to start cut". * After the first click: ghost line from anchor → cursor, * drawn straight through the polygon (the actual extent is * clamped by polygon-clip at commit). * * Same camera-tracking RAF trick the GizmoOverlay uses, so the * preview tracks the element through orbit / zoom without * re-rendering on every camera frame. */ import { useViewerStore } from '@/store'; import { useCameraTickSubscription } from '@/hooks/useCameraTickSubscription'; import { Slice as KnifeIcon } from 'lucide-react'; type Vec2 = { x: number; y: number }; type Vec3 = { x: number; y: number; z: number }; type Project = (worldPos: Vec3) => Vec2 | null; const GUIDE_COLOR = '#a855f7'; // purple-500 — matches edit-mode pill const GUIDE_HALF_LENGTH_PX = 30; /** Storey-local 2D → renderer Y-up world point at the storey floor. */ function ifc2dToRendererWorld(p: [number, number], storeyElevation: number): Vec3 { return { x: p[0], y: storeyElevation, z: -p[1] }; } export function SplitOverlay() { const activeTool = useViewerStore((s) => s.activeTool); const splitMode = useViewerStore((s) => s.splitMode); const splitHoverPoint = useViewerStore((s) => s.splitHoverPoint); const splitHoverDistance = useViewerStore((s) => s.splitHoverDistance); const splitHoverLength = useViewerStore((s) => s.splitHoverLength); const splitHoverCutPoint = useViewerStore((s) => s.splitHoverCutPoint); const splitHoverAxisDirection = useViewerStore((s) => s.splitHoverAxisDirection); const splitTargetModelId = useViewerStore((s) => s.splitTargetModelId); const splitTargetExpressId = useViewerStore((s) => s.splitTargetExpressId); const slabCutAnchor = useViewerStore((s) => s.slabCutAnchor); const slabCutFootprint = useViewerStore((s) => s.slabCutFootprint); const slabCutStoreyElevation = useViewerStore((s) => s.slabCutStoreyElevation); const readSlabFootprint = useViewerStore((s) => s.readSlabFootprint); const projectToScreen = useViewerStore((s) => s.cameraCallbacks.projectToScreen); const getViewpoint = useViewerStore((s) => s.cameraCallbacks.getViewpoint); // Camera-tick subscription — wakes the overlay when the camera // moves so the preview tracks the element through orbit / zoom. // Skipped when nothing is hovered (idle Split tool with no cursor // over an element is free of per-frame work). const active = activeTool === 'split' && (splitMode === 'aiming' || splitMode === 'first-anchor') && splitHoverPoint !== null; void useCameraTickSubscription(getViewpoint, active); // Hint chip when idle (tool armed, nothing under cursor). if (activeTool === 'split' && !active) { return (