Add elevator floorplan overlay
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
type ColumnNode,
|
type ColumnNode,
|
||||||
calculateLevelMiters,
|
calculateLevelMiters,
|
||||||
DoorNode,
|
DoorNode,
|
||||||
|
type ElevatorNode,
|
||||||
emitter,
|
emitter,
|
||||||
type FenceNode,
|
type FenceNode,
|
||||||
type GridEvent,
|
type GridEvent,
|
||||||
@@ -28,6 +29,7 @@ import {
|
|||||||
type Point2D,
|
type Point2D,
|
||||||
type RoofNode,
|
type RoofNode,
|
||||||
type RoofSegmentNode,
|
type RoofSegmentNode,
|
||||||
|
resolveElevatorServiceLevelIds,
|
||||||
type SiteNode,
|
type SiteNode,
|
||||||
SlabNode,
|
SlabNode,
|
||||||
type SpawnNode,
|
type SpawnNode,
|
||||||
@@ -37,6 +39,7 @@ import {
|
|||||||
StairSegmentNode as StairSegmentNodeSchema,
|
StairSegmentNode as StairSegmentNodeSchema,
|
||||||
sampleWallCenterline,
|
sampleWallCenterline,
|
||||||
sceneRegistry,
|
sceneRegistry,
|
||||||
|
useInteractive,
|
||||||
useLiveTransforms,
|
useLiveTransforms,
|
||||||
useScene,
|
useScene,
|
||||||
type WallNode,
|
type WallNode,
|
||||||
@@ -631,6 +634,18 @@ type FloorplanColumnEntry = {
|
|||||||
polygon: Point2D[]
|
polygon: Point2D[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FloorplanElevatorEntry = {
|
||||||
|
center: Point2D
|
||||||
|
elevator: ElevatorNode
|
||||||
|
frontEdge: FloorplanLineSegment
|
||||||
|
frontNormal: Point2D
|
||||||
|
isCarOnLevel: boolean
|
||||||
|
isQueuedLevel: boolean
|
||||||
|
isTargetLevel: boolean
|
||||||
|
points: string
|
||||||
|
polygon: Point2D[]
|
||||||
|
}
|
||||||
|
|
||||||
type ReferenceFloorData = {
|
type ReferenceFloorData = {
|
||||||
ceilingPolygons: CeilingPolygonEntry[]
|
ceilingPolygons: CeilingPolygonEntry[]
|
||||||
columnEntries: ReferenceFloorColumnEntry[]
|
columnEntries: ReferenceFloorColumnEntry[]
|
||||||
@@ -6006,6 +6021,167 @@ const FloorplanFenceLayer = memo(function FloorplanFenceLayer({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const FloorplanElevatorLayer = memo(function FloorplanElevatorLayer({
|
||||||
|
canSelectElevators,
|
||||||
|
elevatorEntries,
|
||||||
|
highlightedIdSet,
|
||||||
|
hoveredElevatorId,
|
||||||
|
isDeleteMode,
|
||||||
|
onElevatorHoverChange,
|
||||||
|
onElevatorHoverEnter,
|
||||||
|
onElevatorSelect,
|
||||||
|
palette,
|
||||||
|
selectedIdSet,
|
||||||
|
wallSelectionHatchId,
|
||||||
|
}: {
|
||||||
|
canSelectElevators: boolean
|
||||||
|
elevatorEntries: FloorplanElevatorEntry[]
|
||||||
|
highlightedIdSet: ReadonlySet<string>
|
||||||
|
hoveredElevatorId: ElevatorNode['id'] | null
|
||||||
|
isDeleteMode: boolean
|
||||||
|
onElevatorHoverChange: (elevatorId: ElevatorNode['id'] | null) => void
|
||||||
|
onElevatorHoverEnter: (elevatorId: ElevatorNode['id']) => void
|
||||||
|
onElevatorSelect: (elevator: ElevatorNode, event: ReactMouseEvent<SVGElement>) => void
|
||||||
|
palette: FloorplanPalette
|
||||||
|
selectedIdSet: ReadonlySet<string>
|
||||||
|
wallSelectionHatchId: string
|
||||||
|
}) {
|
||||||
|
if (elevatorEntries.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g className="floorplan-elevator-layer">
|
||||||
|
{elevatorEntries.map((entry) => {
|
||||||
|
const { elevator } = entry
|
||||||
|
const isSelected = selectedIdSet.has(elevator.id)
|
||||||
|
const isHighlighted = highlightedIdSet.has(elevator.id)
|
||||||
|
const isHovered = hoveredElevatorId === elevator.id
|
||||||
|
const isDeleteHovered = isDeleteMode && isHovered
|
||||||
|
const isActive = isSelected || isHighlighted
|
||||||
|
const showChrome = isActive || isHovered
|
||||||
|
const fill = isDeleteHovered
|
||||||
|
? palette.deleteFill
|
||||||
|
: isActive
|
||||||
|
? `url(#${wallSelectionHatchId})`
|
||||||
|
: '#dbeafe'
|
||||||
|
const fillOpacity = isDeleteHovered ? 0.38 : isActive ? 0.84 : isHovered ? 0.78 : 0.64
|
||||||
|
const stroke = isDeleteHovered
|
||||||
|
? palette.deleteStroke
|
||||||
|
: isActive
|
||||||
|
? palette.selectedStroke
|
||||||
|
: isHovered
|
||||||
|
? palette.wallHoverStroke
|
||||||
|
: '#2563eb'
|
||||||
|
const doorStroke = isDeleteHovered
|
||||||
|
? palette.deleteStroke
|
||||||
|
: isActive
|
||||||
|
? palette.selectedStroke
|
||||||
|
: '#0284c7'
|
||||||
|
const centerX = toSvgX(entry.center.x)
|
||||||
|
const centerY = toSvgY(entry.center.y)
|
||||||
|
const frontCenter = {
|
||||||
|
x: (entry.frontEdge.start.x + entry.frontEdge.end.x) / 2,
|
||||||
|
y: (entry.frontEdge.start.y + entry.frontEdge.end.y) / 2,
|
||||||
|
}
|
||||||
|
const doorIndicatorEnd = {
|
||||||
|
x: frontCenter.x + entry.frontNormal.x * 0.34,
|
||||||
|
y: frontCenter.y + entry.frontNormal.y * 0.34,
|
||||||
|
}
|
||||||
|
const showCarMarker = entry.isCarOnLevel || entry.isTargetLevel || entry.isQueuedLevel
|
||||||
|
const carFill = entry.isCarOnLevel ? '#22c55e' : '#ffffff'
|
||||||
|
const carStroke = entry.isCarOnLevel ? '#15803d' : '#0ea5e9'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g
|
||||||
|
key={elevator.id}
|
||||||
|
onPointerEnter={
|
||||||
|
canSelectElevators ? () => onElevatorHoverEnter(elevator.id) : undefined
|
||||||
|
}
|
||||||
|
onPointerLeave={canSelectElevators ? () => onElevatorHoverChange(null) : undefined}
|
||||||
|
>
|
||||||
|
{showChrome ? (
|
||||||
|
<polygon
|
||||||
|
fill="none"
|
||||||
|
points={entry.points}
|
||||||
|
pointerEvents="none"
|
||||||
|
stroke={isDeleteHovered ? palette.deleteStroke : palette.selectedStroke}
|
||||||
|
strokeOpacity={isDeleteHovered ? 0.18 : isActive ? 0.22 : 0.16}
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={isActive ? '9' : '7'}
|
||||||
|
vectorEffect="non-scaling-stroke"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<polygon
|
||||||
|
fill={fill}
|
||||||
|
fillOpacity={fillOpacity}
|
||||||
|
points={entry.points}
|
||||||
|
pointerEvents="none"
|
||||||
|
stroke={stroke}
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={isActive ? '2.4' : isHovered ? '2.1' : '1.45'}
|
||||||
|
vectorEffect="non-scaling-stroke"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
pointerEvents="none"
|
||||||
|
stroke={doorStroke}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeWidth={isActive ? '3.2' : '2.4'}
|
||||||
|
vectorEffect="non-scaling-stroke"
|
||||||
|
x1={toSvgX(entry.frontEdge.start.x)}
|
||||||
|
x2={toSvgX(entry.frontEdge.end.x)}
|
||||||
|
y1={toSvgY(entry.frontEdge.start.y)}
|
||||||
|
y2={toSvgY(entry.frontEdge.end.y)}
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
pointerEvents="none"
|
||||||
|
stroke={doorStroke}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeOpacity={0.84}
|
||||||
|
strokeWidth="1.7"
|
||||||
|
vectorEffect="non-scaling-stroke"
|
||||||
|
x1={toSvgX(frontCenter.x)}
|
||||||
|
x2={toSvgX(doorIndicatorEnd.x)}
|
||||||
|
y1={toSvgY(frontCenter.y)}
|
||||||
|
y2={toSvgY(doorIndicatorEnd.y)}
|
||||||
|
/>
|
||||||
|
{showCarMarker ? (
|
||||||
|
<circle
|
||||||
|
cx={centerX}
|
||||||
|
cy={centerY}
|
||||||
|
fill={carFill}
|
||||||
|
fillOpacity={entry.isCarOnLevel ? 0.92 : 0.82}
|
||||||
|
pointerEvents="none"
|
||||||
|
r={entry.isCarOnLevel ? 0.14 : 0.11}
|
||||||
|
stroke={carStroke}
|
||||||
|
strokeDasharray={entry.isCarOnLevel ? undefined : '3 2'}
|
||||||
|
strokeWidth="1.8"
|
||||||
|
vectorEffect="non-scaling-stroke"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<polygon
|
||||||
|
fill="transparent"
|
||||||
|
onClick={
|
||||||
|
canSelectElevators
|
||||||
|
? (event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
onElevatorSelect(elevator, event)
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
points={entry.points}
|
||||||
|
pointerEvents={canSelectElevators ? 'all' : 'none'}
|
||||||
|
style={canSelectElevators ? { cursor: EDITOR_CURSOR } : undefined}
|
||||||
|
>
|
||||||
|
<title>{elevator.name || 'Elevator'}</title>
|
||||||
|
</polygon>
|
||||||
|
</g>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</g>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
// Renders an item's 2D floor-plan image (top-down view, object-fit:contain)
|
// Renders an item's 2D floor-plan image (top-down view, object-fit:contain)
|
||||||
// inside its footprint rectangle. Placed at the same scene position/rotation
|
// inside its footprint rectangle. Placed at the same scene position/rotation
|
||||||
// as the polygon so it lines up exactly.
|
// as the polygon so it lines up exactly.
|
||||||
@@ -7574,6 +7750,19 @@ export function FloorplanPanel() {
|
|||||||
walls,
|
walls,
|
||||||
zones,
|
zones,
|
||||||
} = useFloorplanSceneData({ buildingId, levelId })
|
} = useFloorplanSceneData({ buildingId, levelId })
|
||||||
|
const elevators = useScene(
|
||||||
|
useShallow((state) => {
|
||||||
|
const building = currentBuildingId ? state.nodes[currentBuildingId] : null
|
||||||
|
if (!building || building.type !== 'building') {
|
||||||
|
return [] as ElevatorNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
return building.children.flatMap((childId) => {
|
||||||
|
const node = state.nodes[childId]
|
||||||
|
return node?.type === 'elevator' && node.visible !== false ? [node] : []
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
const buildingRotationDeg = (buildingRotationY * 180) / Math.PI
|
const buildingRotationDeg = (buildingRotationY * 180) / Math.PI
|
||||||
const floorplanSceneRotationDeg = FLOORPLAN_VIEW_ROTATION_DEG - buildingRotationDeg
|
const floorplanSceneRotationDeg = FLOORPLAN_VIEW_ROTATION_DEG - buildingRotationDeg
|
||||||
|
|
||||||
@@ -7631,6 +7820,7 @@ export function FloorplanPanel() {
|
|||||||
const [hoveredItemId, setHoveredItemId] = useState<ItemNode['id'] | null>(null)
|
const [hoveredItemId, setHoveredItemId] = useState<ItemNode['id'] | null>(null)
|
||||||
const [hoveredSpawnId, setHoveredSpawnId] = useState<SpawnNode['id'] | null>(null)
|
const [hoveredSpawnId, setHoveredSpawnId] = useState<SpawnNode['id'] | null>(null)
|
||||||
const [hoveredStairId, setHoveredStairId] = useState<StairNode['id'] | null>(null)
|
const [hoveredStairId, setHoveredStairId] = useState<StairNode['id'] | null>(null)
|
||||||
|
const [hoveredElevatorId, setHoveredElevatorId] = useState<ElevatorNode['id'] | null>(null)
|
||||||
const [hoveredZoneId, setHoveredZoneId] = useState<ZoneNodeType['id'] | null>(null)
|
const [hoveredZoneId, setHoveredZoneId] = useState<ZoneNodeType['id'] | null>(null)
|
||||||
const [hoveredEndpointId, setHoveredEndpointId] = useState<string | null>(null)
|
const [hoveredEndpointId, setHoveredEndpointId] = useState<string | null>(null)
|
||||||
const [hoveredWallCurveHandleId, setHoveredWallCurveHandleId] = useState<string | null>(null)
|
const [hoveredWallCurveHandleId, setHoveredWallCurveHandleId] = useState<string | null>(null)
|
||||||
@@ -7655,6 +7845,29 @@ export function FloorplanPanel() {
|
|||||||
const [rotationModifierPressed, setRotationModifierPressed] = useState(false)
|
const [rotationModifierPressed, setRotationModifierPressed] = useState(false)
|
||||||
const [movingFloorplanNodeRevision, setMovingFloorplanNodeRevision] = useState(0)
|
const [movingFloorplanNodeRevision, setMovingFloorplanNodeRevision] = useState(0)
|
||||||
const movingFloorplanNodeRefreshFrameRef = useRef<number | null>(null)
|
const movingFloorplanNodeRefreshFrameRef = useRef<number | null>(null)
|
||||||
|
const elevatorIds = useMemo(() => elevators.map((elevator) => elevator.id), [elevators])
|
||||||
|
const elevatorRuntimeKey = useInteractive(
|
||||||
|
useCallback(
|
||||||
|
(state) =>
|
||||||
|
elevatorIds
|
||||||
|
.map((elevatorId) => {
|
||||||
|
const runtime = state.elevators[elevatorId]
|
||||||
|
if (!runtime) {
|
||||||
|
return `${elevatorId}:`
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
elevatorId,
|
||||||
|
runtime.currentLevelId ?? '',
|
||||||
|
runtime.targetLevelId ?? '',
|
||||||
|
runtime.phase,
|
||||||
|
runtime.queue.join(','),
|
||||||
|
].join(':')
|
||||||
|
})
|
||||||
|
.join('|'),
|
||||||
|
[elevatorIds],
|
||||||
|
),
|
||||||
|
)
|
||||||
const [stairBuildPreviewPoint, setStairBuildPreviewPoint] = useState<WallPlanPoint | null>(null)
|
const [stairBuildPreviewPoint, setStairBuildPreviewPoint] = useState<WallPlanPoint | null>(null)
|
||||||
const [stairBuildPreviewRotation, setStairBuildPreviewRotation] = useState(0)
|
const [stairBuildPreviewRotation, setStairBuildPreviewRotation] = useState(0)
|
||||||
const [isPanning, setIsPanning] = useState(false)
|
const [isPanning, setIsPanning] = useState(false)
|
||||||
@@ -8281,6 +8494,68 @@ export function FloorplanPanel() {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
}, [cursorPoint, floorplanItems, levelDescendantNodeById, movingFloorplanNodeRevision])
|
}, [cursorPoint, floorplanItems, levelDescendantNodeById, movingFloorplanNodeRevision])
|
||||||
|
const floorplanElevatorEntries = useMemo<FloorplanElevatorEntry[]>(() => {
|
||||||
|
if (!levelNode) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodes = useScene.getState().nodes
|
||||||
|
const interactiveElevators = useInteractive.getState().elevators
|
||||||
|
|
||||||
|
return elevators.flatMap((elevator) => {
|
||||||
|
const serviceLevelIds = resolveElevatorServiceLevelIds(elevator, nodes)
|
||||||
|
if (!serviceLevelIds.includes(levelNode.id)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const live = useLiveTransforms.getState().get(elevator.id)
|
||||||
|
const position = live?.position ?? elevator.position
|
||||||
|
const rotation = live?.rotation ?? elevator.rotation
|
||||||
|
const center = { x: position[0], y: position[2] }
|
||||||
|
const halfWidth = Math.max(0.1, elevator.width / 2)
|
||||||
|
const halfDepth = Math.max(0.1, elevator.depth / 2)
|
||||||
|
const footprintCorners: Array<readonly [number, number]> = [
|
||||||
|
[-halfWidth, -halfDepth],
|
||||||
|
[halfWidth, -halfDepth],
|
||||||
|
[halfWidth, halfDepth],
|
||||||
|
[-halfWidth, halfDepth],
|
||||||
|
]
|
||||||
|
const polygon = footprintCorners.map(([localX, localY]) => {
|
||||||
|
const [offsetX, offsetY] = rotatePlanVector(localX, localY, rotation)
|
||||||
|
return {
|
||||||
|
x: center.x + offsetX,
|
||||||
|
y: center.y + offsetY,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const frontStart = polygon[0]
|
||||||
|
const frontEnd = polygon[1]
|
||||||
|
if (!(frontStart && frontEnd)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const [frontNormalX, frontNormalY] = rotatePlanVector(0, -1, rotation)
|
||||||
|
const runtime = interactiveElevators[elevator.id]
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
center,
|
||||||
|
elevator,
|
||||||
|
frontEdge: {
|
||||||
|
start: frontStart,
|
||||||
|
end: frontEnd,
|
||||||
|
},
|
||||||
|
frontNormal: {
|
||||||
|
x: frontNormalX,
|
||||||
|
y: frontNormalY,
|
||||||
|
},
|
||||||
|
isCarOnLevel: runtime?.currentLevelId === levelNode.id,
|
||||||
|
isQueuedLevel: runtime?.queue.includes(levelNode.id) ?? false,
|
||||||
|
isTargetLevel: runtime?.targetLevelId === levelNode.id,
|
||||||
|
points: formatPolygonPoints(polygon),
|
||||||
|
polygon,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}, [elevatorRuntimeKey, elevators, levelNode, movingFloorplanNodeRevision])
|
||||||
const referenceFloorLevel = useMemo(() => {
|
const referenceFloorLevel = useMemo(() => {
|
||||||
if (!(showReferenceFloor && levelNode)) {
|
if (!(showReferenceFloor && levelNode)) {
|
||||||
return null
|
return null
|
||||||
@@ -9114,6 +9389,7 @@ export function FloorplanPanel() {
|
|||||||
!movingFenceEndpoint &&
|
!movingFenceEndpoint &&
|
||||||
isFloorplanStructureContextActive) ||
|
isFloorplanStructureContextActive) ||
|
||||||
isDeleteMode
|
isDeleteMode
|
||||||
|
const canSelectFloorplanElevators = canSelectFloorplanStairs
|
||||||
const canSelectFloorplanSpawns = canSelectFloorplanStairs
|
const canSelectFloorplanSpawns = canSelectFloorplanStairs
|
||||||
const canSelectFloorplanItems =
|
const canSelectFloorplanItems =
|
||||||
(mode === 'select' &&
|
(mode === 'select' &&
|
||||||
@@ -9788,6 +10064,7 @@ export function FloorplanPanel() {
|
|||||||
...(visibleSitePolygon ? visibleSitePolygon.polygon : []),
|
...(visibleSitePolygon ? visibleSitePolygon.polygon : []),
|
||||||
...displayCeilingPolygons.flatMap((entry) => entry.polygon),
|
...displayCeilingPolygons.flatMap((entry) => entry.polygon),
|
||||||
...displaySlabPolygons.flatMap((entry) => entry.polygon),
|
...displaySlabPolygons.flatMap((entry) => entry.polygon),
|
||||||
|
...floorplanElevatorEntries.flatMap((entry) => entry.polygon),
|
||||||
...floorplanFenceEntries.flatMap((entry) => entry.centerline),
|
...floorplanFenceEntries.flatMap((entry) => entry.centerline),
|
||||||
...floorplanItemEntries.flatMap((entry) => entry.polygon),
|
...floorplanItemEntries.flatMap((entry) => entry.polygon),
|
||||||
...floorplanRoofEntries.flatMap((entry) =>
|
...floorplanRoofEntries.flatMap((entry) =>
|
||||||
@@ -9835,6 +10112,7 @@ export function FloorplanPanel() {
|
|||||||
}, [
|
}, [
|
||||||
displayCeilingPolygons,
|
displayCeilingPolygons,
|
||||||
displaySlabPolygons,
|
displaySlabPolygons,
|
||||||
|
floorplanElevatorEntries,
|
||||||
floorplanFenceEntries,
|
floorplanFenceEntries,
|
||||||
floorplanItemEntries,
|
floorplanItemEntries,
|
||||||
floorplanRoofEntries,
|
floorplanRoofEntries,
|
||||||
@@ -13047,6 +13325,7 @@ export function FloorplanPanel() {
|
|||||||
columnPolygons: floorplanColumnEntries,
|
columnPolygons: floorplanColumnEntries,
|
||||||
displaySlabPolygons,
|
displaySlabPolygons,
|
||||||
displayWallPolygons,
|
displayWallPolygons,
|
||||||
|
floorplanElevatorEntries,
|
||||||
floorplanItemEntries,
|
floorplanItemEntries,
|
||||||
floorplanOpeningHitTolerance,
|
floorplanOpeningHitTolerance,
|
||||||
floorplanRoofEntries,
|
floorplanRoofEntries,
|
||||||
@@ -13425,6 +13704,14 @@ export function FloorplanPanel() {
|
|||||||
[syncDeleteHoveredId],
|
[syncDeleteHoveredId],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleElevatorHoverChange = useCallback(
|
||||||
|
(elevatorId: ElevatorNode['id'] | null) => {
|
||||||
|
setHoveredElevatorId(elevatorId)
|
||||||
|
syncDeleteHoveredId(elevatorId)
|
||||||
|
},
|
||||||
|
[syncDeleteHoveredId],
|
||||||
|
)
|
||||||
|
|
||||||
const handleZoneHoverChange = useCallback(
|
const handleZoneHoverChange = useCallback(
|
||||||
(zoneId: ZoneNodeType['id'] | null) => {
|
(zoneId: ZoneNodeType['id'] | null) => {
|
||||||
setHoveredZoneId(zoneId)
|
setHoveredZoneId(zoneId)
|
||||||
@@ -13440,11 +13727,13 @@ export function FloorplanPanel() {
|
|||||||
handleSlabHoverChange(null)
|
handleSlabHoverChange(null)
|
||||||
handleCeilingHoverChange(null)
|
handleCeilingHoverChange(null)
|
||||||
handleStairHoverChange(null)
|
handleStairHoverChange(null)
|
||||||
|
handleElevatorHoverChange(null)
|
||||||
handleSpawnHoverChange(null)
|
handleSpawnHoverChange(null)
|
||||||
handleZoneHoverChange(null)
|
handleZoneHoverChange(null)
|
||||||
handleItemHoverChange(itemId)
|
handleItemHoverChange(itemId)
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
handleElevatorHoverChange,
|
||||||
handleFenceHoverChange,
|
handleFenceHoverChange,
|
||||||
handleItemHoverChange,
|
handleItemHoverChange,
|
||||||
handleOpeningHoverChange,
|
handleOpeningHoverChange,
|
||||||
@@ -13464,11 +13753,13 @@ export function FloorplanPanel() {
|
|||||||
handleSlabHoverChange(null)
|
handleSlabHoverChange(null)
|
||||||
handleCeilingHoverChange(null)
|
handleCeilingHoverChange(null)
|
||||||
handleStairHoverChange(null)
|
handleStairHoverChange(null)
|
||||||
|
handleElevatorHoverChange(null)
|
||||||
handleSpawnHoverChange(null)
|
handleSpawnHoverChange(null)
|
||||||
handleZoneHoverChange(null)
|
handleZoneHoverChange(null)
|
||||||
handleFenceHoverChange(fenceId)
|
handleFenceHoverChange(fenceId)
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
handleElevatorHoverChange,
|
||||||
handleFenceHoverChange,
|
handleFenceHoverChange,
|
||||||
handleItemHoverChange,
|
handleItemHoverChange,
|
||||||
handleOpeningHoverChange,
|
handleOpeningHoverChange,
|
||||||
@@ -13489,10 +13780,12 @@ export function FloorplanPanel() {
|
|||||||
handleCeilingHoverChange(null)
|
handleCeilingHoverChange(null)
|
||||||
handleWallHoverChange(null)
|
handleWallHoverChange(null)
|
||||||
handleSpawnHoverChange(null)
|
handleSpawnHoverChange(null)
|
||||||
|
handleElevatorHoverChange(null)
|
||||||
handleZoneHoverChange(null)
|
handleZoneHoverChange(null)
|
||||||
handleStairHoverChange(stairId)
|
handleStairHoverChange(stairId)
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
handleElevatorHoverChange,
|
||||||
handleFenceHoverChange,
|
handleFenceHoverChange,
|
||||||
handleItemHoverChange,
|
handleItemHoverChange,
|
||||||
handleOpeningHoverChange,
|
handleOpeningHoverChange,
|
||||||
@@ -13513,11 +13806,39 @@ export function FloorplanPanel() {
|
|||||||
handleCeilingHoverChange(null)
|
handleCeilingHoverChange(null)
|
||||||
handleWallHoverChange(null)
|
handleWallHoverChange(null)
|
||||||
handleStairHoverChange(null)
|
handleStairHoverChange(null)
|
||||||
|
handleElevatorHoverChange(null)
|
||||||
handleZoneHoverChange(null)
|
handleZoneHoverChange(null)
|
||||||
handleSpawnHoverChange(spawnId)
|
handleSpawnHoverChange(spawnId)
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
handleCeilingHoverChange,
|
handleCeilingHoverChange,
|
||||||
|
handleElevatorHoverChange,
|
||||||
|
handleFenceHoverChange,
|
||||||
|
handleItemHoverChange,
|
||||||
|
handleOpeningHoverChange,
|
||||||
|
handleSlabHoverChange,
|
||||||
|
handleSpawnHoverChange,
|
||||||
|
handleStairHoverChange,
|
||||||
|
handleWallHoverChange,
|
||||||
|
handleZoneHoverChange,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
const handleFloorplanElevatorHoverEnter = useCallback(
|
||||||
|
(elevatorId: ElevatorNode['id']) => {
|
||||||
|
handleItemHoverChange(null)
|
||||||
|
handleFenceHoverChange(null)
|
||||||
|
handleOpeningHoverChange(null)
|
||||||
|
handleSlabHoverChange(null)
|
||||||
|
handleCeilingHoverChange(null)
|
||||||
|
handleWallHoverChange(null)
|
||||||
|
handleStairHoverChange(null)
|
||||||
|
handleSpawnHoverChange(null)
|
||||||
|
handleZoneHoverChange(null)
|
||||||
|
handleElevatorHoverChange(elevatorId)
|
||||||
|
},
|
||||||
|
[
|
||||||
|
handleCeilingHoverChange,
|
||||||
|
handleElevatorHoverChange,
|
||||||
handleFenceHoverChange,
|
handleFenceHoverChange,
|
||||||
handleItemHoverChange,
|
handleItemHoverChange,
|
||||||
handleOpeningHoverChange,
|
handleOpeningHoverChange,
|
||||||
@@ -13615,6 +13936,7 @@ export function FloorplanPanel() {
|
|||||||
| OpeningNode['id']
|
| OpeningNode['id']
|
||||||
| SlabNode['id']
|
| SlabNode['id']
|
||||||
| CeilingNode['id']
|
| CeilingNode['id']
|
||||||
|
| ElevatorNode['id']
|
||||||
| SpawnNode['id']
|
| SpawnNode['id']
|
||||||
| StairNode['id']
|
| StairNode['id']
|
||||||
| ZoneNodeType['id'],
|
| ZoneNodeType['id'],
|
||||||
@@ -13629,6 +13951,7 @@ export function FloorplanPanel() {
|
|||||||
node.type === 'ceiling' ||
|
node.type === 'ceiling' ||
|
||||||
node.type === 'door' ||
|
node.type === 'door' ||
|
||||||
node.type === 'window' ||
|
node.type === 'window' ||
|
||||||
|
node.type === 'elevator' ||
|
||||||
node.type === 'item' ||
|
node.type === 'item' ||
|
||||||
node.type === 'spawn' ||
|
node.type === 'spawn' ||
|
||||||
node.type === 'stair' ||
|
node.type === 'stair' ||
|
||||||
@@ -13852,6 +14175,12 @@ export function FloorplanPanel() {
|
|||||||
},
|
},
|
||||||
[emitFloorplanNodeClick],
|
[emitFloorplanNodeClick],
|
||||||
)
|
)
|
||||||
|
const handleElevatorSelect = useCallback(
|
||||||
|
(elevator: ElevatorNode, event: ReactMouseEvent<SVGElement>) => {
|
||||||
|
emitFloorplanNodeClick(elevator.id, 'click', event)
|
||||||
|
},
|
||||||
|
[emitFloorplanNodeClick],
|
||||||
|
)
|
||||||
const handleZoneLabelClick = useCallback(
|
const handleZoneLabelClick = useCallback(
|
||||||
(zoneId: ZoneNodeType['id'], _event: ReactMouseEvent<SVGElement>) => {
|
(zoneId: ZoneNodeType['id'], _event: ReactMouseEvent<SVGElement>) => {
|
||||||
const currentZoneId = useViewer.getState().selection.zoneId
|
const currentZoneId = useViewer.getState().selection.zoneId
|
||||||
@@ -15648,6 +15977,7 @@ export function FloorplanPanel() {
|
|||||||
handleCeilingHoverChange(null)
|
handleCeilingHoverChange(null)
|
||||||
handleSpawnHoverChange(null)
|
handleSpawnHoverChange(null)
|
||||||
handleStairHoverChange(null)
|
handleStairHoverChange(null)
|
||||||
|
handleElevatorHoverChange(null)
|
||||||
handleZoneHoverChange(null)
|
handleZoneHoverChange(null)
|
||||||
setHoveredEndpointId(null)
|
setHoveredEndpointId(null)
|
||||||
setHoveredSiteHandleId(null)
|
setHoveredSiteHandleId(null)
|
||||||
@@ -15661,6 +15991,7 @@ export function FloorplanPanel() {
|
|||||||
}, [
|
}, [
|
||||||
emitFloorplanWallLeave,
|
emitFloorplanWallLeave,
|
||||||
handleCeilingHoverChange,
|
handleCeilingHoverChange,
|
||||||
|
handleElevatorHoverChange,
|
||||||
handleItemHoverChange,
|
handleItemHoverChange,
|
||||||
handleOpeningHoverChange,
|
handleOpeningHoverChange,
|
||||||
handleSlabHoverChange,
|
handleSlabHoverChange,
|
||||||
@@ -15772,6 +16103,7 @@ export function FloorplanPanel() {
|
|||||||
handleSlabHoverChange(null)
|
handleSlabHoverChange(null)
|
||||||
handleSpawnHoverChange(null)
|
handleSpawnHoverChange(null)
|
||||||
handleStairHoverChange(null)
|
handleStairHoverChange(null)
|
||||||
|
handleElevatorHoverChange(null)
|
||||||
handleZoneHoverChange(null)
|
handleZoneHoverChange(null)
|
||||||
setHoveredEndpointId(null)
|
setHoveredEndpointId(null)
|
||||||
floorplanMarqueeSnapPointRef.current = snappedPoint
|
floorplanMarqueeSnapPointRef.current = snappedPoint
|
||||||
@@ -15788,6 +16120,7 @@ export function FloorplanPanel() {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
getPlanPointFromClientPoint,
|
getPlanPointFromClientPoint,
|
||||||
|
handleElevatorHoverChange,
|
||||||
handleItemHoverChange,
|
handleItemHoverChange,
|
||||||
handleOpeningHoverChange,
|
handleOpeningHoverChange,
|
||||||
handleSlabHoverChange,
|
handleSlabHoverChange,
|
||||||
@@ -16528,6 +16861,20 @@ export function FloorplanPanel() {
|
|||||||
zonePolygons={visibleZonePolygons}
|
zonePolygons={visibleZonePolygons}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FloorplanElevatorLayer
|
||||||
|
canSelectElevators={canSelectFloorplanElevators}
|
||||||
|
elevatorEntries={floorplanElevatorEntries}
|
||||||
|
highlightedIdSet={highlightedFloorplanIdSet}
|
||||||
|
hoveredElevatorId={hoveredElevatorId}
|
||||||
|
isDeleteMode={isDeleteMode}
|
||||||
|
onElevatorHoverChange={handleElevatorHoverChange}
|
||||||
|
onElevatorHoverEnter={handleFloorplanElevatorHoverEnter}
|
||||||
|
onElevatorSelect={handleElevatorSelect}
|
||||||
|
palette={palette}
|
||||||
|
selectedIdSet={selectedIdSet}
|
||||||
|
wallSelectionHatchId={wallSelectionHatchId}
|
||||||
|
/>
|
||||||
|
|
||||||
<FloorplanNodeLayer
|
<FloorplanNodeLayer
|
||||||
canFocusItems={canFocusFloorplanItems}
|
canFocusItems={canFocusFloorplanItems}
|
||||||
canFocusSpawns={canFocusFloorplanSpawns}
|
canFocusSpawns={canFocusFloorplanSpawns}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
CeilingNode,
|
CeilingNode,
|
||||||
ColumnNode,
|
ColumnNode,
|
||||||
DoorNode,
|
DoorNode,
|
||||||
|
ElevatorNode,
|
||||||
ItemNode,
|
ItemNode,
|
||||||
Point2D,
|
Point2D,
|
||||||
RoofNode,
|
RoofNode,
|
||||||
@@ -52,6 +53,11 @@ type ColumnPolygonEntry = {
|
|||||||
polygon: Point2D[]
|
polygon: Point2D[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ElevatorPolygonEntry = {
|
||||||
|
elevator: ElevatorNode
|
||||||
|
polygon: Point2D[]
|
||||||
|
}
|
||||||
|
|
||||||
type FloorplanRoofEntry = {
|
type FloorplanRoofEntry = {
|
||||||
roof: RoofNode
|
roof: RoofNode
|
||||||
segments: Array<{
|
segments: Array<{
|
||||||
@@ -81,6 +87,7 @@ type UseFloorplanHitTestingArgs = {
|
|||||||
columnPolygons: ColumnPolygonEntry[]
|
columnPolygons: ColumnPolygonEntry[]
|
||||||
displaySlabPolygons: SlabPolygonEntry[]
|
displaySlabPolygons: SlabPolygonEntry[]
|
||||||
displayWallPolygons: WallPolygonEntry[]
|
displayWallPolygons: WallPolygonEntry[]
|
||||||
|
floorplanElevatorEntries: ElevatorPolygonEntry[]
|
||||||
floorplanItemEntries: FloorplanItemEntry[]
|
floorplanItemEntries: FloorplanItemEntry[]
|
||||||
floorplanOpeningHitTolerance: number
|
floorplanOpeningHitTolerance: number
|
||||||
floorplanRoofEntries: FloorplanRoofEntry[]
|
floorplanRoofEntries: FloorplanRoofEntry[]
|
||||||
@@ -98,6 +105,7 @@ export function useFloorplanHitTesting({
|
|||||||
columnPolygons,
|
columnPolygons,
|
||||||
displaySlabPolygons,
|
displaySlabPolygons,
|
||||||
displayWallPolygons,
|
displayWallPolygons,
|
||||||
|
floorplanElevatorEntries,
|
||||||
floorplanItemEntries,
|
floorplanItemEntries,
|
||||||
floorplanOpeningHitTolerance,
|
floorplanOpeningHitTolerance,
|
||||||
floorplanRoofEntries,
|
floorplanRoofEntries,
|
||||||
@@ -121,6 +129,7 @@ export function useFloorplanHitTesting({
|
|||||||
openings: openingsPolygons,
|
openings: openingsPolygons,
|
||||||
roofs: floorplanRoofEntries,
|
roofs: floorplanRoofEntries,
|
||||||
stairs: floorplanStairEntries,
|
stairs: floorplanStairEntries,
|
||||||
|
elevators: floorplanElevatorEntries,
|
||||||
walls: displayWallPolygons,
|
walls: displayWallPolygons,
|
||||||
slabs: displaySlabPolygons,
|
slabs: displaySlabPolygons,
|
||||||
openingHitTolerance: floorplanOpeningHitTolerance,
|
openingHitTolerance: floorplanOpeningHitTolerance,
|
||||||
@@ -135,6 +144,7 @@ export function useFloorplanHitTesting({
|
|||||||
displaySlabPolygons,
|
displaySlabPolygons,
|
||||||
displayWallPolygons,
|
displayWallPolygons,
|
||||||
floorplanItemEntries,
|
floorplanItemEntries,
|
||||||
|
floorplanElevatorEntries,
|
||||||
floorplanOpeningHitTolerance,
|
floorplanOpeningHitTolerance,
|
||||||
floorplanRoofEntries,
|
floorplanRoofEntries,
|
||||||
floorplanStairEntries,
|
floorplanStairEntries,
|
||||||
@@ -160,6 +170,7 @@ export function useFloorplanHitTesting({
|
|||||||
roofs: floorplanRoofEntries,
|
roofs: floorplanRoofEntries,
|
||||||
slabs: displaySlabPolygons,
|
slabs: displaySlabPolygons,
|
||||||
columns: columnPolygons,
|
columns: columnPolygons,
|
||||||
|
elevators: floorplanElevatorEntries,
|
||||||
stairs: floorplanStairEntries,
|
stairs: floorplanStairEntries,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
@@ -168,6 +179,7 @@ export function useFloorplanHitTesting({
|
|||||||
displaySlabPolygons,
|
displaySlabPolygons,
|
||||||
displayWallPolygons,
|
displayWallPolygons,
|
||||||
floorplanItemEntries,
|
floorplanItemEntries,
|
||||||
|
floorplanElevatorEntries,
|
||||||
floorplanRoofEntries,
|
floorplanRoofEntries,
|
||||||
floorplanStairEntries,
|
floorplanStairEntries,
|
||||||
isFloorplanItemContextActive,
|
isFloorplanItemContextActive,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type {
|
|||||||
CeilingNode,
|
CeilingNode,
|
||||||
ColumnNode,
|
ColumnNode,
|
||||||
DoorNode,
|
DoorNode,
|
||||||
|
ElevatorNode,
|
||||||
ItemNode,
|
ItemNode,
|
||||||
Point2D,
|
Point2D,
|
||||||
RoofNode,
|
RoofNode,
|
||||||
@@ -59,6 +60,11 @@ type ColumnEntry = {
|
|||||||
polygon: Point2D[]
|
polygon: Point2D[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ElevatorEntry = {
|
||||||
|
elevator: ElevatorNode
|
||||||
|
polygon: Point2D[]
|
||||||
|
}
|
||||||
|
|
||||||
type RoofEntry = {
|
type RoofEntry = {
|
||||||
roof: RoofNode
|
roof: RoofNode
|
||||||
segments: Array<{
|
segments: Array<{
|
||||||
@@ -78,6 +84,7 @@ type FloorplanSelectionToolContext = {
|
|||||||
slabs: SlabEntry[]
|
slabs: SlabEntry[]
|
||||||
ceilings: CeilingEntry[]
|
ceilings: CeilingEntry[]
|
||||||
columns: ColumnEntry[]
|
columns: ColumnEntry[]
|
||||||
|
elevators: ElevatorEntry[]
|
||||||
roofs: RoofEntry[]
|
roofs: RoofEntry[]
|
||||||
openingHitTolerance: number
|
openingHitTolerance: number
|
||||||
wallHitTolerance: number
|
wallHitTolerance: number
|
||||||
@@ -130,6 +137,13 @@ export function getFloorplanHitNodeId(context: FloorplanSelectionToolContext) {
|
|||||||
return stairHit.stair.id
|
return stairHit.stair.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const elevatorHit = context.elevators.find(({ polygon }) =>
|
||||||
|
isPointInsidePolygon(context.point, polygon),
|
||||||
|
)
|
||||||
|
if (elevatorHit) {
|
||||||
|
return elevatorHit.elevator.id
|
||||||
|
}
|
||||||
|
|
||||||
const columnHit = context.columns.find(({ polygon }) =>
|
const columnHit = context.columns.find(({ polygon }) =>
|
||||||
isPointInsidePolygon(context.point, polygon),
|
isPointInsidePolygon(context.point, polygon),
|
||||||
)
|
)
|
||||||
@@ -181,6 +195,7 @@ type FloorplanSelectionBoundsContext = {
|
|||||||
slabs: SlabEntry[]
|
slabs: SlabEntry[]
|
||||||
ceilings: CeilingEntry[]
|
ceilings: CeilingEntry[]
|
||||||
columns: ColumnEntry[]
|
columns: ColumnEntry[]
|
||||||
|
elevators: ElevatorEntry[]
|
||||||
stairs: StairEntry[]
|
stairs: StairEntry[]
|
||||||
roofs: RoofEntry[]
|
roofs: RoofEntry[]
|
||||||
}
|
}
|
||||||
@@ -195,6 +210,7 @@ export function getFloorplanSelectionIdsInBounds({
|
|||||||
slabs,
|
slabs,
|
||||||
ceilings,
|
ceilings,
|
||||||
columns,
|
columns,
|
||||||
|
elevators,
|
||||||
stairs,
|
stairs,
|
||||||
roofs,
|
roofs,
|
||||||
}: FloorplanSelectionBoundsContext) {
|
}: FloorplanSelectionBoundsContext) {
|
||||||
@@ -223,6 +239,9 @@ export function getFloorplanSelectionIdsInBounds({
|
|||||||
const columnIds = columns
|
const columnIds = columns
|
||||||
.filter(({ polygon }) => doesPolygonIntersectSelectionBounds(polygon, bounds))
|
.filter(({ polygon }) => doesPolygonIntersectSelectionBounds(polygon, bounds))
|
||||||
.map(({ column }) => column.id)
|
.map(({ column }) => column.id)
|
||||||
|
const elevatorIds = elevators
|
||||||
|
.filter(({ polygon }) => doesPolygonIntersectSelectionBounds(polygon, bounds))
|
||||||
|
.map(({ elevator }) => elevator.id)
|
||||||
const stairIds = stairs
|
const stairIds = stairs
|
||||||
.filter((stair) =>
|
.filter((stair) =>
|
||||||
getStairHitPolygons(stair).some((polygon) =>
|
getStairHitPolygons(stair).some((polygon) =>
|
||||||
@@ -244,6 +263,7 @@ export function getFloorplanSelectionIdsInBounds({
|
|||||||
...slabIds,
|
...slabIds,
|
||||||
...ceilingIds,
|
...ceilingIds,
|
||||||
...columnIds,
|
...columnIds,
|
||||||
|
...elevatorIds,
|
||||||
...stairIds,
|
...stairIds,
|
||||||
...roofIds,
|
...roofIds,
|
||||||
]),
|
]),
|
||||||
|
|||||||
Reference in New Issue
Block a user