hovered zones system

This commit is contained in:
wass08
2026-02-09 14:25:36 +09:00
parent e9d579d24e
commit 8876776c0f
3 changed files with 85 additions and 7 deletions
@@ -2,7 +2,7 @@ import { useRegistry, type ZoneNode } from '@pascal-app/core'
import { Html } from '@react-three/drei' import { Html } from '@react-three/drei'
import { useMemo, useRef } from 'react' import { useMemo, useRef } from 'react'
import { BufferGeometry, Color, DoubleSide, Float32BufferAttribute, type Group, Shape } from 'three' import { BufferGeometry, Color, DoubleSide, Float32BufferAttribute, type Group, Shape } from 'three'
import { color, float, uv } from 'three/tsl' import { color, float, uniform, uv } from 'three/tsl'
import { MeshBasicNodeMaterial } from 'three/webgpu' import { MeshBasicNodeMaterial } from 'three/webgpu'
import { useNodeEvents } from '../../../hooks/use-node-events' import { useNodeEvents } from '../../../hooks/use-node-events'
@@ -19,16 +19,20 @@ const createWallGradientMaterial = (zoneColor: string) => {
// Use UV y coordinate for vertical gradient (0 at bottom, 1 at top) // Use UV y coordinate for vertical gradient (0 at bottom, 1 at top)
const gradientT = uv().y const gradientT = uv().y
const opacity = uniform(0);
// Fade opacity from 0.6 at bottom to 0 at top // Fade opacity from 0.6 at bottom to 0 at top
const opacity = float(0.6).mul(float(1).sub(gradientT)) const finalOpacity = float(0.6).mul(float(1).sub(gradientT)).mul(opacity);
return new MeshBasicNodeMaterial({ return new MeshBasicNodeMaterial({
transparent: true, transparent: true,
colorNode: baseColor, colorNode: baseColor,
opacityNode: opacity, opacityNode: finalOpacity,
side: DoubleSide, side: DoubleSide,
depthWrite: false, depthWrite: false,
depthTest: false, depthTest: false,
userData: {
uOpacity: opacity,
}
}) })
} }
@@ -37,13 +41,15 @@ const createWallGradientMaterial = (zoneColor: string) => {
*/ */
const createFloorMaterial = (zoneColor: string) => { const createFloorMaterial = (zoneColor: string) => {
const baseColor = color(new Color(zoneColor)) const baseColor = color(new Color(zoneColor))
const opacity = uniform(0)
return new MeshBasicNodeMaterial({ return new MeshBasicNodeMaterial({
transparent: true, transparent: true,
colorNode: baseColor, colorNode: baseColor,
opacityNode: float(0.15), opacityNode: float(0.25).mul(opacity),
side: DoubleSide, side: DoubleSide,
depthWrite: false, depthWrite: false,
depthTest: false,
userData: { uOpacity: opacity}
}) })
} }
@@ -187,12 +193,12 @@ export const ZoneRenderer = ({ node }: { node: ZoneNode }) => {
{node.name}</div> {node.name}</div>
</Html> </Html>
{/* Floor fill */} {/* Floor fill */}
<mesh position={[0, Y_OFFSET, 0]} rotation={[-Math.PI / 2, 0, 0]} material={floorMaterial}> <mesh position={[0, Y_OFFSET, 0]} rotation={[-Math.PI / 2, 0, 0]} material={floorMaterial} name="floor">
<shapeGeometry args={[floorShape]} /> <shapeGeometry args={[floorShape]} />
</mesh> </mesh>
{/* Wall borders with gradient */} {/* Wall borders with gradient */}
<mesh geometry={wallGeometry} material={wallMaterial} /> <mesh geometry={wallGeometry} material={wallMaterial} name="walls" />
</group> </group>
) )
} }
@@ -8,6 +8,7 @@ import { GuideSystem } from '../../systems/guide/guide-system'
import { LevelSystem } from '../../systems/level/level-system' import { LevelSystem } from '../../systems/level/level-system'
import { ScanSystem } from '../../systems/scan/scan-system' import { ScanSystem } from '../../systems/scan/scan-system'
import { WallCutout } from '../../systems/wall/wall-cutout' import { WallCutout } from '../../systems/wall/wall-cutout'
import { ZoneSystem } from '../../systems/zone/zone-system'
import { SceneRenderer } from '../renderers/scene-renderer' import { SceneRenderer } from '../renderers/scene-renderer'
import { Lights } from './lights' import { Lights } from './lights'
import PostProcessing from './post-processing' import PostProcessing from './post-processing'
@@ -63,6 +64,7 @@ const Viewer: React.FC<ViewerProps> = ({ children, selectionManager = 'default'
<RoofSystem /> <RoofSystem />
<SlabSystem /> <SlabSystem />
<WallSystem /> <WallSystem />
<ZoneSystem />
<PostProcessing /> <PostProcessing />
{selectionManager === 'default' && <SelectionManager />} {selectionManager === 'default' && <SelectionManager />}
@@ -0,0 +1,70 @@
import { sceneRegistry, useScene } from '@pascal-app/core'
import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
import { type Group, MathUtils, type Mesh } from 'three'
import type { MeshBasicNodeMaterial } from 'three/webgpu'
import useViewer from '../../store/use-viewer'
const TRANSITION_DURATION = 400 // ms
export const ZoneSystem = () => {
const lastHighlightedZoneRef = useRef<string | null>(null)
const lastChangeTimeRef = useRef(0)
const isTransitioningRef = useRef(false)
useFrame(({clock}, delta) => {
const hoveredId = useViewer.getState().hoveredId
let highlightedZone: string | null = null
if (hoveredId) {
const hoveredNode = useScene.getState().nodes[hoveredId]
if (hoveredNode?.type === 'zone') {
highlightedZone = hoveredId
}
}
// Detect zone change
if (highlightedZone !== lastHighlightedZoneRef.current) {
lastHighlightedZoneRef.current = highlightedZone
lastChangeTimeRef.current = clock.elapsedTime * 1000
isTransitioningRef.current = true
}
// Skip frame if not transitioning
if (!isTransitioningRef.current) return
const elapsed = clock.elapsedTime * 1000 - lastChangeTimeRef.current
// Stop transitioning after duration
if (elapsed >= TRANSITION_DURATION) {
isTransitioningRef.current = false
}
// Lerp speed: complete transition in ~400ms
const lerpSpeed = 10 * delta
sceneRegistry.byType.zone.forEach((zoneId) => {
const zone = sceneRegistry.nodes.get(zoneId)
if (!zone) return
const isHighlighted = zoneId === highlightedZone
const targetOpacity = isHighlighted ? 1 : 0
const walls = (zone as Group).getObjectByName('walls') as Mesh | undefined
if (walls) {
const material = walls.material as MeshBasicNodeMaterial
const currentOpacity = material.userData.uOpacity.value
material.userData.uOpacity.value = MathUtils.lerp(currentOpacity, targetOpacity, lerpSpeed)
}
const floor = (zone as Group).getObjectByName('floor') as Mesh | undefined
if (floor) {
const material = floor.material as MeshBasicNodeMaterial
const currentOpacity = material.userData.uOpacity.value
material.userData.uOpacity.value = MathUtils.lerp(currentOpacity, targetOpacity, lerpSpeed)
}
})
})
return null
}