/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /** * Sync IFCx-derived point cloud assets to the renderer. * * On every change of the `pointClouds` array we replace the renderer's * asset list and request a fresh frame. When the active scene has no * triangle meshes (the buildingSMART point-cloud-only samples), we * additionally trigger a one-shot camera fit — the geometry streaming * hook bails out early in that case and would otherwise leave points * stranded outside the camera frustum. */ import { useEffect, useRef, type MutableRefObject } from 'react'; import type { PointColorMode, PointSizeMode, Renderer } from '@ifc-lite/renderer'; import type { PointCloudAsset } from '@ifc-lite/geometry'; import { useViewerStore } from '@/store'; export interface UsePointCloudSyncParams { rendererRef: MutableRefObject; isInitialized: boolean; pointClouds: ReadonlyArray | null | undefined; /** True when the scene has triangle meshes — the geometry streaming * hook owns fit-to-view in that case and we shouldn't fight it. */ hasMeshes: boolean; } export function usePointCloudSync(params: UsePointCloudSyncParams): void { const { rendererRef, isInitialized, pointClouds, hasMeshes } = params; const colorMode = useViewerStore((s) => s.pointCloudColorMode) as PointColorMode; const fixedColor = useViewerStore((s) => s.pointCloudFixedColor); const sizeMode = useViewerStore((s) => s.pointCloudSizeMode) as PointSizeMode; const pointSize = useViewerStore((s) => s.pointCloudPointSize); const worldRadius = useViewerStore((s) => s.pointCloudWorldRadius); const roundShape = useViewerStore((s) => s.pointCloudRoundShape); const edlEnabled = useViewerStore((s) => s.pointCloudEdlEnabled); const edlStrength = useViewerStore((s) => s.pointCloudEdlStrength); const classMask = useViewerStore((s) => s.pointCloudClassMask); const previewStride = useViewerStore((s) => s.pointCloudPreviewStride); const deviationCenter = useViewerStore((s) => s.pointCloudDeviationCenterOffset); const deviationHalf = useViewerStore((s) => s.pointCloudDeviationHalfRange); const setAssetCount = useViewerStore((s) => s.setPointCloudAssetCount); const fittedRef = useRef(false); // Reset the one-shot fit flag whenever the asset list identity changes. useEffect(() => { fittedRef.current = false; }, [pointClouds]); // Replace IFCx-owned assets when the merged list changes useEffect(() => { const renderer = rendererRef.current; if (!renderer || !isInitialized) return; const assets = pointClouds ?? []; renderer.setPointClouds(assets); const count = renderer.getPointCloudAssetCount(); setAssetCount(count); // Camera fit for points-only scenes — useGeometryStreaming skips its // own fit branch when meshes is empty, so points stay off-screen // unless we step in. Run once per fresh asset list. if (count > 0 && !hasMeshes && !fittedRef.current) { const bounds = renderer.getModelBounds(); if (bounds && Number.isFinite(bounds.min.x) && Number.isFinite(bounds.max.x)) { renderer.getCamera().fitToBounds(bounds.min, bounds.max); fittedRef.current = true; } } renderer.requestRender(); }, [pointClouds, isInitialized, rendererRef, setAssetCount, hasMeshes]); // Push color + sizing + shape preferences to the renderer whenever the // user changes them. The slice already clamps numeric ranges so the // shader only ever sees sane values. useEffect(() => { const renderer = rendererRef.current; if (!renderer || !isInitialized) return; renderer.setPointCloudOptions({ colorMode, fixedColor, sizeMode, pointSize, worldRadius, roundShape, classMask, previewStride, deviationRange: { centerOffset: deviationCenter, halfRange: deviationHalf }, }); renderer.requestRender(); }, [colorMode, fixedColor, sizeMode, pointSize, worldRadius, roundShape, classMask, previewStride, deviationCenter, deviationHalf, isInitialized, rendererRef]); // Push EDL toggle + strength to the renderer. useEffect(() => { const renderer = rendererRef.current; if (!renderer || !isInitialized) return; renderer.setEdlOptions({ enabled: edlEnabled, strength: edlStrength }); renderer.requestRender(); }, [edlEnabled, edlStrength, isInitialized, rendererRef]); } export default usePointCloudSync;