pin for zones on hover

This commit is contained in:
wass08
2026-03-04 11:13:20 +01:00
parent bee4613b82
commit add637e11c
2 changed files with 89 additions and 14 deletions
@@ -19,9 +19,9 @@ const createWallGradientMaterial = (zoneColor: string) => {
// Use UV y coordinate for vertical gradient (0 at bottom, 1 at top)
const gradientT = uv().y
const opacity = uniform(0);
const opacity = uniform(0)
// Fade opacity from 0.6 at bottom to 0 at top
const finalOpacity = float(0.6).mul(float(1).sub(gradientT)).mul(opacity);
const finalOpacity = float(0.6).mul(float(1).sub(gradientT)).mul(opacity)
return new MeshBasicNodeMaterial({
transparent: true,
@@ -32,7 +32,7 @@ const createWallGradientMaterial = (zoneColor: string) => {
depthTest: false,
userData: {
uOpacity: opacity,
}
},
})
}
@@ -49,7 +49,7 @@ const createFloorMaterial = (zoneColor: string) => {
side: DoubleSide,
depthWrite: false,
depthTest: false,
userData: { uOpacity: opacity}
userData: { uOpacity: opacity },
})
}
@@ -176,25 +176,68 @@ export const ZoneRenderer = ({ node }: { node: ZoneNode }) => {
return null
}
return (
<group ref={ref} {...handlers} userData={{ labelPosition: [centroid[0], 1, centroid[1]] }}>
<Html
name="label"
position={[centroid[0], 1, centroid[1]]}
style={{ pointerEvents: 'none' }}
style={{ pointerEvents: 'none', }}
zIndexRange={[10, 0]}
>
<div style={{
<div id={`${node.id}-label`} style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
transform: 'translate3d(-50%, -50%, 0)',
width: 'max-content',
color: 'white',
textShadow: `-1px -1px 0 ${node.color}, 1px -1px 0 ${node.color}, -1px 1px 0 ${node.color}, 1px 1px 0 ${node.color}`,
}}>
<div
style={{
width: 'max-content',
color: 'white',
textShadow: `-1px -1px 0 ${node.color}, 1px -1px 0 ${node.color}, -1px 1px 0 ${node.color}, 1px 1px 0 ${node.color}`,
textAlign: 'center',
}}
>
<span>{node.name}</span>
</div>
<div
className="label-pin"
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
marginTop: '2px',
opacity: 0,
transition: 'opacity 0.5s ease-in-out',
}}
>
<div
style={{
width: '2px',
height: '40px',
backgroundColor: node.color,
}}/>
<div
style={{
width: '10px',
height: '10px',
borderRadius: '50%',
backgroundColor: node.color,
border: `1px solid white`,
}}
/>
</div>
</div>
</Html>
{/* Floor fill */}
<mesh position={[0, Y_OFFSET, 0]} rotation={[-Math.PI / 2, 0, 0]} material={floorMaterial} name="floor">
<mesh
position={[0, Y_OFFSET, 0]}
rotation={[-Math.PI / 2, 0, 0]}
material={floorMaterial}
name="floor"
>
<shapeGeometry args={[floorShape]} />
</mesh>
@@ -6,25 +6,56 @@ import type { MeshBasicNodeMaterial } from 'three/webgpu'
import useViewer from '../../store/use-viewer'
const TRANSITION_DURATION = 400 // ms
const EXIT_DEBOUNCE_MS = 50 // ignore rapid exit→re-enter within this window
export const ZoneSystem = () => {
const lastHighlightedZoneRef = useRef<string | null>(null)
const lastChangeTimeRef = useRef(0)
const isTransitioningRef = useRef(false)
// Debounce exit-to-null: track the raw pending value and when it last changed
const pendingZoneRef = useRef<string | null>(null)
const pendingZoneSinceRef = useRef(0)
useFrame(({clock}, delta) => {
const hoveredId = useViewer.getState().hoveredId
let highlightedZone: string | null = null
let rawZone: string | null = null
if (hoveredId) {
const hoveredNode = useScene.getState().nodes[hoveredId]
if (hoveredNode?.type === 'zone') {
highlightedZone = hoveredId
rawZone = hoveredId
}
}
// Detect zone change
// Update pending zone when the raw value changes
if (rawZone !== pendingZoneRef.current) {
pendingZoneRef.current = rawZone
pendingZoneSinceRef.current = clock.elapsedTime * 1000
}
// Apply non-null immediately; debounce null to filter out brief exits
const age = clock.elapsedTime * 1000 - pendingZoneSinceRef.current
const highlightedZone = rawZone !== null
? rawZone
: age >= EXIT_DEBOUNCE_MS ? null : lastHighlightedZoneRef.current
// Detect stable zone change
if (highlightedZone !== lastHighlightedZoneRef.current) {
// Fade out previous zone label-pin
if (lastHighlightedZoneRef.current) {
const prevLabel = document.getElementById(`${lastHighlightedZoneRef.current}-label`)
const pin = prevLabel?.querySelector('.label-pin') as HTMLElement | null
if (pin) pin.style.opacity = '0'
console.log('setting opacity to 0 for', lastHighlightedZoneRef.current)
}
// Fade in new zone label-pin
if (highlightedZone) {
const label = document.getElementById(`${highlightedZone}-label`)
const pin = label?.querySelector('.label-pin') as HTMLElement | null
if (pin) pin.style.opacity = '1'
console.log('setting opacity to 1 for', highlightedZone)
}
lastHighlightedZoneRef.current = highlightedZone
lastChangeTimeRef.current = clock.elapsedTime * 1000
isTransitioningRef.current = true
@@ -63,6 +94,7 @@ export const ZoneSystem = () => {
const currentOpacity = material.userData.uOpacity.value
material.userData.uOpacity.value = MathUtils.lerp(currentOpacity, targetOpacity, lerpSpeed)
}
})
})