Add lower floor reference overlay

This commit is contained in:
sudhir
2026-04-30 14:15:24 +05:30
parent 8631622e60
commit 25927dea3b
3 changed files with 497 additions and 2 deletions
@@ -2,6 +2,7 @@
import { Icon } from '@iconify/react' import { Icon } from '@iconify/react'
import { import {
type AnyNode,
type AnyNodeId, type AnyNodeId,
type BuildingNode, type BuildingNode,
type CeilingNode, type CeilingNode,
@@ -57,6 +58,7 @@ import { createPortal } from 'react-dom'
import { import {
buildFloorplanItemEntry, buildFloorplanItemEntry,
buildFloorplanStairEntry as buildSharedFloorplanStairEntry, buildFloorplanStairEntry as buildSharedFloorplanStairEntry,
collectLevelDescendants,
getFloorplanWall as getSharedFloorplanWall, getFloorplanWall as getSharedFloorplanWall,
rotatePlanVector as rotateSharedPlanVector, rotatePlanVector as rotateSharedPlanVector,
type FloorplanNodeTransform as SharedFloorplanNodeTransform, type FloorplanNodeTransform as SharedFloorplanNodeTransform,
@@ -582,6 +584,15 @@ type FloorplanItemEntry = {
depth: number depth: number
} }
type ReferenceFloorData = {
ceilingPolygons: CeilingPolygonEntry[]
fenceEntries: FloorplanFenceEntry[]
itemEntries: FloorplanItemEntry[]
openingPolygons: OpeningPolygonEntry[]
slabPolygons: SlabPolygonEntry[]
wallPolygons: WallPolygonEntry[]
}
type FloorplanStairSegmentEntry = { type FloorplanStairSegmentEntry = {
centerLine: FloorplanLineSegment | null centerLine: FloorplanLineSegment | null
innerPoints: string innerPoints: string
@@ -3611,6 +3622,97 @@ function FloorplanGuideHandleHint({
) )
} }
const FloorplanReferenceFloorLayer = memo(function FloorplanReferenceFloorLayer({
data,
opacity,
}: {
data: ReferenceFloorData | null
opacity: number
}) {
if (!data) {
return null
}
const clampedOpacity = clamp(opacity, 0.1, 0.8)
return (
<g opacity={clampedOpacity} pointerEvents="none">
{data.slabPolygons.map(({ path, slab }) => (
<path
d={path}
fill="rgba(100, 116, 139, 0.14)"
fillRule="evenodd"
key={slab.id}
stroke="rgba(100, 116, 139, 0.45)"
strokeWidth={1.2}
vectorEffect="non-scaling-stroke"
/>
))}
{data.ceilingPolygons.map(({ ceiling, path }) => (
<path
d={path}
fill="rgba(245, 158, 11, 0.06)"
fillRule="evenodd"
key={ceiling.id}
stroke="rgba(245, 158, 11, 0.28)"
strokeDasharray="6 4"
strokeWidth={1}
vectorEffect="non-scaling-stroke"
/>
))}
{data.wallPolygons.map(({ polygon, points, wall }) =>
polygon.length >= 3 ? (
<polygon
fill="rgba(100, 116, 139, 0.18)"
key={wall.id}
points={points}
stroke="rgba(71, 85, 105, 0.7)"
strokeWidth={1.25}
vectorEffect="non-scaling-stroke"
/>
) : null,
)}
{data.fenceEntries.map(({ fence, path }) => (
<path
d={path}
fill="none"
key={fence.id}
stroke="rgba(71, 85, 105, 0.65)"
strokeDasharray="5 4"
strokeLinecap="round"
strokeWidth={1.5}
vectorEffect="non-scaling-stroke"
/>
))}
{data.openingPolygons.map(({ opening, points }) => (
<polygon
fill="rgba(255, 255, 255, 0.72)"
key={opening.id}
points={points}
stroke="rgba(51, 65, 85, 0.72)"
strokeWidth={1.1}
vectorEffect="non-scaling-stroke"
/>
))}
{data.itemEntries.map(({ item, points }) => (
<polygon
fill="rgba(71, 85, 105, 0.12)"
key={item.id}
points={points}
stroke="rgba(71, 85, 105, 0.5)"
strokeWidth={1}
vectorEffect="non-scaling-stroke"
/>
))}
</g>
)
})
const FloorplanGeometryLayer = memo(function FloorplanGeometryLayer({ const FloorplanGeometryLayer = memo(function FloorplanGeometryLayer({
canFocusGeometry, canFocusGeometry,
canSelectGeometry, canSelectGeometry,
@@ -6065,6 +6167,10 @@ export function FloorplanPanel() {
const [hoveredGuideCorner, setHoveredGuideCorner] = useState<GuideCorner | null>(null) const [hoveredGuideCorner, setHoveredGuideCorner] = useState<GuideCorner | null>(null)
const floorplanSelectionTool = useEditor((s) => s.floorplanSelectionTool) const floorplanSelectionTool = useEditor((s) => s.floorplanSelectionTool)
const setFloorplanSelectionTool = useEditor((s) => s.setFloorplanSelectionTool) const setFloorplanSelectionTool = useEditor((s) => s.setFloorplanSelectionTool)
const showReferenceFloor = useEditor((s) => s.showReferenceFloor)
const referenceFloorOffset = useEditor((s) => s.referenceFloorOffset)
const referenceFloorOpacity = useEditor((s) => s.referenceFloorOpacity)
const sceneNodes = useScene((state) => state.nodes)
const [floorplanMarqueeState, setFloorplanMarqueeState] = useState<FloorplanMarqueeState | null>( const [floorplanMarqueeState, setFloorplanMarqueeState] = useState<FloorplanMarqueeState | null>(
null, null,
) )
@@ -6658,6 +6764,174 @@ export function FloorplanPanel() {
] ]
}) })
}, [cursorPoint, floorplanItems, levelDescendantNodeById, movingFloorplanNodeRevision]) }, [cursorPoint, floorplanItems, levelDescendantNodeById, movingFloorplanNodeRevision])
const referenceFloorLevel = useMemo(() => {
if (!(showReferenceFloor && levelNode)) {
return null
}
const lowerLevels = floorplanLevels
.filter((floorLevel) => floorLevel.id !== levelNode.id && floorLevel.level < levelNode.level)
.sort((a, b) => b.level - a.level)
return lowerLevels[referenceFloorOffset - 1] ?? lowerLevels[0] ?? null
}, [floorplanLevels, levelNode, referenceFloorOffset, showReferenceFloor])
const referenceFloorData = useMemo<ReferenceFloorData | null>(() => {
if (!referenceFloorLevel) {
return null
}
const children = referenceFloorLevel.children
.map((childId) => sceneNodes[childId])
.filter((node): node is AnyNode => Boolean(node && node.visible !== false))
const referenceWalls = children.filter((node): node is WallNode => node.type === 'wall')
const referenceFences = children.filter((node): node is FenceNode => node.type === 'fence')
const referenceSlabs = children.filter((node): node is SlabNode => node.type === 'slab')
const referenceCeilings = children.filter(
(node): node is CeilingNode => node.type === 'ceiling',
)
const referenceDescendants = collectLevelDescendants(
referenceFloorLevel,
sceneNodes as Record<string, AnyNode>,
).filter((node) => node.visible !== false)
const referenceDescendantById = new Map(referenceDescendants.map((node) => [node.id, node]))
const referenceFloorplanWalls = referenceWalls.map(getFloorplanWall)
const referenceWallMiterData = calculateLevelMiters(referenceFloorplanWalls)
const referenceFloorplanWallById = new Map(
referenceFloorplanWalls.map((wall) => [wall.id, wall] as const),
)
const wallPolygons = referenceWalls.map((wall) => {
const floorplanWall = referenceFloorplanWallById.get(wall.id) ?? getFloorplanWall(wall)
const polygon = getWallPlanFootprint(floorplanWall, referenceWallMiterData)
return {
points: formatPolygonPoints(polygon),
polygon,
wall,
}
})
const openingPolygons = referenceDescendants.flatMap((node) => {
if (!(node.type === 'door' || node.type === 'window')) {
return []
}
const wall = referenceFloorplanWallById.get(node.parentId as WallNode['id'])
if (!wall) {
return []
}
const polygon = getOpeningFootprint(wall, node)
return [
{
opening: node,
points: formatPolygonPoints(polygon),
polygon,
},
]
})
const slabPolygons = referenceSlabs.flatMap((slab) => {
const polygon = toFloorplanPolygon(slab.polygon)
if (polygon.length < 3) {
return []
}
const holes = (slab.holes ?? [])
.map((hole) => toFloorplanPolygon(hole))
.filter((hole) => hole.length >= 3)
const visualPolygon = toFloorplanPolygon(getRenderableSlabPolygon(slab))
const visualHoles = holes
return [
{
slab,
polygon,
holes,
visualPolygon,
visualHoles,
path: formatPolygonPath(visualPolygon, visualHoles),
},
]
})
const ceilingPolygons = referenceCeilings.flatMap((ceiling) => {
const polygon = toFloorplanPolygon(ceiling.polygon)
if (polygon.length < 3) {
return []
}
const holes = (ceiling.holes ?? [])
.map((hole) => toFloorplanPolygon(hole))
.filter((hole) => hole.length >= 3)
return [
{
ceiling,
polygon,
holes,
path: formatPolygonPath(polygon, holes),
},
]
})
const fenceEntries = referenceFences.flatMap((fence) => {
const centerline = isCurvedWall(fence)
? sampleWallCenterline(fence, 24)
: [
{ x: fence.start[0], y: fence.start[1] },
{ x: fence.end[0], y: fence.end[1] },
]
const path = buildSvgPolylinePath(centerline)
if (!path) {
return []
}
return [{ fence, centerline, markerFrames: [], path }]
})
const transformCache = new Map<string, SharedFloorplanNodeTransform | null>()
const itemEntries = referenceDescendants.flatMap((node) => {
if (
!(
node.type === 'item' &&
node.asset.category !== 'door' &&
node.asset.category !== 'window'
)
) {
return []
}
const entry = buildFloorplanItemEntry(node, referenceDescendantById, transformCache)
if (!entry) {
return []
}
return [
{
dimensionPolygon: entry.dimensionPolygon,
item: entry.item,
points: formatPolygonPoints(entry.polygon),
polygon: entry.polygon,
usesRealMesh: entry.usesRealMesh,
center: entry.center,
rotation: entry.rotation,
width: entry.width,
depth: entry.depth,
},
]
})
return {
ceilingPolygons,
fenceEntries,
itemEntries,
openingPolygons,
slabPolygons,
wallPolygons,
}
}, [referenceFloorLevel, sceneNodes])
const hasPendingItemMeshFootprints = floorplanItemEntries.some((entry) => !entry.usesRealMesh) const hasPendingItemMeshFootprints = floorplanItemEntries.some((entry) => !entry.usesRealMesh)
const floorplanStairEntries = useMemo( const floorplanStairEntries = useMemo(
() => () =>
@@ -13901,6 +14175,11 @@ export function FloorplanPanel() {
showGrid={showGrid} showGrid={showGrid}
/> />
<FloorplanReferenceFloorLayer
data={referenceFloorData}
opacity={referenceFloorOpacity}
/>
<FloorplanGuideLayer <FloorplanGuideLayer
activeGuideInteractionGuideId={activeGuideInteractionGuideId} activeGuideInteractionGuideId={activeGuideInteractionGuideId}
activeGuideInteractionMode={activeGuideInteractionMode} activeGuideInteractionMode={activeGuideInteractionMode}
@@ -2,13 +2,14 @@
import { import {
type AnyNodeId, type AnyNodeId,
type BuildingNode,
type GuideNode, type GuideNode,
type LevelNode, type LevelNode,
type ScanNode, type ScanNode,
useScene, useScene,
} from '@pascal-app/core' } from '@pascal-app/core'
import { useViewer } from '@pascal-app/viewer' import { useViewer } from '@pascal-app/viewer'
import { Check, ChevronDown, Plus, Trash2 } from 'lucide-react' import { Check, ChevronDown, Eye, EyeOff, Layers2, Plus, Trash2 } from 'lucide-react'
import { useCallback, useRef, useState } from 'react' import { useCallback, useRef, useState } from 'react'
import { useShallow } from 'zustand/react/shallow' import { useShallow } from 'zustand/react/shallow'
import { createLocalGuideImage } from '../../../lib/local-guide-image' import { createLocalGuideImage } from '../../../lib/local-guide-image'
@@ -60,6 +61,28 @@ function useLevelScans(): ScanNode[] {
) )
} }
function useLowerReferenceLevels(): LevelNode[] {
const levelId = useViewer((s) => s.selection.levelId)
return useScene(
useShallow((state) => {
if (!levelId) return [] as LevelNode[]
const activeLevel = state.nodes[levelId]
if (!activeLevel || activeLevel.type !== 'level') return [] as LevelNode[]
const buildingId = activeLevel.parentId as BuildingNode['id'] | undefined
const building = buildingId ? state.nodes[buildingId] : null
if (!building || building.type !== 'building') return [] as LevelNode[]
return (building.children ?? [])
.map((id) => state.nodes[id])
.filter(
(node): node is LevelNode =>
node?.type === 'level' && node.id !== levelId && node.level < activeLevel.level,
)
.sort((a, b) => b.level - a.level)
}),
)
}
// ── Shared upload button for dropdowns ────────────────────────────────────── // ── Shared upload button for dropdowns ──────────────────────────────────────
function UploadButton({ onError }: { onError: (message: string | null) => void }) { function UploadButton({ onError }: { onError: (message: string | null) => void }) {
@@ -578,6 +601,156 @@ function ScansControl() {
) )
} }
function ReferenceFloorControl() {
const showReferenceFloor = useEditor((state) => state.showReferenceFloor)
const toggleReferenceFloor = useEditor((state) => state.toggleReferenceFloor)
const referenceFloorOffset = useEditor((state) => state.referenceFloorOffset)
const setReferenceFloorOffset = useEditor((state) => state.setReferenceFloorOffset)
const referenceFloorOpacity = useEditor((state) => state.referenceFloorOpacity)
const setReferenceFloorOpacity = useEditor((state) => state.setReferenceFloorOpacity)
const [isOpen, setIsOpen] = useState(false)
const lowerLevels = useLowerReferenceLevels()
const hasLowerLevels = lowerLevels.length > 0
const selectedLevel = lowerLevels[referenceFloorOffset - 1] ?? lowerLevels[0] ?? null
return (
<Popover onOpenChange={setIsOpen} open={isOpen}>
<div className="flex items-center">
<ActionButton
className={cn(
'rounded-r-none p-0',
showReferenceFloor && selectedLevel
? 'bg-white/15'
: 'opacity-60 grayscale hover:bg-white/5 hover:opacity-100 hover:grayscale-0',
)}
disabled={!hasLowerLevels}
label={
selectedLevel && showReferenceFloor
? `Reference floor: ${selectedLevel.name}`
: 'Reference floor'
}
onClick={() => {
if (hasLowerLevels) toggleReferenceFloor()
}}
size="icon"
variant="ghost"
>
<div className="relative">
<Layers2 className="h-4 w-4" />
<span className="absolute -right-1.5 -bottom-1 min-w-[14px] rounded-full bg-white/20 px-[3px] text-center font-medium text-[9px] text-white/70 leading-[14px]">
{lowerLevels.length}
</span>
</div>
</ActionButton>
<PopoverTrigger asChild>
<button
aria-expanded={isOpen}
aria-label="Reference floor settings"
className={cn(
'flex h-11 w-6 items-center justify-center rounded-r-lg transition-colors',
showReferenceFloor && selectedLevel
? isOpen
? 'bg-white/10'
: 'bg-white/5 hover:bg-white/8'
: isOpen
? 'bg-white/8'
: 'opacity-60 hover:bg-white/5 hover:opacity-100',
)}
disabled={!hasLowerLevels}
type="button"
>
<ChevronDown className={cn('h-3 w-3 transition-transform', isOpen && 'rotate-180')} />
</button>
</PopoverTrigger>
</div>
<PopoverContent
align="center"
className="w-72 rounded-xl border-border/45 bg-background/96 p-3 shadow-[0_14px_28px_-18px_rgba(15,23,42,0.55),0_6px_16px_-10px_rgba(15,23,42,0.2)] backdrop-blur-xl"
side="top"
sideOffset={14}
>
<div className="space-y-3">
<div className="flex items-center gap-2">
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-background/80">
<Layers2 className="h-4 w-4" />
</span>
<div className="min-w-0 flex-1">
<p className="font-medium text-foreground text-sm">Reference floor</p>
{selectedLevel && (
<p className="truncate text-muted-foreground text-xs">{selectedLevel.name}</p>
)}
</div>
<button
aria-label={showReferenceFloor ? 'Hide reference floor' : 'Show reference floor'}
className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-border/40 text-muted-foreground transition-colors hover:bg-white/10 hover:text-foreground disabled:pointer-events-none disabled:opacity-40"
disabled={!hasLowerLevels}
onClick={toggleReferenceFloor}
type="button"
>
{showReferenceFloor ? <Eye className="h-3.5 w-3.5" /> : <EyeOff className="h-3.5 w-3.5" />}
</button>
</div>
{hasLowerLevels ? (
<>
<div className="max-h-44 space-y-1 overflow-y-auto rounded-xl border border-border/45 bg-background/60 p-1.5">
{lowerLevels.map((level, index) => {
const isSelected = referenceFloorOffset === index + 1
return (
<button
className={cn(
'flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-sm transition-colors hover:bg-white/8',
isSelected && showReferenceFloor && 'bg-white/10 text-foreground',
)}
key={level.id}
onClick={() => {
setReferenceFloorOffset(index + 1)
if (!showReferenceFloor) {
toggleReferenceFloor()
}
}}
type="button"
>
<span
className={cn(
'h-3.5 w-3.5 rounded-full border',
isSelected && showReferenceFloor
? 'border-foreground bg-foreground'
: 'border-muted-foreground/35',
)}
/>
<span className="min-w-0 flex-1 truncate">{level.name}</span>
<span className="text-[10px] text-muted-foreground">
{index + 1} below
</span>
</button>
)
})}
</div>
<SliderControl
label="Opacity"
max={0.8}
min={0.1}
onChange={setReferenceFloorOpacity}
precision={2}
step={0.05}
value={referenceFloorOpacity}
/>
</>
) : (
<div className="rounded-xl border border-border/45 border-dashed bg-background/60 px-3 py-4 text-muted-foreground text-sm">
No lower floor available.
</div>
)}
</div>
</PopoverContent>
</Popover>
)
}
// ── Main ViewToggles ──────────────────────────────────────────────────────── // ── Main ViewToggles ────────────────────────────────────────────────────────
export function ViewToggles() { export function ViewToggles() {
@@ -588,6 +761,8 @@ export function ViewToggles() {
{/* Guides (toggle + dropdown) */} {/* Guides (toggle + dropdown) */}
<GuidesControl /> <GuidesControl />
<ReferenceFloorControl />
</div> </div>
) )
} }
@@ -599,6 +774,7 @@ export function SecondaryToggles() {
<GridSnapControl /> <GridSnapControl />
<ScansControl /> <ScansControl />
<GuidesControl /> <GuidesControl />
<ReferenceFloorControl />
</div> </div>
) )
} }
+41 -1
View File
@@ -96,7 +96,11 @@ export type MovingFenceEndpoint = {
endpoint: 'start' | 'end' endpoint: 'start' | 'end'
} }
export type MaterialTargetRole = WallSurfaceSide | StairSurfaceMaterialRole | RoofSurfaceMaterialRole | SingleSurfaceMaterialRole export type MaterialTargetRole =
| WallSurfaceSide
| StairSurfaceMaterialRole
| RoofSurfaceMaterialRole
| SingleSurfaceMaterialRole
export type SelectedMaterialTarget = { export type SelectedMaterialTarget = {
nodeId: AnyNodeId nodeId: AnyNodeId
@@ -197,6 +201,13 @@ type EditorState = {
setFloorplanSelectionTool: (tool: FloorplanSelectionTool) => void setFloorplanSelectionTool: (tool: FloorplanSelectionTool) => void
gridSnapStep: GridSnapStep gridSnapStep: GridSnapStep
setGridSnapStep: (step: GridSnapStep) => void setGridSnapStep: (step: GridSnapStep) => void
showReferenceFloor: boolean
toggleReferenceFloor: () => void
setShowReferenceFloor: (show: boolean) => void
referenceFloorOffset: number
setReferenceFloorOffset: (offset: number) => void
referenceFloorOpacity: number
setReferenceFloorOpacity: (opacity: number) => void
// First-person walkthrough mode (street view) // First-person walkthrough mode (street view)
isFirstPersonMode: boolean isFirstPersonMode: boolean
_viewModeBeforeFirstPerson: ViewMode | null _viewModeBeforeFirstPerson: ViewMode | null
@@ -226,6 +237,9 @@ type PersistedEditorLayoutState = Pick<
| 'splitOrientation' | 'splitOrientation'
| 'floorplanSelectionTool' | 'floorplanSelectionTool'
| 'gridSnapStep' | 'gridSnapStep'
| 'showReferenceFloor'
| 'referenceFloorOffset'
| 'referenceFloorOpacity'
> >
type PersistedEditorState = PersistedEditorUiState & PersistedEditorLayoutState type PersistedEditorState = PersistedEditorUiState & PersistedEditorLayoutState
@@ -245,6 +259,9 @@ export const DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE: PersistedEditorLayoutState =
splitOrientation: 'horizontal', splitOrientation: 'horizontal',
floorplanSelectionTool: 'click', floorplanSelectionTool: 'click',
gridSnapStep: 0.5, gridSnapStep: 0.5,
showReferenceFloor: false,
referenceFloorOffset: 1,
referenceFloorOpacity: 0.35,
} }
const GRID_SNAP_STEPS: GridSnapStep[] = [0.5, 0.25, 0.1, 0.05] const GRID_SNAP_STEPS: GridSnapStep[] = [0.5, 0.25, 0.1, 0.05]
@@ -354,6 +371,16 @@ function normalizePersistedEditorLayoutState(
gridSnapStep: GRID_SNAP_STEPS.includes(state?.gridSnapStep as GridSnapStep) gridSnapStep: GRID_SNAP_STEPS.includes(state?.gridSnapStep as GridSnapStep)
? (state?.gridSnapStep as GridSnapStep) ? (state?.gridSnapStep as GridSnapStep)
: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.gridSnapStep, : DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.gridSnapStep,
showReferenceFloor: state?.showReferenceFloor === true,
referenceFloorOffset:
typeof state?.referenceFloorOffset === 'number' && state.referenceFloorOffset >= 1
? Math.floor(state.referenceFloorOffset)
: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.referenceFloorOffset,
referenceFloorOpacity:
typeof state?.referenceFloorOpacity === 'number' &&
Number.isFinite(state.referenceFloorOpacity)
? Math.min(0.8, Math.max(0.1, state.referenceFloorOpacity))
: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.referenceFloorOpacity,
} }
} }
@@ -632,6 +659,16 @@ const useEditor = create<EditorState>()(
setFloorplanSelectionTool: (tool) => set({ floorplanSelectionTool: tool }), setFloorplanSelectionTool: (tool) => set({ floorplanSelectionTool: tool }),
gridSnapStep: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.gridSnapStep, gridSnapStep: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.gridSnapStep,
setGridSnapStep: (step) => set({ gridSnapStep: step }), setGridSnapStep: (step) => set({ gridSnapStep: step }),
showReferenceFloor: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.showReferenceFloor,
toggleReferenceFloor: () =>
set((state) => ({ showReferenceFloor: !state.showReferenceFloor })),
setShowReferenceFloor: (show) => set({ showReferenceFloor: show }),
referenceFloorOffset: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.referenceFloorOffset,
setReferenceFloorOffset: (offset) =>
set({ referenceFloorOffset: Math.max(1, Math.floor(offset)) }),
referenceFloorOpacity: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.referenceFloorOpacity,
setReferenceFloorOpacity: (opacity) =>
set({ referenceFloorOpacity: Math.min(0.8, Math.max(0.1, opacity)) }),
allowUndergroundCamera: false, allowUndergroundCamera: false,
setAllowUndergroundCamera: (enabled) => set({ allowUndergroundCamera: enabled }), setAllowUndergroundCamera: (enabled) => set({ allowUndergroundCamera: enabled }),
isFirstPersonMode: false, isFirstPersonMode: false,
@@ -702,6 +739,9 @@ const useEditor = create<EditorState>()(
splitOrientation: state.splitOrientation, splitOrientation: state.splitOrientation,
floorplanSelectionTool: state.floorplanSelectionTool, floorplanSelectionTool: state.floorplanSelectionTool,
gridSnapStep: state.gridSnapStep, gridSnapStep: state.gridSnapStep,
showReferenceFloor: state.showReferenceFloor,
referenceFloorOffset: state.referenceFloorOffset,
referenceFloorOpacity: state.referenceFloorOpacity,
}), }),
}, },
), ),