From b33059ef813fc7f54056964d518d3f5e637b43eb Mon Sep 17 00:00:00 2001 From: Aymeric Rabot Date: Mon, 8 Jun 2026 17:40:52 -0400 Subject: [PATCH] feat: add persistent site boundary handles --- apps/editor/components/build-tab.tsx | 2 +- apps/editor/public/icons/site-flag.png | Bin 0 -> 53644 bytes apps/editor/public/icons/spawn-point.png | Bin 0 -> 87035 bytes .../src/components/editor/floorplan-panel.tsx | 489 ++++++++++++++---- .../components/editor/site-edge-labels.tsx | 31 +- .../tools/shared/polygon-editor.tsx | 329 ++++++++---- .../tools/site/site-boundary-editor.tsx | 474 ++++++++++++++++- .../components/tools/site/site-flag-model.ts | 18 + .../src/components/tools/tool-manager.tsx | 5 +- .../ui/action-menu/control-modes.tsx | 10 +- .../ui/action-menu/structure-tools.tsx | 2 +- .../ui/sidebar/panels/site-panel/index.tsx | 2 +- .../panels/site-panel/spawn-tree-node.tsx | 8 +- packages/editor/src/lib/site-boundary.ts | 1 + packages/editor/src/store/use-editor.tsx | 111 ++-- packages/nodes/src/site/definition.ts | 2 +- packages/nodes/src/site/renderer.tsx | 17 +- .../nodes/src/spawn/__tests__/parity.test.ts | 11 +- packages/nodes/src/spawn/definition.ts | 2 +- packages/nodes/src/spawn/floorplan.ts | 113 +++- packages/nodes/src/spawn/panel.tsx | 7 +- 21 files changed, 1346 insertions(+), 288 deletions(-) create mode 100644 apps/editor/public/icons/site-flag.png create mode 100644 apps/editor/public/icons/spawn-point.png create mode 100644 packages/editor/src/components/tools/site/site-flag-model.ts create mode 100644 packages/editor/src/lib/site-boundary.ts diff --git a/apps/editor/components/build-tab.tsx b/apps/editor/components/build-tab.tsx index 32af88e0..8d25a5eb 100644 --- a/apps/editor/components/build-tab.tsx +++ b/apps/editor/components/build-tab.tsx @@ -53,7 +53,7 @@ const BUILD_TYPES: BuildType[] = [ { id: 'window', label: 'Window', iconSrc: '/icons/window.png', kind: 'window' }, { id: 'column', label: 'Column', iconSrc: '/icons/column.png', kind: 'column' }, { id: 'shelf', label: 'Shelf', iconSrc: '/icons/shelf.png', kind: 'shelf' }, - { id: 'spawn', label: 'Spawn Point', iconSrc: '/icons/site.png', kind: 'spawn' }, + { id: 'spawn', label: 'Spawn Point', iconSrc: '/icons/spawn-point.png', kind: 'spawn' }, { id: 'painting', label: 'Painting', iconSrc: '/icons/paint.png', mode: 'material-paint' }, ] diff --git a/apps/editor/public/icons/site-flag.png b/apps/editor/public/icons/site-flag.png new file mode 100644 index 0000000000000000000000000000000000000000..593e71924e53a0230d7aa54faa1769fadf9f9c3f GIT binary patch literal 53644 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 z

    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
    nOv#bBQxDGhZWYkSNs)Jh~ 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: boolean return false } - return pointsEqual(point, otherPoint) + return Math.abs(point[0] - otherPoint[0]) < 1e-6 && Math.abs(point[1] - otherPoint[1]) < 1e-6 }) ) } @@ -3710,29 +3710,175 @@ const FloorplanReferenceFloorLayer = memo(function FloorplanReferenceFloorLayer( }) const FloorplanSiteLayer = memo(function FloorplanSiteLayer({ - isEditing, + isHighlighted, + palette, sitePolygon, }: { - isEditing: boolean + isHighlighted: boolean + palette: FloorplanPalette sitePolygon: SitePolygonEntry | null }) { if (!sitePolygon) { return null } + const stroke = isHighlighted ? palette.cursor : palette.measurementStroke + const dashLength = isHighlighted ? 8 : 7 + const gapLength = isHighlighted ? 5 : 6 + const strokeWidth = isHighlighted ? 2.2 : 1.5 + const contrastStrokeWidth = isHighlighted ? 4.2 : 3.4 + const dashPattern = `${dashLength} ${gapLength}` + return ( - + <> + + + + ) +}) + +const FloorplanSiteEdgeLabelLayer = memo(function FloorplanSiteEdgeLabelLayer({ + labelBackground, + labelText, + palette, + sceneRotationDeg, + shouldShow, + sitePolygon, + unit, + unitsPerPixel, +}: { + labelBackground: string + labelText: string + palette: FloorplanPalette + sceneRotationDeg: number + shouldShow: boolean + sitePolygon: SitePolygonEntry | null + unit: 'metric' | 'imperial' + unitsPerPixel: number +}) { + if (!(shouldShow && sitePolygon && sitePolygon.polygon.length >= 2)) { + return null + } + + const upx = unitsPerPixel + const fontSize = Math.max(upx * 10, 0.08) + const padX = upx * 6 + const padY = upx * 3 + const minWidth = upx * 38 + const labelOffset = upx * 18 + const centroid = sitePolygon.polygon.reduce( + (acc, point) => ({ + x: acc.x + point.x / sitePolygon.polygon.length, + y: acc.y + point.y / sitePolygon.polygon.length, + }), + { x: 0, y: 0 }, + ) + const edges = sitePolygon.polygon.flatMap((start, edgeIndex, polygon) => { + const end = polygon[(edgeIndex + 1) % polygon.length] + if (!end) { + return [] + } + + const dx = end.x - start.x + const dy = end.y - start.y + const length = Math.hypot(dx, dy) + if (length < 1e-6) { + return [] + } + + const midX = (start.x + end.x) / 2 + const midY = (start.y + end.y) / 2 + let normalX = -dy / length + let normalY = dx / length + if (normalX * (midX - centroid.x) + normalY * (midY - centroid.y) < 0) { + normalX = -normalX + normalY = -normalY + } + + let labelAngleDeg = (Math.atan2(dy, dx) * 180) / Math.PI + let screenDeg = labelAngleDeg + sceneRotationDeg + screenDeg = ((((screenDeg + 180) % 360) + 360) % 360) - 180 + if (screenDeg > 90) { + labelAngleDeg -= 180 + } else if (screenDeg <= -90) { + labelAngleDeg += 180 + } + + const label = formatMeasurement(length, unit) + const width = Math.max(label.length * upx * 6.4 + padX * 2, minWidth) + const height = fontSize + padY * 2 + + return [ + { + edgeIndex, + height, + label, + labelAngleDeg, + labelX: midX + normalX * labelOffset, + labelY: midY + normalY * labelOffset, + width, + }, + ] + }) + + return ( + + {edges.map(({ edgeIndex, height, label, labelAngleDeg, labelX, labelY, width }) => ( + + + + {label} + + + ))} + ) }) @@ -4035,7 +4181,7 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({ onVertexDoubleClick: ( nodeId: string, vertexIndex: number, - event: ReactPointerEvent, + event: ReactPointerEvent | ReactMouseEvent, ) => void onMidpointPointerDown: ( nodeId: string, @@ -4050,6 +4196,8 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({ palette: FloorplanPalette unitsPerPixel: number }) { + const vertexPointerDoubleClickRef = useRef(null) + return ( <> {edgeHandles.map(({ nodeId, edgeIndex, start, end, isActive }) => { @@ -4057,7 +4205,7 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({ const isHovered = hoveredHandleId === handleId const startSvg = toSvgPlanPoint(start) const endSvg = toSvgPlanPoint(end) - const visibleStroke = isActive ? palette.endpointHandleActiveStroke : palette.selectedStroke + const visibleStroke = isActive || isHovered ? palette.cursor : palette.measurementStroke return ( { const handleId = `${nodeId}:vertex:${vertexIndex}` const isHovered = hoveredHandleId === handleId - const stroke = isActive ? palette.endpointHandleActiveStroke : palette.endpointHandleStroke + const hoveredEdgePrefix = `${nodeId}:edge:` + const hoveredMidpointPrefix = `${nodeId}:midpoint:` + const hoveredEdgeIndex = hoveredHandleId?.startsWith(hoveredEdgePrefix) + ? Number(hoveredHandleId.slice(hoveredEdgePrefix.length)) + : hoveredHandleId?.startsWith(hoveredMidpointPrefix) + ? Number(hoveredHandleId.slice(hoveredMidpointPrefix.length)) + : null + const isNearHoveredEdge = + Number.isInteger(hoveredEdgeIndex) && + hoveredEdgeIndex !== null && + (vertexIndex === hoveredEdgeIndex || + vertexIndex === (hoveredEdgeIndex + 1) % vertexHandles.length) + const stroke = + isActive || isHovered || isNearHoveredEdge ? palette.cursor : palette.measurementStroke + const handleOpacity = isActive || isHovered || isNearHoveredEdge ? 1 : 0 const outerRadius = (isActive ? FLOORPLAN_POLYGON_VERTEX_ACTIVE_RADIUS_PX @@ -4154,7 +4316,7 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({ strokeOpacity={0.18} strokeWidth={FLOORPLAN_ENDPOINT_HOVER_GLOW_STROKE_WIDTH} style={{ - opacity: isHovered ? 1 : 0, + opacity: handleOpacity, transition: FLOORPLAN_HOVER_TRANSITION, }} vectorEffect="non-scaling-stroke" @@ -4163,19 +4325,23 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({ cx={svgPoint.x} cy={svgPoint.y} fill={isActive ? palette.endpointHandleActiveFill : palette.endpointHandleFill} - fillOpacity={0.96} + fillOpacity={0.96 * handleOpacity} pointerEvents="none" r={outerRadius} stroke={stroke} + strokeOpacity={handleOpacity} strokeWidth="0.045" + style={{ transition: FLOORPLAN_HOVER_TRANSITION }} vectorEffect="non-scaling-stroke" /> { event.preventDefault() event.stopPropagation() - onVertexDoubleClick(nodeId, vertexIndex, event as any) + if (vertexPointerDoubleClickRef.current === handleId) { + vertexPointerDoubleClickRef.current = null + return + } + + onVertexDoubleClick(nodeId, vertexIndex, event) }} onPointerDown={(event) => { + if (event.button === 0 && event.detail >= 2) { + vertexPointerDoubleClickRef.current = handleId + window.setTimeout(() => { + if (vertexPointerDoubleClickRef.current === handleId) { + vertexPointerDoubleClickRef.current = null + } + }, 400) + onVertexDoubleClick(nodeId, vertexIndex, event) + return + } + onVertexPointerDown(nodeId, vertexIndex, event) }} pointerEvents="all" @@ -4204,12 +4386,14 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({ {midpointHandles.map(({ nodeId, edgeIndex, point }) => { const handleId = `${nodeId}:midpoint:${edgeIndex}` const isHovered = hoveredHandleId === handleId + const isEdgeHovered = hoveredHandleId === `${nodeId}:edge:${edgeIndex}` + const isVisible = isHovered || isEdgeHovered const isAddHandle = midpointStyle === 'add' const stroke = isAddHandle ? '#111827' - : isHovered - ? palette.endpointHandleHoverStroke - : palette.endpointHandleStroke + : isVisible + ? palette.cursor + : palette.measurementStroke const radius = (isAddHandle ? isHovered @@ -4241,7 +4425,7 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({ strokeOpacity={0.16} strokeWidth={FLOORPLAN_ENDPOINT_HOVER_RING_STROKE_WIDTH} style={{ - opacity: isHovered ? 1 : 0, + opacity: isVisible ? 1 : 0, transition: FLOORPLAN_HOVER_TRANSITION, }} vectorEffect="non-scaling-stroke" @@ -4250,12 +4434,13 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({ cx={svgPoint.x} cy={svgPoint.y} fill={isAddHandle ? '#ffffff' : palette.surface} - fillOpacity={isAddHandle ? 1 : 0.94} + fillOpacity={isVisible ? (isAddHandle ? 1 : 0.94) : 0} pointerEvents="none" r={radius} stroke={stroke} - strokeOpacity={0.9} + strokeOpacity={isVisible ? 0.9 : 0} strokeWidth={isAddHandle ? '1.4' : '0.035'} + style={{ transition: FLOORPLAN_HOVER_TRANSITION }} vectorEffect="non-scaling-stroke" /> {isAddHandle ? ( @@ -4263,8 +4448,10 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({ )} @@ -4374,6 +4564,7 @@ export function FloorplanPanel() { const curvingFence = useEditor((state) => state.curvingFence) const phase = useEditor((state) => state.phase) const mode = useEditor((state) => state.mode) + const activeHandleDrag = useEditor((state) => state.activeHandleDrag) const setPhase = useEditor((state) => state.setPhase) const setMovingFenceEndpoint = useEditor((state) => state.setMovingFenceEndpoint) const setMovingNode = useEditor((state) => state.setMovingNode) @@ -4553,6 +4744,18 @@ export function FloorplanPanel() { [elevatorIds], ), ) + const siteLivePolygon = useLiveNodeOverrides( + useCallback( + (state) => { + if (!site?.id) { + return null + } + + return (state.overrides.get(site.id)?.polygon as SiteNode['polygon'] | undefined) ?? null + }, + [site?.id], + ), + ) const [stairBuildPreviewPoint, setStairBuildPreviewPoint] = useState(null) const [stairBuildPreviewRotation, setStairBuildPreviewRotation] = useState(0) const [isSpacePanPressed, setIsSpacePanPressed] = useState(false) @@ -4616,12 +4819,14 @@ export function FloorplanPanel() { ) const sitePolygonEntry = useMemo(() => { - const polygonPoints = site?.polygon?.points + const polygonPoints = siteLivePolygon?.points ?? site?.polygon?.points if (!(site && polygonPoints)) { return null } - const polygon = toFloorplanPolygon(polygonPoints) + const polygon = polygonPoints.map(([x, z]) => + worldToFloorplanLocalPoint(x, z, buildingPosition, buildingRotationY), + ) if (polygon.length < 3) { return null } @@ -4631,7 +4836,7 @@ export function FloorplanPanel() { polygon, points: formatPolygonPoints(polygon), } - }, [site]) + }, [buildingPosition, buildingRotationY, site, siteLivePolygon]) const displaySitePolygon = useMemo(() => { if (!sitePolygonEntry) { return null @@ -4649,6 +4854,28 @@ export function FloorplanPanel() { points: formatPolygonPoints(polygon), } }, [siteBoundaryDraft, sitePolygonEntry]) + const siteBoundaryWorldPolygon = useCallback( + (polygon: WallPlanPoint[]) => + polygon.map((point) => { + const worldPoint = floorplanLocalToWorldPoint(point, buildingPosition, buildingRotationY) + return [worldPoint.x, worldPoint.z] as [number, number] + }), + [buildingPosition, buildingRotationY], + ) + const setSiteBoundaryLivePreview = useCallback( + (siteId: SiteNode['id'], polygon: WallPlanPoint[]) => { + useLiveNodeOverrides.getState().set(siteId, { + polygon: { + type: 'polygon', + points: siteBoundaryWorldPolygon(polygon), + }, + }) + }, + [siteBoundaryWorldPolygon], + ) + const clearSiteBoundaryLivePreview = useCallback((siteId: SiteNode['id']) => { + useLiveNodeOverrides.getState().clearFields(siteId, ['polygon']) + }, []) const movingOpeningType = movingNode?.type === 'door' || movingNode?.type === 'window' ? movingNode.type : null @@ -5331,8 +5558,14 @@ export function FloorplanPanel() { !movingNode && !movingFenceEndpoint && isFloorplanItemContextActive - const visibleSitePolygon = phase === 'site' ? displaySitePolygon : null - const shouldShowSiteBoundaryHandles = isSiteEditActive && visibleSitePolygon !== null + const visibleSitePolygon = displaySitePolygon + const canUseSiteBoundaryVertexHandles = + visibleSitePolygon !== null && (isSiteEditActive || mode === 'select') + const isSiteBoundaryHighlighted = isSiteEditActive || siteVertexDragState !== null + const shouldShowSiteEdgeLabels = + Boolean(visibleSitePolygon) && + activeHandleDrag?.nodeId === visibleSitePolygon?.site.id && + activeHandleDrag?.label === SITE_BOUNDARY_DRAG_LABEL const visibleZonePolygons = displayZonePolygons const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]) const highlightedFloorplanIdSet = useMemo( @@ -5369,7 +5602,7 @@ export function FloorplanPanel() { return toSvgSelectionBounds(visibleMarqueeBounds) }, [visibleMarqueeBounds]) const siteVertexHandles = useMemo(() => { - if (!(shouldShowSiteBoundaryHandles && visibleSitePolygon)) { + if (!(canUseSiteBoundaryVertexHandles && visibleSitePolygon)) { return [] } @@ -5381,9 +5614,24 @@ export function FloorplanPanel() { siteVertexDragState?.siteId === visibleSitePolygon.site.id && siteVertexDragState.vertexIndex === vertexIndex, })) - }, [shouldShowSiteBoundaryHandles, siteVertexDragState, visibleSitePolygon]) + }, [canUseSiteBoundaryVertexHandles, siteVertexDragState, visibleSitePolygon]) + const siteEdgeHandles = useMemo(() => { + if (!(canUseSiteBoundaryVertexHandles && visibleSitePolygon && !siteVertexDragState)) { + return [] + } + + return visibleSitePolygon.polygon.map((point, edgeIndex, polygon) => { + const nextPoint = polygon[(edgeIndex + 1) % polygon.length] + return { + nodeId: visibleSitePolygon.site.id, + edgeIndex, + start: toWallPlanPoint(point), + end: toWallPlanPoint(nextPoint ?? point), + } + }) + }, [canUseSiteBoundaryVertexHandles, siteVertexDragState, visibleSitePolygon]) const siteMidpointHandles = useMemo(() => { - if (!(shouldShowSiteBoundaryHandles && visibleSitePolygon && !siteVertexDragState)) { + if (!(canUseSiteBoundaryVertexHandles && visibleSitePolygon && !siteVertexDragState)) { return [] } @@ -5398,7 +5646,7 @@ export function FloorplanPanel() { ] as WallPlanPoint, } }) - }, [shouldShowSiteBoundaryHandles, siteVertexDragState, visibleSitePolygon]) + }, [canUseSiteBoundaryVertexHandles, siteVertexDragState, visibleSitePolygon]) const draftPolygon = useMemo(() => { if (!(levelId && draftStart && draftEnd && isSegmentLongEnough(draftStart, draftEnd))) { @@ -6917,10 +7165,28 @@ export function FloorplanPanel() { setHoveredWallCurveHandleId(null) }, []) const clearSiteBoundaryInteraction = useCallback(() => { + const draft = siteBoundaryDraftRef.current + if (draft) { + clearSiteBoundaryLivePreview(draft.siteId) + const editor = useEditor.getState() + if ( + editor.activeHandleDrag?.nodeId === draft.siteId && + editor.activeHandleDrag.label === SITE_BOUNDARY_DRAG_LABEL + ) { + editor.setActiveHandleDrag(null) + } + } + + siteBoundaryDraftRef.current = null setSiteVertexDragState(null) setSiteBoundaryDraft(null) setHoveredSiteHandleId(null) - }, []) + }, [clearSiteBoundaryLivePreview]) + const exitSiteEditingToSelect = useCallback(() => { + setPhase('structure') + setStructureLayer('elements') + setMode('select') + }, [setMode, setPhase, setStructureLayer]) const clearDraft = useCallback(() => { clearWallPlacementDraft() @@ -7629,12 +7895,12 @@ export function FloorplanPanel() { }, [clearWallCurveDrag, clearWallEndpointDrag]) useEffect(() => { - if (shouldShowSiteBoundaryHandles) { + if (canUseSiteBoundaryVertexHandles) { return } clearSiteBoundaryInteraction() - }, [clearSiteBoundaryInteraction, shouldShowSiteBoundaryHandles]) + }, [canUseSiteBoundaryVertexHandles, clearSiteBoundaryInteraction]) useEffect(() => { const dragState = siteVertexDragState @@ -7657,26 +7923,27 @@ export function FloorplanPanel() { const snappedPoint: WallPlanPoint = [snapToHalf(planPoint[0]), snapToHalf(planPoint[1])] setCursorPoint(snappedPoint) - setSiteBoundaryDraft((currentDraft) => { - if (!currentDraft || currentDraft.siteId !== dragState.siteId) { - return currentDraft - } + const currentDraft = siteBoundaryDraftRef.current + if (!currentDraft || currentDraft.siteId !== dragState.siteId) { + return + } - const currentPoint = currentDraft.polygon[dragState.vertexIndex] - if (currentPoint && pointsEqual(currentPoint, snappedPoint)) { - return currentDraft - } + const currentPoint = currentDraft.polygon[dragState.vertexIndex] + if (currentPoint && pointsEqual(currentPoint, snappedPoint)) { + return + } - sfxEmitter.emit('sfx:grid-snap') + sfxEmitter.emit('sfx:grid-snap') - const nextPolygon = [...currentDraft.polygon] - nextPolygon[dragState.vertexIndex] = snappedPoint - - return { - ...currentDraft, - polygon: nextPolygon, - } - }) + const nextPolygon = [...currentDraft.polygon] + nextPolygon[dragState.vertexIndex] = snappedPoint + const nextDraft = { + ...currentDraft, + polygon: nextPolygon, + } + siteBoundaryDraftRef.current = nextDraft + setSiteBoundaryDraft(nextDraft) + setSiteBoundaryLivePreview(dragState.siteId, nextPolygon) } const commitSiteVertexDrag = (event: PointerEvent) => { @@ -7685,11 +7952,13 @@ export function FloorplanPanel() { } const draft = siteBoundaryDraftRef.current + const worldPolygon = draft ? siteBoundaryWorldPolygon(draft.polygon) : null if ( draft && + worldPolygon && site && draft.siteId === site.id && - !polygonsEqual(draft.polygon, site.polygon?.points ?? []) + !polygonsEqual(worldPolygon, site.polygon?.points ?? []) ) { const suppressClick = (clickEvent: MouseEvent) => { clickEvent.stopImmediatePropagation() @@ -7704,13 +7973,14 @@ export function FloorplanPanel() { updateNode(draft.siteId, { polygon: { type: 'polygon', - points: draft.polygon, + points: worldPolygon, }, }) sfxEmitter.emit('sfx:structure-build') } clearSiteBoundaryInteraction() + exitSiteEditingToSelect() setCursorPoint(null) } @@ -7720,6 +7990,7 @@ export function FloorplanPanel() { } clearSiteBoundaryInteraction() + exitSiteEditingToSelect() setCursorPoint(null) } @@ -7734,8 +8005,11 @@ export function FloorplanPanel() { } }, [ clearSiteBoundaryInteraction, + exitSiteEditingToSelect, getPlanPointFromClientPoint, + setSiteBoundaryLivePreview, site, + siteBoundaryWorldPolygon, siteVertexDragState, updateNode, ]) @@ -9249,10 +9523,19 @@ export function FloorplanPanel() { return } - setSiteBoundaryDraft({ + if (useEditor.getState().phase !== 'site') { + useEditor.setState({ catalogCategory: null, mode: 'select', phase: 'site', tool: null }) + } + selectSiteFloorplanContext() + + const nextDraft = { siteId, polygon: displaySitePolygon.polygon.map(toWallPlanPoint), - }) + } + siteBoundaryDraftRef.current = nextDraft + setSiteBoundaryDraft(nextDraft) + setSiteBoundaryLivePreview(siteId, nextDraft.polygon) + useEditor.getState().setActiveHandleDrag({ nodeId: siteId, label: SITE_BOUNDARY_DRAG_LABEL }) setSiteVertexDragState({ pointerId: event.pointerId, siteId, @@ -9260,10 +9543,14 @@ export function FloorplanPanel() { }) setCursorPoint(toWallPlanPoint(vertexPoint)) }, - [displaySitePolygon], + [displaySitePolygon, setSiteBoundaryLivePreview], ) const handleSiteVertexDoubleClick = useCallback( - (siteId: SiteNode['id'], vertexIndex: number, event: ReactPointerEvent) => { + ( + siteId: SiteNode['id'], + vertexIndex: number, + event: ReactPointerEvent | ReactMouseEvent, + ) => { if (event.button !== 0) { return } @@ -9275,7 +9562,6 @@ export function FloorplanPanel() { return } - siteBoundaryDraftRef.current = null clearSiteBoundaryInteraction() updateNode(siteId, { @@ -9319,10 +9605,19 @@ export function FloorplanPanel() { ...basePolygon.slice(insertIndex), ] - setSiteBoundaryDraft({ + if (useEditor.getState().phase !== 'site') { + useEditor.setState({ catalogCategory: null, mode: 'select', phase: 'site', tool: null }) + } + selectSiteFloorplanContext() + + const nextDraft = { siteId, polygon: nextPolygon, - }) + } + siteBoundaryDraftRef.current = nextDraft + setSiteBoundaryDraft(nextDraft) + setSiteBoundaryLivePreview(siteId, nextPolygon) + useEditor.getState().setActiveHandleDrag({ nodeId: siteId, label: SITE_BOUNDARY_DRAG_LABEL }) setSiteVertexDragState({ pointerId: event.pointerId, siteId, @@ -9330,7 +9625,7 @@ export function FloorplanPanel() { }) setCursorPoint(insertedPoint) }, - [displaySitePolygon], + [displaySitePolygon, setSiteBoundaryLivePreview], ) const handlePointerLeave = useCallback(() => { @@ -10020,8 +10315,6 @@ export function FloorplanPanel() { selectedGuideId={selectedGuideId} /> - - {/* Stair is fully registry-driven for committed nodes (`def.floorplan` on the stair kind). This layer only carries the in-flight stair preview, which lives outside @@ -10058,24 +10351,6 @@ export function FloorplanPanel() { unitsPerPixel={floorplanUnitsPerPixel} /> - - handleSiteMidpointPointerDown(nodeId as SiteNode['id'], edgeIndex, event) - } - onVertexDoubleClick={(nodeId, vertexIndex, event) => - handleSiteVertexDoubleClick(nodeId as SiteNode['id'], vertexIndex, event) - } - onVertexPointerDown={(nodeId, vertexIndex, event) => - handleSiteVertexPointerDown(nodeId as SiteNode['id'], vertexIndex, event) - } - palette={palette} - unitsPerPixel={floorplanUnitsPerPixel} - vertexHandles={siteVertexHandles} - /> - {isMarqueeSelectionToolActive && ( + + + + handleSiteMidpointPointerDown(nodeId as SiteNode['id'], edgeIndex, event) + } + onVertexDoubleClick={(nodeId, vertexIndex, event) => + handleSiteVertexDoubleClick(nodeId as SiteNode['id'], vertexIndex, event) + } + onVertexPointerDown={(nodeId, vertexIndex, event) => + handleSiteVertexPointerDown(nodeId as SiteNode['id'], vertexIndex, event) + } + palette={palette} + unitsPerPixel={floorplanUnitsPerPixel} + vertexHandles={siteVertexHandles} + /> + + + {/* "Magnetic" wall-snap beacon — per-kind glyph at the active draft / endpoint-move snap point. Same store + coord space as the alignment guides. */} diff --git a/packages/editor/src/components/editor/site-edge-labels.tsx b/packages/editor/src/components/editor/site-edge-labels.tsx index c3fc7c6c..8cd99b06 100644 --- a/packages/editor/src/components/editor/site-edge-labels.tsx +++ b/packages/editor/src/components/editor/site-edge-labels.tsx @@ -1,12 +1,15 @@ 'use client' import type { SiteNode } from '@pascal-app/core' -import { sceneRegistry, useScene } from '@pascal-app/core' +import { sceneRegistry, useLiveNodeOverrides, useScene } from '@pascal-app/core' import { getSceneTheme, useViewer } from '@pascal-app/viewer' import { Html } from '@react-three/drei' import { createPortal, useFrame, useThree } from '@react-three/fiber' import { useCallback, useMemo, useRef, useState } from 'react' import { type Camera, type Object3D, Vector3 } from 'three' +import { SITE_BOUNDARY_DRAG_LABEL } from '../../lib/site-boundary' +import useEditor from '../../store/use-editor' +import { formatMeasurement } from './measurement-pill' type ViewportSize = { width: number @@ -24,17 +27,6 @@ function calculateHtmlPosition(el: Object3D, camera: Camera, size: ViewportSize) return [htmlPosition.x * widthHalf + widthHalf, -htmlPosition.y * heightHalf + heightHalf] } -function formatMeasurement(value: number, unit: 'metric' | 'imperial') { - if (unit === 'imperial') { - const feet = value * 3.280_84 - const wholeFeet = Math.floor(feet) - const inches = Math.round((feet - wholeFeet) * 12) - if (inches === 12) return `${wholeFeet + 1}'0"` - return `${wholeFeet}'${inches}"` - } - return `${Number.parseFloat(value.toFixed(2))}m` -} - export function SiteEdgeLabels() { // Narrow subscription to just the site node — subscribing to the full // s.nodes dict re-rendered this on every wall/level mutation even though @@ -45,6 +37,7 @@ export function SiteEdgeLabels() { const node = state.nodes[firstRoot] return node?.type === 'site' ? (node as SiteNode) : null }) + const activeHandleDrag = useEditor((state) => state.activeHandleDrag) const unit = useViewer((state) => state.unit) const cameraMode = useViewer((state) => state.cameraMode) const isNight = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark') @@ -56,6 +49,15 @@ export function SiteEdgeLabels() { ) const siteNodeId = siteNode?.id + const livePolygon = useLiveNodeOverrides((state) => { + if (!siteNodeId) return null + return (state.overrides.get(siteNodeId)?.polygon as SiteNode['polygon'] | undefined) ?? null + }) + const polygon = livePolygon?.points ?? siteNode?.polygon?.points ?? [] + const shouldShowLabels = + Boolean(siteNodeId) && + activeHandleDrag?.nodeId === siteNodeId && + activeHandleDrag?.label === SITE_BOUNDARY_DRAG_LABEL const color = isNight ? '#ffffff' : '#111111' const shadowColor = isNight ? '#111111' : '#ffffff' @@ -77,7 +79,6 @@ export function SiteEdgeLabels() { }) const edges = useMemo(() => { - const polygon = siteNode?.polygon?.points ?? [] if (polygon.length < 2) return [] return polygon.map(([x1, z1], i) => { const [x2, z2] = polygon[(i + 1) % polygon.length]! @@ -86,9 +87,9 @@ export function SiteEdgeLabels() { const dist = Math.sqrt((x2 - x1!) ** 2 + (z2 - z1!) ** 2) return { midX, midZ, dist } }) - }, [siteNode?.polygon?.points]) + }, [polygon]) - if (!siteObj || edges.length === 0) return null + if (!shouldShowLabels || !siteObj || edges.length === 0) return null return createPortal( <> diff --git a/packages/editor/src/components/tools/shared/polygon-editor.tsx b/packages/editor/src/components/tools/shared/polygon-editor.tsx index ce8c4b6f..2257bcf9 100644 --- a/packages/editor/src/components/tools/shared/polygon-editor.tsx +++ b/packages/editor/src/components/tools/shared/polygon-editor.tsx @@ -1,7 +1,7 @@ import { emitter, type GridEvent, sceneRegistry } from '@pascal-app/core' import { SCENE_LAYER, useViewer } from '@pascal-app/viewer' import { createPortal, type ThreeEvent } from '@react-three/fiber' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { BufferGeometry, Color, @@ -98,6 +98,26 @@ export interface PolygonEditorProps { allowPolygonMove?: boolean /** Whether polygon edges can be dragged along their perpendicular normal. */ allowEdgeMove?: boolean + /** Called just before a vertex drag session starts. */ + onBeforeVertexDrag?: (vertexIndex: number, position: [number, number]) => void + /** Called when a vertex handle enters or leaves hover. */ + onVertexHoverChange?: (vertexIndex: number | null) => void + /** Called when a midpoint add-vertex handle enters or leaves hover. */ + onMidpointHoverChange?: (edgeIndex: number | null) => void + /** Called when any polygon drag starts or ends. */ + onDragStateChange?: (isDragging: boolean) => void + /** Called once when a polygon drag starts. */ + onDragStart?: () => void + /** Called once when a polygon drag commits on pointer release. */ + onDragCommit?: () => void + /** Whether to render the editor-owned polygon outline. */ + showBorderLine?: boolean + /** Whether midpoint handles can add new vertices. */ + showMidpointHandles?: boolean + /** Optional vertex handle renderer for host-specific affordances. */ + renderVertexHandle?: PolygonVertexHandleRenderer + /** Optional midpoint handle renderer for host-specific add-vertex affordances. */ + renderMidpointHandle?: PolygonMidpointHandleRenderer } /** @@ -120,7 +140,7 @@ function getEdgeNormal(start: [number, number], end: [number, number]): [number, type HandleClickHandler = (event: ThreeEvent) => void type HandlePointerHandler = (event: ThreeEvent) => void -type HandleHandlers = { +export type PolygonHandleHandlers = { onClick?: HandleClickHandler onDoubleClick?: HandleClickHandler onPointerDown?: HandlePointerHandler @@ -128,14 +148,42 @@ type HandleHandlers = { onPointerLeave?: HandlePointerHandler } +export type PolygonVertexHandleRenderProps = { + canDelete: boolean + handleProps: PolygonHandleHandlers + height: number + index: number + isDragging: boolean + isHovered: boolean + point: [number, number] + position: [number, number, number] + radius: number +} + +export type PolygonVertexHandleRenderer = (props: PolygonVertexHandleRenderProps) => React.ReactNode + +export type PolygonMidpointHandleRenderProps = { + handleProps: PolygonHandleHandlers + height: number + index: number + isHovered: boolean + point: [number, number] + position: [number, number, number] + radius: number +} + +export type PolygonMidpointHandleRenderer = ( + props: PolygonMidpointHandleRenderProps, +) => React.ReactNode + function usePolygonNodeMaterial(color: string, opacity = 1): MeshBasicNodeMaterial { const material = useMemo( () => new MeshBasicNodeMaterial({ - color: new Color(color), + color: new Color('#ffffff'), depthTest: false, depthWrite: true, - opacity, + opacity: 1, transparent: true, }), [], @@ -168,7 +216,7 @@ function OutlinedCylinderHandle({ color: string opacity?: number position: [number, number, number] -} & HandleHandlers) { +} & PolygonHandleHandlers) { const geometry = useMemo(() => new CylinderGeometry(radius, radius, height, 16), [height, radius]) const material = usePolygonNodeMaterial(color, opacity) useEffect(() => () => geometry.dispose(), [geometry]) @@ -197,7 +245,7 @@ function OutlinedCrossHandle({ }: { color: string position: [number, number, number] -} & HandleHandlers) { +} & PolygonHandleHandlers) { const geometry = useMemo(() => createMoveCrossHandleGeometry(), []) const material = usePolygonNodeMaterial(color) const hitGeometry = useMemo(() => new CylinderGeometry(0.24, 0.24, 0.18, 24), []) @@ -241,7 +289,7 @@ function OutlinedEdgeArrowHandle({ position: [number, number, number] rotationY: number scale: number -} & HandleHandlers) { +} & PolygonHandleHandlers) { const material = useArrowMaterial() useEffect(() => { material.color.set(color) @@ -273,6 +321,16 @@ export const PolygonEditor: React.FC = ({ surfaceHeight = 0, allowPolygonMove = false, allowEdgeMove = false, + onBeforeVertexDrag, + onVertexHoverChange, + onMidpointHoverChange, + onDragStateChange, + onDragStart, + onDragCommit, + showBorderLine = true, + showMidpointHandles = true, + renderMidpointHandle, + renderVertexHandle, }) => { const [levelNode, setLevelNode] = useState(() => levelId ? (sceneRegistry.nodes.get(levelId) ?? null) : null, @@ -319,6 +377,21 @@ export const PolygonEditor: React.FC = ({ const previewPolygonRef = useRef | null>(null) const previousInputDraggingRef = useRef(false) + const onDragStateChangeRef = useRef(onDragStateChange) + useEffect(() => { + onDragStateChangeRef.current = onDragStateChange + }, [onDragStateChange]) + + const onDragStartRef = useRef(onDragStart) + useEffect(() => { + onDragStartRef.current = onDragStart + }, [onDragStart]) + + const onDragCommitRef = useRef(onDragCommit) + useEffect(() => { + onDragCommitRef.current = onDragCommit + }, [onDragCommit]) + const onPolygonPreviewRef = useRef(onPolygonPreview) useEffect(() => { onPolygonPreviewRef.current = onPolygonPreview @@ -344,13 +417,32 @@ export const PolygonEditor: React.FC = ({ const [hoveredEdge, setHoveredEdge] = useState(null) const [cursorPosition, setCursorPosition] = useState<[number, number]>([0, 0]) + useEffect(() => { + onVertexHoverChange?.(hoveredVertex) + }, [hoveredVertex, onVertexHoverChange]) + + useEffect(() => () => onVertexHoverChange?.(null), [onVertexHoverChange]) + + useEffect(() => { + onMidpointHoverChange?.(hoveredMidpoint) + }, [hoveredMidpoint, onMidpointHoverChange]) + + useEffect(() => () => onMidpointHoverChange?.(null), [onMidpointHoverChange]) + const lineRef = useRef(null!) const previousPositionRef = useRef<[number, number] | null>(null) + useEffect(() => { + onDragStateChangeRef.current?.(dragState?.isDragging ?? false) + }, [dragState?.isDragging]) + + useEffect(() => () => onDragStateChangeRef.current?.(false), []) + const startDrag = useCallback((nextDragState: DragState) => { previousInputDraggingRef.current = useViewer.getState().inputDragging useViewer.getState().setInputDragging(true) setDragState(nextDragState) + onDragStartRef.current?.() }, []) useEffect(() => { @@ -483,6 +575,7 @@ export const PolygonEditor: React.FC = ({ if (previewPolygonRef.current) { onPolygonChange(previewPolygonRef.current) } + onDragCommitRef.current?.() updatePreviewPolygon(null) setDragState(null) }, [onPolygonChange, updatePreviewPolygon]) @@ -522,8 +615,9 @@ export const PolygonEditor: React.FC = ({ // Listen to grid:move events to track cursor position useEffect(() => { const onGridMove = (event: GridEvent) => { - const gridX = snapToHalf(event.localPosition[0]) - const gridZ = snapToHalf(event.localPosition[2]) + const point = levelNode ? event.localPosition : event.position + const gridX = snapToHalf(point[0]) + const gridZ = snapToHalf(point[2]) const newPosition: [number, number] = [gridX, gridZ] // Play snap sound when cursor moves to a new grid cell during drag @@ -579,7 +673,7 @@ export const PolygonEditor: React.FC = ({ return () => { emitter.off('grid:move', onGridMove) } - }, [dragState, handleVertexDrag, updatePreviewPolygon]) + }, [dragState, handleVertexDrag, levelNode, updatePreviewPolygon]) // Set up pointer up listener for ending drag useEffect(() => { @@ -624,7 +718,7 @@ export const PolygonEditor: React.FC = ({ // Update line geometry when polygon changes useEffect(() => { - if (!lineRef.current || displayPolygon.length < 2) return + if (!showBorderLine || !lineRef.current || displayPolygon.length < 2) return const positions: number[] = [] for (const [x, z] of displayPolygon) { @@ -639,7 +733,7 @@ export const PolygonEditor: React.FC = ({ lineRef.current.geometry.dispose() lineRef.current.geometry = geometry - }, [displayPolygon, editY]) + }, [displayPolygon, editY, showBorderLine]) if (displayPolygon.length < minVertices) return null @@ -656,24 +750,26 @@ export const PolygonEditor: React.FC = ({ const editorContent = ( {/* Border line */} - element conflicts with SVG type - ref={lineRef} - renderOrder={10} - > - - - + {showBorderLine && ( + element conflicts with SVG type + ref={lineRef} + renderOrder={10} + > + + + + )} {/* Vertex handles - blue cylinders that match surface height */} {displayPolygon.map(([x, z], index) => { @@ -681,45 +777,69 @@ export const PolygonEditor: React.FC = ({ const isDragging = dragState?.mode === 'vertex' && dragState.vertexIndex === index const radius = 0.1 const height = handleHeight + const point: [number, number] = [x!, z!] + const position: [number, number, number] = [x!, editY + height / 2, z!] + const handleProps: PolygonHandleHandlers = { + onClick: (e) => { + if (e.button !== 0) return + e.stopPropagation() + }, + onDoubleClick: (e) => { + if (e.button !== 0) return + e.stopPropagation() + if (canDelete) { + handleDeleteVertex(index) + } + }, + onPointerDown: (e) => { + if (e.button !== 0) return + e.stopPropagation() + setHoveredEdge(null) + onBeforeVertexDrag?.(index, point) + startDrag({ + isDragging: true, + mode: 'vertex', + vertexIndex: index, + initialPosition: [x!, z!], + initialPolygon: displayPolygon.map(([px, pz]) => [px, pz] as [number, number]), + pointerId: e.pointerId, + }) + }, + onPointerEnter: (e) => { + e.stopPropagation() + setHoveredVertex(index) + }, + onPointerLeave: (e) => { + e.stopPropagation() + setHoveredVertex(null) + }, + } + + if (renderVertexHandle) { + return ( + + {renderVertexHandle({ + canDelete, + handleProps, + height, + index, + isDragging, + isHovered, + point, + position, + radius, + })} + + ) + } return ( { - if (e.button !== 0) return - e.stopPropagation() - }} - onDoubleClick={(e) => { - if (e.button !== 0) return - e.stopPropagation() - if (canDelete) { - handleDeleteVertex(index) - } - }} - onPointerDown={(e) => { - if (e.button !== 0) return - e.stopPropagation() - setHoveredEdge(null) - startDrag({ - isDragging: true, - mode: 'vertex', - vertexIndex: index, - initialPosition: [x!, z!], - initialPolygon: displayPolygon.map(([px, pz]) => [px, pz] as [number, number]), - pointerId: e.pointerId, - }) - }} - onPointerEnter={(e) => { - e.stopPropagation() - setHoveredVertex(index) - }} - onPointerLeave={(e) => { - e.stopPropagation() - setHoveredVertex(null) - }} - position={[x!, editY + height / 2, z!]} + {...handleProps} + position={position} radius={radius} /> ) @@ -830,47 +950,70 @@ export const PolygonEditor: React.FC = ({ })} {/* Midpoint handles - smaller green cylinders for adding vertices (hidden while dragging) */} - {!dragState && + {showMidpointHandles && + !dragState && midpoints.map(([x, z], index) => { const isHovered = hoveredMidpoint === index const radius = 0.06 const height = handleHeight + const point: [number, number] = [x!, z!] + const position: [number, number, number] = [x!, editY + height / 2, z!] + const handleProps: PolygonHandleHandlers = { + onClick: (e) => { + if (e.button !== 0) return + e.stopPropagation() + }, + onPointerDown: (e) => { + if (e.button !== 0) return + e.stopPropagation() + onBeforeVertexDrag?.(index + 1, point) + const insertedVertex = handleAddVertex(index, point) + if (insertedVertex.vertexIndex >= 0) { + startDrag({ + isDragging: true, + mode: 'vertex', + vertexIndex: insertedVertex.vertexIndex, + initialPosition: point, + initialPolygon: insertedVertex.polygon, + pointerId: e.pointerId, + }) + setHoveredMidpoint(null) + } + }, + onPointerEnter: (e) => { + e.stopPropagation() + setHoveredMidpoint(index) + }, + onPointerLeave: (e) => { + e.stopPropagation() + setHoveredMidpoint(null) + }, + } + + if (renderMidpointHandle) { + return ( + + {renderMidpointHandle({ + handleProps, + height, + index, + isHovered, + point, + position, + radius, + })} + + ) + } return ( { - if (e.button !== 0) return - e.stopPropagation() - }} - onPointerDown={(e) => { - if (e.button !== 0) return - e.stopPropagation() - const insertedVertex = handleAddVertex(index, [x!, z!]) - if (insertedVertex.vertexIndex >= 0) { - startDrag({ - isDragging: true, - mode: 'vertex', - vertexIndex: insertedVertex.vertexIndex, - initialPosition: [x!, z!], - initialPolygon: insertedVertex.polygon, - pointerId: e.pointerId, - }) - setHoveredMidpoint(null) - } - }} - onPointerEnter={(e) => { - e.stopPropagation() - setHoveredMidpoint(index) - }} - onPointerLeave={(e) => { - e.stopPropagation() - setHoveredMidpoint(null) - }} + {...handleProps} opacity={isHovered ? 1 : 0.7} - position={[x!, editY + height / 2, z!]} + position={position} radius={radius} /> ) diff --git a/packages/editor/src/components/tools/site/site-boundary-editor.tsx b/packages/editor/src/components/tools/site/site-boundary-editor.tsx index 3fc38f11..5a77f886 100644 --- a/packages/editor/src/components/tools/site/site-boundary-editor.tsx +++ b/packages/editor/src/components/tools/site/site-boundary-editor.tsx @@ -1,19 +1,319 @@ -import { type SiteNode, useScene } from '@pascal-app/core' -import { useCallback } from 'react' -import { PolygonEditor } from '../shared/polygon-editor' +import { emitter, type SiteNode, useLiveNodeOverrides, useScene } from '@pascal-app/core' +import { SCENE_LAYER } from '@pascal-app/viewer' +import { useGLTF } from '@react-three/drei/core/Gltf' +import { useFrame } from '@react-three/fiber' +import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Color, CylinderGeometry, DoubleSide, type Mesh, type Object3D, RingGeometry } from 'three' +import { MeshBasicNodeMaterial } from 'three/webgpu' +import { EDITOR_LAYER } from '../../../lib/constants' +import { sfxEmitter } from '../../../lib/sfx-bus' +import { SITE_BOUNDARY_DRAG_LABEL } from '../../../lib/site-boundary' +import useEditor, { selectSiteFloorplanContext } from '../../../store/use-editor' +import { + ARROW_COLOR, + ARROW_HOVER_COLOR, + NO_RAYCAST, + useInvisibleHitAreaMaterial, +} from '../../editor/node-arrow-handles' +import { + PolygonEditor, + type PolygonHandleHandlers, + type PolygonMidpointHandleRenderProps, + type PolygonVertexHandleRenderProps, +} from '../shared/polygon-editor' +import { SITE_FLAG_MODEL_URL } from './site-flag-model' + +const SITE_FLAG_BASE_Y = 0 +const SITE_FLAG_HIT_HEIGHT = 0.5 +const SITE_FLAG_HIT_RADIUS = 0.24 +const SITE_FLAG_HIT_Y = SITE_FLAG_BASE_Y + SITE_FLAG_HIT_HEIGHT / 2 +const SITE_FLAG_MODEL_MIN_Y = -0.034798760316334665 +const SITE_FLAG_SCALE = 0.5 +const SITE_FLAG_ACTIVE_LIFT = 0.08 +const SITE_FLAG_MODEL_Y = SITE_FLAG_BASE_Y - SITE_FLAG_MODEL_MIN_Y * SITE_FLAG_SCALE +const SITE_FLAG_HALO_INNER_RADIUS = 0.08 +const SITE_FLAG_HALO_OUTER_RADIUS = SITE_FLAG_HIT_RADIUS * 0.95 +const SITE_FLAG_HALO_Y = SITE_FLAG_BASE_Y + 0.002 +const SITE_FLAG_HALO_COLOR = '#6366f1' + +type TintableMaterial = { + color?: Color + depthWrite: boolean + opacity: number + needsUpdate: boolean + transparent: boolean +} + +function SiteFlagModel({ + active, + hovered, + opacity = 1, + scale = SITE_FLAG_SCALE, +}: { + active: boolean + hovered: boolean + opacity?: number + scale?: number +}) { + const { scene } = useGLTF(SITE_FLAG_MODEL_URL, true) + const modelRef = useRef(null) + const flagScene = useMemo(() => { + const cloned = scene.clone(true) + + cloned.traverse((object) => { + object.layers.set(SCENE_LAYER) + + if ((object as Mesh).isMesh) { + const mesh = object as Mesh + mesh.castShadow = false + mesh.frustumCulled = false + mesh.raycast = NO_RAYCAST + mesh.receiveShadow = false + mesh.renderOrder = 1010 + mesh.material = new MeshBasicNodeMaterial({ + color: new Color(ARROW_COLOR), + depthTest: false, + depthWrite: opacity >= 0.999, + opacity, + transparent: opacity < 0.999, + }) + } + }) + + return cloned + }, [opacity, scene]) + + useEffect(() => { + const color = new Color(active || hovered ? ARROW_HOVER_COLOR : ARROW_COLOR) + + flagScene.traverse((object) => { + if ((object as Mesh).isMesh) { + const mesh = object as Mesh + const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material] + + for (const material of materials as Array) { + material.color?.copy(color) + material.opacity = opacity + material.transparent = opacity < 0.999 + material.depthWrite = opacity >= 0.999 + material.needsUpdate = true + } + } + }) + }, [active, flagScene, hovered, opacity]) + + useFrame((_, delta) => { + const model = modelRef.current + if (!model) return + + const smoothing = 1 - Math.exp(-delta * 18) + const targetY = SITE_FLAG_MODEL_Y + (active ? SITE_FLAG_ACTIVE_LIFT : 0) + model.position.y += (targetY - model.position.y) * smoothing + }) + + useEffect( + () => () => { + flagScene.traverse((object) => { + if ((object as Mesh).isMesh) { + const materials = (object as Mesh).material + if (Array.isArray(materials)) { + materials.forEach((material) => { + material.dispose() + }) + } else { + materials.dispose() + } + } + }) + }, + [flagScene], + ) + + return ( + + ) +} + +function SiteFlagHoverHalo({ visible }: { visible: boolean }) { + const haloRef = useRef(null) + const geometry = useMemo(() => { + const nextGeometry = new RingGeometry( + SITE_FLAG_HALO_INNER_RADIUS, + SITE_FLAG_HALO_OUTER_RADIUS, + 56, + ) + nextGeometry.rotateX(-Math.PI / 2) + return nextGeometry + }, []) + const material = useMemo( + () => + new MeshBasicNodeMaterial({ + color: new Color(SITE_FLAG_HALO_COLOR), + depthTest: true, + depthWrite: false, + opacity: 0, + side: DoubleSide, + transparent: true, + }), + [], + ) + + useFrame((_, delta) => { + const mesh = haloRef.current + if (!mesh) return + + const smoothing = 1 - Math.exp(-delta * 18) + const targetOpacity = visible ? 0.52 : 0 + const targetScale = visible ? 1 : 0.08 + const nextOpacity = material.opacity + (targetOpacity - material.opacity) * smoothing + const nextScale = mesh.scale.x + (targetScale - mesh.scale.x) * smoothing + + material.opacity = nextOpacity + mesh.scale.setScalar(nextScale) + mesh.visible = visible || nextOpacity > 0.01 + }) + + useEffect( + () => () => { + geometry.dispose() + material.dispose() + }, + [geometry, material], + ) + + return ( + + ) +} + +function SiteFlagFallback({ + active, + hovered, + opacity = 1, +}: { + active: boolean + hovered: boolean + opacity?: number +}) { + const color = active || hovered ? ARROW_HOVER_COLOR : ARROW_COLOR + + return ( + + + + + + + + + + + ) +} + +function SiteBoundaryFlagHandle({ + active = false, + baseY, + handleProps, + hovered, + modelOpacity = 1, + modelScale = SITE_FLAG_SCALE, + modelVisible = true, + point, +}: { + active?: boolean + baseY: number + handleProps: PolygonHandleHandlers + hovered: boolean + modelOpacity?: number + modelScale?: number + modelVisible?: boolean + point: [number, number] +}) { + const hitGeometry = useMemo( + () => + new CylinderGeometry(SITE_FLAG_HIT_RADIUS, SITE_FLAG_HIT_RADIUS, SITE_FLAG_HIT_HEIGHT, 24), + [], + ) + const hitMaterial = useInvisibleHitAreaMaterial() + + useEffect(() => () => hitGeometry.dispose(), [hitGeometry]) + + return ( + + + {(modelVisible || active || hovered) && ( + } + > + + + )} + + + ) +} -/** - * Site boundary editor - allows editing site polygon when in site phase - * Uses the generic PolygonEditor component - */ export const SiteBoundaryEditor: React.FC = () => { const nodes = useScene((state) => state.nodes) const rootNodeIds = useScene((state) => state.rootNodeIds) const updateNode = useScene((state) => state.updateNode) + const phase = useEditor((state) => state.phase) + const mode = useEditor((state) => state.mode) + const [hoveredVertex, setHoveredVertex] = useState(null) + const [hoveredMidpoint, setHoveredMidpoint] = useState(null) - // Get the site node (first root node) const siteNode = rootNodeIds[0] ? nodes[rootNodeIds[0]] : null const site = siteNode?.type === 'site' ? (siteNode as SiteNode) : null + const siteId = site?.id + const isSiteEditing = phase === 'site' + const showSiteHandles = isSiteEditing || mode === 'select' + const isSiteBoundaryHighlighted = + isSiteEditing || hoveredVertex !== null || hoveredMidpoint !== null + const [isDraggingSiteBoundary, setIsDraggingSiteBoundary] = useState(false) + const isDraggingSiteBoundaryRef = useRef(false) + const livePolygon = useLiveNodeOverrides( + useCallback( + (state) => { + if (!siteId) return null + return (state.overrides.get(siteId)?.polygon as SiteNode['polygon'] | undefined) ?? null + }, + [siteId], + ), + ) + const displayPolygon = + !(isDraggingSiteBoundary || isDraggingSiteBoundaryRef.current) && livePolygon?.points + ? livePolygon.points + : site?.polygon?.points const handlePolygonChange = useCallback( (newPolygon: Array<[number, number]>) => { @@ -29,14 +329,166 @@ export const SiteBoundaryEditor: React.FC = () => { [site, updateNode], ) - if (!site?.polygon?.points || site.polygon.points.length < 3) return null + const handlePolygonPreview = useCallback( + (previewPolygon: ReadonlyArray | null) => { + if (!siteId) return + + if (!previewPolygon) { + useLiveNodeOverrides.getState().clearFields(siteId, ['polygon']) + return + } + + useLiveNodeOverrides.getState().set(siteId, { + polygon: { + type: 'polygon', + points: previewPolygon.map(([x, z]) => [x, z] as [number, number]), + }, + }) + }, + [siteId], + ) + + const handleSiteBoundaryDragChange = useCallback( + (isDragging: boolean) => { + isDraggingSiteBoundaryRef.current = isDragging + setIsDraggingSiteBoundary(isDragging) + + if (!siteId) return + + const editor = useEditor.getState() + if (isDragging) { + editor.setActiveHandleDrag({ nodeId: siteId, label: SITE_BOUNDARY_DRAG_LABEL }) + } else if ( + editor.activeHandleDrag?.nodeId === siteId && + editor.activeHandleDrag.label === SITE_BOUNDARY_DRAG_LABEL + ) { + editor.setActiveHandleDrag(null) + } + + if (!isDragging) { + useLiveNodeOverrides.getState().clearFields(siteId, ['polygon']) + } + }, + [siteId], + ) + + useEffect( + () => () => { + if (!siteId) return + const editor = useEditor.getState() + if ( + editor.activeHandleDrag?.nodeId === siteId && + editor.activeHandleDrag.label === SITE_BOUNDARY_DRAG_LABEL + ) { + editor.setActiveHandleDrag(null) + } + useLiveNodeOverrides.getState().clearFields(siteId, ['polygon']) + isDraggingSiteBoundaryRef.current = false + }, + [siteId], + ) + + useEffect(() => { + if (isSiteEditing && siteId) { + selectSiteFloorplanContext() + } + }, [isSiteEditing, siteId]) + + const activateSiteEditing = useCallback(() => { + isDraggingSiteBoundaryRef.current = true + setIsDraggingSiteBoundary(true) + if (useEditor.getState().phase !== 'site') { + useEditor.setState({ catalogCategory: null, mode: 'select', phase: 'site', tool: null }) + } + selectSiteFloorplanContext() + }, []) + + const exitSiteEditing = useCallback(() => { + const editor = useEditor.getState() + editor.setPhase('structure') + editor.setStructureLayer('elements') + editor.setMode('select') + }, []) + + useEffect(() => { + if (!isSiteEditing) return + + const onGridClick = () => { + if (useEditor.getState().phase !== 'site') return + exitSiteEditing() + } + + emitter.on('grid:click', onGridClick) + return () => { + emitter.off('grid:click', onGridClick) + } + }, [exitSiteEditing, isSiteEditing]) + + const renderSiteFlagVertex = useCallback( + ({ + handleProps, + height, + isDragging, + isHovered, + point, + position, + }: PolygonVertexHandleRenderProps) => { + const baseY = position[1] - height / 2 + + return ( + + ) + }, + [], + ) + const renderSiteFlagMidpoint = useCallback( + ({ handleProps, height, isHovered, point, position }: PolygonMidpointHandleRenderProps) => { + const baseY = position[1] - height / 2 + + return ( + + ) + }, + [], + ) + + if (!displayPolygon || displayPolygon.length < 3) return null + if (!showSiteHandles) return null return ( { + sfxEmitter.emit('sfx:item-place') + exitSiteEditing() + }} + onDragStart={() => sfxEmitter.emit('sfx:item-pick')} + onDragStateChange={handleSiteBoundaryDragChange} + onMidpointHoverChange={setHoveredMidpoint} onPolygonChange={handlePolygonChange} - polygon={site.polygon.points} + onPolygonPreview={handlePolygonPreview} + onVertexHoverChange={setHoveredVertex} + polygon={displayPolygon} + renderMidpointHandle={renderSiteFlagMidpoint} + renderVertexHandle={renderSiteFlagVertex} + showBorderLine={isSiteBoundaryHighlighted} + showMidpointHandles={showSiteHandles} /> ) } diff --git a/packages/editor/src/components/tools/site/site-flag-model.ts b/packages/editor/src/components/tools/site/site-flag-model.ts new file mode 100644 index 00000000..fce30c7c --- /dev/null +++ b/packages/editor/src/components/tools/site/site-flag-model.ts @@ -0,0 +1,18 @@ +const SITE_FLAG_MODEL_BASE64 = [ + 'Z2xURgIAAADgAwAAAAMAAEpTT057ImFzc2V0Ijp7ImdlbmVyYXRvciI6ImdsVEYtVHJhbnNmb3JtIHY0LjMuMCIsInZlcnNpb24i', + 'OiIyLjAifSwiYWNjZXNzb3JzIjpbeyJ0eXBlIjoiU0NBTEFSIiwiY29tcG9uZW50VHlwZSI6NTEyMywiY291bnQiOjE2OH0seyJ0', + 'eXBlIjoiVkVDMyIsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjozMCwibWF4IjpbMC4wMDY3MzM3MTM2NzE1NjUwNTYsMC4w', + 'MDYyNDAyODE3NDIwNjYxNDUsMC4wMDAzNDc5ODc2MDMxNjMzNDY2NV0sIm1pbiI6Wy0wLjAwMDMwMTM2NjExODk0NzA0NCwtMC4w', + 'MDAzNDc5ODc2MDMxNjMzNDY2NSwtMC4wMDAzNDc5ODc4NjUwOTc4MjA3Nl19XSwiYnVmZmVyVmlld3MiOlt7ImJ1ZmZlciI6MCwi', + 'Ynl0ZU9mZnNldCI6MCwiYnl0ZUxlbmd0aCI6MTkzfV0sImJ1ZmZlcnMiOlt7ImJ5dGVMZW5ndGgiOjE5M31dLCJtZXNoZXMiOlt7', + 'InByaW1pdGl2ZXMiOlt7ImF0dHJpYnV0ZXMiOnsiUE9TSVRJT04iOjF9LCJtb2RlIjo0LCJpbmRpY2VzIjowLCJleHRlbnNpb25z', + 'Ijp7IktIUl9kcmFjb19tZXNoX2NvbXByZXNzaW9uIjp7ImJ1ZmZlclZpZXciOjAsImF0dHJpYnV0ZXMiOnsiUE9TSVRJT04iOjB9', + 'fX19XX1dLCJub2RlcyI6W3sic2NhbGUiOlsxMDAsMTAwLDEwMF0sIm1lc2giOjB9XSwic2NlbmVzIjpbeyJub2RlcyI6WzBdfV0s', + 'InNjZW5lIjowLCJleHRlbnNpb25zVXNlZCI6WyJLSFJfZHJhY29fbWVzaF9jb21wcmVzc2lvbiJdLCJleHRlbnNpb25zUmVxdWly', + 'ZWQiOlsiS0hSX2RyYWNvX21lc2hfY29tcHJlc3Npb24iXX0gICDEAAAAQklOAERSQUNPAgIBAQAAAB44ADcDAA7f6qkv1VVRZfUo', + '1qKKAgEBEAH/AAABAAkDAAACAQEBAA0DEREXaQaJCEUEiQg1EwsuVMV2v4nwGqm5Tl7xigAAAFMDACwAZQAcaFlXAMoAAJS7Umpl', + 'CAIAyqteasnAqAAAAEt9IJaxqKVWGb4CAIBXSq0MAKDUyQAA/CcAQABAmVEBAADA1KdWGV4BKPOK/1QGgAWQDAAAAAD/DwAArQCe', + 'uRtytrkkcra5h4bmOwwAAAA=', +].join('') + +export const SITE_FLAG_MODEL_URL = `data:model/gltf-binary;base64,${SITE_FLAG_MODEL_BASE64}` diff --git a/packages/editor/src/components/tools/tool-manager.tsx b/packages/editor/src/components/tools/tool-manager.tsx index d9009345..d942d25f 100644 --- a/packages/editor/src/components/tools/tool-manager.tsx +++ b/packages/editor/src/components/tools/tool-manager.tsx @@ -90,8 +90,9 @@ export const ToolManager: React.FC = () => { | CeilingNode['id'] | undefined - // Show site boundary editor when in site phase (toggle controls entry/exit) - const showSiteBoundaryEditor = phase === 'site' + // Keep the site vertex flags available in select mode; the editor component + // switches to full polygon editing only after a flag activates site mode. + const showSiteBoundaryEditor = phase === 'site' || mode === 'select' // Show slab boundary editor when in structure/select mode with a slab selected (but not editing a hole) const showSlabBoundaryEditor = diff --git a/packages/editor/src/components/ui/action-menu/control-modes.tsx b/packages/editor/src/components/ui/action-menu/control-modes.tsx index 74d46f98..5cd84fcf 100644 --- a/packages/editor/src/components/ui/action-menu/control-modes.tsx +++ b/packages/editor/src/components/ui/action-menu/control-modes.tsx @@ -6,7 +6,7 @@ import { useViewer } from '@pascal-app/viewer' import { type LucideIcon, Trash2 } from 'lucide-react' import Image from 'next/image' import { cn } from './../../../lib/utils' -import useEditor from './../../../store/use-editor' +import useEditor, { selectSiteFloorplanContext } from './../../../store/use-editor' import { ActionButton } from './action-button' type ControlId = 'select' | 'box-select' | 'site-edit' | 'zone' | 'delete' @@ -34,7 +34,7 @@ const controls: ControlConfig[] = [ }, { id: 'site-edit', - imageSrc: '/icons/site.png', + imageSrc: '/icons/site-flag.png', label: 'Edit site', color: 'hover:bg-white/5', activeColor: 'bg-white/10 hover:bg-white/10', @@ -100,12 +100,8 @@ export function ControlModes() { setMode('select') setStructureLayer('elements') } else if (isGroundFloor) { - // Enter site editing — set state directly to preserve level selection. - // setPhase('site') calls viewer.resetSelection() which clears levelId, - // breaking the 2D floorplan (it needs a level to render the SVG). useEditor.setState({ phase: 'site', mode: 'select', tool: null, catalogCategory: null }) - // Clear object selection so the polygon editor handles receive pointer events - useViewer.getState().setSelection({ selectedIds: [] }) + selectSiteFloorplanContext() } return } diff --git a/packages/editor/src/components/ui/action-menu/structure-tools.tsx b/packages/editor/src/components/ui/action-menu/structure-tools.tsx index 76e7d6ef..30f897ee 100644 --- a/packages/editor/src/components/ui/action-menu/structure-tools.tsx +++ b/packages/editor/src/components/ui/action-menu/structure-tools.tsx @@ -23,6 +23,6 @@ export const tools: ToolConfig[] = [ { id: 'slab', iconSrc: '/icons/floor.png', label: 'Slab' }, { id: 'ceiling', iconSrc: '/icons/ceiling.png', label: 'Ceiling' }, { id: 'zone', iconSrc: '/icons/zone.png', label: 'Zone' }, - { id: 'spawn', iconSrc: '/icons/site.png', label: 'Spawn Point' }, + { id: 'spawn', iconSrc: '/icons/spawn-point.png', label: 'Spawn Point' }, { id: 'shelf', iconSrc: '/icons/shelf.png', label: 'Shelf' }, ] diff --git a/packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx b/packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx index 4bc8ffea..788054f2 100644 --- a/packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +++ b/packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx @@ -1569,7 +1569,7 @@ export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanel 'h-5 w-5 object-contain transition-all', phase !== 'site' && 'opacity-60 grayscale', )} - src="/icons/site.png" + src="/icons/site-flag.png" /> {siteNode.name || 'Site'} diff --git a/packages/editor/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx b/packages/editor/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx index 4ca4fe18..d973df1b 100644 --- a/packages/editor/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +++ b/packages/editor/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx @@ -50,7 +50,13 @@ export const SpawnTreeNode = memo(function SpawnTreeNode({ expanded={false} hasChildren={false} icon={ - + } isHovered={isHovered} isLast={isLast} diff --git a/packages/editor/src/lib/site-boundary.ts b/packages/editor/src/lib/site-boundary.ts new file mode 100644 index 00000000..2e68718d --- /dev/null +++ b/packages/editor/src/lib/site-boundary.ts @@ -0,0 +1 @@ +export const SITE_BOUNDARY_DRAG_LABEL = 'site-boundary' diff --git a/packages/editor/src/store/use-editor.tsx b/packages/editor/src/store/use-editor.tsx index 1d6d2079..d67a9b5b 100644 --- a/packages/editor/src/store/use-editor.tsx +++ b/packages/editor/src/store/use-editor.tsx @@ -2,6 +2,7 @@ import type { AssetInput } from '@pascal-app/core' import { + type AnyNode, type AnyNodeId, type BuildingNode, type CeilingNode, @@ -422,6 +423,10 @@ export const DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE: PersistedEditorLayoutState = const GRID_SNAP_STEPS: GridSnapStep[] = [0.5, 0.25, 0.1, 0.05] +type SelectDefaultBuildingAndLevelOptions = { + forceGroundLevel?: boolean +} + function normalizeModeForPhase(phase: Phase, mode: Mode | undefined): Mode { if (phase === 'site') { return 'select' @@ -558,49 +563,94 @@ export function hasCustomPersistedEditorUiState( ) } +function getDefaultLevelId( + buildingNode: BuildingNode, + nodes: Record, +): LevelNode['id'] | null { + const levels = buildingNode.children + .map((childId) => nodes[childId as AnyNodeId]) + .filter((node): node is LevelNode => node?.type === 'level') + + if (levels.length === 0) { + return null + } + + const groundLevel = levels.find((level) => level.level === 0) + if (groundLevel) { + return groundLevel.id + } + + const firstLevel = levels[0] + if (!firstLevel) { + return null + } + + let lowestLevel = firstLevel + for (const level of levels.slice(1)) { + if (level.level < lowestLevel.level) { + lowestLevel = level + } + } + + return lowestLevel.id +} + /** * Selects the first building and level 0 in the scene. * Safe to call any time — no-ops if already selected or scene is empty. */ -export function selectDefaultBuildingAndLevel() { +export function selectDefaultBuildingAndLevel(options: SelectDefaultBuildingAndLevelOptions = {}) { const viewer = useViewer.getState() const scene = useScene.getState() - let buildingId = viewer.selection.buildingId + const selectedBuilding = viewer.selection.buildingId + ? scene.nodes[viewer.selection.buildingId] + : null + let buildingNode = + selectedBuilding?.type === 'building' ? (selectedBuilding as BuildingNode) : null // If no building selected, find the first one from site's children - if (!buildingId) { + if (!buildingNode) { const siteNode = scene.rootNodeIds[0] ? scene.nodes[scene.rootNodeIds[0]] : null if (siteNode?.type === 'site') { - const firstBuilding = siteNode.children - .map((childId) => scene.nodes[childId as AnyNodeId]) - .find((node) => node?.type === 'building') - if (firstBuilding) { - buildingId = firstBuilding.id as BuildingNode['id'] - viewer.setSelection({ buildingId }) - } + buildingNode = + siteNode.children + .map((childId) => scene.nodes[childId as AnyNodeId]) + .find((node): node is BuildingNode => node?.type === 'building') ?? null } } - // If no level selected, find level 0 in the building - if (buildingId && !viewer.selection.levelId) { - const buildingNode = scene.nodes[buildingId] as BuildingNode - const level0Id = buildingNode.children.find((childId) => { - const levelNode = scene.nodes[childId] as LevelNode - return levelNode?.type === 'level' && levelNode.level === 0 - }) - if (level0Id) { - viewer.setSelection({ levelId: level0Id as LevelNode['id'] }) - } else { - // Fallback to first level if level 0 doesn't exist - const firstLevelId = buildingNode.children.find( - (childId) => scene.nodes[childId]?.type === 'level', - ) - if (firstLevelId) { - viewer.setSelection({ levelId: firstLevelId as LevelNode['id'] }) - } - } + if (!buildingNode) { + return } + + const selectedLevel = viewer.selection.levelId ? scene.nodes[viewer.selection.levelId] : null + const selectedLevelBelongsToBuilding = + selectedLevel?.type === 'level' && selectedLevel.parentId === buildingNode.id + const shouldSelectDefaultLevel = options.forceGroundLevel || !selectedLevelBelongsToBuilding + const defaultLevelId = shouldSelectDefaultLevel + ? getDefaultLevelId(buildingNode, scene.nodes as Record) + : null + + const selectionUpdate: Parameters[0] = {} + if (viewer.selection.buildingId !== buildingNode.id) { + selectionUpdate.buildingId = buildingNode.id + } + if (defaultLevelId) { + selectionUpdate.levelId = defaultLevelId + } + + if (Object.keys(selectionUpdate).length > 0) { + viewer.setSelection(selectionUpdate) + } +} + +export function selectSiteFloorplanContext() { + selectDefaultBuildingAndLevel({ forceGroundLevel: true }) + useViewer.getState().setSelection({ + selectedIds: [], + zoneId: null, + }) } const useEditor = create()( @@ -631,12 +681,9 @@ const useEditor = create()( set({ mode: 'select', tool: null, catalogCategory: null }) } - const viewer = useViewer.getState() - switch (phase) { case 'site': - // In Site mode, we zoom out and deselect specific levels/buildings - viewer.resetSelection() + selectSiteFloorplanContext() break case 'structure': diff --git a/packages/nodes/src/site/definition.ts b/packages/nodes/src/site/definition.ts index 70dd62b0..b8d8375e 100644 --- a/packages/nodes/src/site/definition.ts +++ b/packages/nodes/src/site/definition.ts @@ -39,7 +39,7 @@ export const siteDefinition: NodeDefinition = { presentation: { label: 'Site', description: 'The top-level container holding buildings, zones, and the property boundary.', - icon: { kind: 'url', src: '/icons/site.png' }, + icon: { kind: 'url', src: '/icons/site-flag.png' }, paletteSection: 'site', paletteOrder: 5, }, diff --git a/packages/nodes/src/site/renderer.tsx b/packages/nodes/src/site/renderer.tsx index 17c9315e..33e3ba34 100644 --- a/packages/nodes/src/site/renderer.tsx +++ b/packages/nodes/src/site/renderer.tsx @@ -4,6 +4,7 @@ import { type AnyNodeId, type SiteNode, type SlabNode, + useLiveNodeOverrides, useRegistry, useScene, } from '@pascal-app/core' @@ -51,6 +52,10 @@ export const SiteRenderer = ({ node }: { node: SiteNode }) => { useRegistry(node.id, 'site', ref) const bgColor = useViewer((state) => getSceneTheme(state.sceneTheme).ground) + const livePolygon = useLiveNodeOverrides( + (state) => (state.overrides.get(node.id)?.polygon as SiteNode['polygon'] | undefined) ?? null, + ) + const polygonPoints = livePolygon?.points ?? node.polygon?.points // Lit (not Basic) so the site ground receives the directional shadow — Basic // is unlit, which is why shadows used to stop dead at the slab edge. polygonOffset @@ -102,9 +107,9 @@ export const SiteRenderer = ({ node }: { node: SiteNode }) => { // Ground shape: site polygon with slab footprints punched as holes const groundShape = useMemo(() => { - if (!node?.polygon?.points || node.polygon.points.length < 3) return null + if (!polygonPoints || polygonPoints.length < 3) return null - const pts = node.polygon.points + const pts = polygonPoints const shape = new Shape() shape.moveTo(pts[0]![0], -pts[0]![1]) for (let i = 1; i < pts.length; i++) shape.lineTo(pts[i]![0], -pts[i]![1]) @@ -122,13 +127,13 @@ export const SiteRenderer = ({ node }: { node: SiteNode }) => { } return shape - }, [node?.polygon?.points, slabPolygons]) + }, [polygonPoints, slabPolygons]) // Create boundary line geometry const lineGeometry = useMemo(() => { - if (!node?.polygon?.points || node.polygon.points.length < 2) return null - return createBoundaryLineGeometry(node.polygon.points) - }, [node?.polygon?.points]) + if (!polygonPoints || polygonPoints.length < 2) return null + return createBoundaryLineGeometry(polygonPoints) + }, [polygonPoints]) const handlers = useNodeEvents(node, 'site') diff --git a/packages/nodes/src/spawn/__tests__/parity.test.ts b/packages/nodes/src/spawn/__tests__/parity.test.ts index 3c1d8d01..458b5ccd 100644 --- a/packages/nodes/src/spawn/__tests__/parity.test.ts +++ b/packages/nodes/src/spawn/__tests__/parity.test.ts @@ -66,7 +66,7 @@ describe('spawn definition', () => { ]) }) - test('floorplan uses indigo marker color and selected rotation affordance', () => { + test('floorplan uses footprint marker oriented to the spawn view and selected rotation affordance', () => { const spawn = SpawnNode.parse({ id: 'spawn_test1234567890ab', position: [1, 0, 2], @@ -102,8 +102,15 @@ describe('spawn definition', () => { }, } satisfies GeometryContext) + expect(geometry.kind).toBe('group') + const marker = geometry.kind === 'group' ? geometry.children[0] : null + expect(marker?.kind).toBe('group') + if (marker?.kind === 'group') { + expect(marker.transform?.rotate).toBe(-spawn.rotation) + } + const flat = flattenFloorplan(geometry) - expect(flat.some((entry) => entry.kind === 'polygon' && entry.fill === '#818cf8')).toBe(true) + expect(flat.some((entry) => entry.kind === 'path' && entry.stroke === '#818cf8')).toBe(true) expect(flat.some((entry) => entry.kind === 'rotate-arrow')).toBe(true) }) diff --git a/packages/nodes/src/spawn/definition.ts b/packages/nodes/src/spawn/definition.ts index f942b893..e5e7f41a 100644 --- a/packages/nodes/src/spawn/definition.ts +++ b/packages/nodes/src/spawn/definition.ts @@ -104,7 +104,7 @@ export const spawnDefinition: NodeDefinition = { presentation: { label: 'Spawn Point', description: 'Player or camera origin within a level. One per level.', - icon: { kind: 'url', src: '/icons/site.png' }, + icon: { kind: 'url', src: '/icons/spawn-point.png' }, paletteSection: 'structure', paletteOrder: 90, // bottom of structure list — matches legacy palette order }, diff --git a/packages/nodes/src/spawn/floorplan.ts b/packages/nodes/src/spawn/floorplan.ts index 2922369a..ac72b1ab 100644 --- a/packages/nodes/src/spawn/floorplan.ts +++ b/packages/nodes/src/spawn/floorplan.ts @@ -2,12 +2,45 @@ import type { FloorplanGeometry, FloorplanPoint, GeometryContext } from '@pascal import type { SpawnNode } from './schema' const SPAWN_COLOR = '#818cf8' +const SPAWN_MARKER_HIT_RADIUS = 0.52 +const FOOTPRINT_ICON_SCALE = 0.045 +const FOOTPRINT_ICON_CENTER = 12 const ROTATE_ARROW_CORNER_OFFSET = 0.22 +const iconPoint = (x: number, y: number) => + `${formatIconCoord(x - FOOTPRINT_ICON_CENTER)} ${formatIconCoord(y - FOOTPRINT_ICON_CENTER)}` +const iconY = (y: number) => formatIconCoord(y - FOOTPRINT_ICON_CENTER) +const iconX = (x: number) => formatIconCoord(x - FOOTPRINT_ICON_CENTER) +const iconArcRadius = formatIconCoord(2) + +const FOOTPRINT_LEFT_PATH = [ + `M ${iconPoint(4, 16)}`, + `V ${iconY(13.62)}`, + `C ${iconPoint(4, 11.5)} ${iconPoint(2.97, 10.5)} ${iconPoint(3, 8)}`, + `C ${iconPoint(3.03, 5.28)} ${iconPoint(4.49, 2)} ${iconPoint(7.5, 2)}`, + `C ${iconPoint(9.37, 2)} ${iconPoint(10, 3.8)} ${iconPoint(10, 5.5)}`, + `C ${iconPoint(10, 8.61)} ${iconPoint(8, 11.16)} ${iconPoint(8, 14.18)}`, + `V ${iconY(16)}`, + `A ${iconArcRadius} ${iconArcRadius} 0 1 1 ${iconPoint(4, 16)}`, + 'Z', +].join(' ') + +const FOOTPRINT_RIGHT_PATH = [ + `M ${iconPoint(20, 20)}`, + `V ${iconY(17.62)}`, + `C ${iconPoint(20, 15.5)} ${iconPoint(21.03, 14.5)} ${iconPoint(21, 12)}`, + `C ${iconPoint(20.97, 9.28)} ${iconPoint(19.51, 6)} ${iconPoint(16.5, 6)}`, + `C ${iconPoint(14.63, 6)} ${iconPoint(14, 7.8)} ${iconPoint(14, 9.5)}`, + `C ${iconPoint(14, 12.61)} ${iconPoint(16, 15.16)} ${iconPoint(16, 18.18)}`, + `V ${iconY(20)}`, + `A ${iconArcRadius} ${iconArcRadius} 0 1 0 ${iconPoint(20, 20)}`, + 'Z', +].join(' ') + /** - * 2D floor-plan marker for a spawn point. A small filled circle at the - * spawn's position, with a triangular arrow indicating the facing - * direction (rotation around Y, looking down at the X-Z plane). + * 2D floor-plan marker for a spawn point. Uses the same footprint icon + * as the walkthrough-mode control and rotates it toward the spawn's + * first-person starting view. * * Color matches the 3D renderer's indigo spawn material so the user * sees the same visual identity in both views. @@ -17,36 +50,63 @@ const ROTATE_ARROW_CORNER_OFFSET = 0.22 export function buildSpawnFloorplan(node: SpawnNode, ctx: GeometryContext): FloorplanGeometry { const [px, , pz] = node.position const ry = node.rotation + const planRotation = -ry const isSelected = ctx.viewState?.selected ?? false const children: FloorplanGeometry[] = [ { kind: 'group', - transform: { translate: [px, pz], rotate: ry }, + transform: { translate: [px, pz], rotate: planRotation }, children: [ - // Direction-pointing triangle, base centered at origin, tip in -Z - // (forward). Matches the 3D arrow's orientation. - { - kind: 'polygon', - points: [ - [0, -0.28], - [-0.18, 0.12], - [0.18, 0.12], - ], - fill: SPAWN_COLOR, - opacity: 0.85, - }, - // Spawn body marker — circle outline so the spawn is legible at - // small zoom levels where the triangle would shrink past visibility. { kind: 'circle', cx: 0, cy: 0, - r: 0.34, + r: SPAWN_MARKER_HIT_RADIUS, + fill: 'transparent', + pointerEvents: 'all', + }, + { + kind: 'path', + d: FOOTPRINT_LEFT_PATH, stroke: SPAWN_COLOR, - strokeWidth: 0.025, - fill: SPAWN_COLOR, - opacity: 0.18, + strokeWidth: 2, + vectorEffect: 'non-scaling-stroke', + strokeLinecap: 'round', + strokeLinejoin: 'round', + fill: 'none', + }, + { + kind: 'path', + d: FOOTPRINT_RIGHT_PATH, + stroke: SPAWN_COLOR, + strokeWidth: 2, + vectorEffect: 'non-scaling-stroke', + strokeLinecap: 'round', + strokeLinejoin: 'round', + fill: 'none', + }, + { + kind: 'line', + x1: iconX(16), + y1: iconY(17), + x2: iconX(20), + y2: iconY(17), + stroke: SPAWN_COLOR, + strokeWidth: 2, + vectorEffect: 'non-scaling-stroke', + strokeLinecap: 'round', + }, + { + kind: 'line', + x1: iconX(4), + y1: iconY(13), + x2: iconX(8), + y2: iconY(13), + stroke: SPAWN_COLOR, + strokeWidth: 2, + vectorEffect: 'non-scaling-stroke', + strokeLinecap: 'round', }, ], }, @@ -55,8 +115,8 @@ export function buildSpawnFloorplan(node: SpawnNode, ctx: GeometryContext): Floo if (isSelected) { const cornerLocalX = 0.34 + ROTATE_ARROW_CORNER_OFFSET const cornerLocalZ = 0.34 + ROTATE_ARROW_CORNER_OFFSET - const [cornerX, cornerZ] = rotatePlanVector(cornerLocalX, cornerLocalZ, ry) - const [radialX, radialZ] = rotatePlanVector(1, 1, ry) + const [cornerX, cornerZ] = rotatePlanVector(cornerLocalX, cornerLocalZ, planRotation) + const [radialX, radialZ] = rotatePlanVector(1, 1, planRotation) children.push({ kind: 'rotate-arrow', point: [px + cornerX, pz + cornerZ], @@ -77,3 +137,8 @@ function rotatePlanVector(x: number, y: number, rotation: number): FloorplanPoin const s = Math.sin(rotation) return [x * c - y * s, x * s + y * c] } + +function formatIconCoord(value: number): number { + const scaled = value * FOOTPRINT_ICON_SCALE + return Number(scaled.toFixed(4)) +} diff --git a/packages/nodes/src/spawn/panel.tsx b/packages/nodes/src/spawn/panel.tsx index b4352c0c..e8a29302 100644 --- a/packages/nodes/src/spawn/panel.tsx +++ b/packages/nodes/src/spawn/panel.tsx @@ -94,7 +94,12 @@ export default function SpawnPanel() { const storedRotationDegrees = Math.round((node.rotation * 180) / Math.PI) return ( - +