@crystaldesign/real-time-viewer-next 26.11.0-beta.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/esm/DeckCutoutSubsystem-76dfe828.js +2536 -0
- package/build/esm/SunLensflare-e4ab3f81.js +483 -0
- package/build/esm/WaterSurface-93383fdf.js +2041 -0
- package/build/esm/index-920dd901.js +23273 -0
- package/build/esm/index.js +32 -0
- package/build/types/real-time-viewer-next/src/RealTimeRendererNext.d.ts +58 -0
- package/build/types/real-time-viewer-next/src/RealTimeRendererNext.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/analyzerComponents.d.ts +23 -0
- package/build/types/real-time-viewer-next/src/analyzerComponents.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/analyzerStageContext.d.ts +9 -0
- package/build/types/real-time-viewer-next/src/analyzerStageContext.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/capture/applyShot.d.ts +8 -0
- package/build/types/real-time-viewer-next/src/capture/applyShot.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/capture/blob.d.ts +11 -0
- package/build/types/real-time-viewer-next/src/capture/blob.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/capture/materialMute.d.ts +16 -0
- package/build/types/real-time-viewer-next/src/capture/materialMute.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/capture/shotState.d.ts +24 -0
- package/build/types/real-time-viewer-next/src/capture/shotState.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/capture/types.d.ts +80 -0
- package/build/types/real-time-viewer-next/src/capture/types.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/capture/useCaptureEngine.d.ts +7 -0
- package/build/types/real-time-viewer-next/src/capture/useCaptureEngine.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/config/descriptor.d.ts +156 -0
- package/build/types/real-time-viewer-next/src/config/descriptor.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/config/descriptor.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/config/descriptor.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/config/environment.config.d.ts +511 -0
- package/build/types/real-time-viewer-next/src/config/environment.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/config/environment.config.test.d.ts +5 -0
- package/build/types/real-time-viewer-next/src/config/environment.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/BBoxPreloader.d.ts +15 -0
- package/build/types/real-time-viewer-next/src/core/BBoxPreloader.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/FloorGrid.d.ts +3 -0
- package/build/types/real-time-viewer-next/src/core/FloorGrid.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/GeoBBoxReporter.d.ts +10 -0
- package/build/types/real-time-viewer-next/src/core/GeoBBoxReporter.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/GeoInstance.d.ts +13 -0
- package/build/types/real-time-viewer-next/src/core/GeoInstance.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/PaletteDragPreview.d.ts +19 -0
- package/build/types/real-time-viewer-next/src/core/PaletteDragPreview.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/PerfLog.d.ts +38 -0
- package/build/types/real-time-viewer-next/src/core/PerfLog.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/PlannerGrid.d.ts +83 -0
- package/build/types/real-time-viewer-next/src/core/PlannerGrid.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/R3FAssetBoundary.d.ts +31 -0
- package/build/types/real-time-viewer-next/src/core/R3FAssetBoundary.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/RTVErrorBoundary.d.ts +16 -0
- package/build/types/real-time-viewer-next/src/core/RTVErrorBoundary.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/SceneRenderer.d.ts +3 -0
- package/build/types/real-time-viewer-next/src/core/SceneRenderer.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/SceneTree.d.ts +16 -0
- package/build/types/real-time-viewer-next/src/core/SceneTree.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/VariantMeshOverride.d.ts +62 -0
- package/build/types/real-time-viewer-next/src/core/VariantMeshOverride.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/CameraController.d.ts +3 -0
- package/build/types/real-time-viewer-next/src/core/camera/CameraController.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/cameraAnimationEngine.d.ts +69 -0
- package/build/types/real-time-viewer-next/src/core/camera/cameraAnimationEngine.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/cameraPoseBook.d.ts +24 -0
- package/build/types/real-time-viewer-next/src/core/camera/cameraPoseBook.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/controlsLock.d.ts +25 -0
- package/build/types/real-time-viewer-next/src/core/camera/controlsLock.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/sectorLimits.d.ts +38 -0
- package/build/types/real-time-viewer-next/src/core/camera/sectorLimits.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/sectorLimits.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/camera/sectorLimits.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/useAutoFrame.d.ts +29 -0
- package/build/types/real-time-viewer-next/src/core/camera/useAutoFrame.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/useAutoRotate.d.ts +8 -0
- package/build/types/real-time-viewer-next/src/core/camera/useAutoRotate.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/useCameraActions.d.ts +13 -0
- package/build/types/real-time-viewer-next/src/core/camera/useCameraActions.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/useCameraAnimation.d.ts +15 -0
- package/build/types/real-time-viewer-next/src/core/camera/useCameraAnimation.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/useCameraConstraints.d.ts +25 -0
- package/build/types/real-time-viewer-next/src/core/camera/useCameraConstraints.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/useCameraSmoothing.d.ts +57 -0
- package/build/types/real-time-viewer-next/src/core/camera/useCameraSmoothing.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/useMaterialFade.d.ts +5 -0
- package/build/types/real-time-viewer-next/src/core/camera/useMaterialFade.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/camera/useViewModeTransition.d.ts +19 -0
- package/build/types/real-time-viewer-next/src/core/camera/useViewModeTransition.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/DimensionLine2D.d.ts +28 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/DimensionLine2D.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/DimensionLines2D.d.ts +3 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/DimensionLines2D.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/DimensionLines3D.d.ts +3 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/DimensionLines3D.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/dimensionGeometry.d.ts +34 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/dimensionGeometry.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/dimensionLineBuilders.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/dimensionLineBuilders.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/dimensionLineBuilders2D.d.ts +82 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/dimensionLineBuilders2D.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/dimensionLineBuilders3D.d.ts +76 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/dimensionLineBuilders3D.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/dimensionLines.config.d.ts +25 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/dimensionLines.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/dimensionLines.config.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/dimensionLines.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/index.d.ts +3 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/index.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/useDimensionFade.d.ts +12 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/useDimensionFade.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/useLabelScale.d.ts +11 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/useLabelScale.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/utils.d.ts +26 -0
- package/build/types/real-time-viewer-next/src/core/dimension-lines/utils.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/EnvironmentDome.d.ts +14 -0
- package/build/types/real-time-viewer-next/src/core/environment/EnvironmentDome.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/EnvironmentSceneLoader.d.ts +14 -0
- package/build/types/real-time-viewer-next/src/core/environment/EnvironmentSceneLoader.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/EnvironmentSources.d.ts +33 -0
- package/build/types/real-time-viewer-next/src/core/environment/EnvironmentSources.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/EnvironmentSunLensflare.d.ts +3 -0
- package/build/types/real-time-viewer-next/src/core/environment/EnvironmentSunLensflare.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/LightingRig.d.ts +28 -0
- package/build/types/real-time-viewer-next/src/core/environment/LightingRig.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/PlainGround.d.ts +21 -0
- package/build/types/real-time-viewer-next/src/core/environment/PlainGround.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/RuntimePerfAdjuster.d.ts +15 -0
- package/build/types/real-time-viewer-next/src/core/environment/RuntimePerfAdjuster.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/SceneEnvironment.d.ts +19 -0
- package/build/types/real-time-viewer-next/src/core/environment/SceneEnvironment.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/SkydomeBackground.d.ts +11 -0
- package/build/types/real-time-viewer-next/src/core/environment/SkydomeBackground.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/SunLensflare.d.ts +29 -0
- package/build/types/real-time-viewer-next/src/core/environment/SunLensflare.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/SunLensflareLazy.d.ts +11 -0
- package/build/types/real-time-viewer-next/src/core/environment/SunLensflareLazy.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/adaptive.config.d.ts +24 -0
- package/build/types/real-time-viewer-next/src/core/environment/adaptive.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/adaptive.config.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/environment/adaptive.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/DeckCutoutArticleSync.d.ts +22 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/DeckCutoutArticleSync.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/DeckCutoutCollar.d.ts +15 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/DeckCutoutCollar.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/DeckCutoutLazy.d.ts +10 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/DeckCutoutLazy.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/DeckCutoutSubsystem.d.ts +9 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/DeckCutoutSubsystem.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/anchorElements.d.ts +40 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/anchorElements.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/anchorElements.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/anchorElements.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/articleFootprint.d.ts +62 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/articleFootprint.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/articleFootprint.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/articleFootprint.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/buildCollarGeometry.d.ts +25 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/buildCollarGeometry.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/buildCollarGeometry.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/buildCollarGeometry.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/collectDeckTriangles.d.ts +86 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/collectDeckTriangles.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/deriveWaterVolume.d.ts +28 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/deriveWaterVolume.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/deriveWaterVolume.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/deriveWaterVolume.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/measureArticle.d.ts +49 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/measureArticle.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/measureArticle.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/measureArticle.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/measureDiagnostics.d.ts +20 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/measureDiagnostics.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/useCollarTextures.d.ts +16 -0
- package/build/types/real-time-viewer-next/src/core/environment/deckCutout/useCollarTextures.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/dome.config.d.ts +191 -0
- package/build/types/real-time-viewer-next/src/core/environment/dome.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/dome.config.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/environment/dome.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/ground.config.d.ts +130 -0
- package/build/types/real-time-viewer-next/src/core/environment/ground.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/ground.config.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/environment/ground.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/lighting.config.d.ts +297 -0
- package/build/types/real-time-viewer-next/src/core/environment/lighting.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/lighting.config.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/environment/lighting.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/scene.config.d.ts +440 -0
- package/build/types/real-time-viewer-next/src/core/environment/scene.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/scene.config.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/environment/scene.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/useEnvironmentMap.d.ts +19 -0
- package/build/types/real-time-viewer-next/src/core/environment/useEnvironmentMap.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/environment/useStudioLightEffects.d.ts +28 -0
- package/build/types/real-time-viewer-next/src/core/environment/useStudioLightEffects.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/frameOrder.d.ts +91 -0
- package/build/types/real-time-viewer-next/src/core/frameOrder.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/handles.d.ts +35 -0
- package/build/types/real-time-viewer-next/src/core/handles.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/interaction/AccessoryDropController.d.ts +9 -0
- package/build/types/real-time-viewer-next/src/core/interaction/AccessoryDropController.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/interaction/AccessoryFollowController.d.ts +15 -0
- package/build/types/real-time-viewer-next/src/core/interaction/AccessoryFollowController.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/interaction/DragController.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/interaction/DragController.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/interaction/ElementOverlay.d.ts +16 -0
- package/build/types/real-time-viewer-next/src/core/interaction/ElementOverlay.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/interaction/ElementOverlayIcons.d.ts +18 -0
- package/build/types/real-time-viewer-next/src/core/interaction/ElementOverlayIcons.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/interaction/FreeGridLayer.d.ts +10 -0
- package/build/types/real-time-viewer-next/src/core/interaction/FreeGridLayer.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/interaction/InteractionGuides.d.ts +3 -0
- package/build/types/real-time-viewer-next/src/core/interaction/InteractionGuides.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/interaction/accessoryCellTargeting.d.ts +31 -0
- package/build/types/real-time-viewer-next/src/core/interaction/accessoryCellTargeting.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/interaction/selection.config.d.ts +65 -0
- package/build/types/real-time-viewer-next/src/core/interaction/selection.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/interaction/selection.config.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/interaction/selection.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/interaction/useAccessoryMoveDrag.d.ts +21 -0
- package/build/types/real-time-viewer-next/src/core/interaction/useAccessoryMoveDrag.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/interaction/useElementInteraction.d.ts +35 -0
- package/build/types/real-time-viewer-next/src/core/interaction/useElementInteraction.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/lazySubsystems.d.ts +16 -0
- package/build/types/real-time-viewer-next/src/core/lazySubsystems.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/materials/MaterialContext.d.ts +23 -0
- package/build/types/real-time-viewer-next/src/core/materials/MaterialContext.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/materials/MaterialManager.d.ts +4 -0
- package/build/types/real-time-viewer-next/src/core/materials/MaterialManager.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/materials/apply.d.ts +17 -0
- package/build/types/real-time-viewer-next/src/core/materials/apply.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/materials/caches.d.ts +44 -0
- package/build/types/real-time-viewer-next/src/core/materials/caches.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/materials/factory.d.ts +37 -0
- package/build/types/real-time-viewer-next/src/core/materials/factory.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/materials/materials.config.d.ts +13 -0
- package/build/types/real-time-viewer-next/src/core/materials/materials.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/materials/materials.config.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/materials/materials.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/materials/textureCache.d.ts +26 -0
- package/build/types/real-time-viewer-next/src/core/materials/textureCache.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/materials/types.d.ts +5 -0
- package/build/types/real-time-viewer-next/src/core/materials/types.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/meshBVH.d.ts +22 -0
- package/build/types/real-time-viewer-next/src/core/meshBVH.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/meshBVH.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/meshBVH.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/postprocessing/PostProcessingDynamicEffects.d.ts +11 -0
- package/build/types/real-time-viewer-next/src/core/postprocessing/PostProcessingDynamicEffects.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/postprocessing/PostProcessingStack.d.ts +28 -0
- package/build/types/real-time-viewer-next/src/core/postprocessing/PostProcessingStack.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/postprocessing/applyRendererToneMapping.d.ts +10 -0
- package/build/types/real-time-viewer-next/src/core/postprocessing/applyRendererToneMapping.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/postprocessing/index.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/postprocessing/index.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/postprocessing/postProcessing.config.d.ts +453 -0
- package/build/types/real-time-viewer-next/src/core/postprocessing/postProcessing.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/postprocessing/postProcessing.config.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/postprocessing/postProcessing.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/renderer.config.d.ts +55 -0
- package/build/types/real-time-viewer-next/src/core/renderer.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/renderer.config.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/renderer.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/shadows/SceneShadowSystem.d.ts +18 -0
- package/build/types/real-time-viewer-next/src/core/shadows/SceneShadowSystem.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/shadows/SunShadow.d.ts +29 -0
- package/build/types/real-time-viewer-next/src/core/shadows/SunShadow.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/shadows/index.d.ts +4 -0
- package/build/types/real-time-viewer-next/src/core/shadows/index.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/shadows/shadowMapSize.d.ts +20 -0
- package/build/types/real-time-viewer-next/src/core/shadows/shadowMapSize.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/shadows/shadowMapSize.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/shadows/shadowMapSize.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/shadows/shadowRegistry.d.ts +24 -0
- package/build/types/real-time-viewer-next/src/core/shadows/shadowRegistry.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/shadows/shadows.config.d.ts +75 -0
- package/build/types/real-time-viewer-next/src/core/shadows/shadows.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/shadows/shadows.config.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/shadows/shadows.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/shadows/useShadowDirty.d.ts +14 -0
- package/build/types/real-time-viewer-next/src/core/shadows/useShadowDirty.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/shadows/useShadowFlags.d.ts +28 -0
- package/build/types/real-time-viewer-next/src/core/shadows/useShadowFlags.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/shadows/useShadowRebake.d.ts +14 -0
- package/build/types/real-time-viewer-next/src/core/shadows/useShadowRebake.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/useFrameloopVisibility.d.ts +37 -0
- package/build/types/real-time-viewer-next/src/core/useFrameloopVisibility.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/view2d/DockingPoints2D.d.ts +17 -0
- package/build/types/real-time-viewer-next/src/core/view2d/DockingPoints2D.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/view2d/Element2DPlane.d.ts +18 -0
- package/build/types/real-time-viewer-next/src/core/view2d/Element2DPlane.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/view2d/ElementAnnotation2D.d.ts +16 -0
- package/build/types/real-time-viewer-next/src/core/view2d/ElementAnnotation2D.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/view2d/Scene2DOverlay.d.ts +10 -0
- package/build/types/real-time-viewer-next/src/core/view2d/Scene2DOverlay.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/viewMode/ViewModeRuntimeContext.d.ts +20 -0
- package/build/types/real-time-viewer-next/src/core/viewMode/ViewModeRuntimeContext.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/viewMode/index.d.ts +4 -0
- package/build/types/real-time-viewer-next/src/core/viewMode/index.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/viewMode/selectViewModeRuntime.d.ts +9 -0
- package/build/types/real-time-viewer-next/src/core/viewMode/selectViewModeRuntime.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/viewMode/selectViewModeRuntime.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/viewMode/selectViewModeRuntime.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/viewMode/types.d.ts +67 -0
- package/build/types/real-time-viewer-next/src/core/viewMode/types.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/CausticReceiver.d.ts +49 -0
- package/build/types/real-time-viewer-next/src/core/water/CausticReceiver.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/CausticsTexture.d.ts +48 -0
- package/build/types/real-time-viewer-next/src/core/water/CausticsTexture.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/Heightfield.d.ts +88 -0
- package/build/types/real-time-viewer-next/src/core/water/Heightfield.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/PoolHeightMap.d.ts +36 -0
- package/build/types/real-time-viewer-next/src/core/water/PoolHeightMap.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/RefractionCapture.d.ts +73 -0
- package/build/types/real-time-viewer-next/src/core/water/RefractionCapture.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/RefractionCapture.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/water/RefractionCapture.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/SceneCapture.d.ts +66 -0
- package/build/types/real-time-viewer-next/src/core/water/SceneCapture.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/WaterSurface.d.ts +8 -0
- package/build/types/real-time-viewer-next/src/core/water/WaterSurface.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/WaterSurfaceLazy.d.ts +10 -0
- package/build/types/real-time-viewer-next/src/core/water/WaterSurfaceLazy.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/WaterSurfaceRenderer.d.ts +71 -0
- package/build/types/real-time-viewer-next/src/core/water/WaterSurfaceRenderer.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/useCausticReceivers.d.ts +11 -0
- package/build/types/real-time-viewer-next/src/core/water/useCausticReceivers.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/useWaterPointer.d.ts +13 -0
- package/build/types/real-time-viewer-next/src/core/water/useWaterPointer.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/useWaveSimulation.d.ts +44 -0
- package/build/types/real-time-viewer-next/src/core/water/useWaveSimulation.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/useWaveSimulation.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/water/useWaveSimulation.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/water.config.d.ts +90 -0
- package/build/types/real-time-viewer-next/src/core/water/water.config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/water.config.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/core/water/water.config.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/waterDebug.d.ts +14 -0
- package/build/types/real-time-viewer-next/src/core/water/waterDebug.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/waterShaders.d.ts +9 -0
- package/build/types/real-time-viewer-next/src/core/water/waterShaders.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/waterSurfaceMaterial.d.ts +21 -0
- package/build/types/real-time-viewer-next/src/core/water/waterSurfaceMaterial.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/core/water/waterUniforms.d.ts +51 -0
- package/build/types/real-time-viewer-next/src/core/water/waterUniforms.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/hooks/useAssembledScene.d.ts +17 -0
- package/build/types/real-time-viewer-next/src/hooks/useAssembledScene.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/hooks/useHostPropsSync.d.ts +20 -0
- package/build/types/real-time-viewer-next/src/hooks/useHostPropsSync.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/index.d.ts +17 -0
- package/build/types/real-time-viewer-next/src/index.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/internals.d.ts +61 -0
- package/build/types/real-time-viewer-next/src/internals.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/loadingGates.d.ts +34 -0
- package/build/types/real-time-viewer-next/src/loadingGates.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/parser/parseRenderScript.d.ts +51 -0
- package/build/types/real-time-viewer-next/src/parser/parseRenderScript.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/parser/types.d.ts +58 -0
- package/build/types/real-time-viewer-next/src/parser/types.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/presets/mergePresetBundle.d.ts +33 -0
- package/build/types/real-time-viewer-next/src/presets/mergePresetBundle.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/presets/polyfaser-planner.d.ts +4 -0
- package/build/types/real-time-viewer-next/src/presets/polyfaser-planner.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/presets/polyfaser-pool.d.ts +4 -0
- package/build/types/real-time-viewer-next/src/presets/polyfaser-pool.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/presets/polyfaserPlannerOverride.d.ts +9 -0
- package/build/types/real-time-viewer-next/src/presets/polyfaserPlannerOverride.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/presets/villa-cliff.d.ts +4 -0
- package/build/types/real-time-viewer-next/src/presets/villa-cliff.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/publicApi.d.ts +35 -0
- package/build/types/real-time-viewer-next/src/publicApi.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/publicPath.d.ts +1 -0
- package/build/types/real-time-viewer-next/src/publicPath.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/index.d.ts +18 -0
- package/build/types/real-time-viewer-next/src/store/index.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/shared.d.ts +25 -0
- package/build/types/real-time-viewer-next/src/store/shared.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/accessory.d.ts +147 -0
- package/build/types/real-time-viewer-next/src/store/slices/accessory.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/analyzerGate.d.ts +15 -0
- package/build/types/real-time-viewer-next/src/store/slices/analyzerGate.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/camera.d.ts +66 -0
- package/build/types/real-time-viewer-next/src/store/slices/camera.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/capture.d.ts +60 -0
- package/build/types/real-time-viewer-next/src/store/slices/capture.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/config.d.ts +52 -0
- package/build/types/real-time-viewer-next/src/store/slices/config.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/deckCutout.d.ts +29 -0
- package/build/types/real-time-viewer-next/src/store/slices/deckCutout.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/environment.d.ts +47 -0
- package/build/types/real-time-viewer-next/src/store/slices/environment.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/glGeneration.d.ts +18 -0
- package/build/types/real-time-viewer-next/src/store/slices/glGeneration.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/hostSelection.test.d.ts +5 -0
- package/build/types/real-time-viewer-next/src/store/slices/hostSelection.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/interaction.d.ts +53 -0
- package/build/types/real-time-viewer-next/src/store/slices/interaction.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/performanceProfile.d.ts +71 -0
- package/build/types/real-time-viewer-next/src/store/slices/performanceProfile.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/plannerUi.d.ts +46 -0
- package/build/types/real-time-viewer-next/src/store/slices/plannerUi.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/scene.d.ts +26 -0
- package/build/types/real-time-viewer-next/src/store/slices/scene.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/sceneLoad.d.ts +75 -0
- package/build/types/real-time-viewer-next/src/store/slices/sceneLoad.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/sceneLoad.test.d.ts +5 -0
- package/build/types/real-time-viewer-next/src/store/slices/sceneLoad.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/selection.d.ts +66 -0
- package/build/types/real-time-viewer-next/src/store/slices/selection.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/versions.d.ts +34 -0
- package/build/types/real-time-viewer-next/src/store/slices/versions.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/slices/viewMode.d.ts +16 -0
- package/build/types/real-time-viewer-next/src/store/slices/viewMode.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/store/types.d.ts +96 -0
- package/build/types/real-time-viewer-next/src/store/types.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/testUtils/deepDiff.d.ts +26 -0
- package/build/types/real-time-viewer-next/src/testUtils/deepDiff.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/testUtils/deepDiff.test.d.ts +11 -0
- package/build/types/real-time-viewer-next/src/testUtils/deepDiff.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/testUtils/divaCoreMock.d.ts +47 -0
- package/build/types/real-time-viewer-next/src/testUtils/divaCoreMock.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/testUtils/resolvedConfigSnapshots.d.ts +36 -0
- package/build/types/real-time-viewer-next/src/testUtils/resolvedConfigSnapshots.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/testUtils/structuredClonePolyfill.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/testUtils/structuredClonePolyfill.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/types/accessoryGrids.d.ts +134 -0
- package/build/types/real-time-viewer-next/src/types/accessoryGrids.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/types/camera.d.ts +288 -0
- package/build/types/real-time-viewer-next/src/types/camera.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/types/environment.d.ts +146 -0
- package/build/types/real-time-viewer-next/src/types/environment.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/types/plannerUi.d.ts +68 -0
- package/build/types/real-time-viewer-next/src/types/plannerUi.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/types/scene2d.d.ts +168 -0
- package/build/types/real-time-viewer-next/src/types/scene2d.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/bootPhase.d.ts +75 -0
- package/build/types/real-time-viewer-next/src/utils/bootPhase.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/bootPhase.test.d.ts +5 -0
- package/build/types/real-time-viewer-next/src/utils/bootPhase.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/camera.d.ts +8 -0
- package/build/types/real-time-viewer-next/src/utils/camera.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/cameraFraming.d.ts +47 -0
- package/build/types/real-time-viewer-next/src/utils/cameraFraming.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/collisionTargets.d.ts +34 -0
- package/build/types/real-time-viewer-next/src/utils/collisionTargets.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/collisionTargets.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/utils/collisionTargets.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/cursorStack.d.ts +21 -0
- package/build/types/real-time-viewer-next/src/utils/cursorStack.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/detectDeviceTier.d.ts +24 -0
- package/build/types/real-time-viewer-next/src/utils/detectDeviceTier.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/detectDeviceTier.test.d.ts +5 -0
- package/build/types/real-time-viewer-next/src/utils/detectDeviceTier.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/dockingGroups.d.ts +7 -0
- package/build/types/real-time-viewer-next/src/utils/dockingGroups.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/elementIds.d.ts +27 -0
- package/build/types/real-time-viewer-next/src/utils/elementIds.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/equirectCache.d.ts +45 -0
- package/build/types/real-time-viewer-next/src/utils/equirectCache.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/equirectUrl.d.ts +22 -0
- package/build/types/real-time-viewer-next/src/utils/equirectUrl.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/equirectUrl.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/utils/equirectUrl.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/footprint.d.ts +15 -0
- package/build/types/real-time-viewer-next/src/utils/footprint.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/footprint.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/utils/footprint.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/footprintTrace.d.ts +55 -0
- package/build/types/real-time-viewer-next/src/utils/footprintTrace.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/footprintTrace.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/utils/footprintTrace.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/geoViewAngle.d.ts +17 -0
- package/build/types/real-time-viewer-next/src/utils/geoViewAngle.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/gltfLoader.d.ts +34 -0
- package/build/types/real-time-viewer-next/src/utils/gltfLoader.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/gridGeometryApi.d.ts +17 -0
- package/build/types/real-time-viewer-next/src/utils/gridGeometryApi.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/localUVSampler.d.ts +89 -0
- package/build/types/real-time-viewer-next/src/utils/localUVSampler.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/localUVSampler.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/utils/localUVSampler.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/overlayGeometry.d.ts +28 -0
- package/build/types/real-time-viewer-next/src/utils/overlayGeometry.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/overlayGeometry.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/utils/overlayGeometry.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/planarTransform.d.ts +20 -0
- package/build/types/real-time-viewer-next/src/utils/planarTransform.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/planarTransform.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/utils/planarTransform.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/polygon2d.d.ts +25 -0
- package/build/types/real-time-viewer-next/src/utils/polygon2d.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/preloadEnvironment.d.ts +15 -0
- package/build/types/real-time-viewer-next/src/utils/preloadEnvironment.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/profiles.d.ts +113 -0
- package/build/types/real-time-viewer-next/src/utils/profiles.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/profiles.test.d.ts +17 -0
- package/build/types/real-time-viewer-next/src/utils/profiles.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/rasterMask.d.ts +31 -0
- package/build/types/real-time-viewer-next/src/utils/rasterMask.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/rasterMask.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/utils/rasterMask.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/resolveEnvironment.d.ts +16 -0
- package/build/types/real-time-viewer-next/src/utils/resolveEnvironment.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/resolveGridCells.d.ts +35 -0
- package/build/types/real-time-viewer-next/src/utils/resolveGridCells.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/scene.d.ts +3 -0
- package/build/types/real-time-viewer-next/src/utils/scene.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/screenProjection.d.ts +42 -0
- package/build/types/real-time-viewer-next/src/utils/screenProjection.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/screenProjection.test.d.ts +2 -0
- package/build/types/real-time-viewer-next/src/utils/screenProjection.test.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/selectionScopeNames.d.ts +6 -0
- package/build/types/real-time-viewer-next/src/utils/selectionScopeNames.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/selectionScopeScene.d.ts +8 -0
- package/build/types/real-time-viewer-next/src/utils/selectionScopeScene.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/smoothDamp.d.ts +19 -0
- package/build/types/real-time-viewer-next/src/utils/smoothDamp.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/sunAngles.d.ts +29 -0
- package/build/types/real-time-viewer-next/src/utils/sunAngles.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/suspense.d.ts +11 -0
- package/build/types/real-time-viewer-next/src/utils/suspense.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/whiteRoomPreset.d.ts +48 -0
- package/build/types/real-time-viewer-next/src/utils/whiteRoomPreset.d.ts.map +1 -0
- package/build/types/real-time-viewer-next/src/utils/withRenderTarget.d.ts +48 -0
- package/build/types/real-time-viewer-next/src/utils/withRenderTarget.d.ts.map +1 -0
- package/build/umd/114.real-time-viewer-next.umd.min.js +2 -0
- package/build/umd/114.real-time-viewer-next.umd.min.js.LICENSE.txt +1 -0
- package/build/umd/699.real-time-viewer-next.umd.min.js +2 -0
- package/build/umd/699.real-time-viewer-next.umd.min.js.LICENSE.txt +1 -0
- package/build/umd/964.real-time-viewer-next.umd.min.js +2 -0
- package/build/umd/964.real-time-viewer-next.umd.min.js.LICENSE.txt +1 -0
- package/build/umd/real-time-viewer-next.umd.min.js +2 -0
- package/build/umd/real-time-viewer-next.umd.min.js.LICENSE.txt +117 -0
- package/build/umd/report.html +38 -0
- package/package.json +31 -0
|
@@ -0,0 +1,2536 @@
|
|
|
1
|
+
import { p as parseElementKey, u as useRTVStore, b as bootPhase, D as DECK_COLLAR_LOAD_ID } from './index-920dd901.js';
|
|
2
|
+
import { useMemo, useRef, useEffect, useState, useCallback } from 'react';
|
|
3
|
+
import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
|
|
4
|
+
import _defineProperty from '@babel/runtime/helpers/defineProperty';
|
|
5
|
+
import { useThree, useFrame } from '@react-three/fiber';
|
|
6
|
+
import { getLogger } from '@crystaldesign/diva-core';
|
|
7
|
+
import { Box3, Matrix4, Vector3, Matrix3, MeshStandardMaterial, Shape, Path, ExtrudeGeometry, BufferAttribute, TextureLoader, SRGBColorSpace, RepeatWrapping } from 'three';
|
|
8
|
+
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
|
|
9
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
10
|
+
import '@babel/runtime/helpers/asyncToGenerator';
|
|
11
|
+
import '@babel/runtime/regenerator';
|
|
12
|
+
import 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
13
|
+
import 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
14
|
+
import 'zustand';
|
|
15
|
+
import 'zustand/middleware';
|
|
16
|
+
import '@babel/runtime/helpers/typeof';
|
|
17
|
+
import 'postprocessing';
|
|
18
|
+
import '@react-three/drei';
|
|
19
|
+
import '@babel/runtime/helpers/classCallCheck';
|
|
20
|
+
import '@babel/runtime/helpers/createClass';
|
|
21
|
+
import '@babel/runtime/helpers/possibleConstructorReturn';
|
|
22
|
+
import '@babel/runtime/helpers/getPrototypeOf';
|
|
23
|
+
import '@babel/runtime/helpers/inherits';
|
|
24
|
+
import 'classnames';
|
|
25
|
+
import 'three-mesh-bvh';
|
|
26
|
+
import '@babel/runtime/helpers/objectWithoutProperties';
|
|
27
|
+
import 'three/examples/jsm/environments/RoomEnvironment.js';
|
|
28
|
+
import 'three/examples/jsm/loaders/RGBELoader.js';
|
|
29
|
+
import 'three/examples/jsm/loaders/EXRLoader.js';
|
|
30
|
+
import '@react-three/postprocessing';
|
|
31
|
+
import '@babel/runtime/helpers/construct';
|
|
32
|
+
import 'detect-gpu';
|
|
33
|
+
|
|
34
|
+
var ELEMENT_PREFIX = 'Element#';
|
|
35
|
+
var _elementBox = new Box3();
|
|
36
|
+
var _unionBox = new Box3();
|
|
37
|
+
|
|
38
|
+
/** Article bounds in the deck mesh's local frame. */
|
|
39
|
+
|
|
40
|
+
/** One planner element's article codex and its plan area, for the rule lookup. */
|
|
41
|
+
|
|
42
|
+
/** Top-level planner element groups (no `Element#` ancestor) — the ones the anchor moves and the projection walks. */
|
|
43
|
+
function topLevelElements(elementObjects) {
|
|
44
|
+
return elementObjects.filter(function (obj) {
|
|
45
|
+
for (var p = obj.parent; p; p = p.parent) {
|
|
46
|
+
if (p.name.startsWith(ELEMENT_PREFIX)) return false;
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Same rect moved by a floor-local delta. */
|
|
53
|
+
function shiftRect(rect, delta) {
|
|
54
|
+
return {
|
|
55
|
+
minX: rect.minX + delta.dx,
|
|
56
|
+
maxX: rect.maxX + delta.dx,
|
|
57
|
+
minY: rect.minY + delta.dy,
|
|
58
|
+
maxY: rect.maxY + delta.dy,
|
|
59
|
+
minZ: rect.minZ + delta.dz,
|
|
60
|
+
maxZ: rect.maxZ + delta.dz
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Union bounds of every planner `Element#` group, expressed in the floor meshes'
|
|
66
|
+
* shared local space via the first name-matched deck mesh's inverse world
|
|
67
|
+
* matrix. That mesh is the one `meshesMatching` also hands the collar as its
|
|
68
|
+
* reference, so both work in identical coordinates.
|
|
69
|
+
*
|
|
70
|
+
* Returns null while the article or the deck isn't in the scene yet.
|
|
71
|
+
*/
|
|
72
|
+
function measureArticle(scene, patterns, elementsInfo) {
|
|
73
|
+
var _floorGeometry$boundi, _floorGeometry$boundi2;
|
|
74
|
+
var elementObjects = [];
|
|
75
|
+
var codexesByArea = [];
|
|
76
|
+
// Single-element holder rather than a `let`: the reference is assigned inside
|
|
77
|
+
// the traversal callback, which TypeScript's flow analysis does not follow.
|
|
78
|
+
var floorMatch = [];
|
|
79
|
+
_unionBox.makeEmpty();
|
|
80
|
+
scene.traverse(function (obj) {
|
|
81
|
+
var _elementsInfo$get;
|
|
82
|
+
// Same pre-order and same first-match rule as `meshesMatching`, folded into
|
|
83
|
+
// this traversal: a measure runs every frame while the pipeline converges,
|
|
84
|
+
// and walking the whole scene twice for it is the avoidable half.
|
|
85
|
+
if (floorMatch.length === 0) {
|
|
86
|
+
var mesh = obj;
|
|
87
|
+
if (mesh.isMesh && patterns.some(function (p) {
|
|
88
|
+
return mesh.name.includes(p);
|
|
89
|
+
})) floorMatch.push(mesh);
|
|
90
|
+
}
|
|
91
|
+
if (!obj.name.startsWith(ELEMENT_PREFIX)) return;
|
|
92
|
+
elementObjects.push(obj);
|
|
93
|
+
_elementBox.setFromObject(obj);
|
|
94
|
+
if (_elementBox.isEmpty()) return;
|
|
95
|
+
_unionBox.union(_elementBox);
|
|
96
|
+
var codex = (_elementsInfo$get = elementsInfo.get(parseElementKey(obj.name))) === null || _elementsInfo$get === void 0 ? void 0 : _elementsInfo$get.articleCodex;
|
|
97
|
+
if (codex !== undefined) {
|
|
98
|
+
codexesByArea.push({
|
|
99
|
+
codex: codex,
|
|
100
|
+
area: (_elementBox.max.x - _elementBox.min.x) * (_elementBox.max.z - _elementBox.min.z)
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
codexesByArea.sort(function (a, b) {
|
|
105
|
+
return b.area - a.area;
|
|
106
|
+
});
|
|
107
|
+
if (_unionBox.isEmpty()) return null;
|
|
108
|
+
var floorMesh = floorMatch[0];
|
|
109
|
+
if (!floorMesh) return null;
|
|
110
|
+
var floorLocal = new Matrix4().copy(floorMesh.matrixWorld).invert();
|
|
111
|
+
var floorGeometry = floorMesh.geometry;
|
|
112
|
+
if (!floorGeometry.boundingBox) floorGeometry.computeBoundingBox();
|
|
113
|
+
var floorTopY = (_floorGeometry$boundi = (_floorGeometry$boundi2 = floorGeometry.boundingBox) === null || _floorGeometry$boundi2 === void 0 ? void 0 : _floorGeometry$boundi2.max.y) !== null && _floorGeometry$boundi !== void 0 ? _floorGeometry$boundi : 0;
|
|
114
|
+
var worldBottomY = _unionBox.min.y;
|
|
115
|
+
// applyMatrix4 transforms all 8 corners and re-wraps them axis-aligned —
|
|
116
|
+
// exactly the article's footprint in floor-local space.
|
|
117
|
+
_unionBox.applyMatrix4(floorLocal);
|
|
118
|
+
return {
|
|
119
|
+
rect: {
|
|
120
|
+
minX: _unionBox.min.x,
|
|
121
|
+
maxX: _unionBox.max.x,
|
|
122
|
+
minY: _unionBox.min.y,
|
|
123
|
+
maxY: _unionBox.max.y,
|
|
124
|
+
minZ: _unionBox.min.z,
|
|
125
|
+
maxZ: _unionBox.max.z
|
|
126
|
+
},
|
|
127
|
+
worldBottomY: worldBottomY,
|
|
128
|
+
elementObjects: elementObjects,
|
|
129
|
+
codexesByArea: codexesByArea,
|
|
130
|
+
floorWorld: floorMesh.matrixWorld,
|
|
131
|
+
floorLocal: floorLocal,
|
|
132
|
+
floorTopY: floorTopY
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function _createForOfIteratorHelper$6(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$6(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
137
|
+
function _unsupportedIterableToArray$6(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$6(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$6(r, a) : void 0; } }
|
|
138
|
+
function _arrayLikeToArray$6(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
139
|
+
/*
|
|
140
|
+
* 2D polygon primitives — area, simplification, self-intersection guard, hull.
|
|
141
|
+
*/
|
|
142
|
+
|
|
143
|
+
/** 2D point; axis meaning depends on context (path space: x/y, floor space: x/z). */
|
|
144
|
+
|
|
145
|
+
var EPS$2 = 1e-9;
|
|
146
|
+
|
|
147
|
+
/** Shoelace signed area — positive for counter-clockwise winding (y-up convention). */
|
|
148
|
+
function polygonSignedArea(points) {
|
|
149
|
+
var area = 0;
|
|
150
|
+
for (var i = 0; i < points.length; i++) {
|
|
151
|
+
var _points$i = _slicedToArray(points[i], 2),
|
|
152
|
+
x1 = _points$i[0],
|
|
153
|
+
y1 = _points$i[1];
|
|
154
|
+
var _points = _slicedToArray(points[(i + 1) % points.length], 2),
|
|
155
|
+
x2 = _points[0],
|
|
156
|
+
y2 = _points[1];
|
|
157
|
+
area += x1 * y2 - x2 * y1;
|
|
158
|
+
}
|
|
159
|
+
return area / 2;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Douglas–Peucker on an open chain (endpoints kept). */
|
|
163
|
+
function simplifyChain(points, tolerance, out) {
|
|
164
|
+
if (points.length < 3) {
|
|
165
|
+
for (var i = 0; i < points.length - 1; i++) out.push(points[i]);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
var _points$ = _slicedToArray(points[0], 2),
|
|
169
|
+
ax = _points$[0],
|
|
170
|
+
az = _points$[1];
|
|
171
|
+
var _points2 = _slicedToArray(points[points.length - 1], 2),
|
|
172
|
+
bx = _points2[0],
|
|
173
|
+
bz = _points2[1];
|
|
174
|
+
var len = Math.hypot(bx - ax, bz - az) || EPS$2;
|
|
175
|
+
var maxDist = -1;
|
|
176
|
+
var maxIdx = -1;
|
|
177
|
+
for (var _i = 1; _i < points.length - 1; _i++) {
|
|
178
|
+
var _points$_i = _slicedToArray(points[_i], 2),
|
|
179
|
+
px = _points$_i[0],
|
|
180
|
+
pz = _points$_i[1];
|
|
181
|
+
var dist = Math.abs((bx - ax) * (az - pz) - (ax - px) * (bz - az)) / len;
|
|
182
|
+
if (dist > maxDist) {
|
|
183
|
+
maxDist = dist;
|
|
184
|
+
maxIdx = _i;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (maxDist <= tolerance) {
|
|
188
|
+
out.push(points[0]);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
simplifyChain(points.slice(0, maxIdx + 1), tolerance, out);
|
|
192
|
+
simplifyChain(points.slice(maxIdx), tolerance, out);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Douglas–Peucker for a closed polygon: split at the point farthest from the start, simplify both halves. */
|
|
196
|
+
function simplifyClosedPolygon(points, tolerance) {
|
|
197
|
+
if (points.length < 4 || tolerance <= 0) return _toConsumableArray(points);
|
|
198
|
+
var _points$2 = _slicedToArray(points[0], 2),
|
|
199
|
+
sx = _points$2[0],
|
|
200
|
+
sz = _points$2[1];
|
|
201
|
+
var farIdx = 1;
|
|
202
|
+
var farDist = -1;
|
|
203
|
+
for (var i = 1; i < points.length; i++) {
|
|
204
|
+
var d = Math.hypot(points[i][0] - sx, points[i][1] - sz);
|
|
205
|
+
if (d > farDist) {
|
|
206
|
+
farDist = d;
|
|
207
|
+
farIdx = i;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
var out = [];
|
|
211
|
+
simplifyChain(points.slice(0, farIdx + 1), tolerance, out);
|
|
212
|
+
simplifyChain([].concat(_toConsumableArray(points.slice(farIdx)), [points[0]]), tolerance, out);
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Do the closed segments a→b and c→d properly cross? Shared endpoints don't count. */
|
|
217
|
+
function segmentsCross(ax, az, bx, bz, cx, cz, dx, dz) {
|
|
218
|
+
var cross = function cross(px, pz, qx, qz, rx, rz) {
|
|
219
|
+
return (qx - px) * (rz - pz) - (qz - pz) * (rx - px);
|
|
220
|
+
};
|
|
221
|
+
var d1 = cross(ax, az, bx, bz, cx, cz);
|
|
222
|
+
var d2 = cross(ax, az, bx, bz, dx, dz);
|
|
223
|
+
var d3 = cross(cx, cz, dx, dz, ax, az);
|
|
224
|
+
var d4 = cross(cx, cz, dx, dz, bx, bz);
|
|
225
|
+
// Strict sign change on both segments = a proper crossing. Collinear/touching
|
|
226
|
+
// cases are deliberately NOT flagged: traced outlines touch constantly and a
|
|
227
|
+
// triangulator handles that fine.
|
|
228
|
+
return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** True when no two non-adjacent edges of the closed polygon cross. O(n²) — fine for outline-sized input. */
|
|
232
|
+
function isSimplePolygon(points) {
|
|
233
|
+
var n = points.length;
|
|
234
|
+
if (n < 4) return true;
|
|
235
|
+
for (var i = 0; i < n; i++) {
|
|
236
|
+
var _points$i2 = _slicedToArray(points[i], 2),
|
|
237
|
+
ax = _points$i2[0],
|
|
238
|
+
az = _points$i2[1];
|
|
239
|
+
var _points3 = _slicedToArray(points[(i + 1) % n], 2),
|
|
240
|
+
bx = _points3[0],
|
|
241
|
+
bz = _points3[1];
|
|
242
|
+
for (var j = i + 1; j < n; j++) {
|
|
243
|
+
// Skip the shared-endpoint neighbours (j === i+1, and the wrap pair).
|
|
244
|
+
if (j === i + 1 || i === 0 && j === n - 1) continue;
|
|
245
|
+
var _points$j = _slicedToArray(points[j], 2),
|
|
246
|
+
cx = _points$j[0],
|
|
247
|
+
cz = _points$j[1];
|
|
248
|
+
var _points4 = _slicedToArray(points[(j + 1) % n], 2),
|
|
249
|
+
dx = _points4[0],
|
|
250
|
+
dz = _points4[1];
|
|
251
|
+
if (segmentsCross(ax, az, bx, bz, cx, cz, dx, dz)) return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Convex hull (Andrew's monotone chain), counter-clockwise. */
|
|
258
|
+
function convexHull(points) {
|
|
259
|
+
if (points.length < 3) return _toConsumableArray(points);
|
|
260
|
+
var sorted = _toConsumableArray(points).sort(function (a, b) {
|
|
261
|
+
return a[0] - b[0] || a[1] - b[1];
|
|
262
|
+
});
|
|
263
|
+
var cross = function cross(o, a, b) {
|
|
264
|
+
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
|
|
265
|
+
};
|
|
266
|
+
var build = function build(input) {
|
|
267
|
+
var chain = [];
|
|
268
|
+
var _iterator = _createForOfIteratorHelper$6(input),
|
|
269
|
+
_step;
|
|
270
|
+
try {
|
|
271
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
272
|
+
var p = _step.value;
|
|
273
|
+
while (chain.length >= 2 && cross(chain[chain.length - 2], chain[chain.length - 1], p) <= 0) chain.pop();
|
|
274
|
+
chain.push(p);
|
|
275
|
+
}
|
|
276
|
+
} catch (err) {
|
|
277
|
+
_iterator.e(err);
|
|
278
|
+
} finally {
|
|
279
|
+
_iterator.f();
|
|
280
|
+
}
|
|
281
|
+
return chain;
|
|
282
|
+
};
|
|
283
|
+
var lower = build(sorted);
|
|
284
|
+
var upper = build(_toConsumableArray(sorted).reverse());
|
|
285
|
+
return [].concat(_toConsumableArray(lower.slice(0, -1)), _toConsumableArray(upper.slice(0, -1)));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Guarantee a polygon a triangulator can use. A self-intersecting outline is
|
|
290
|
+
* NOT rejected by earcut — it silently produces garbage (a stray wedge across
|
|
291
|
+
* the shape), so anything feeding generated geometry has to be checked first.
|
|
292
|
+
*
|
|
293
|
+
* The repair is the convex hull: a pool/article silhouette is convex or nearly
|
|
294
|
+
* so, which makes the hull a close and always-simple stand-in — visibly a
|
|
295
|
+
* little too generous rather than visibly broken. Returns null when even that
|
|
296
|
+
* degenerates.
|
|
297
|
+
*/
|
|
298
|
+
function ensureSimplePolygon(points) {
|
|
299
|
+
if (points.length < 3) return null;
|
|
300
|
+
if (isSimplePolygon(points)) {
|
|
301
|
+
// Collinear points pass the crossing test but enclose nothing, and a polygon the triangulator
|
|
302
|
+
// turns into zero triangles is not one this may hand back as clean.
|
|
303
|
+
if (Math.abs(polygonSignedArea(points)) <= EPS$2) return null;
|
|
304
|
+
return {
|
|
305
|
+
points: _toConsumableArray(points),
|
|
306
|
+
repaired: false
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
var hull = convexHull(points);
|
|
310
|
+
// The area check must stay inside the branches: a symmetric bowtie has signed area exactly 0,
|
|
311
|
+
// and it is the hull repair - not a rejection - that such an outline needs.
|
|
312
|
+
if (hull.length < 3 || Math.abs(polygonSignedArea(hull)) <= EPS$2) return null;
|
|
313
|
+
return {
|
|
314
|
+
points: hull,
|
|
315
|
+
repaired: true
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function _createForOfIteratorHelper$5(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$5(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
320
|
+
function _unsupportedIterableToArray$5(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$5(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$5(r, a) : void 0; } }
|
|
321
|
+
function _arrayLikeToArray$5(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
322
|
+
/*
|
|
323
|
+
* Binary raster mask operations — morphology (closing, offsetting, enclosed
|
|
324
|
+
* fill) and marching-squares contouring.
|
|
325
|
+
*
|
|
326
|
+
* A mask is an `nx * nz` Uint8Array of 0/1, row-major, one value per cell
|
|
327
|
+
* centre; `originX/originZ` and `cell` map cell indices back to world units.
|
|
328
|
+
*/
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Morphological closing (dilate then erode) with a square kernel of the given
|
|
332
|
+
* radius — bridges seams between separately modelled parts without moving the
|
|
333
|
+
* silhouette.
|
|
334
|
+
*
|
|
335
|
+
* Each of the four passes is a SLIDING-WINDOW box count: the window is updated
|
|
336
|
+
* by one entering and one leaving row/column instead of being re-summed, so
|
|
337
|
+
* the cost is O(cells) independent of radius and every access is sequential.
|
|
338
|
+
* The naive per-cell kernel form (especially its column-strided half) costs an
|
|
339
|
+
* order of magnitude more at a fine raster.
|
|
340
|
+
*/
|
|
341
|
+
function closeMask(mask, nx, nz, radius) {
|
|
342
|
+
if (radius < 1) return;
|
|
343
|
+
var scratch = new Uint8Array(mask.length);
|
|
344
|
+
boxPassX(mask, scratch, nx, nz, radius, true);
|
|
345
|
+
boxPassZ(scratch, mask, nx, nz, radius, true);
|
|
346
|
+
boxPassX(mask, scratch, nx, nz, radius, false);
|
|
347
|
+
boxPassZ(scratch, mask, nx, nz, radius, false);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Turn the mask into the SOLID region its outer contour encloses: flood the
|
|
352
|
+
* background inward from the (always empty) border and set everything the
|
|
353
|
+
* flood could not reach. Returns how many cells were added.
|
|
354
|
+
*/
|
|
355
|
+
function fillEnclosed(mask, nx, nz) {
|
|
356
|
+
var reached = new Uint8Array(mask.length);
|
|
357
|
+
var stack = [];
|
|
358
|
+
var visit = function visit(i) {
|
|
359
|
+
if (mask[i] !== 0 || reached[i] !== 0) return;
|
|
360
|
+
reached[i] = 1;
|
|
361
|
+
stack.push(i);
|
|
362
|
+
};
|
|
363
|
+
for (var ix = 0; ix < nx; ix++) {
|
|
364
|
+
visit(ix);
|
|
365
|
+
visit((nz - 1) * nx + ix);
|
|
366
|
+
}
|
|
367
|
+
for (var iz = 0; iz < nz; iz++) {
|
|
368
|
+
visit(iz * nx);
|
|
369
|
+
visit(iz * nx + nx - 1);
|
|
370
|
+
}
|
|
371
|
+
while (stack.length > 0) {
|
|
372
|
+
var i = stack.pop();
|
|
373
|
+
var _ix = i % nx;
|
|
374
|
+
var _iz = (i - _ix) / nx;
|
|
375
|
+
if (_ix > 0) visit(i - 1);
|
|
376
|
+
if (_ix < nx - 1) visit(i + 1);
|
|
377
|
+
if (_iz > 0) visit(i - nx);
|
|
378
|
+
if (_iz < nz - 1) visit(i + nx);
|
|
379
|
+
}
|
|
380
|
+
var added = 0;
|
|
381
|
+
for (var _i = 0; _i < mask.length; _i++) {
|
|
382
|
+
if (mask[_i] === 0 && reached[_i] === 0) {
|
|
383
|
+
mask[_i] = 1;
|
|
384
|
+
added += 1;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return added;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Single morphological pass — dilate (grow) or erode (shrink) by a square
|
|
392
|
+
* kernel of the given radius. The raster form of a polygon offset: the same
|
|
393
|
+
* operation applied uniformly to every boundary cell, so it can neither miter
|
|
394
|
+
* a corner off nor cross itself across a concavity. The kernel is a square, so
|
|
395
|
+
* the offset is Chebyshev — exact on axis-aligned edges, up to √2 larger on
|
|
396
|
+
* 45° ones.
|
|
397
|
+
*/
|
|
398
|
+
function offsetMask(mask, nx, nz, radius, dilate) {
|
|
399
|
+
var scratch = new Uint8Array(mask.length);
|
|
400
|
+
boxPassX(mask, scratch, nx, nz, radius, dilate);
|
|
401
|
+
boxPassZ(scratch, mask, nx, nz, radius, dilate);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Per-index window sizes along an axis of length `n`, clipped at both ends (erode compares against these). */
|
|
405
|
+
function windowSizes(radius, n) {
|
|
406
|
+
var sizes = new Int32Array(n);
|
|
407
|
+
for (var i = 0; i < n; i++) sizes[i] = Math.min(n - 1, i + radius) - Math.max(0, i - radius) + 1;
|
|
408
|
+
return sizes;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Horizontal box pass: `dilate` = any cell set in the window, else = all cells set. */
|
|
412
|
+
function boxPassX(src, dst, nx, nz, radius, dilate) {
|
|
413
|
+
var sizes = windowSizes(radius, nx);
|
|
414
|
+
for (var iz = 0; iz < nz; iz++) {
|
|
415
|
+
var row = iz * nx;
|
|
416
|
+
var count = 0;
|
|
417
|
+
for (var k = 0; k <= radius && k < nx; k++) count += src[row + k];
|
|
418
|
+
for (var ix = 0; ix < nx; ix++) {
|
|
419
|
+
dst[row + ix] = (dilate ? count > 0 : count === sizes[ix]) ? 1 : 0;
|
|
420
|
+
var leaving = ix - radius;
|
|
421
|
+
var entering = ix + radius + 1;
|
|
422
|
+
if (leaving >= 0) count -= src[row + leaving];
|
|
423
|
+
if (entering < nx) count += src[row + entering];
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Vertical box pass, carrying one count per column so rows are read in order. */
|
|
429
|
+
function boxPassZ(src, dst, nx, nz, radius, dilate) {
|
|
430
|
+
var counts = new Int32Array(nx);
|
|
431
|
+
var sizes = windowSizes(radius, nz);
|
|
432
|
+
for (var k = 0; k <= radius && k < nz; k++) {
|
|
433
|
+
var row = k * nx;
|
|
434
|
+
for (var ix = 0; ix < nx; ix++) counts[ix] += src[row + ix];
|
|
435
|
+
}
|
|
436
|
+
for (var iz = 0; iz < nz; iz++) {
|
|
437
|
+
var _row = iz * nx;
|
|
438
|
+
var size = sizes[iz];
|
|
439
|
+
for (var _ix2 = 0; _ix2 < nx; _ix2++) dst[_row + _ix2] = (dilate ? counts[_ix2] > 0 : counts[_ix2] === size) ? 1 : 0;
|
|
440
|
+
var leaving = iz - radius;
|
|
441
|
+
var entering = iz + radius + 1;
|
|
442
|
+
if (leaving >= 0) {
|
|
443
|
+
var o = leaving * nx;
|
|
444
|
+
for (var _ix3 = 0; _ix3 < nx; _ix3++) counts[_ix3] -= src[o + _ix3];
|
|
445
|
+
}
|
|
446
|
+
if (entering < nz) {
|
|
447
|
+
var _o = entering * nx;
|
|
448
|
+
for (var _ix4 = 0; _ix4 < nx; _ix4++) counts[_ix4] += src[_o + _ix4];
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Segment table per marching-squares case. Corners c0=(ix,iz) c1=(ix+1,iz)
|
|
455
|
+
* c2=(ix+1,iz+1) c3=(ix,iz+1); edges e0=c0c1 e1=c1c2 e2=c2c3 e3=c3c0. The
|
|
456
|
+
* saddle cases (5, 10) are split into two separate blobs.
|
|
457
|
+
*/
|
|
458
|
+
var MS_SEGMENTS = [[], [[3, 0]], [[0, 1]], [[3, 1]], [[1, 2]], [[3, 0], [1, 2]], [[0, 2]], [[3, 2]], [[2, 3]], [[0, 2]], [[0, 1], [2, 3]], [[1, 2]], [[1, 3]], [[0, 1]], [[3, 0]], []];
|
|
459
|
+
|
|
460
|
+
/** Contour a binary node grid (values at cell centers) into closed loops of edge-midpoint coordinates. */
|
|
461
|
+
function marchingSquaresLoops(mask, nx, nz, originX, originZ, cell) {
|
|
462
|
+
// Edge keys: horizontal edge between nodes (ix,iz)-(ix+1,iz) → 2*(iz*nx+ix); vertical (ix,iz)-(ix,iz+1) → 2*(iz*nx+ix)+1.
|
|
463
|
+
var edgeKey = function edgeKey(edge, ix, iz) {
|
|
464
|
+
if (edge === 0) return 2 * (iz * nx + ix);
|
|
465
|
+
if (edge === 2) return 2 * ((iz + 1) * nx + ix);
|
|
466
|
+
if (edge === 3) return 2 * (iz * nx + ix) + 1;
|
|
467
|
+
return 2 * (iz * nx + ix + 1) + 1;
|
|
468
|
+
};
|
|
469
|
+
var edgePoint = function edgePoint(key) {
|
|
470
|
+
var idx = Math.floor(key / 2);
|
|
471
|
+
var ix = idx % nx;
|
|
472
|
+
var iz = Math.floor(idx / nx);
|
|
473
|
+
return key % 2 === 0 ? [originX + (ix + 1) * cell, originZ + (iz + 0.5) * cell] : [originX + (ix + 0.5) * cell, originZ + (iz + 1) * cell];
|
|
474
|
+
};
|
|
475
|
+
var adjacency = new Map();
|
|
476
|
+
var link = function link(a, b) {
|
|
477
|
+
var la = adjacency.get(a);
|
|
478
|
+
if (la) la.push(b);else adjacency.set(a, [b]);
|
|
479
|
+
var lb = adjacency.get(b);
|
|
480
|
+
if (lb) lb.push(a);else adjacency.set(b, [a]);
|
|
481
|
+
};
|
|
482
|
+
for (var iz = 0; iz < nz - 1; iz++) {
|
|
483
|
+
for (var ix = 0; ix < nx - 1; ix++) {
|
|
484
|
+
var c = mask[iz * nx + ix] | mask[iz * nx + ix + 1] << 1 | mask[(iz + 1) * nx + ix + 1] << 2 | mask[(iz + 1) * nx + ix] << 3;
|
|
485
|
+
// Fully inside / fully outside is the overwhelming majority of cells —
|
|
486
|
+
// returning before the segment lookup keeps the sweep near memory speed.
|
|
487
|
+
if (c === 0 || c === 15) continue;
|
|
488
|
+
var _iterator = _createForOfIteratorHelper$5(MS_SEGMENTS[c]),
|
|
489
|
+
_step;
|
|
490
|
+
try {
|
|
491
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
492
|
+
var _step$value = _slicedToArray(_step.value, 2),
|
|
493
|
+
ea = _step$value[0],
|
|
494
|
+
eb = _step$value[1];
|
|
495
|
+
link(edgeKey(ea, ix, iz), edgeKey(eb, ix, iz));
|
|
496
|
+
}
|
|
497
|
+
} catch (err) {
|
|
498
|
+
_iterator.e(err);
|
|
499
|
+
} finally {
|
|
500
|
+
_iterator.f();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
var visited = new Set();
|
|
505
|
+
var loops = [];
|
|
506
|
+
var _iterator2 = _createForOfIteratorHelper$5(adjacency.keys()),
|
|
507
|
+
_step2;
|
|
508
|
+
try {
|
|
509
|
+
var _loop = function _loop() {
|
|
510
|
+
var start = _step2.value;
|
|
511
|
+
if (visited.has(start)) return 1; // continue
|
|
512
|
+
var loop = [];
|
|
513
|
+
var cur = start;
|
|
514
|
+
var prev = -1;
|
|
515
|
+
while (!visited.has(cur)) {
|
|
516
|
+
var _adjacency$get;
|
|
517
|
+
visited.add(cur);
|
|
518
|
+
loop.push(edgePoint(cur));
|
|
519
|
+
var next = ((_adjacency$get = adjacency.get(cur)) !== null && _adjacency$get !== void 0 ? _adjacency$get : []).find(function (k) {
|
|
520
|
+
return k !== prev && !visited.has(k);
|
|
521
|
+
});
|
|
522
|
+
if (next === undefined) break;
|
|
523
|
+
prev = cur;
|
|
524
|
+
cur = next;
|
|
525
|
+
}
|
|
526
|
+
if (loop.length >= 3) loops.push(loop);
|
|
527
|
+
};
|
|
528
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
529
|
+
if (_loop()) continue;
|
|
530
|
+
}
|
|
531
|
+
} catch (err) {
|
|
532
|
+
_iterator2.e(err);
|
|
533
|
+
} finally {
|
|
534
|
+
_iterator2.f();
|
|
535
|
+
}
|
|
536
|
+
return loops;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
var EPS$1 = 1e-9;
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Ceiling on the raster's cell count. The extent comes from article geometry, so a mis-scaled
|
|
543
|
+
* model (metres authored as millimetres) can ask for a mask no browser can hold — and
|
|
544
|
+
* `closeMask` / `fillEnclosed` / `offsetMask` each allocate another buffer this size. 16M cells
|
|
545
|
+
* is a 20 m x 20 m article at the default 5 mm cell, far beyond anything real, and refusing is
|
|
546
|
+
* graceful: the caller falls back to the measured rect.
|
|
547
|
+
*/
|
|
548
|
+
var MAX_MASK_CELLS = 16000000;
|
|
549
|
+
|
|
550
|
+
/** What a trace did internally — see {@link TraceFootprintOptions.diagnostics}. */
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Trace the outer silhouette of a triangle soup already projected onto the
|
|
554
|
+
* plane: `triangleXZ` holds 6 numbers per triangle (x0 z0 x1 z1 x2 z2). The
|
|
555
|
+
* triangles are rasterized into a binary mask (cell centers + vertices + edge
|
|
556
|
+
* samples, then a 3×3 closing to seal sliver gaps), the mask is contoured with
|
|
557
|
+
* marching squares, and the largest closed loop is simplified. Returns [] when
|
|
558
|
+
* nothing usable comes out (fewer than 3 triangles, degenerate extent).
|
|
559
|
+
*/
|
|
560
|
+
function traceFootprintFromTriangles(triangleXZ) {
|
|
561
|
+
var _options$cellSize, _options$simplifyTole, _options$closeGap, _options$offset;
|
|
562
|
+
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
563
|
+
var cell = (_options$cellSize = options.cellSize) !== null && _options$cellSize !== void 0 ? _options$cellSize : 0.005;
|
|
564
|
+
var tolerance = (_options$simplifyTole = options.simplifyTolerance) !== null && _options$simplifyTole !== void 0 ? _options$simplifyTole : cell;
|
|
565
|
+
// Closing radius in cells, so the sealed gap stays ~`closeGap` at any resolution.
|
|
566
|
+
var closeGap = (_options$closeGap = options.closeGap) !== null && _options$closeGap !== void 0 ? _options$closeGap : 0.02;
|
|
567
|
+
var closeRadius = closeGap <= 0 ? 0 : Math.max(1, Math.round(closeGap / (cell * 2)));
|
|
568
|
+
var offset = (_options$offset = options.offset) !== null && _options$offset !== void 0 ? _options$offset : 0;
|
|
569
|
+
var offsetRadius = Math.round(Math.abs(offset) / cell);
|
|
570
|
+
var grow = offset > 0;
|
|
571
|
+
var triCount = Math.floor(triangleXZ.length / 6);
|
|
572
|
+
if (triCount < 1 || cell <= 0) return [];
|
|
573
|
+
var minX = Infinity;
|
|
574
|
+
var maxX = -Infinity;
|
|
575
|
+
var minZ = Infinity;
|
|
576
|
+
var maxZ = -Infinity;
|
|
577
|
+
for (var i = 0; i < triCount * 6; i += 2) {
|
|
578
|
+
var x = triangleXZ[i];
|
|
579
|
+
var z = triangleXZ[i + 1];
|
|
580
|
+
if (x < minX) minX = x;
|
|
581
|
+
if (x > maxX) maxX = x;
|
|
582
|
+
if (z < minZ) minZ = z;
|
|
583
|
+
if (z > maxZ) maxZ = z;
|
|
584
|
+
}
|
|
585
|
+
if (!Number.isFinite(minX) || maxX - minX < cell || maxZ - minZ < cell) return [];
|
|
586
|
+
|
|
587
|
+
// Empty border of `closeRadius + 1` cells per side: the closing's dilation
|
|
588
|
+
// must have room to spread without the erosion then eating the shape's
|
|
589
|
+
// outermost cells, and the last ring keeps the contour closed.
|
|
590
|
+
var border = closeRadius + (grow ? offsetRadius : 0) + 1;
|
|
591
|
+
var originX = minX - border * cell;
|
|
592
|
+
var originZ = minZ - border * cell;
|
|
593
|
+
var nx = Math.ceil((maxX - minX) / cell) + 2 * border + 1;
|
|
594
|
+
var nz = Math.ceil((maxZ - minZ) / cell) + 2 * border + 1;
|
|
595
|
+
if (nx * nz > MAX_MASK_CELLS) return [];
|
|
596
|
+
var mask = new Uint8Array(nx * nz);
|
|
597
|
+
var cellOf = function cellOf(v, origin, n) {
|
|
598
|
+
return Math.min(n - border - 1, Math.max(border, Math.floor((v - origin) / cell)));
|
|
599
|
+
};
|
|
600
|
+
var mark = function mark(x, z) {
|
|
601
|
+
mask[cellOf(z, originZ, nz) * nx + cellOf(x, originX, nx)] = 1;
|
|
602
|
+
};
|
|
603
|
+
for (var t = 0; t < triCount; t++) {
|
|
604
|
+
var o = t * 6;
|
|
605
|
+
var x0 = triangleXZ[o];
|
|
606
|
+
var z0 = triangleXZ[o + 1];
|
|
607
|
+
var x1 = triangleXZ[o + 2];
|
|
608
|
+
var z1 = triangleXZ[o + 3];
|
|
609
|
+
var x2 = triangleXZ[o + 4];
|
|
610
|
+
var z2 = triangleXZ[o + 5];
|
|
611
|
+
mark(x0, z0);
|
|
612
|
+
mark(x1, z1);
|
|
613
|
+
mark(x2, z2);
|
|
614
|
+
|
|
615
|
+
// Edge sampling is only needed for SLIVERS — a triangle thinner than a
|
|
616
|
+
// raster row can fall between row centers and be missed by the scanline
|
|
617
|
+
// fill below. Fat triangles (the vast majority in a real mesh) are fully
|
|
618
|
+
// covered by the fill, and skipping their edges is the difference between
|
|
619
|
+
// a fine raster being affordable and not.
|
|
620
|
+
var twiceArea = Math.abs((x1 - x0) * (z2 - z0) - (x2 - x0) * (z1 - z0));
|
|
621
|
+
var longestEdge = Math.max(Math.hypot(x1 - x0, z1 - z0), Math.hypot(x2 - x1, z2 - z1), Math.hypot(x0 - x2, z0 - z2));
|
|
622
|
+
var thickness = longestEdge > EPS$1 ? twiceArea / longestEdge : 0;
|
|
623
|
+
if (thickness < cell * 1.5) {
|
|
624
|
+
var edges = [[x0, z0, x1, z1], [x1, z1, x2, z2], [x2, z2, x0, z0]];
|
|
625
|
+
for (var _i = 0, _edges = edges; _i < _edges.length; _i++) {
|
|
626
|
+
var _edges$_i = _slicedToArray(_edges[_i], 4),
|
|
627
|
+
ax = _edges$_i[0],
|
|
628
|
+
az = _edges$_i[1],
|
|
629
|
+
bx = _edges$_i[2],
|
|
630
|
+
bz = _edges$_i[3];
|
|
631
|
+
var steps = Math.ceil(Math.hypot(bx - ax, bz - az) / (cell * 0.7));
|
|
632
|
+
for (var st = 1; st < steps; st++) {
|
|
633
|
+
var f = st / steps;
|
|
634
|
+
mark(ax + (bx - ax) * f, az + (bz - az) * f);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
// Interior: scanline spans. Each raster row crosses a triangle in exactly
|
|
639
|
+
// one x interval, so the row is filled with a single `fill()` — no
|
|
640
|
+
// per-cell inside test, which is what makes a fine raster affordable.
|
|
641
|
+
var cz0 = cellOf(Math.min(z0, z1, z2), originZ, nz);
|
|
642
|
+
var cz1 = cellOf(Math.max(z0, z1, z2), originZ, nz);
|
|
643
|
+
for (var iz = cz0; iz <= cz1; iz++) {
|
|
644
|
+
var pz = originZ + (iz + 0.5) * cell;
|
|
645
|
+
var xa = Infinity;
|
|
646
|
+
var xb = -Infinity;
|
|
647
|
+
// Half-open [lo, hi) span test per edge ⇒ exactly two crossings, no
|
|
648
|
+
// double-count at shared vertices. Horizontal edges never cross.
|
|
649
|
+
for (var e = 0; e < 3; e++) {
|
|
650
|
+
var _ax = e === 0 ? x0 : e === 1 ? x1 : x2;
|
|
651
|
+
var _az = e === 0 ? z0 : e === 1 ? z1 : z2;
|
|
652
|
+
var _bx = e === 0 ? x1 : e === 1 ? x2 : x0;
|
|
653
|
+
var _bz = e === 0 ? z1 : e === 1 ? z2 : z0;
|
|
654
|
+
if (_az === _bz) continue;
|
|
655
|
+
if (pz < Math.min(_az, _bz) || pz >= Math.max(_az, _bz)) continue;
|
|
656
|
+
var _x = _ax + (pz - _az) / (_bz - _az) * (_bx - _ax);
|
|
657
|
+
if (_x < xa) xa = _x;
|
|
658
|
+
if (_x > xb) xb = _x;
|
|
659
|
+
}
|
|
660
|
+
if (xa > xb) continue;
|
|
661
|
+
// Fill the cells whose CENTER lies inside the span.
|
|
662
|
+
var from = Math.max(border, Math.ceil((xa - originX) / cell - 0.5));
|
|
663
|
+
var to = Math.min(nx - border - 1, Math.floor((xb - originX) / cell - 0.5));
|
|
664
|
+
if (from <= to) mask.fill(1, iz * nx + from, iz * nx + to + 1);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
closeMask(mask, nx, nz, closeRadius);
|
|
668
|
+
// Offset AFTER the closing: seams have to be sealed before the boundary is
|
|
669
|
+
// moved, or the erosion widens them instead. An erosion strong enough to
|
|
670
|
+
// consume a thin shape empties the mask and the trace reports no contour,
|
|
671
|
+
// which the caller handles as a failed trace.
|
|
672
|
+
// Solidify BEFORE offsetting. The caller's slice keeps only geometry near the
|
|
673
|
+
// article's top, so a basin projects as a ring — its rim band with an empty
|
|
674
|
+
// middle. Eroding that ring thins it from both sides and destroys it once it
|
|
675
|
+
// is narrower than the offset, instead of moving the silhouette's outer
|
|
676
|
+
// boundary inward. Filling the enclosed middle first makes the erosion mean
|
|
677
|
+
// what it should. After the closing, so a sealed seam can't leak the fill out.
|
|
678
|
+
var enclosedFilled = fillEnclosed(mask, nx, nz);
|
|
679
|
+
if (offsetRadius >= 1) offsetMask(mask, nx, nz, offsetRadius, grow);
|
|
680
|
+
var diag = options.diagnostics;
|
|
681
|
+
if (diag) {
|
|
682
|
+
diag.enclosedFilled = enclosedFilled;
|
|
683
|
+
var filled = 0;
|
|
684
|
+
for (var _i2 = 0; _i2 < mask.length; _i2++) filled += mask[_i2];
|
|
685
|
+
diag.filledCells = filled;
|
|
686
|
+
}
|
|
687
|
+
var loops = marchingSquaresLoops(mask, nx, nz, originX, originZ, cell);
|
|
688
|
+
if (diag) diag.loops = loops.length;
|
|
689
|
+
if (loops.length === 0) return [];
|
|
690
|
+
var best = loops[0];
|
|
691
|
+
var bestArea = Math.abs(polygonSignedArea(best));
|
|
692
|
+
var totalArea = bestArea;
|
|
693
|
+
for (var _i3 = 1; _i3 < loops.length; _i3++) {
|
|
694
|
+
var a = Math.abs(polygonSignedArea(loops[_i3]));
|
|
695
|
+
totalArea += a;
|
|
696
|
+
if (a > bestArea) {
|
|
697
|
+
bestArea = a;
|
|
698
|
+
best = loops[_i3];
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
var simplified = simplifyClosedPolygon(best, tolerance);
|
|
702
|
+
if (diag) {
|
|
703
|
+
diag.chosenAreaShare = totalArea > 0 ? bestArea / totalArea : 0;
|
|
704
|
+
diag.pointsBeforeSimplify = best.length;
|
|
705
|
+
diag.pointsAfterSimplify = simplified.length;
|
|
706
|
+
}
|
|
707
|
+
return simplified.length >= 3 ? simplified : [];
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function _createForOfIteratorHelper$4(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$4(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
711
|
+
function _unsupportedIterableToArray$4(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$4(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$4(r, a) : void 0; } }
|
|
712
|
+
function _arrayLikeToArray$4(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
713
|
+
|
|
714
|
+
/** The subset of a deckCutout article rule the matcher needs. */
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* First-match-wins lookup of an article rule by codex. Wildcard rules
|
|
718
|
+
* (`match: '*'` or empty) match even when no codex is known — they double as
|
|
719
|
+
* the fallback entry and keep footprints testable before the host ships
|
|
720
|
+
* per-element codexes.
|
|
721
|
+
*/
|
|
722
|
+
function matchArticleRule(rules, articleCodex) {
|
|
723
|
+
var _iterator = _createForOfIteratorHelper$4(rules),
|
|
724
|
+
_step;
|
|
725
|
+
try {
|
|
726
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
727
|
+
var rule = _step.value;
|
|
728
|
+
var m = rule.match.trim();
|
|
729
|
+
if (m === '' || m === '*') return rule;
|
|
730
|
+
if (articleCodex === undefined) continue;
|
|
731
|
+
var hit = m.split(',').map(function (p) {
|
|
732
|
+
return p.trim();
|
|
733
|
+
}).filter(function (p) {
|
|
734
|
+
return p.length > 0;
|
|
735
|
+
}).some(function (p) {
|
|
736
|
+
return articleCodex.includes(p);
|
|
737
|
+
});
|
|
738
|
+
if (hit) return rule;
|
|
739
|
+
}
|
|
740
|
+
} catch (err) {
|
|
741
|
+
_iterator.e(err);
|
|
742
|
+
} finally {
|
|
743
|
+
_iterator.f();
|
|
744
|
+
}
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function _createForOfIteratorHelper$3(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$3(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
749
|
+
function _unsupportedIterableToArray$3(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$3(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$3(r, a) : void 0; } }
|
|
750
|
+
function _arrayLikeToArray$3(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Raster cell (m) for the geometry-derived footprint. 5 mm follows cast-in
|
|
754
|
+
* steps and tight rim radii; the trace runs once per geometry change (cached),
|
|
755
|
+
* so the finer grid costs a one-off, not per-frame work.
|
|
756
|
+
*/
|
|
757
|
+
var GEOMETRY_FOOTPRINT_CELL = 0.005;
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Minimum share of the measured article extent a traced outline has to cover on
|
|
761
|
+
* BOTH axes to be believed. Below that it is a detail that happened to reach
|
|
762
|
+
* above the slice plane (a nozzle, a fitting) rather than the article's rim —
|
|
763
|
+
* and its bbox would otherwise drive the article's resting position too.
|
|
764
|
+
*/
|
|
765
|
+
var MIN_TRACE_COVERAGE = 0.6;
|
|
766
|
+
/** Smallest usable rect extent (m) — guards the fallback against a margin that would invert it. */
|
|
767
|
+
var MIN_RECT_EXTENT = 0.1;
|
|
768
|
+
var _meshToFloor = new Matrix4();
|
|
769
|
+
var _vertex$1 = new Vector3();
|
|
770
|
+
|
|
771
|
+
/** XZ extents of the article's traced rim. */
|
|
772
|
+
|
|
773
|
+
var EMPTY_TRACE_DIAGNOSTICS = function EMPTY_TRACE_DIAGNOSTICS() {
|
|
774
|
+
return {
|
|
775
|
+
filledCells: 0,
|
|
776
|
+
enclosedFilled: 0,
|
|
777
|
+
loops: 0,
|
|
778
|
+
chosenAreaShare: 0,
|
|
779
|
+
pointsBeforeSimplify: 0,
|
|
780
|
+
pointsAfterSimplify: 0
|
|
781
|
+
};
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Rectangular fallback outline in floor-local XZ: the measured article rect
|
|
786
|
+
* grown by `margin` per side (negative laps the deck over the article). Null
|
|
787
|
+
* when the margin would collapse the rect.
|
|
788
|
+
*/
|
|
789
|
+
function measuredRectFootprint(rect, margin) {
|
|
790
|
+
var minX = rect.minX - margin;
|
|
791
|
+
var maxX = rect.maxX + margin;
|
|
792
|
+
var minZ = rect.minZ - margin;
|
|
793
|
+
var maxZ = rect.maxZ + margin;
|
|
794
|
+
if (maxX - minX < MIN_RECT_EXTENT || maxZ - minZ < MIN_RECT_EXTENT) return null;
|
|
795
|
+
return [[minX, minZ], [maxX, minZ], [maxX, maxZ], [minX, maxZ]];
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* First rule matching any element's codex, largest element first — but every
|
|
800
|
+
* element gets a look: an accessory bigger in plan than the article it belongs
|
|
801
|
+
* to (a roof, a surround) would otherwise name the scene, match nothing, and
|
|
802
|
+
* leave the deck area with no collar at all. `primaryCodex` reports whichever
|
|
803
|
+
* codex found the rule.
|
|
804
|
+
*/
|
|
805
|
+
function pickArticleRule(rules, codexesByArea) {
|
|
806
|
+
var _codexesByArea$;
|
|
807
|
+
var primaryCodex = (_codexesByArea$ = codexesByArea[0]) === null || _codexesByArea$ === void 0 ? void 0 : _codexesByArea$.codex;
|
|
808
|
+
var rule = matchArticleRule(rules, undefined);
|
|
809
|
+
var _iterator = _createForOfIteratorHelper$3(codexesByArea),
|
|
810
|
+
_step;
|
|
811
|
+
try {
|
|
812
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
813
|
+
var candidate = _step.value;
|
|
814
|
+
var hit = matchArticleRule(rules, candidate.codex);
|
|
815
|
+
if (hit) {
|
|
816
|
+
rule = hit;
|
|
817
|
+
primaryCodex = candidate.codex;
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
} catch (err) {
|
|
822
|
+
_iterator.e(err);
|
|
823
|
+
} finally {
|
|
824
|
+
_iterator.f();
|
|
825
|
+
}
|
|
826
|
+
return {
|
|
827
|
+
rule: rule,
|
|
828
|
+
primaryCodex: primaryCodex
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Silhouette of the article meshes projected onto the floor plane: every
|
|
834
|
+
* triangle with a vertex above `minLocalY` (floor-local) is rasterized and the
|
|
835
|
+
* outer contour traced. Expensive (walks all vertices), so the result is
|
|
836
|
+
* cached by a geometry signature and only re-traced when the article's
|
|
837
|
+
* meshes change; anchor moves are pure translations and reuse the cache.
|
|
838
|
+
* Returns the outline in CURRENT floor-local coordinates.
|
|
839
|
+
*/
|
|
840
|
+
function traceArticleFootprint(elements, floorLocal, rect, minLocalY, offset, diagnostics, cache) {
|
|
841
|
+
var meshes = [];
|
|
842
|
+
var sigParts = [];
|
|
843
|
+
var _iterator2 = _createForOfIteratorHelper$3(elements),
|
|
844
|
+
_step2;
|
|
845
|
+
try {
|
|
846
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
847
|
+
var el = _step2.value;
|
|
848
|
+
el.traverse(function (obj) {
|
|
849
|
+
var mesh = obj;
|
|
850
|
+
if (!mesh.isMesh || !mesh.visible) return;
|
|
851
|
+
var pos = mesh.geometry.getAttribute('position');
|
|
852
|
+
if (!pos) return;
|
|
853
|
+
meshes.push(mesh);
|
|
854
|
+
sigParts.push("".concat(mesh.name, ":").concat(pos.count));
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
} catch (err) {
|
|
858
|
+
_iterator2.e(err);
|
|
859
|
+
} finally {
|
|
860
|
+
_iterator2.f();
|
|
861
|
+
}
|
|
862
|
+
if (meshes.length === 0) return null;
|
|
863
|
+
var sizeX = rect.maxX - rect.minX;
|
|
864
|
+
var sizeY = rect.maxY - rect.minY;
|
|
865
|
+
var sizeZ = rect.maxZ - rect.minZ;
|
|
866
|
+
// The article's top belongs in the signature even though the outline is stored
|
|
867
|
+
// relative to the bbox: the slice plane is fixed, so moving the article
|
|
868
|
+
// VERTICALLY changes which triangles clear it, i.e. the traced shape itself.
|
|
869
|
+
// Without it the anchor's lift would keep returning the pre-lift outline.
|
|
870
|
+
var signature = "".concat(minLocalY.toFixed(3), "|").concat(rect.maxY.toFixed(3), "|").concat(offset.toFixed(3), "|").concat(sizeX.toFixed(3), "x").concat(sizeY.toFixed(3), "x").concat(sizeZ.toFixed(3), "|").concat(sigParts.join(','));
|
|
871
|
+
if (!cache.current || cache.current.signature !== signature) {
|
|
872
|
+
var started = performance.now();
|
|
873
|
+
var xz = [];
|
|
874
|
+
var _triCount = 0;
|
|
875
|
+
var _vertexCount = 0;
|
|
876
|
+
var _iterator3 = _createForOfIteratorHelper$3(meshes),
|
|
877
|
+
_step3;
|
|
878
|
+
try {
|
|
879
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
880
|
+
var mesh = _step3.value;
|
|
881
|
+
var geometry = mesh.geometry;
|
|
882
|
+
var pos = geometry.getAttribute('position');
|
|
883
|
+
_vertexCount += pos.count;
|
|
884
|
+
_meshToFloor.multiplyMatrices(floorLocal, mesh.matrixWorld);
|
|
885
|
+
// Transform every vertex once; triangles then index into the flat array.
|
|
886
|
+
var local = new Float32Array(pos.count * 3);
|
|
887
|
+
for (var i = 0; i < pos.count; i++) {
|
|
888
|
+
_vertex$1.set(pos.getX(i), pos.getY(i), pos.getZ(i)).applyMatrix4(_meshToFloor);
|
|
889
|
+
local[i * 3] = _vertex$1.x;
|
|
890
|
+
local[i * 3 + 1] = _vertex$1.y;
|
|
891
|
+
local[i * 3 + 2] = _vertex$1.z;
|
|
892
|
+
}
|
|
893
|
+
var index = geometry.index;
|
|
894
|
+
var triTotal = index ? Math.floor(index.count / 3) : Math.floor(pos.count / 3);
|
|
895
|
+
for (var t = 0; t < triTotal; t++) {
|
|
896
|
+
var a = index ? index.getX(t * 3) : t * 3;
|
|
897
|
+
var b = index ? index.getX(t * 3 + 1) : t * 3 + 1;
|
|
898
|
+
var c = index ? index.getX(t * 3 + 2) : t * 3 + 2;
|
|
899
|
+
var ya = local[a * 3 + 1];
|
|
900
|
+
var yb = local[b * 3 + 1];
|
|
901
|
+
var yc = local[c * 3 + 1];
|
|
902
|
+
if (ya < minLocalY && yb < minLocalY && yc < minLocalY) continue;
|
|
903
|
+
xz.push(local[a * 3], local[a * 3 + 2], local[b * 3], local[b * 3 + 2], local[c * 3], local[c * 3 + 2]);
|
|
904
|
+
_triCount += 1;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
} catch (err) {
|
|
908
|
+
_iterator3.e(err);
|
|
909
|
+
} finally {
|
|
910
|
+
_iterator3.f();
|
|
911
|
+
}
|
|
912
|
+
var traced = traceFootprintFromTriangles(xz, {
|
|
913
|
+
cellSize: GEOMETRY_FOOTPRINT_CELL,
|
|
914
|
+
offset: offset,
|
|
915
|
+
diagnostics: diagnostics
|
|
916
|
+
});
|
|
917
|
+
cache.current = {
|
|
918
|
+
signature: signature,
|
|
919
|
+
relPoints: traced.map(function (_ref) {
|
|
920
|
+
var _ref2 = _slicedToArray(_ref, 2),
|
|
921
|
+
x = _ref2[0],
|
|
922
|
+
z = _ref2[1];
|
|
923
|
+
return [x - rect.minX, z - rect.minZ];
|
|
924
|
+
}),
|
|
925
|
+
triCount: _triCount,
|
|
926
|
+
vertexCount: _vertexCount
|
|
927
|
+
};
|
|
928
|
+
return {
|
|
929
|
+
points: traced,
|
|
930
|
+
triCount: _triCount,
|
|
931
|
+
vertexCount: _vertexCount,
|
|
932
|
+
retraced: true,
|
|
933
|
+
traceMs: performance.now() - started
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
var _cache$current = cache.current,
|
|
937
|
+
relPoints = _cache$current.relPoints,
|
|
938
|
+
triCount = _cache$current.triCount,
|
|
939
|
+
vertexCount = _cache$current.vertexCount;
|
|
940
|
+
return {
|
|
941
|
+
points: relPoints.map(function (_ref3) {
|
|
942
|
+
var _ref4 = _slicedToArray(_ref3, 2),
|
|
943
|
+
x = _ref4[0],
|
|
944
|
+
z = _ref4[1];
|
|
945
|
+
return [x + rect.minX, z + rect.minZ];
|
|
946
|
+
}),
|
|
947
|
+
triCount: triCount,
|
|
948
|
+
vertexCount: vertexCount,
|
|
949
|
+
retraced: false,
|
|
950
|
+
traceMs: 0
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* The cutout outline for a measured article — offset by the deck margin, so it
|
|
956
|
+
* comes back as the collar's inner boundary. Needs no calibration: the trace
|
|
957
|
+
* follows the visible rim. Only `footprintFromGeometry` rules produce one;
|
|
958
|
+
* without such a rule the deck's authored hole stays fully open.
|
|
959
|
+
*/
|
|
960
|
+
function deriveArticleFootprint(measurement, rule, margin, cache) {
|
|
961
|
+
var _result$points$length, _result$points;
|
|
962
|
+
var result = {
|
|
963
|
+
points: null,
|
|
964
|
+
rim: null,
|
|
965
|
+
sampledCount: 0,
|
|
966
|
+
source: '',
|
|
967
|
+
repairNote: '',
|
|
968
|
+
tracedBounds: '',
|
|
969
|
+
trace: EMPTY_TRACE_DIAGNOSTICS()
|
|
970
|
+
};
|
|
971
|
+
if (!rule || !rule.footprintFromGeometry) return result;
|
|
972
|
+
var rect = measurement.rect,
|
|
973
|
+
floorLocal = measurement.floorLocal,
|
|
974
|
+
floorTopY = measurement.floorTopY,
|
|
975
|
+
elementObjects = measurement.elementObjects;
|
|
976
|
+
var rawSizeX = rect.maxX - rect.minX;
|
|
977
|
+
var rawSizeZ = rect.maxZ - rect.minZ;
|
|
978
|
+
var traced = traceArticleFootprint(topLevelElements(elementObjects), floorLocal, rect, floorTopY - rule.footprintSliceDepth, margin, result.trace, cache);
|
|
979
|
+
var minX = Infinity;
|
|
980
|
+
var maxX = -Infinity;
|
|
981
|
+
var minZ = Infinity;
|
|
982
|
+
var maxZ = -Infinity;
|
|
983
|
+
if (traced && traced.points.length >= 3) {
|
|
984
|
+
var _iterator4 = _createForOfIteratorHelper$3(traced.points),
|
|
985
|
+
_step4;
|
|
986
|
+
try {
|
|
987
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
988
|
+
var _step4$value = _slicedToArray(_step4.value, 2),
|
|
989
|
+
x = _step4$value[0],
|
|
990
|
+
z = _step4$value[1];
|
|
991
|
+
if (x < minX) minX = x;
|
|
992
|
+
if (x > maxX) maxX = x;
|
|
993
|
+
if (z < minZ) minZ = z;
|
|
994
|
+
if (z > maxZ) maxZ = z;
|
|
995
|
+
}
|
|
996
|
+
// The contour comes back with `margin` already applied, so undo it here:
|
|
997
|
+
// what the steps below need is the article's RIM extent (it sizes the
|
|
998
|
+
// water volume), not the collar's inner boundary.
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
_iterator4.e(err);
|
|
1001
|
+
} finally {
|
|
1002
|
+
_iterator4.f();
|
|
1003
|
+
}
|
|
1004
|
+
minX += margin;
|
|
1005
|
+
maxX -= margin;
|
|
1006
|
+
minZ += margin;
|
|
1007
|
+
maxZ -= margin;
|
|
1008
|
+
result.tracedBounds = "X [".concat(minX.toFixed(2), ", ").concat(maxX.toFixed(2), "] Z [").concat(minZ.toFixed(2), ", ").concat(maxZ.toFixed(2), "]");
|
|
1009
|
+
}
|
|
1010
|
+
// A trace covering only a fraction of the measured article is not the rim
|
|
1011
|
+
// (see MIN_TRACE_COVERAGE). Believing it is worse than not tracing at all:
|
|
1012
|
+
// its bbox replaces the measured one, so it would also decide where the
|
|
1013
|
+
// article comes to rest.
|
|
1014
|
+
var believable = maxX > minX && maxX - minX >= rawSizeX * MIN_TRACE_COVERAGE && maxZ - minZ >= rawSizeZ * MIN_TRACE_COVERAGE;
|
|
1015
|
+
if (traced && believable) {
|
|
1016
|
+
var _guarded$points;
|
|
1017
|
+
result.rim = {
|
|
1018
|
+
minX: minX,
|
|
1019
|
+
maxX: maxX,
|
|
1020
|
+
minZ: minZ,
|
|
1021
|
+
maxZ: maxZ
|
|
1022
|
+
};
|
|
1023
|
+
// The margin was applied to the raster, so the contour is already the
|
|
1024
|
+
// collar's inner boundary. It still passes the simple-polygon guard:
|
|
1025
|
+
// marching squares can loop around a thin feature in a way earcut turns
|
|
1026
|
+
// into a stray wedge instead of rejecting.
|
|
1027
|
+
var guarded = ensureSimplePolygon(traced.points);
|
|
1028
|
+
if (guarded !== null && guarded !== void 0 && guarded.repaired) result.repairNote = ' | HULL-REPAIRED, traced outline self-intersects (concavities lost)';
|
|
1029
|
+
if (guarded === null) result.repairNote = ' | outline UNUSABLE even as a hull';
|
|
1030
|
+
result.points = (_guarded$points = guarded === null || guarded === void 0 ? void 0 : guarded.points) !== null && _guarded$points !== void 0 ? _guarded$points : null;
|
|
1031
|
+
result.sampledCount = traced.points.length;
|
|
1032
|
+
result.source = traced.retraced ? "geometry traced in ".concat(traced.traceMs.toFixed(1), " ms (").concat(traced.vertexCount, " verts, ").concat(traced.triCount, " tris in slice)") : "geometry cached (".concat(traced.triCount, " tris in slice)");
|
|
1033
|
+
return result;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Rectangular cutout from the measured rect instead. Correctly sized and
|
|
1037
|
+
// placed, just not shaped — which beats both a hole shaped by a stray detail
|
|
1038
|
+
// and no cutout at all in a deck-sized authored hole.
|
|
1039
|
+
result.points = measuredRectFootprint(rect, margin);
|
|
1040
|
+
result.sampledCount = (_result$points$length = (_result$points = result.points) === null || _result$points === void 0 ? void 0 : _result$points.length) !== null && _result$points$length !== void 0 ? _result$points$length : 0;
|
|
1041
|
+
result.source = maxX > minX ? "geometry: TRACE COVERS ONLY ".concat(((maxX - minX) / rawSizeX * 100).toFixed(0), "%\xD7").concat(((maxZ - minZ) / rawSizeZ * 100).toFixed(0), "% of the article \u2014 fell back to the measured rect") : 'geometry: NO TRIANGLES ABOVE THE SLICE PLANE — fell back to the measured rect';
|
|
1042
|
+
return result;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function _createForOfIteratorHelper$2(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$2(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
1046
|
+
function _unsupportedIterableToArray$2(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$2(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$2(r, a) : void 0; } }
|
|
1047
|
+
function _arrayLikeToArray$2(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
1048
|
+
|
|
1049
|
+
/** Below this world delta (m) the element anchor correction is considered settled. */
|
|
1050
|
+
var ANCHOR_EPSILON = 1e-3;
|
|
1051
|
+
var _desired = new Vector3();
|
|
1052
|
+
var _measured = new Vector3();
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Distance to move one axis so the anchored part of the measured article lands
|
|
1056
|
+
* on the anchored part of the region. `'min'`/`'max'` pin that edge (the offset
|
|
1057
|
+
* reads as an inset from it) so the article grows away from it; `'center'`
|
|
1058
|
+
* centres, so it grows both ways.
|
|
1059
|
+
*/
|
|
1060
|
+
function anchorDelta1D(mode, center, halfExtent, measuredMin, measuredMax, offset) {
|
|
1061
|
+
if (mode === 'min') return center - halfExtent + offset - measuredMin;
|
|
1062
|
+
if (mode === 'max') return center + halfExtent - offset - measuredMax;
|
|
1063
|
+
return center + offset - (measuredMin + measuredMax) / 2;
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Nudge the top-level element groups so the measured article rect settles at
|
|
1067
|
+
* the hole center (+ configured fine-tune offsets), optionally lifting it so
|
|
1068
|
+
* its top meets the deck. The measured rect already includes the currently
|
|
1069
|
+
* applied offsets, so the correction is incremental and converges in one
|
|
1070
|
+
* measurement. Null once it is within {@link ANCHOR_EPSILON} — or when there is
|
|
1071
|
+
* no top-level element to move.
|
|
1072
|
+
*/
|
|
1073
|
+
function deriveElementAnchor(_ref) {
|
|
1074
|
+
var rect = _ref.rect,
|
|
1075
|
+
hole = _ref.hole,
|
|
1076
|
+
anchor = _ref.anchor,
|
|
1077
|
+
floorWorld = _ref.floorWorld,
|
|
1078
|
+
elementObjects = _ref.elementObjects,
|
|
1079
|
+
deckTopLocalY = _ref.deckTopLocalY,
|
|
1080
|
+
currentOffsets = _ref.currentOffsets;
|
|
1081
|
+
var localDx = anchorDelta1D(anchor.anchorX, hole.centerX, hole.halfExtentX, rect.minX, rect.maxX, anchor.offsetX);
|
|
1082
|
+
var localDz = anchorDelta1D(anchor.anchorZ, hole.centerZ, hole.halfExtentZ, rect.minZ, rect.maxZ, anchor.offsetZ);
|
|
1083
|
+
var centerX = (rect.minX + rect.maxX) / 2;
|
|
1084
|
+
var centerZ = (rect.minZ + rect.maxZ) / 2;
|
|
1085
|
+
// Vertical: HQS places every element on the ground, so a shallower basin
|
|
1086
|
+
// would sit with its rim below the deck — out of the footprint slice, and
|
|
1087
|
+
// visibly sunk. Aligning the TOP instead makes the basin's depth irrelevant.
|
|
1088
|
+
var measuredTopY = rect.maxY;
|
|
1089
|
+
var localDy = (anchor.alignTopToDeck ? deckTopLocalY - measuredTopY : 0) + anchor.offsetY;
|
|
1090
|
+
|
|
1091
|
+
// Convert the local-space correction to world space via two mapped points.
|
|
1092
|
+
_desired.set(centerX + localDx, measuredTopY + localDy, centerZ + localDz).applyMatrix4(floorWorld);
|
|
1093
|
+
_measured.set(centerX, measuredTopY, centerZ).applyMatrix4(floorWorld);
|
|
1094
|
+
var dx = _desired.x - _measured.x;
|
|
1095
|
+
var dy = _desired.y - _measured.y;
|
|
1096
|
+
var dz = _desired.z - _measured.z;
|
|
1097
|
+
if (Math.abs(dx) < ANCHOR_EPSILON && Math.abs(dy) < ANCHOR_EPSILON && Math.abs(dz) < ANCHOR_EPSILON) return null;
|
|
1098
|
+
var topLevel = topLevelElements(elementObjects);
|
|
1099
|
+
if (topLevel.length === 0) return null;
|
|
1100
|
+
var offsets = {};
|
|
1101
|
+
var _iterator = _createForOfIteratorHelper$2(topLevel),
|
|
1102
|
+
_step;
|
|
1103
|
+
try {
|
|
1104
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
1105
|
+
var _currentOffsets$key;
|
|
1106
|
+
var obj = _step.value;
|
|
1107
|
+
// MUST be the same key `ElementGroup` subscribes with — the composite
|
|
1108
|
+
// element key ("2", "2.0.6"), NOT the raw `Element#` name suffix (which
|
|
1109
|
+
// still carries the setId, e.g. "PLANNING_xE2").
|
|
1110
|
+
var key = parseElementKey(obj.name);
|
|
1111
|
+
var prev = (_currentOffsets$key = currentOffsets[key]) !== null && _currentOffsets$key !== void 0 ? _currentOffsets$key : [0, 0, 0];
|
|
1112
|
+
offsets[key] = [prev[0] + dx, prev[1] + dy, prev[2] + dz];
|
|
1113
|
+
}
|
|
1114
|
+
} catch (err) {
|
|
1115
|
+
_iterator.e(err);
|
|
1116
|
+
} finally {
|
|
1117
|
+
_iterator.f();
|
|
1118
|
+
}
|
|
1119
|
+
return {
|
|
1120
|
+
delta: {
|
|
1121
|
+
dx: localDx,
|
|
1122
|
+
dy: localDy,
|
|
1123
|
+
dz: localDz
|
|
1124
|
+
},
|
|
1125
|
+
offsets: offsets
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/** Floor for the derived water-volume depth (m) — guards degenerate measurements while geo streams in. */
|
|
1130
|
+
var MIN_WATER_DEPTH = 0.5;
|
|
1131
|
+
var _basis = new Matrix3();
|
|
1132
|
+
var _center = new Vector3();
|
|
1133
|
+
var _extentX = new Vector3();
|
|
1134
|
+
var _extentZ = new Vector3();
|
|
1135
|
+
/**
|
|
1136
|
+
* Water volume following the MEASURED article rect — the water surface is a
|
|
1137
|
+
* property of the pool, not of the cutout, so it tracks the pool wherever the
|
|
1138
|
+
* element anchor settles it. No feedback: water never influences the measurement.
|
|
1139
|
+
*
|
|
1140
|
+
* The DEPTH is derived too: waterline down to the measured article bottom. The
|
|
1141
|
+
* volume only needs to BOUND the pool interior; the PoolHeightMap finds the real
|
|
1142
|
+
* floor within it.
|
|
1143
|
+
*/
|
|
1144
|
+
function deriveWaterVolume(_ref) {
|
|
1145
|
+
var rect = _ref.rect,
|
|
1146
|
+
waterSync = _ref.waterSync,
|
|
1147
|
+
floorWorld = _ref.floorWorld,
|
|
1148
|
+
articleWorldBottomY = _ref.articleWorldBottomY,
|
|
1149
|
+
waterlineY = _ref.waterlineY;
|
|
1150
|
+
var inset = waterSync.inset;
|
|
1151
|
+
var sizeX = Math.max(rect.maxX - rect.minX - 2 * inset, 0.1);
|
|
1152
|
+
var sizeZ = Math.max(rect.maxZ - rect.minZ - 2 * inset, 0.1);
|
|
1153
|
+
_center.set((rect.minX + rect.maxX) / 2 + waterSync.offsetX, 0, (rect.minZ + rect.maxZ) / 2 + waterSync.offsetZ);
|
|
1154
|
+
_center.applyMatrix4(floorWorld);
|
|
1155
|
+
|
|
1156
|
+
// World-axis extents of the rotated local rect (scenes rotate in 90° steps,
|
|
1157
|
+
// so this stays exact; diagonal rotations would conservatively grow it).
|
|
1158
|
+
_basis.setFromMatrix4(floorWorld);
|
|
1159
|
+
_extentX.set(sizeX, 0, 0).applyMatrix3(_basis);
|
|
1160
|
+
_extentZ.set(0, 0, sizeZ).applyMatrix3(_basis);
|
|
1161
|
+
var worldSizeX = Math.abs(_extentX.x) + Math.abs(_extentZ.x);
|
|
1162
|
+
var worldSizeZ = Math.abs(_extentX.z) + Math.abs(_extentZ.z);
|
|
1163
|
+
var depth = Math.max(waterlineY - articleWorldBottomY, MIN_WATER_DEPTH);
|
|
1164
|
+
return {
|
|
1165
|
+
center: [_center.x, waterlineY, _center.z],
|
|
1166
|
+
size: [worldSizeX, depth, worldSizeZ]
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* One line describing the whole cutout pipeline, plus the `key` it changes with:
|
|
1172
|
+
* the caller logs only when the key moves, so the line appears once per codex /
|
|
1173
|
+
* rule / outline state (bounds coarse-rounded to cm) rather than every measure.
|
|
1174
|
+
*/
|
|
1175
|
+
function measureLog(_ref) {
|
|
1176
|
+
var _rule$match, _points$length, _fpBounds$map$join;
|
|
1177
|
+
var measurement = _ref.measurement,
|
|
1178
|
+
rule = _ref.rule,
|
|
1179
|
+
primaryCodex = _ref.primaryCodex,
|
|
1180
|
+
footprint = _ref.footprint,
|
|
1181
|
+
hole = _ref.hole;
|
|
1182
|
+
var rect = measurement.rect,
|
|
1183
|
+
floorTopY = measurement.floorTopY;
|
|
1184
|
+
var points = footprint.points;
|
|
1185
|
+
var fpBounds = points ? points.reduce(function (acc, _ref2) {
|
|
1186
|
+
var _ref3 = _slicedToArray(_ref2, 2),
|
|
1187
|
+
x = _ref3[0],
|
|
1188
|
+
z = _ref3[1];
|
|
1189
|
+
return [Math.min(acc[0], x), Math.max(acc[1], x), Math.min(acc[2], z), Math.max(acc[3], z)];
|
|
1190
|
+
}, [Infinity, -Infinity, Infinity, -Infinity]) : null;
|
|
1191
|
+
var key = "".concat(primaryCodex !== null && primaryCodex !== void 0 ? primaryCodex : '', "|").concat((_rule$match = rule === null || rule === void 0 ? void 0 : rule.match) !== null && _rule$match !== void 0 ? _rule$match : '', "|").concat(footprint.sampledCount, "|").concat((_points$length = points === null || points === void 0 ? void 0 : points.length) !== null && _points$length !== void 0 ? _points$length : 0, "|").concat((_fpBounds$map$join = fpBounds === null || fpBounds === void 0 ? void 0 : fpBounds.map(function (v) {
|
|
1192
|
+
return v.toFixed(2);
|
|
1193
|
+
}).join(',')) !== null && _fpBounds$map$join !== void 0 ? _fpBounds$map$join : '');
|
|
1194
|
+
var rawSizeX = rect.maxX - rect.minX;
|
|
1195
|
+
var rawSizeZ = rect.maxZ - rect.minZ;
|
|
1196
|
+
// Where the slice plane sits relative to the article. The plane is anchored to
|
|
1197
|
+
// the DECK top, so an article whose rim doesn't reach that high contributes
|
|
1198
|
+
// nothing but its topmost stray detail.
|
|
1199
|
+
var sliceInfo = rule && rule.footprintFromGeometry ? " | Y: deck top ".concat(floorTopY.toFixed(2), ", article [").concat(rect.minY.toFixed(2), ", ").concat(rect.maxY.toFixed(2), "], slice keeps \u2265 ").concat((floorTopY - rule.footprintSliceDepth).toFixed(2)) : '';
|
|
1200
|
+
var fpInfo;
|
|
1201
|
+
if (footprint.source === '') {
|
|
1202
|
+
fpInfo = ' | NO outline rule matched this codex → NO COLLAR, the authored hole stays fully open';
|
|
1203
|
+
} else if (fpBounds && points) {
|
|
1204
|
+
// Room left between the outline and each authored hole edge. Negative on any
|
|
1205
|
+
// side = the article is bigger than the hole it sits in; the collar clamps
|
|
1206
|
+
// the outline to the hole and degrades toward a solid slab.
|
|
1207
|
+
var slack = [fpBounds[0] - (hole.centerX - hole.halfExtentX), hole.centerX + hole.halfExtentX - fpBounds[1], fpBounds[2] - (hole.centerZ - hole.halfExtentZ), hole.centerZ + hole.halfExtentZ - fpBounds[3]].map(function (v) {
|
|
1208
|
+
return v.toFixed(2);
|
|
1209
|
+
});
|
|
1210
|
+
fpInfo = " | measured ".concat(rawSizeX.toFixed(2), "\xD7").concat(rawSizeZ.toFixed(2)) + (footprint.tracedBounds === '' ? '' : " | traced ".concat(footprint.sampledCount, " pts ").concat(footprint.tracedBounds)) + " | outline ".concat(points.length, " pts X [").concat(fpBounds[0].toFixed(2), ", ").concat(fpBounds[1].toFixed(2), "] Z [").concat(fpBounds[2].toFixed(2), ", ").concat(fpBounds[3].toFixed(2), "]") + " | hole X [".concat((hole.centerX - hole.halfExtentX).toFixed(2), ", ").concat((hole.centerX + hole.halfExtentX).toFixed(2), "] Z [").concat((hole.centerZ - hole.halfExtentZ).toFixed(2), ", ").concat((hole.centerZ + hole.halfExtentZ).toFixed(2), "]") + " | slack X ".concat(slack[0], "/").concat(slack[1], " Z ").concat(slack[2], "/").concat(slack[3]) + " | ".concat(footprint.source).concat(footprint.repairNote) + (footprint.trace.loops > 0 ? " | trace: ".concat(footprint.trace.filledCells, " cells (").concat(footprint.trace.enclosedFilled, " filled in), ").concat(footprint.trace.loops, " loop(s), kept ").concat((footprint.trace.chosenAreaShare * 100).toFixed(0), "% of contour area, ").concat(footprint.trace.pointsBeforeSimplify, " \u2192 ").concat(footprint.trace.pointsAfterSimplify, " pts") : '');
|
|
1211
|
+
} else {
|
|
1212
|
+
fpInfo = " | ".concat(footprint.source, " produced NO usable outline (").concat(footprint.sampledCount, " pts)").concat(footprint.repairNote, " \u2192 NO COLLAR, the authored hole stays fully open");
|
|
1213
|
+
}
|
|
1214
|
+
return {
|
|
1215
|
+
key: key,
|
|
1216
|
+
message: "[DeckCutout] article codex: ".concat(primaryCodex !== null && primaryCodex !== void 0 ? primaryCodex : '(unknown)', " \u2192 rule: ").concat(rule ? "'".concat(rule.match, "'") : '(none — articleSync globals)').concat(sliceInfo).concat(fpInfo)
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
1221
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
1222
|
+
var LOG$2 = getLogger('RealTimeViewer', 'DeckCutoutArticleSync');
|
|
1223
|
+
|
|
1224
|
+
/** Re-measure cadence in frames — article geo loads/changes are not frame-critical. */
|
|
1225
|
+
var MEASURE_INTERVAL_FRAMES = 30;
|
|
1226
|
+
/**
|
|
1227
|
+
* Consecutive no-change measures after which the pipeline is considered settled
|
|
1228
|
+
* and the cheap idle cadence resumes.
|
|
1229
|
+
*
|
|
1230
|
+
* Deliberately NOT paired with hiding the scene: a scene-load gate registered
|
|
1231
|
+
* from here cannot hide the one frame that matters (the new article paints with
|
|
1232
|
+
* the previous outline before `articleVersion` — a passive effect — can react),
|
|
1233
|
+
* so it would only hide the frames that the folded-in anchor delta already
|
|
1234
|
+
* eliminates, at the price of a blank/overlay flash on every change.
|
|
1235
|
+
*/
|
|
1236
|
+
var SETTLE_STABLE_MEASURES = 3;
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Drives the cutout outline, element placement and water volume from the live
|
|
1240
|
+
* article geometry (`deckCutout.articleSync` and friends):
|
|
1241
|
+
*
|
|
1242
|
+
* 1. {@link measureArticle} takes the union AABB of all planner `Element#`
|
|
1243
|
+
* groups in the floor meshes' shared local space.
|
|
1244
|
+
* 2. {@link deriveArticleFootprint} shapes the cutout outline →
|
|
1245
|
+
* `deckCutoutFootprintOverride`, which `DeckCutoutCollar` fills against the
|
|
1246
|
+
* deck's authored hole. Its traced rim replaces the measured extents for the
|
|
1247
|
+
* steps below, so parts under the deck don't inflate placement/water.
|
|
1248
|
+
* 3. {@link deriveElementAnchor} repositions the elements so their bbox rests
|
|
1249
|
+
* inside the authored hole (HQS `PosPivot === 'CENTER'` grows articles from
|
|
1250
|
+
* center, so the resting spot has to be re-derived) → `elementAnchorOffsets`.
|
|
1251
|
+
* 4. {@link deriveWaterVolume} follows the anchored rect → `waterVolumeOverride`.
|
|
1252
|
+
*
|
|
1253
|
+
* Re-converges (per-frame measuring) when article or decor geometry lands, and
|
|
1254
|
+
* otherwise re-measures on a slow idle cadence for the inputs that have no
|
|
1255
|
+
* event (imperative scene-transform edits). All writers are epsilon-compared,
|
|
1256
|
+
* so unchanged measurements are no-ops.
|
|
1257
|
+
*/
|
|
1258
|
+
function DeckCutoutArticleSync() {
|
|
1259
|
+
var scene = useThree(function (s) {
|
|
1260
|
+
return s.scene;
|
|
1261
|
+
});
|
|
1262
|
+
var deckCutoutJson = useRTVStore(function (s) {
|
|
1263
|
+
return JSON.stringify(s.resolvedEnvironment.scene.deckCutout);
|
|
1264
|
+
});
|
|
1265
|
+
var deform = useMemo(function () {
|
|
1266
|
+
return JSON.parse(deckCutoutJson);
|
|
1267
|
+
}, [deckCutoutJson]);
|
|
1268
|
+
var setWaterOverride = useRTVStore(function (s) {
|
|
1269
|
+
return s.setWaterVolumeOverride;
|
|
1270
|
+
});
|
|
1271
|
+
var setFootprintOverride = useRTVStore(function (s) {
|
|
1272
|
+
return s.setDeckCutoutFootprintOverride;
|
|
1273
|
+
});
|
|
1274
|
+
var frameRef = useRef(0);
|
|
1275
|
+
// Boot diagnostics: how many measure passes the pipeline needed before it produced an outline.
|
|
1276
|
+
var measureCountRef = useRef(0);
|
|
1277
|
+
var footprintMarkedRef = useRef(false);
|
|
1278
|
+
// One-shot codex/rule log: fires only when the measured codex or matched rule changes.
|
|
1279
|
+
var matchLogRef = useRef(null);
|
|
1280
|
+
var geometryCacheRef = useRef(null);
|
|
1281
|
+
// Settle tracking: measure every frame while the pipeline is still changing.
|
|
1282
|
+
var settlingRef = useRef(false);
|
|
1283
|
+
var stableMeasuresRef = useRef(0);
|
|
1284
|
+
var active = deform.enabled && deform.articleSync.enabled;
|
|
1285
|
+
var anchorActive = active && deform.elementAnchor.enabled;
|
|
1286
|
+
var waterActive = active && deform.waterSync.enabled;
|
|
1287
|
+
|
|
1288
|
+
// Clear derived state when a driver is turned off (and on unmount) so the
|
|
1289
|
+
// manual config values take effect again.
|
|
1290
|
+
// The cleanup is registered even while the driver is ACTIVE: an unmount that leaves
|
|
1291
|
+
// `deckCutout.enabled` on — the host dropping the 3D scene takes `mountScene3D` down while the
|
|
1292
|
+
// water stays on `mountWater` — would otherwise strand the last article's derived geometry, and
|
|
1293
|
+
// the pool would keep that article's centre and size.
|
|
1294
|
+
useEffect(function () {
|
|
1295
|
+
if (!active) setFootprintOverride(null);
|
|
1296
|
+
return function () {
|
|
1297
|
+
return setFootprintOverride(null);
|
|
1298
|
+
};
|
|
1299
|
+
}, [active, setFootprintOverride]);
|
|
1300
|
+
useEffect(function () {
|
|
1301
|
+
var clear = function clear() {
|
|
1302
|
+
if (Object.keys(useRTVStore.getState().elementAnchorOffsets).length > 0) {
|
|
1303
|
+
useRTVStore.getState().setElementAnchorOffsets({});
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
if (!anchorActive) clear();
|
|
1307
|
+
return clear;
|
|
1308
|
+
}, [anchorActive]);
|
|
1309
|
+
useEffect(function () {
|
|
1310
|
+
if (!waterActive) setWaterOverride(null);
|
|
1311
|
+
return function () {
|
|
1312
|
+
return setWaterOverride(null);
|
|
1313
|
+
};
|
|
1314
|
+
}, [waterActive, setWaterOverride]);
|
|
1315
|
+
var patterns = useMemo(function () {
|
|
1316
|
+
return deform.nodeMatch.split(',').map(function (p) {
|
|
1317
|
+
return p.trim();
|
|
1318
|
+
}).filter(function (p) {
|
|
1319
|
+
return p.length > 0;
|
|
1320
|
+
});
|
|
1321
|
+
}, [deform.nodeMatch]);
|
|
1322
|
+
|
|
1323
|
+
// `articleVersion` fires from inside the article's Suspense boundary — when a
|
|
1324
|
+
// new article's geometry is fully loaded and live; `decorVersion` when a deck
|
|
1325
|
+
// GLB (the floor frame) has mounted. Those are exactly the moments the
|
|
1326
|
+
// pipeline has to re-converge, and the moments before which nothing should be
|
|
1327
|
+
// shown.
|
|
1328
|
+
var articleVersion = useRTVStore(function (s) {
|
|
1329
|
+
return s.articleVersion;
|
|
1330
|
+
});
|
|
1331
|
+
var decorVersion = useRTVStore(function (s) {
|
|
1332
|
+
return s.decorVersion;
|
|
1333
|
+
});
|
|
1334
|
+
useEffect(function () {
|
|
1335
|
+
if (!active) return;
|
|
1336
|
+
settlingRef.current = true;
|
|
1337
|
+
stableMeasuresRef.current = 0;
|
|
1338
|
+
}, [articleVersion, decorVersion, active]);
|
|
1339
|
+
useFrame(function () {
|
|
1340
|
+
if (!active || patterns.length === 0) return;
|
|
1341
|
+
frameRef.current += 1;
|
|
1342
|
+
// While unsettled, measure EVERY frame: the pipeline converges by
|
|
1343
|
+
// re-measuring (the element anchor corrects against its own last move), so
|
|
1344
|
+
// on the idle cadence each step of that convergence is a visible ~0.5 s
|
|
1345
|
+
// state. Per-frame measuring collapses them into consecutive frames.
|
|
1346
|
+
if (!settlingRef.current && frameRef.current % MEASURE_INTERVAL_FRAMES !== 1) return;
|
|
1347
|
+
|
|
1348
|
+
// A measure that changes nothing marks progress toward "settled". State is
|
|
1349
|
+
// epsilon-compared in the store, so an unchanged write keeps the SAME
|
|
1350
|
+
// object reference — comparing references detects real change exactly.
|
|
1351
|
+
var before = useRTVStore.getState();
|
|
1352
|
+
var beforeWater = before.waterVolumeOverride;
|
|
1353
|
+
var beforeFootprint = before.deckCutoutFootprintOverride;
|
|
1354
|
+
var anchorMoved = false;
|
|
1355
|
+
// `waiting` = the article or floor isn't there yet. Nothing to converge on,
|
|
1356
|
+
// so drop to the idle cadence; their arrival re-arms per-frame measuring
|
|
1357
|
+
// through the version effect above.
|
|
1358
|
+
var finishMeasure = function finishMeasure() {
|
|
1359
|
+
var waiting = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
1360
|
+
if (waiting) {
|
|
1361
|
+
stableMeasuresRef.current = 0;
|
|
1362
|
+
settlingRef.current = false;
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
var after = useRTVStore.getState();
|
|
1366
|
+
var changed = anchorMoved || after.waterVolumeOverride !== beforeWater || after.deckCutoutFootprintOverride !== beforeFootprint;
|
|
1367
|
+
if (changed) {
|
|
1368
|
+
stableMeasuresRef.current = 0;
|
|
1369
|
+
settlingRef.current = true;
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
stableMeasuresRef.current += 1;
|
|
1373
|
+
if (stableMeasuresRef.current >= SETTLE_STABLE_MEASURES) settlingRef.current = false;
|
|
1374
|
+
};
|
|
1375
|
+
measureCountRef.current += 1;
|
|
1376
|
+
var measured = measureArticle(scene, patterns, useRTVStore.getState().elementsInfo);
|
|
1377
|
+
if (!measured) {
|
|
1378
|
+
setWaterOverride(null);
|
|
1379
|
+
setFootprintOverride(null);
|
|
1380
|
+
finishMeasure(true);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
var _pickArticleRule = pickArticleRule(deform.articleRules, measured.codexesByArea),
|
|
1384
|
+
rule = _pickArticleRule.rule,
|
|
1385
|
+
primaryCodex = _pickArticleRule.primaryCodex;
|
|
1386
|
+
var traced = deriveArticleFootprint(measured, rule, deform.articleSync.margin, geometryCacheRef);
|
|
1387
|
+
var rect = traced.rim ? _objectSpread(_objectSpread({}, measured.rect), traced.rim) : measured.rect;
|
|
1388
|
+
var log = measureLog({
|
|
1389
|
+
measurement: measured,
|
|
1390
|
+
rule: rule,
|
|
1391
|
+
primaryCodex: primaryCodex,
|
|
1392
|
+
footprint: traced,
|
|
1393
|
+
hole: deform.authoredHole
|
|
1394
|
+
});
|
|
1395
|
+
if (matchLogRef.current !== log.key) {
|
|
1396
|
+
matchLogRef.current = log.key;
|
|
1397
|
+
LOG$2.debug(log.message);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// The deck is never reshaped: the collar fills everything between its
|
|
1401
|
+
// authored hole and the article outline, so the hole the article rests in is
|
|
1402
|
+
// a constant of the asset. The anchor MUST use that same `authoredHole` —
|
|
1403
|
+
// anchoring to a different rect strands the article against one edge of the
|
|
1404
|
+
// region and leaves the far side unused (it shows up as clamped outline points).
|
|
1405
|
+
var anchor = anchorActive ? deriveElementAnchor({
|
|
1406
|
+
rect: rect,
|
|
1407
|
+
hole: deform.authoredHole,
|
|
1408
|
+
anchor: deform.elementAnchor,
|
|
1409
|
+
floorWorld: measured.floorWorld,
|
|
1410
|
+
elementObjects: measured.elementObjects,
|
|
1411
|
+
deckTopLocalY: measured.floorTopY,
|
|
1412
|
+
currentOffsets: useRTVStore.getState().elementAnchorOffsets
|
|
1413
|
+
}) : null;
|
|
1414
|
+
anchorMoved = anchor !== null;
|
|
1415
|
+
if (anchor) useRTVStore.getState().setElementAnchorOffsets(anchor.offsets);
|
|
1416
|
+
// The anchor's move only shows from the NEXT frame, so everything derived
|
|
1417
|
+
// from the article's position — the water surface, the shaped outline —
|
|
1418
|
+
// would otherwise describe the old spot and visibly catch up one measure
|
|
1419
|
+
// later. Folding the delta in here makes a single measure produce the final
|
|
1420
|
+
// state: one visible update instead of a chain of them.
|
|
1421
|
+
var anchored = anchor ? shiftRect(rect, anchor.delta) : rect;
|
|
1422
|
+
setFootprintOverride(traced.points && anchor ? traced.points.map(function (_ref) {
|
|
1423
|
+
var _ref2 = _slicedToArray(_ref, 2),
|
|
1424
|
+
x = _ref2[0],
|
|
1425
|
+
z = _ref2[1];
|
|
1426
|
+
return [x + anchor.delta.dx, z + anchor.delta.dz];
|
|
1427
|
+
}) : traced.points);
|
|
1428
|
+
if (!footprintMarkedRef.current && useRTVStore.getState().deckCutoutFootprintOverride !== null) {
|
|
1429
|
+
footprintMarkedRef.current = true;
|
|
1430
|
+
bootPhase('deck: first footprint published', {
|
|
1431
|
+
measures: measureCountRef.current
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
if (waterActive) {
|
|
1435
|
+
setWaterOverride(deriveWaterVolume({
|
|
1436
|
+
rect: anchored,
|
|
1437
|
+
waterSync: deform.waterSync,
|
|
1438
|
+
floorWorld: measured.floorWorld,
|
|
1439
|
+
articleWorldBottomY: measured.worldBottomY,
|
|
1440
|
+
waterlineY: useRTVStore.getState().resolvedEnvironment.water.volume.center[1]
|
|
1441
|
+
}));
|
|
1442
|
+
}
|
|
1443
|
+
finishMeasure();
|
|
1444
|
+
});
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
/*
|
|
1449
|
+
* Local UV sampling — continues a mesh's texture mapping onto generated
|
|
1450
|
+
* geometry that sits BESIDE it (e.g. a collar filling a floor's cutout).
|
|
1451
|
+
*
|
|
1452
|
+
* A single global affine mapping fitted over the whole surface only matches
|
|
1453
|
+
* where the surface's unwrap really is one plane at one density. It fails for
|
|
1454
|
+
* per-board UV islands, for surfaces whose density varies (a warped slab whose
|
|
1455
|
+
* pattern anchoring is imperfect), and for neighbours whose unwraps differ.
|
|
1456
|
+
* Sampling the NEAREST triangle's own mapping and extrapolating it sidesteps
|
|
1457
|
+
* all of that: the generated surface continues exactly what its neighbour
|
|
1458
|
+
* shows, locally.
|
|
1459
|
+
*
|
|
1460
|
+
* Deliberately three.js-free: a flat triangle soup in, UV out.
|
|
1461
|
+
*/
|
|
1462
|
+
|
|
1463
|
+
/** Floats per triangle in the soup: (x, z, u, v) per vertex. */
|
|
1464
|
+
var UV_TRIANGLE_STRIDE = 12;
|
|
1465
|
+
var EPS = 1e-12;
|
|
1466
|
+
|
|
1467
|
+
/** One affine UV mapping over XZ: `u = ux·x + uz·z + u0`, likewise for v. */
|
|
1468
|
+
|
|
1469
|
+
/** Solve a 3x3 system by Cramer's rule; null when singular. */
|
|
1470
|
+
function solve3(m, b) {
|
|
1471
|
+
var det = m[0] * (m[4] * m[8] - m[5] * m[7]) - m[1] * (m[3] * m[8] - m[5] * m[6]) + m[2] * (m[3] * m[7] - m[4] * m[6]);
|
|
1472
|
+
if (Math.abs(det) < EPS) return null;
|
|
1473
|
+
var d = function d(c0, c1, c2) {
|
|
1474
|
+
return c0 * (m[4] * m[8] - m[5] * m[7]) - m[1] * (c1 * m[8] - m[5] * c2) + m[2] * (c1 * m[7] - m[4] * c2);
|
|
1475
|
+
};
|
|
1476
|
+
var x = d(b[0], b[1], b[2]) / det;
|
|
1477
|
+
var y = (m[0] * (b[1] * m[8] - m[5] * b[2]) - b[0] * (m[3] * m[8] - m[5] * m[6]) + m[2] * (m[3] * b[2] - b[1] * m[6])) / det;
|
|
1478
|
+
var z = (m[0] * (m[4] * b[2] - b[1] * m[7]) - m[1] * (m[3] * b[2] - b[1] * m[6]) + b[0] * (m[3] * m[7] - m[4] * m[6])) / det;
|
|
1479
|
+
return [x, y, z];
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
/**
|
|
1483
|
+
* Least-squares fit of ONE affine mapping over every vertex in the soup.
|
|
1484
|
+
*
|
|
1485
|
+
* Where it fits (a planar unwrap at uniform density), it beats
|
|
1486
|
+
* {@link sampleNearestTriangleUV} on generated geometry: the local sampler
|
|
1487
|
+
* decides per vertex, so two ends of a generated triangle can land on different
|
|
1488
|
+
* source triangles and the interpolation between them blends two mappings —
|
|
1489
|
+
* a visible shear, worst on long thin triangles like those a curved outline
|
|
1490
|
+
* produces. One mapping for every vertex cannot shear. Check
|
|
1491
|
+
* {@link AffineUV.residualMetres} before trusting it.
|
|
1492
|
+
*/
|
|
1493
|
+
function fitAffineUV(triangles, triangleCount) {
|
|
1494
|
+
var samples = triangleCount * 3;
|
|
1495
|
+
if (samples < 3) return null;
|
|
1496
|
+
// Normal equations for the design matrix [x, z, 1], shared by u and v.
|
|
1497
|
+
var sxx = 0;
|
|
1498
|
+
var sxz = 0;
|
|
1499
|
+
var sx = 0;
|
|
1500
|
+
var szz = 0;
|
|
1501
|
+
var sz = 0;
|
|
1502
|
+
var sxu = 0;
|
|
1503
|
+
var szu = 0;
|
|
1504
|
+
var su = 0;
|
|
1505
|
+
var sxv = 0;
|
|
1506
|
+
var szv = 0;
|
|
1507
|
+
var sv = 0;
|
|
1508
|
+
for (var i = 0; i < samples; i++) {
|
|
1509
|
+
var o = i * 4;
|
|
1510
|
+
var x = triangles[o];
|
|
1511
|
+
var z = triangles[o + 1];
|
|
1512
|
+
var u = triangles[o + 2];
|
|
1513
|
+
var v = triangles[o + 3];
|
|
1514
|
+
sxx += x * x;
|
|
1515
|
+
sxz += x * z;
|
|
1516
|
+
sx += x;
|
|
1517
|
+
szz += z * z;
|
|
1518
|
+
sz += z;
|
|
1519
|
+
sxu += x * u;
|
|
1520
|
+
szu += z * u;
|
|
1521
|
+
su += u;
|
|
1522
|
+
sxv += x * v;
|
|
1523
|
+
szv += z * v;
|
|
1524
|
+
sv += v;
|
|
1525
|
+
}
|
|
1526
|
+
var m = [sxx, sxz, sx, sxz, szz, sz, sx, sz, samples];
|
|
1527
|
+
var uSol = solve3(m, [sxu, szu, su]);
|
|
1528
|
+
var vSol = solve3(m, [sxv, szv, sv]);
|
|
1529
|
+
if (!uSol || !vSol) return null;
|
|
1530
|
+
var errSq = 0;
|
|
1531
|
+
for (var _i = 0; _i < samples; _i++) {
|
|
1532
|
+
var _o = _i * 4;
|
|
1533
|
+
var _x = triangles[_o];
|
|
1534
|
+
var _z = triangles[_o + 1];
|
|
1535
|
+
var du = uSol[0] * _x + uSol[1] * _z + uSol[2] - triangles[_o + 2];
|
|
1536
|
+
var dv = vSol[0] * _x + vSol[1] * _z + vSol[2] - triangles[_o + 3];
|
|
1537
|
+
errSq += du * du + dv * dv;
|
|
1538
|
+
}
|
|
1539
|
+
// UV error → metres, via the mapping's own uv-per-metre.
|
|
1540
|
+
var perMetre = (Math.hypot(uSol[0], uSol[1]) + Math.hypot(vSol[0], vSol[1])) / 2;
|
|
1541
|
+
var rms = Math.sqrt(errSq / samples);
|
|
1542
|
+
return {
|
|
1543
|
+
ux: uSol[0],
|
|
1544
|
+
uz: uSol[1],
|
|
1545
|
+
u0: uSol[2],
|
|
1546
|
+
vx: vSol[0],
|
|
1547
|
+
vz: vSol[1],
|
|
1548
|
+
v0: vSol[2],
|
|
1549
|
+
residualMetres: perMetre > EPS ? rms / perMetre : Infinity
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
/** Squared distance from (px, pz) to the segment (ax, az)–(bx, bz). */
|
|
1554
|
+
function segmentDistanceSq(px, pz, ax, az, bx, bz) {
|
|
1555
|
+
var dx = bx - ax;
|
|
1556
|
+
var dz = bz - az;
|
|
1557
|
+
var lengthSq = dx * dx + dz * dz;
|
|
1558
|
+
var t = lengthSq < EPS ? 0 : Math.max(0, Math.min(1, ((px - ax) * dx + (pz - az) * dz) / lengthSq));
|
|
1559
|
+
var cx = ax + t * dx - px;
|
|
1560
|
+
var cz = az + t * dz - pz;
|
|
1561
|
+
return cx * cx + cz * cz;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* Squared distance from a point to a triangle in the XZ plane — 0 inside it,
|
|
1566
|
+
* else the nearest edge distance.
|
|
1567
|
+
*/
|
|
1568
|
+
function triangleDistanceSq(px, pz, x0, z0, x1, z1, x2, z2) {
|
|
1569
|
+
// Zero area makes all three edge signs zero, so the inside test below would answer "contains"
|
|
1570
|
+
// for every point on the plane and this triangle would win every query. It can stand in for
|
|
1571
|
+
// nothing — keep it out of the running.
|
|
1572
|
+
if ((x1 - x0) * (z2 - z0) - (x2 - x0) * (z1 - z0) === 0) return Infinity;
|
|
1573
|
+
// Inside test via consistent edge signs (winding-agnostic).
|
|
1574
|
+
var d0 = (x1 - x0) * (pz - z0) - (z1 - z0) * (px - x0);
|
|
1575
|
+
var d1 = (x2 - x1) * (pz - z1) - (z2 - z1) * (px - x1);
|
|
1576
|
+
var d2 = (x0 - x2) * (pz - z2) - (z0 - z2) * (px - x2);
|
|
1577
|
+
var allNonNeg = d0 >= 0 && d1 >= 0 && d2 >= 0;
|
|
1578
|
+
var allNonPos = d0 <= 0 && d1 <= 0 && d2 <= 0;
|
|
1579
|
+
if (allNonNeg || allNonPos) return 0;
|
|
1580
|
+
return Math.min(segmentDistanceSq(px, pz, x0, z0, x1, z1), segmentDistanceSq(px, pz, x1, z1, x2, z2), segmentDistanceSq(px, pz, x2, z2, x0, z0));
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/** Nearest triangle by linear scan: minimum distance, ties to the lowest index. */
|
|
1584
|
+
function nearestTriangleLinear(triangles, triangleCount, x, z) {
|
|
1585
|
+
var bestDistance = Infinity;
|
|
1586
|
+
var best = -1;
|
|
1587
|
+
for (var t = 0; t < triangleCount; t++) {
|
|
1588
|
+
var o = t * UV_TRIANGLE_STRIDE;
|
|
1589
|
+
var distance = triangleDistanceSq(x, z, triangles[o], triangles[o + 1], triangles[o + 4], triangles[o + 5], triangles[o + 8], triangles[o + 9]);
|
|
1590
|
+
if (distance < bestDistance) {
|
|
1591
|
+
// A containing triangle (distance 0) is as good as it gets.
|
|
1592
|
+
bestDistance = distance;
|
|
1593
|
+
best = t;
|
|
1594
|
+
if (distance === 0) break;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
return best;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
/** Below this the scan is already shorter than the ring walk that would replace it. */
|
|
1601
|
+
var MIN_INDEXED_TRIANGLES = 64;
|
|
1602
|
+
/** Cap on the grid's columns and rows — bounds memory when the deck is a long thin strip. */
|
|
1603
|
+
var MAX_GRID_DIM = 256;
|
|
1604
|
+
/** A triangle covering more cells than this is scanned on every query instead of registered in all of them. */
|
|
1605
|
+
var MAX_TRIANGLE_CELLS = 64;
|
|
1606
|
+
/** Give up on the index once this share of the soup would be scanned on every query anyway. */
|
|
1607
|
+
var MAX_EXTRA_SHARE = 0.25;
|
|
1608
|
+
/**
|
|
1609
|
+
* Relative slack on the ring-termination bound. The bound — nothing unvisited is
|
|
1610
|
+
* nearer than the gap to the visited block — holds exactly in real arithmetic;
|
|
1611
|
+
* the slack keeps it true in floating point, so the walk can never stop one
|
|
1612
|
+
* rounding error before the triangle a linear scan would have found.
|
|
1613
|
+
*/
|
|
1614
|
+
var RING_SLACK = 1e-9;
|
|
1615
|
+
/** A query this many cells outside the grid is scanned linearly rather than walked to. */
|
|
1616
|
+
var MAX_QUERY_CELL_OFFSET = 1e7;
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Uniform XZ grid over a triangle soup, so a nearest-triangle query touches a
|
|
1620
|
+
* handful of cells instead of every triangle. Build it once per surface with
|
|
1621
|
+
* {@link buildTriangleGridIndex} and pass it to {@link sampleNearestTriangleUV},
|
|
1622
|
+
* which then returns exactly what the linear scan returns — same triangle, same
|
|
1623
|
+
* tie-break, same UV.
|
|
1624
|
+
*/
|
|
1625
|
+
|
|
1626
|
+
function isFiniteXZ(x0, z0, x1, z1, x2, z2) {
|
|
1627
|
+
return Number.isFinite(x0) && Number.isFinite(z0) && Number.isFinite(x1) && Number.isFinite(z1) && Number.isFinite(x2) && Number.isFinite(z2);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/** Column/row count for one axis, clamped to at least one cell and at most {@link MAX_GRID_DIM}. */
|
|
1631
|
+
function gridDim(extent, cellSize) {
|
|
1632
|
+
return Math.min(MAX_GRID_DIM, Math.max(1, Math.ceil(extent / cellSize)));
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
/** Cell containing `value` on one axis, clamped into the grid (only the far edge needs the clamp). */
|
|
1636
|
+
function cellOf(value, min, cellSize, count) {
|
|
1637
|
+
var i = Math.floor((value - min) / cellSize);
|
|
1638
|
+
if (i < 0) return 0;
|
|
1639
|
+
return i >= count ? count - 1 : i;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/**
|
|
1643
|
+
* Uniform grid over the soup's XZ bounds, each triangle registered in every cell
|
|
1644
|
+
* its bounding box overlaps. Build it once per surface — it depends on the soup
|
|
1645
|
+
* alone — and reuse it for every query against that soup.
|
|
1646
|
+
*
|
|
1647
|
+
* Cell size is the larger of the mean triangle extent and the size that puts
|
|
1648
|
+
* about one triangle in a cell: smaller than the triangles themselves only
|
|
1649
|
+
* multiplies registrations without narrowing a query, larger leaves too many
|
|
1650
|
+
* triangles per cell to skip.
|
|
1651
|
+
*
|
|
1652
|
+
* Returns null when an index would not pay for itself or could not be trusted —
|
|
1653
|
+
* too few triangles, a soup that is one point, or too much of it non-finite. The
|
|
1654
|
+
* caller then scans linearly, which is what the index reproduces anyway.
|
|
1655
|
+
*/
|
|
1656
|
+
function buildTriangleGridIndex(triangles, triangleCount) {
|
|
1657
|
+
if (triangleCount < MIN_INDEXED_TRIANGLES) return null;
|
|
1658
|
+
var minX = Infinity;
|
|
1659
|
+
var maxX = -Infinity;
|
|
1660
|
+
var minZ = Infinity;
|
|
1661
|
+
var maxZ = -Infinity;
|
|
1662
|
+
var extentSum = 0;
|
|
1663
|
+
var finite = 0;
|
|
1664
|
+
for (var t = 0; t < triangleCount; t++) {
|
|
1665
|
+
var o = t * UV_TRIANGLE_STRIDE;
|
|
1666
|
+
var x0 = triangles[o];
|
|
1667
|
+
var z0 = triangles[o + 1];
|
|
1668
|
+
var x1 = triangles[o + 4];
|
|
1669
|
+
var z1 = triangles[o + 5];
|
|
1670
|
+
var x2 = triangles[o + 8];
|
|
1671
|
+
var z2 = triangles[o + 9];
|
|
1672
|
+
if (!isFiniteXZ(x0, z0, x1, z1, x2, z2)) continue;
|
|
1673
|
+
var tMinX = Math.min(x0, x1, x2);
|
|
1674
|
+
var tMaxX = Math.max(x0, x1, x2);
|
|
1675
|
+
var tMinZ = Math.min(z0, z1, z2);
|
|
1676
|
+
var tMaxZ = Math.max(z0, z1, z2);
|
|
1677
|
+
if (tMinX < minX) minX = tMinX;
|
|
1678
|
+
if (tMaxX > maxX) maxX = tMaxX;
|
|
1679
|
+
if (tMinZ < minZ) minZ = tMinZ;
|
|
1680
|
+
if (tMaxZ > maxZ) maxZ = tMaxZ;
|
|
1681
|
+
extentSum += Math.max(tMaxX - tMinX, tMaxZ - tMinZ);
|
|
1682
|
+
finite += 1;
|
|
1683
|
+
}
|
|
1684
|
+
if (finite < MIN_INDEXED_TRIANGLES || triangleCount - finite > triangleCount * MAX_EXTRA_SHARE) return null;
|
|
1685
|
+
var width = maxX - minX;
|
|
1686
|
+
var depth = maxZ - minZ;
|
|
1687
|
+
var span = Math.max(width, depth);
|
|
1688
|
+
// Every triangle on one spot — no partition can separate them.
|
|
1689
|
+
if (!(span > 0)) return null;
|
|
1690
|
+
var perCell = width * depth / finite;
|
|
1691
|
+
var cellSize = Math.max(extentSum / finite, perCell > 0 ? Math.sqrt(perCell) : 0, span / MAX_GRID_DIM);
|
|
1692
|
+
var cols = gridDim(width, cellSize);
|
|
1693
|
+
var rows = gridDim(depth, cellSize);
|
|
1694
|
+
// An extreme aspect ratio can still ask for far more cells than triangles.
|
|
1695
|
+
var maxCells = Math.max(1024, finite * 4);
|
|
1696
|
+
if (cols * rows > maxCells) {
|
|
1697
|
+
cellSize *= Math.sqrt(cols * rows / maxCells);
|
|
1698
|
+
cols = gridDim(width, cellSize);
|
|
1699
|
+
rows = gridDim(depth, cellSize);
|
|
1700
|
+
}
|
|
1701
|
+
var cellCount = cols * rows;
|
|
1702
|
+
var spans = new Int32Array(triangleCount * 4);
|
|
1703
|
+
// One slot ahead, so the prefix sum below turns counts into CSR offsets in place.
|
|
1704
|
+
var cellStart = new Int32Array(cellCount + 1);
|
|
1705
|
+
var extras = [];
|
|
1706
|
+
for (var _t = 0; _t < triangleCount; _t++) {
|
|
1707
|
+
var _o2 = _t * UV_TRIANGLE_STRIDE;
|
|
1708
|
+
var _x2 = triangles[_o2];
|
|
1709
|
+
var _z2 = triangles[_o2 + 1];
|
|
1710
|
+
var _x3 = triangles[_o2 + 4];
|
|
1711
|
+
var _z3 = triangles[_o2 + 5];
|
|
1712
|
+
var _x4 = triangles[_o2 + 8];
|
|
1713
|
+
var _z4 = triangles[_o2 + 9];
|
|
1714
|
+
// Zero area makes every edge sign zero, so the inside test answers "distance
|
|
1715
|
+
// 0" for any point whatsoever — position tells nothing about such a triangle.
|
|
1716
|
+
if (!isFiniteXZ(_x2, _z2, _x3, _z3, _x4, _z4) || (_x3 - _x2) * (_z4 - _z2) - (_x4 - _x2) * (_z3 - _z2) === 0) {
|
|
1717
|
+
spans[_t * 4] = -1;
|
|
1718
|
+
extras.push(_t);
|
|
1719
|
+
continue;
|
|
1720
|
+
}
|
|
1721
|
+
var i0 = cellOf(Math.min(_x2, _x3, _x4), minX, cellSize, cols);
|
|
1722
|
+
var i1 = cellOf(Math.max(_x2, _x3, _x4), minX, cellSize, cols);
|
|
1723
|
+
var j0 = cellOf(Math.min(_z2, _z3, _z4), minZ, cellSize, rows);
|
|
1724
|
+
var j1 = cellOf(Math.max(_z2, _z3, _z4), minZ, cellSize, rows);
|
|
1725
|
+
if ((i1 - i0 + 1) * (j1 - j0 + 1) > MAX_TRIANGLE_CELLS) {
|
|
1726
|
+
spans[_t * 4] = -1;
|
|
1727
|
+
extras.push(_t);
|
|
1728
|
+
continue;
|
|
1729
|
+
}
|
|
1730
|
+
spans[_t * 4] = i0;
|
|
1731
|
+
spans[_t * 4 + 1] = i1;
|
|
1732
|
+
spans[_t * 4 + 2] = j0;
|
|
1733
|
+
spans[_t * 4 + 3] = j1;
|
|
1734
|
+
for (var j = j0; j <= j1; j++) {
|
|
1735
|
+
for (var i = i0; i <= i1; i++) cellStart[j * cols + i + 1] += 1;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
if (extras.length > triangleCount * MAX_EXTRA_SHARE) return null;
|
|
1739
|
+
for (var c = 0; c < cellCount; c++) cellStart[c + 1] += cellStart[c];
|
|
1740
|
+
var cellItems = new Int32Array(cellStart[cellCount]);
|
|
1741
|
+
var cursor = cellStart.slice(0, cellCount);
|
|
1742
|
+
for (var _t2 = 0; _t2 < triangleCount; _t2++) {
|
|
1743
|
+
var _i2 = spans[_t2 * 4];
|
|
1744
|
+
if (_i2 < 0) continue;
|
|
1745
|
+
var _i3 = spans[_t2 * 4 + 1];
|
|
1746
|
+
var _j = spans[_t2 * 4 + 2];
|
|
1747
|
+
var _j2 = spans[_t2 * 4 + 3];
|
|
1748
|
+
for (var _j3 = _j; _j3 <= _j2; _j3++) {
|
|
1749
|
+
for (var _i4 = _i2; _i4 <= _i3; _i4++) {
|
|
1750
|
+
var _c = _j3 * cols + _i4;
|
|
1751
|
+
cellItems[cursor[_c]] = _t2;
|
|
1752
|
+
cursor[_c] += 1;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return {
|
|
1757
|
+
triangleCount: triangleCount,
|
|
1758
|
+
minX: minX,
|
|
1759
|
+
minZ: minZ,
|
|
1760
|
+
cellSize: cellSize,
|
|
1761
|
+
cols: cols,
|
|
1762
|
+
rows: rows,
|
|
1763
|
+
cellStart: cellStart,
|
|
1764
|
+
cellItems: cellItems,
|
|
1765
|
+
extras: Int32Array.from(extras)
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
/**
|
|
1770
|
+
* Nearest triangle via {@link TriangleGridIndex}: the point's own cell first,
|
|
1771
|
+
* then ring by ring outward.
|
|
1772
|
+
*
|
|
1773
|
+
* A ring may only end the walk once the best distance found is provably below
|
|
1774
|
+
* the gap between the point and the block of cells already visited — every
|
|
1775
|
+
* unvisited triangle lies outside that block, so nothing out there can be
|
|
1776
|
+
* nearer. Ties go to the lowest triangle index whatever order the cells are
|
|
1777
|
+
* walked in, which is what a linear scan's `distance < bestDistance` produces.
|
|
1778
|
+
*/
|
|
1779
|
+
function nearestTriangleIndexed(triangles, x, z, index) {
|
|
1780
|
+
var minX = index.minX,
|
|
1781
|
+
minZ = index.minZ,
|
|
1782
|
+
cellSize = index.cellSize,
|
|
1783
|
+
cols = index.cols,
|
|
1784
|
+
rows = index.rows,
|
|
1785
|
+
cellStart = index.cellStart,
|
|
1786
|
+
cellItems = index.cellItems,
|
|
1787
|
+
extras = index.extras;
|
|
1788
|
+
var cx = Math.floor((x - minX) / cellSize);
|
|
1789
|
+
var cz = Math.floor((z - minZ) / cellSize);
|
|
1790
|
+
// A non-finite or absurdly distant query has no cell to start from.
|
|
1791
|
+
if (!Number.isFinite(cx) || !Number.isFinite(cz) || Math.abs(cx) > MAX_QUERY_CELL_OFFSET || Math.abs(cz) > MAX_QUERY_CELL_OFFSET) {
|
|
1792
|
+
return nearestTriangleLinear(triangles, index.triangleCount, x, z);
|
|
1793
|
+
}
|
|
1794
|
+
var bestDistance = Infinity;
|
|
1795
|
+
var best = -1;
|
|
1796
|
+
var consider = function consider(t) {
|
|
1797
|
+
var o = t * UV_TRIANGLE_STRIDE;
|
|
1798
|
+
var distance = triangleDistanceSq(x, z, triangles[o], triangles[o + 1], triangles[o + 4], triangles[o + 5], triangles[o + 8], triangles[o + 9]);
|
|
1799
|
+
// Lowest index wins a tie, so cell order cannot change the answer. NaN and
|
|
1800
|
+
// Infinity fail both tests, exactly as `distance < bestDistance` alone does.
|
|
1801
|
+
if (distance < bestDistance || distance === bestDistance && t < best) {
|
|
1802
|
+
bestDistance = distance;
|
|
1803
|
+
best = t;
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
var scanCell = function scanCell(cell) {
|
|
1807
|
+
var end = cellStart[cell + 1];
|
|
1808
|
+
for (var k = cellStart[cell]; k < end; k++) consider(cellItems[k]);
|
|
1809
|
+
};
|
|
1810
|
+
for (var k = 0; k < extras.length; k++) consider(extras[k]);
|
|
1811
|
+
|
|
1812
|
+
// Rings are centred on the point's cell even when that sits outside the grid,
|
|
1813
|
+
// so the gap below grows with distance instead of stalling at a clamped edge.
|
|
1814
|
+
var first = Math.max(0, -cx, cx - cols + 1, -cz, cz - rows + 1);
|
|
1815
|
+
var last = Math.max(cx, cols - 1 - cx, cz, rows - 1 - cz);
|
|
1816
|
+
for (var r = first; r <= last; r++) {
|
|
1817
|
+
var i0 = cx - r;
|
|
1818
|
+
var i1 = cx + r;
|
|
1819
|
+
var j0 = cz - r;
|
|
1820
|
+
var j1 = cz + r;
|
|
1821
|
+
var ia = Math.max(0, i0);
|
|
1822
|
+
var ib = Math.min(cols - 1, i1);
|
|
1823
|
+
if (j0 >= 0 && j0 < rows) {
|
|
1824
|
+
for (var i = ia; i <= ib; i++) scanCell(j0 * cols + i);
|
|
1825
|
+
}
|
|
1826
|
+
if (r > 0) {
|
|
1827
|
+
if (j1 >= 0 && j1 < rows) {
|
|
1828
|
+
for (var _i5 = ia; _i5 <= ib; _i5++) scanCell(j1 * cols + _i5);
|
|
1829
|
+
}
|
|
1830
|
+
var ja = Math.max(0, j0 + 1);
|
|
1831
|
+
var jb = Math.min(rows - 1, j1 - 1);
|
|
1832
|
+
if (i0 >= 0 && i0 < cols) {
|
|
1833
|
+
for (var j = ja; j <= jb; j++) scanCell(j * cols + i0);
|
|
1834
|
+
}
|
|
1835
|
+
if (i1 >= 0 && i1 < cols) {
|
|
1836
|
+
for (var _j4 = ja; _j4 <= jb; _j4++) scanCell(_j4 * cols + i1);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
// Distance from the point to the edge of everything visited so far. Strictly
|
|
1840
|
+
// below it, no unvisited triangle can beat OR tie the best found.
|
|
1841
|
+
var gap = Math.min(x - (minX + i0 * cellSize), minX + (i1 + 1) * cellSize - x, z - (minZ + j0 * cellSize), minZ + (j1 + 1) * cellSize - z);
|
|
1842
|
+
if (gap > 0 && bestDistance < gap * gap * (1 - RING_SLACK)) return best;
|
|
1843
|
+
}
|
|
1844
|
+
return best;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
/**
|
|
1848
|
+
* UV at (x, z) taken from the triangle nearest to it, evaluated through that
|
|
1849
|
+
* triangle's own affine mapping. Points outside the triangle EXTRAPOLATE it,
|
|
1850
|
+
* which is the point: the sampled position generally lies in the hole beside
|
|
1851
|
+
* the surface, and extrapolating its neighbour's plank mapping continues the
|
|
1852
|
+
* pattern across the seam.
|
|
1853
|
+
*
|
|
1854
|
+
* `triangles` holds {@link UV_TRIANGLE_STRIDE} floats per triangle:
|
|
1855
|
+
* `x0, z0, u0, v0, x1, z1, u1, v1, x2, z2, u2, v2`. Returns null for an empty
|
|
1856
|
+
* soup or when every triangle is degenerate in XZ.
|
|
1857
|
+
*
|
|
1858
|
+
* Pass the soup's {@link TriangleGridIndex} to skip the linear scan over every
|
|
1859
|
+
* triangle; the result is identical either way.
|
|
1860
|
+
*/
|
|
1861
|
+
function sampleNearestTriangleUV(triangles, triangleCount, x, z, index) {
|
|
1862
|
+
var best = index && index.triangleCount === triangleCount ? nearestTriangleIndexed(triangles, x, z, index) : nearestTriangleLinear(triangles, triangleCount, x, z);
|
|
1863
|
+
if (best < 0) return null;
|
|
1864
|
+
var o = best * UV_TRIANGLE_STRIDE;
|
|
1865
|
+
var x0 = triangles[o];
|
|
1866
|
+
var z0 = triangles[o + 1];
|
|
1867
|
+
var x1 = triangles[o + 4];
|
|
1868
|
+
var z1 = triangles[o + 5];
|
|
1869
|
+
var x2 = triangles[o + 8];
|
|
1870
|
+
var z2 = triangles[o + 9];
|
|
1871
|
+
var det = (z1 - z2) * (x0 - x2) + (x2 - x1) * (z0 - z2);
|
|
1872
|
+
if (Math.abs(det) < EPS) return null;
|
|
1873
|
+
// Barycentric weights, deliberately unclamped so the mapping extrapolates.
|
|
1874
|
+
var w0 = ((z1 - z2) * (x - x2) + (x2 - x1) * (z - z2)) / det;
|
|
1875
|
+
var w1 = ((z2 - z0) * (x - x2) + (x0 - x2) * (z - z2)) / det;
|
|
1876
|
+
var w2 = 1 - w0 - w1;
|
|
1877
|
+
return [w0 * triangles[o + 2] + w1 * triangles[o + 6] + w2 * triangles[o + 10], w0 * triangles[o + 3] + w1 * triangles[o + 7] + w2 * triangles[o + 11]];
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
function _createForOfIteratorHelper$1(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$1(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
1881
|
+
function _unsupportedIterableToArray$1(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$1(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$1(r, a) : void 0; } }
|
|
1882
|
+
function _arrayLikeToArray$1(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
1883
|
+
|
|
1884
|
+
/** Minimum +Y normal for a deck triangle to count as top surface (excludes side faces). */
|
|
1885
|
+
var UP_NORMAL_MIN = 0.9;
|
|
1886
|
+
/**
|
|
1887
|
+
* How far (m) a single fitted mapping may miss the deck's own triangles before
|
|
1888
|
+
* it is rejected in favour of per-vertex sampling. Below this the deck's unwrap
|
|
1889
|
+
* is one plane, and one mapping reproduces it without the shear per-vertex
|
|
1890
|
+
* sampling introduces between neighbouring generated triangles.
|
|
1891
|
+
*/
|
|
1892
|
+
var UV_FIT_MAX_RESIDUAL = 0.005;
|
|
1893
|
+
var _toReference = new Matrix4();
|
|
1894
|
+
var _normalMatrix = new Matrix3();
|
|
1895
|
+
var _vertex = new Vector3();
|
|
1896
|
+
var _normal = new Vector3();
|
|
1897
|
+
|
|
1898
|
+
/** What the collar needs to look like a continuation of the deck it fills. */
|
|
1899
|
+
|
|
1900
|
+
/** Every mesh under `root` whose name contains one of `patterns`, in traversal order. */
|
|
1901
|
+
function meshesMatching(root, patterns) {
|
|
1902
|
+
var found = [];
|
|
1903
|
+
root.traverse(function (obj) {
|
|
1904
|
+
var mesh = obj;
|
|
1905
|
+
if (mesh.isMesh && patterns.some(function (p) {
|
|
1906
|
+
return mesh.name.includes(p);
|
|
1907
|
+
})) found.push(mesh);
|
|
1908
|
+
});
|
|
1909
|
+
return found;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
/**
|
|
1913
|
+
* Deck material for generated geometry. The unique bakes (AO / light map) are
|
|
1914
|
+
* authored in the deck's own 0–1 UV space and would smear arbitrarily across a
|
|
1915
|
+
* mesh that isn't part of that unwrap, so they are dropped; the repeating
|
|
1916
|
+
* pattern maps are kept as they are.
|
|
1917
|
+
*/
|
|
1918
|
+
function cloneDeckMaterial(source) {
|
|
1919
|
+
var clone = source.clone();
|
|
1920
|
+
if (clone instanceof MeshStandardMaterial) {
|
|
1921
|
+
clone.aoMap = null;
|
|
1922
|
+
clone.lightMap = null;
|
|
1923
|
+
}
|
|
1924
|
+
clone.needsUpdate = true;
|
|
1925
|
+
return clone;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
/**
|
|
1929
|
+
* The UV attribute the deck's VISIBLE pattern reads. Read from the material
|
|
1930
|
+
* rather than assumed, because copying the wrong channel of a multi-unwrap
|
|
1931
|
+
* asset yields a pattern that is subtly the wrong size.
|
|
1932
|
+
*/
|
|
1933
|
+
function patternAttributeName(material) {
|
|
1934
|
+
var single = Array.isArray(material) ? material[0] : material;
|
|
1935
|
+
if (single instanceof MeshStandardMaterial && single.map) return single.map.channel === 0 ? 'uv' : "uv".concat(single.map.channel);
|
|
1936
|
+
return 'uv';
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
/**
|
|
1940
|
+
* Collect the top-surface triangles of every matched deck mesh as a flat
|
|
1941
|
+
* (x, z, u, v)-per-vertex soup in `reference`'s local space, reading the UV
|
|
1942
|
+
* attribute the deck's pattern actually samples.
|
|
1943
|
+
*
|
|
1944
|
+
* No proximity filter: `nodeMatch` names the deck to continue, so everything it
|
|
1945
|
+
* names is the sample set. Filtering by distance to the cutout drops slabs whose
|
|
1946
|
+
* TRIANGLE CENTROIDS fall outside the radius — which silently discards a deck
|
|
1947
|
+
* modelled as a few large quads and leaves the collar with no material at all.
|
|
1948
|
+
*/
|
|
1949
|
+
function collectDeckTriangles(meshes, reference, attributeName) {
|
|
1950
|
+
var referenceInverse = _toReference.copy(reference.matrixWorld).invert().clone();
|
|
1951
|
+
var out = [];
|
|
1952
|
+
var extent = {
|
|
1953
|
+
minX: Infinity,
|
|
1954
|
+
maxX: -Infinity,
|
|
1955
|
+
minZ: Infinity,
|
|
1956
|
+
maxZ: -Infinity
|
|
1957
|
+
};
|
|
1958
|
+
var _iterator = _createForOfIteratorHelper$1(meshes),
|
|
1959
|
+
_step;
|
|
1960
|
+
try {
|
|
1961
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
1962
|
+
var _geometry$getAttribut;
|
|
1963
|
+
var mesh = _step.value;
|
|
1964
|
+
var geometry = mesh.geometry;
|
|
1965
|
+
var position = geometry.getAttribute('position');
|
|
1966
|
+
var uv = (_geometry$getAttribut = geometry.getAttribute(attributeName)) !== null && _geometry$getAttribut !== void 0 ? _geometry$getAttribut : geometry.getAttribute('uv');
|
|
1967
|
+
if (!position || !uv) continue;
|
|
1968
|
+
var normal = geometry.getAttribute('normal');
|
|
1969
|
+
|
|
1970
|
+
// Mesh-local → reference-local (identity for parts sharing the frame, correct otherwise).
|
|
1971
|
+
_toReference.multiplyMatrices(referenceInverse, mesh.matrixWorld);
|
|
1972
|
+
var local = new Float32Array(position.count * 3);
|
|
1973
|
+
for (var i = 0; i < position.count; i++) {
|
|
1974
|
+
_vertex.set(position.getX(i), position.getY(i), position.getZ(i)).applyMatrix4(_toReference);
|
|
1975
|
+
local[i * 3] = _vertex.x;
|
|
1976
|
+
local[i * 3 + 1] = _vertex.y;
|
|
1977
|
+
local[i * 3 + 2] = _vertex.z;
|
|
1978
|
+
if (_vertex.x < extent.minX) extent.minX = _vertex.x;
|
|
1979
|
+
if (_vertex.x > extent.maxX) extent.maxX = _vertex.x;
|
|
1980
|
+
if (_vertex.z < extent.minZ) extent.minZ = _vertex.z;
|
|
1981
|
+
if (_vertex.z > extent.maxZ) extent.maxZ = _vertex.z;
|
|
1982
|
+
}
|
|
1983
|
+
// Normals need the rotation part of the SAME transform the positions took, or a deck part
|
|
1984
|
+
// rotated relative to `reference` is classified against its own axes and its top faces drop out.
|
|
1985
|
+
var localNormalY = new Float32Array(normal ? normal.count : 0);
|
|
1986
|
+
if (normal) {
|
|
1987
|
+
_normalMatrix.getNormalMatrix(_toReference);
|
|
1988
|
+
for (var _i = 0; _i < normal.count; _i++) {
|
|
1989
|
+
_normal.set(normal.getX(_i), normal.getY(_i), normal.getZ(_i)).applyMatrix3(_normalMatrix).normalize();
|
|
1990
|
+
localNormalY[_i] = _normal.y;
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// Height fallback when the mesh ships no normals: the top face is the upper half.
|
|
1995
|
+
var yMid = -Infinity;
|
|
1996
|
+
if (!normal) {
|
|
1997
|
+
var yMin = Infinity;
|
|
1998
|
+
var yMax = -Infinity;
|
|
1999
|
+
for (var _i2 = 0; _i2 < position.count; _i2++) {
|
|
2000
|
+
var y = local[_i2 * 3 + 1];
|
|
2001
|
+
if (y < yMin) yMin = y;
|
|
2002
|
+
if (y > yMax) yMax = y;
|
|
2003
|
+
}
|
|
2004
|
+
yMid = (yMin + yMax) / 2;
|
|
2005
|
+
}
|
|
2006
|
+
var index = geometry.index;
|
|
2007
|
+
var triCount = index ? Math.floor(index.count / 3) : Math.floor(position.count / 3);
|
|
2008
|
+
for (var t = 0; t < triCount; t++) {
|
|
2009
|
+
var a = index ? index.getX(t * 3) : t * 3;
|
|
2010
|
+
var b = index ? index.getX(t * 3 + 1) : t * 3 + 1;
|
|
2011
|
+
var c = index ? index.getX(t * 3 + 2) : t * 3 + 2;
|
|
2012
|
+
var isTop = normal ? localNormalY[a] >= UP_NORMAL_MIN && localNormalY[b] >= UP_NORMAL_MIN && localNormalY[c] >= UP_NORMAL_MIN : local[a * 3 + 1] > yMid && local[b * 3 + 1] > yMid && local[c * 3 + 1] > yMid;
|
|
2013
|
+
if (!isTop) continue;
|
|
2014
|
+
for (var _i3 = 0, _arr = [a, b, c]; _i3 < _arr.length; _i3++) {
|
|
2015
|
+
var vi = _arr[_i3];
|
|
2016
|
+
out.push(local[vi * 3], local[vi * 3 + 2], uv.getX(vi), uv.getY(vi));
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
} catch (err) {
|
|
2021
|
+
_iterator.e(err);
|
|
2022
|
+
} finally {
|
|
2023
|
+
_iterator.f();
|
|
2024
|
+
}
|
|
2025
|
+
var triangleCount = out.length / UV_TRIANGLE_STRIDE;
|
|
2026
|
+
if (triangleCount < 1) return null;
|
|
2027
|
+
return {
|
|
2028
|
+
triangles: new Float32Array(out),
|
|
2029
|
+
triangleCount: triangleCount,
|
|
2030
|
+
extent: extent
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
/**
|
|
2035
|
+
* Sample the deck the collar has to continue: the first name-matched mesh is the
|
|
2036
|
+
* reference frame, all matched meshes feed the UV sample.
|
|
2037
|
+
*/
|
|
2038
|
+
function sampleDeckSurface(scene, patterns) {
|
|
2039
|
+
var _meshes$, _bounds$max$y, _collected$triangles, _collected$triangleCo, _collected$extent;
|
|
2040
|
+
var meshes = meshesMatching(scene, patterns);
|
|
2041
|
+
var reference = (_meshes$ = meshes[0]) !== null && _meshes$ !== void 0 ? _meshes$ : null;
|
|
2042
|
+
if (!reference) return {
|
|
2043
|
+
reference: null,
|
|
2044
|
+
surface: null
|
|
2045
|
+
};
|
|
2046
|
+
var geometry = reference.geometry;
|
|
2047
|
+
if (!geometry.boundingBox) geometry.computeBoundingBox();
|
|
2048
|
+
var attributeName = patternAttributeName(reference.material);
|
|
2049
|
+
var collected = collectDeckTriangles(meshes, reference, attributeName);
|
|
2050
|
+
var bounds = geometry.boundingBox;
|
|
2051
|
+
return {
|
|
2052
|
+
reference: reference,
|
|
2053
|
+
surface: {
|
|
2054
|
+
meshName: reference.name,
|
|
2055
|
+
topY: (_bounds$max$y = bounds === null || bounds === void 0 ? void 0 : bounds.max.y) !== null && _bounds$max$y !== void 0 ? _bounds$max$y : 0,
|
|
2056
|
+
slabThickness: bounds ? bounds.max.y - bounds.min.y : 0,
|
|
2057
|
+
material: collected ? cloneDeckMaterial(Array.isArray(reference.material) ? reference.material[0] : reference.material) : null,
|
|
2058
|
+
triangles: (_collected$triangles = collected === null || collected === void 0 ? void 0 : collected.triangles) !== null && _collected$triangles !== void 0 ? _collected$triangles : null,
|
|
2059
|
+
triangleCount: (_collected$triangleCo = collected === null || collected === void 0 ? void 0 : collected.triangleCount) !== null && _collected$triangleCo !== void 0 ? _collected$triangleCo : 0,
|
|
2060
|
+
triangleIndex: collected ? buildTriangleGridIndex(collected.triangles, collected.triangleCount) : null,
|
|
2061
|
+
attributeName: attributeName,
|
|
2062
|
+
uvFit: collected ? fitAffineUV(collected.triangles, collected.triangleCount) : null,
|
|
2063
|
+
extent: (_collected$extent = collected === null || collected === void 0 ? void 0 : collected.extent) !== null && _collected$extent !== void 0 ? _collected$extent : null
|
|
2064
|
+
}
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
/** Identity of a sampled surface — a fresh surface object rebuilds the collar geometry, so only an actual change may be applied. */
|
|
2069
|
+
function deckSurfaceKey(surface) {
|
|
2070
|
+
return surface ? "".concat(surface.meshName, "|").concat(surface.topY.toFixed(4), "|").concat(surface.attributeName, "|").concat(surface.triangleCount) : 'none';
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
/** One-shot diagnostic lines for a newly acquired deck surface. */
|
|
2074
|
+
function describeDeckSurface(surface, hasOwnMaterial) {
|
|
2075
|
+
var lines = [];
|
|
2076
|
+
var e = surface.extent;
|
|
2077
|
+
if (e) {
|
|
2078
|
+
lines.push("[DeckCutoutCollar] matched deck meshes span X [".concat(e.minX.toFixed(3), ", ").concat(e.maxX.toFixed(3), "] Z [").concat(e.minZ.toFixed(3), ", ").concat(e.maxZ.toFixed(3), "]"));
|
|
2079
|
+
}
|
|
2080
|
+
lines.push("[DeckCutoutCollar] UV source: ".concat(hasOwnMaterial ? 'own material (declared world-planar tiling)' : surface.uvFit === null ? 'no fit possible (per-vertex sampling)' : surface.uvFit.residualMetres <= UV_FIT_MAX_RESIDUAL ? "one fitted mapping, residual ".concat((surface.uvFit.residualMetres * 1000).toFixed(2), " mm") : "per-vertex sampling \u2014 fit residual ".concat((surface.uvFit.residualMetres * 1000).toFixed(1), " mm exceeds ").concat(UV_FIT_MAX_RESIDUAL * 1000, " mm, deck unwrap is not one plane")));
|
|
2081
|
+
return lines;
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
/** How far (m) the outline is held inside the ring's outer boundary. */
|
|
2085
|
+
var CLAMP_INSET = 1e-3;
|
|
2086
|
+
/**
|
|
2087
|
+
* The collar ring, built in the floor meshes' shared local space (outline and
|
|
2088
|
+
* hole rect are floor-local): a skirt is extruded downward along the inner edge
|
|
2089
|
+
* so nothing shows through under the deck, the top sits `collar.recess` below
|
|
2090
|
+
* the deck surface and the outer edge tucks `collar.overlap` under the deck —
|
|
2091
|
+
* the last two kill z-fighting with the slab.
|
|
2092
|
+
*/
|
|
2093
|
+
function buildCollarGeometry(_ref) {
|
|
2094
|
+
var footprint = _ref.footprint,
|
|
2095
|
+
hole = _ref.hole,
|
|
2096
|
+
collar = _ref.collar,
|
|
2097
|
+
deck = _ref.deck,
|
|
2098
|
+
ownMaterial = _ref.ownMaterial;
|
|
2099
|
+
// Shape plane (x, y) becomes floor-local (x, z) after rotateX(π/2); the
|
|
2100
|
+
// extrusion depth runs toward -Y (downward skirt). Winding of outer ring
|
|
2101
|
+
// and hole is normalized by ExtrudeGeometry itself.
|
|
2102
|
+
var shape = new Shape();
|
|
2103
|
+
var minX = hole.centerX - hole.halfExtentX - collar.overlap;
|
|
2104
|
+
var maxX = hole.centerX + hole.halfExtentX + collar.overlap;
|
|
2105
|
+
var minZ = hole.centerZ - hole.halfExtentZ - collar.overlap;
|
|
2106
|
+
var maxZ = hole.centerZ + hole.halfExtentZ + collar.overlap;
|
|
2107
|
+
shape.moveTo(minX, minZ);
|
|
2108
|
+
shape.lineTo(maxX, minZ);
|
|
2109
|
+
shape.lineTo(maxX, maxZ);
|
|
2110
|
+
shape.lineTo(minX, maxZ);
|
|
2111
|
+
shape.closePath();
|
|
2112
|
+
|
|
2113
|
+
// The hole path MUST stay strictly inside the ring's outer boundary: a
|
|
2114
|
+
// crossing path makes the triangulator emit garbage (a wedge of floor over
|
|
2115
|
+
// the pool) rather than fail. Clamping degrades to a collar pinched flat
|
|
2116
|
+
// against its outer edge, which is recoverable and obvious.
|
|
2117
|
+
var clamped = 0;
|
|
2118
|
+
var innerMinX = Infinity;
|
|
2119
|
+
var innerMaxX = -Infinity;
|
|
2120
|
+
var innerMinZ = Infinity;
|
|
2121
|
+
var innerMaxZ = -Infinity;
|
|
2122
|
+
var inner = new Path();
|
|
2123
|
+
for (var i = 0; i < footprint.length; i++) {
|
|
2124
|
+
var x = Math.min(maxX - CLAMP_INSET, Math.max(minX + CLAMP_INSET, footprint[i][0]));
|
|
2125
|
+
var z = Math.min(maxZ - CLAMP_INSET, Math.max(minZ + CLAMP_INSET, footprint[i][1]));
|
|
2126
|
+
if (x !== footprint[i][0] || z !== footprint[i][1]) clamped += 1;
|
|
2127
|
+
if (x < innerMinX) innerMinX = x;
|
|
2128
|
+
if (x > innerMaxX) innerMaxX = x;
|
|
2129
|
+
if (z < innerMinZ) innerMinZ = z;
|
|
2130
|
+
if (z > innerMaxZ) innerMaxZ = z;
|
|
2131
|
+
if (i === 0) inner.moveTo(x, z);else inner.lineTo(x, z);
|
|
2132
|
+
}
|
|
2133
|
+
inner.closePath();
|
|
2134
|
+
shape.holes.push(inner);
|
|
2135
|
+
// What the ring actually came out as. `coverage` is the share of the hole
|
|
2136
|
+
// the OPENING keeps: near 1 the collar is a hairline, near 0 it is a solid
|
|
2137
|
+
// slab over the article (no visible cutout at all).
|
|
2138
|
+
var coverage = (innerMaxX - innerMinX) * (innerMaxZ - innerMinZ) / ((maxX - minX) * (maxZ - minZ));
|
|
2139
|
+
var diagnostic = "[DeckCutoutCollar] ring built: hole X [".concat(minX.toFixed(2), ", ").concat(maxX.toFixed(2), "] Z [").concat(minZ.toFixed(2), ", ").concat(maxZ.toFixed(2), "] | opening X [").concat(innerMinX.toFixed(2), ", ").concat(innerMaxX.toFixed(2), "] Z [").concat(innerMinZ.toFixed(2), ", ").concat(innerMaxZ.toFixed(2), "] = ").concat((coverage * 100).toFixed(0), "% of it | ").concat(clamped, "/").concat(footprint.length, " outline pts clamped").concat(clamped > 0 ? ' — THE ARTICLE IS LARGER THAN ITS HOLE' : '');
|
|
2140
|
+
|
|
2141
|
+
// Skirt as thin as the deck itself unless configured otherwise; a floor
|
|
2142
|
+
// with no measurable thickness still needs a non-degenerate extrusion.
|
|
2143
|
+
var skirtDepth = collar.skirtDepth > 0 ? collar.skirtDepth : Math.max(deck.slabThickness, 0.01);
|
|
2144
|
+
var geo = new ExtrudeGeometry(shape, {
|
|
2145
|
+
depth: skirtDepth,
|
|
2146
|
+
bevelEnabled: false
|
|
2147
|
+
});
|
|
2148
|
+
geo.rotateX(Math.PI / 2);
|
|
2149
|
+
geo.translate(0, deck.topY - collar.recess, 0);
|
|
2150
|
+
if (ownMaterial) {
|
|
2151
|
+
// Own material ⇒ the tiling is DECLARED, not inherited: a world-planar
|
|
2152
|
+
// projection at `textureScale` metres per tile. Nothing to fit or sample,
|
|
2153
|
+
// and swapping the maps for a different wood or stone needs no other change.
|
|
2154
|
+
var textureScale = ownMaterial.textureScale,
|
|
2155
|
+
rotationDeg = ownMaterial.rotationDeg,
|
|
2156
|
+
offsetX = ownMaterial.offsetX,
|
|
2157
|
+
offsetZ = ownMaterial.offsetZ;
|
|
2158
|
+
var scale = textureScale > 0 ? textureScale : 1;
|
|
2159
|
+
var angle = rotationDeg * Math.PI / 180;
|
|
2160
|
+
var cos = Math.cos(angle);
|
|
2161
|
+
var sin = Math.sin(angle);
|
|
2162
|
+
var position = geo.getAttribute('position');
|
|
2163
|
+
var uvs = new Float32Array(position.count * 2);
|
|
2164
|
+
for (var _i = 0; _i < position.count; _i++) {
|
|
2165
|
+
var _x = position.getX(_i) + offsetX;
|
|
2166
|
+
var _z = position.getZ(_i) + offsetZ;
|
|
2167
|
+
uvs[_i * 2] = (_x * cos - _z * sin) / scale;
|
|
2168
|
+
uvs[_i * 2 + 1] = (_x * sin + _z * cos) / scale;
|
|
2169
|
+
}
|
|
2170
|
+
geo.setAttribute('uv', new BufferAttribute(uvs, 2));
|
|
2171
|
+
geo.setAttribute('uv1', new BufferAttribute(uvs, 2));
|
|
2172
|
+
} else if (deck.triangles) {
|
|
2173
|
+
// Continue the deck's own mapping. One fitted mapping when the deck's
|
|
2174
|
+
// unwrap is a single plane — it cannot shear, whereas deciding per vertex
|
|
2175
|
+
// lets the two ends of a generated triangle land on different deck
|
|
2176
|
+
// triangles and blend their mappings across it. Per-vertex sampling stays
|
|
2177
|
+
// for the unwraps a single mapping cannot describe (per-board islands,
|
|
2178
|
+
// varying density), where matching locally is the only faithful option.
|
|
2179
|
+
//
|
|
2180
|
+
// Written to BOTH channels so it lands wherever the material's maps look;
|
|
2181
|
+
// the skirt's vertical faces inherit the same planar projection, which
|
|
2182
|
+
// reads as shadow in a recessed band.
|
|
2183
|
+
var fit = deck.uvFit && deck.uvFit.residualMetres <= UV_FIT_MAX_RESIDUAL ? deck.uvFit : null;
|
|
2184
|
+
var _position = geo.getAttribute('position');
|
|
2185
|
+
var _uvs = new Float32Array(_position.count * 2);
|
|
2186
|
+
for (var _i2 = 0; _i2 < _position.count; _i2++) {
|
|
2187
|
+
var _x2 = _position.getX(_i2);
|
|
2188
|
+
var _z2 = _position.getZ(_i2);
|
|
2189
|
+
if (fit) {
|
|
2190
|
+
_uvs[_i2 * 2] = fit.ux * _x2 + fit.uz * _z2 + fit.u0;
|
|
2191
|
+
_uvs[_i2 * 2 + 1] = fit.vx * _x2 + fit.vz * _z2 + fit.v0;
|
|
2192
|
+
continue;
|
|
2193
|
+
}
|
|
2194
|
+
var sampled = sampleNearestTriangleUV(deck.triangles, deck.triangleCount, _x2, _z2, deck.triangleIndex);
|
|
2195
|
+
if (!sampled) continue;
|
|
2196
|
+
_uvs[_i2 * 2] = sampled[0];
|
|
2197
|
+
_uvs[_i2 * 2 + 1] = sampled[1];
|
|
2198
|
+
}
|
|
2199
|
+
geo.setAttribute('uv', new BufferAttribute(_uvs, 2));
|
|
2200
|
+
geo.setAttribute('uv1', new BufferAttribute(_uvs, 2));
|
|
2201
|
+
}
|
|
2202
|
+
return {
|
|
2203
|
+
geometry: geo,
|
|
2204
|
+
diagnostic: diagnostic
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
var LOG$1 = getLogger('RealTimeViewer', 'DeckCutoutCollar');
|
|
2209
|
+
|
|
2210
|
+
/** Shared loader — the collar's own maps are plain URLs, like `MaterialManager`'s. */
|
|
2211
|
+
var textureLoader = new TextureLoader();
|
|
2212
|
+
/**
|
|
2213
|
+
* Load the collar's declared maps imperatively rather than through
|
|
2214
|
+
* `useTexture`/`useLoader`: those suspend, and the collar mounts outside a
|
|
2215
|
+
* Suspense boundary, so a slow texture would blank the whole scene group.
|
|
2216
|
+
* Returns null until `mapUrl` resolves; the other maps are optional and simply
|
|
2217
|
+
* aren't applied if they fail.
|
|
2218
|
+
*/
|
|
2219
|
+
function useCollarTextures(material) {
|
|
2220
|
+
var mapUrl = material.mapUrl,
|
|
2221
|
+
normalMapUrl = material.normalMapUrl,
|
|
2222
|
+
roughnessMapUrl = material.roughnessMapUrl;
|
|
2223
|
+
var _useState = useState(null),
|
|
2224
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
2225
|
+
loaded = _useState2[0],
|
|
2226
|
+
setLoaded = _useState2[1];
|
|
2227
|
+
useEffect(function () {
|
|
2228
|
+
if (mapUrl === '') {
|
|
2229
|
+
setLoaded(null);
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
var live = true;
|
|
2233
|
+
var opened = [];
|
|
2234
|
+
var load = function load(url) {
|
|
2235
|
+
return url === '' ? Promise.resolve(null) : textureLoader.loadAsync(url).then(function (t) {
|
|
2236
|
+
// A texture resolving after cleanup would never be reached by the
|
|
2237
|
+
// cleanup's dispose loop — dispose it here instead of leaking it.
|
|
2238
|
+
if (!live) {
|
|
2239
|
+
t.dispose();
|
|
2240
|
+
return null;
|
|
2241
|
+
}
|
|
2242
|
+
opened.push(t);
|
|
2243
|
+
return t;
|
|
2244
|
+
}, function () {
|
|
2245
|
+
LOG$1.debug("[DeckCutoutCollar] collar texture failed to load: ".concat(url));
|
|
2246
|
+
return null;
|
|
2247
|
+
});
|
|
2248
|
+
};
|
|
2249
|
+
Promise.all([load(mapUrl), load(normalMapUrl), load(roughnessMapUrl)]).then(function (_ref) {
|
|
2250
|
+
var _ref2 = _slicedToArray(_ref, 3),
|
|
2251
|
+
map = _ref2[0],
|
|
2252
|
+
normalMap = _ref2[1],
|
|
2253
|
+
roughnessMap = _ref2[2];
|
|
2254
|
+
if (!live || !map) return;
|
|
2255
|
+
// Repeat wrapping is what makes the world-planar UVs tile at all; the
|
|
2256
|
+
// colour map is the only one that carries colour (the others are data).
|
|
2257
|
+
map.colorSpace = SRGBColorSpace;
|
|
2258
|
+
for (var _i = 0, _arr = [map, normalMap, roughnessMap]; _i < _arr.length; _i++) {
|
|
2259
|
+
var t = _arr[_i];
|
|
2260
|
+
if (!t) continue;
|
|
2261
|
+
t.wrapS = RepeatWrapping;
|
|
2262
|
+
t.wrapT = RepeatWrapping;
|
|
2263
|
+
t.needsUpdate = true;
|
|
2264
|
+
}
|
|
2265
|
+
setLoaded({
|
|
2266
|
+
map: map,
|
|
2267
|
+
normalMap: normalMap,
|
|
2268
|
+
roughnessMap: roughnessMap
|
|
2269
|
+
});
|
|
2270
|
+
});
|
|
2271
|
+
return function () {
|
|
2272
|
+
live = false;
|
|
2273
|
+
for (var _i2 = 0, _opened = opened; _i2 < _opened.length; _i2++) {
|
|
2274
|
+
var t = _opened[_i2];
|
|
2275
|
+
t.dispose();
|
|
2276
|
+
}
|
|
2277
|
+
setLoaded(null);
|
|
2278
|
+
};
|
|
2279
|
+
}, [mapUrl, normalMapUrl, roughnessMapUrl]);
|
|
2280
|
+
return loaded;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
2284
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
2285
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
2286
|
+
var LOG = getLogger('RealTimeViewer', 'DeckCutoutCollar');
|
|
2287
|
+
|
|
2288
|
+
/**
|
|
2289
|
+
* Page-lifetime latch for the collar hold — not the registry's `once`: StrictMode's simulated
|
|
2290
|
+
* unmount runs the cleanup `end`, which would latch a once-id and kill the gate before the real
|
|
2291
|
+
* boot. Flips only on a real completion (first collar geometry applied, or timeout).
|
|
2292
|
+
*/
|
|
2293
|
+
var collarGateSettled = false;
|
|
2294
|
+
|
|
2295
|
+
/**
|
|
2296
|
+
* Frames to wait after a decor bump before sampling. Every deck GLB writes its
|
|
2297
|
+
* scene transform from its OWN `useFrame`, and this component's frame callback
|
|
2298
|
+
* can run before theirs — sampling in the same frame would fold a just-mounted,
|
|
2299
|
+
* still-untransformed part and the already-placed ones into one reference frame.
|
|
2300
|
+
* One frame of slack means every mounted deck GLB has written its transform at
|
|
2301
|
+
* least once before the sample reads their world matrices.
|
|
2302
|
+
*/
|
|
2303
|
+
var ACQUIRE_DELAY_FRAMES = 1;
|
|
2304
|
+
|
|
2305
|
+
/**
|
|
2306
|
+
* Generated coping collar for footprint-rule cutouts (`deckCutout.articleRules`):
|
|
2307
|
+
* a flat ring filling the space between the deck's authored rectangular hole and
|
|
2308
|
+
* the article's shaped outline. This is what makes the cutout follow the article
|
|
2309
|
+
* — the deck GLB itself is never touched. Rendered only while
|
|
2310
|
+
* `DeckCutoutArticleSync` publishes a footprint outline.
|
|
2311
|
+
*
|
|
2312
|
+
* It wears the DECK's material and continues the deck's own UV mapping (see
|
|
2313
|
+
* {@link buildCollarGeometry}), unless the config declares own maps. The ring
|
|
2314
|
+
* follows a name-matched floor mesh's world matrix every frame, so
|
|
2315
|
+
* scene-transform edits need no rebuild.
|
|
2316
|
+
*/
|
|
2317
|
+
function DeckCutoutCollar() {
|
|
2318
|
+
var _deck$material;
|
|
2319
|
+
var scene = useThree(function (s) {
|
|
2320
|
+
return s.scene;
|
|
2321
|
+
});
|
|
2322
|
+
var deckCutoutJson = useRTVStore(function (s) {
|
|
2323
|
+
return JSON.stringify(s.resolvedEnvironment.scene.deckCutout);
|
|
2324
|
+
});
|
|
2325
|
+
var deform = useMemo(function () {
|
|
2326
|
+
return JSON.parse(deckCutoutJson);
|
|
2327
|
+
}, [deckCutoutJson]);
|
|
2328
|
+
var footprint = useRTVStore(function (s) {
|
|
2329
|
+
return s.deckCutoutFootprintOverride;
|
|
2330
|
+
});
|
|
2331
|
+
// Bumps once per mounted GLB — the deck arrives as several part GLBs, so this
|
|
2332
|
+
// is the signal that there may be more meshes to sample than last time.
|
|
2333
|
+
var decorVersion = useRTVStore(function (s) {
|
|
2334
|
+
return s.decorVersion;
|
|
2335
|
+
});
|
|
2336
|
+
var ownTextures = useCollarTextures(deform.collar.material);
|
|
2337
|
+
var meshRef = useRef(null);
|
|
2338
|
+
var floorRef = useRef(null);
|
|
2339
|
+
var surfaceKeyRef = useRef(null);
|
|
2340
|
+
/** Frames left before the armed sample runs; negative means nothing is armed. */
|
|
2341
|
+
var acquireDelayRef = useRef(-1);
|
|
2342
|
+
var _useState = useState(null),
|
|
2343
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
2344
|
+
deck = _useState2[0],
|
|
2345
|
+
setDeck = _useState2[1];
|
|
2346
|
+
var patterns = useMemo(function () {
|
|
2347
|
+
return deform.nodeMatch.split(',').map(function (p) {
|
|
2348
|
+
return p.trim();
|
|
2349
|
+
}).filter(function (p) {
|
|
2350
|
+
return p.length > 0;
|
|
2351
|
+
});
|
|
2352
|
+
}, [deform.nodeMatch]);
|
|
2353
|
+
|
|
2354
|
+
// Re-acquire the deck when the match config changes or the scene swaps.
|
|
2355
|
+
useEffect(function () {
|
|
2356
|
+
floorRef.current = null;
|
|
2357
|
+
surfaceKeyRef.current = null;
|
|
2358
|
+
setDeck(null);
|
|
2359
|
+
}, [patterns]);
|
|
2360
|
+
var collar = deform.collar;
|
|
2361
|
+
// The area the collar owns, straight from the config. `nodeMatch` names the
|
|
2362
|
+
// deck to CONTINUE, which is a different thing and may sit entirely outside
|
|
2363
|
+
// this rect (see the preset).
|
|
2364
|
+
var holeRect = deform.authoredHole;
|
|
2365
|
+
var enabled = deform.enabled;
|
|
2366
|
+
var active = enabled && footprint !== null && footprint.length >= 3;
|
|
2367
|
+
|
|
2368
|
+
// A configured cutout means a collar WILL appear — hold the reveal until its first GEOMETRY
|
|
2369
|
+
// lands, otherwise it pops in about a second after the scene shows. The footprint needs the
|
|
2370
|
+
// mounted article, so the initial window is generous and tightens once the footprint is in.
|
|
2371
|
+
// Once-latched: later scene swaps rebuild behind the visible scene.
|
|
2372
|
+
useEffect(function () {
|
|
2373
|
+
if (!enabled || collarGateSettled) return;
|
|
2374
|
+
useRTVStore.getState().beginSceneLoad(DECK_COLLAR_LOAD_ID, {
|
|
2375
|
+
timeoutMs: 25000,
|
|
2376
|
+
onTimeout: function onTimeout() {
|
|
2377
|
+
return collarGateSettled = true;
|
|
2378
|
+
}
|
|
2379
|
+
});
|
|
2380
|
+
return function () {
|
|
2381
|
+
return useRTVStore.getState().endSceneLoad(DECK_COLLAR_LOAD_ID);
|
|
2382
|
+
};
|
|
2383
|
+
}, [enabled]);
|
|
2384
|
+
useEffect(function () {
|
|
2385
|
+
if (active && !collarGateSettled) {
|
|
2386
|
+
useRTVStore.getState().beginSceneLoad(DECK_COLLAR_LOAD_ID, {
|
|
2387
|
+
timeoutMs: 8000,
|
|
2388
|
+
onTimeout: function onTimeout() {
|
|
2389
|
+
return collarGateSettled = true;
|
|
2390
|
+
}
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
}, [active]);
|
|
2394
|
+
var acquireDeck = useCallback(function () {
|
|
2395
|
+
var _sampleDeckSurface = sampleDeckSurface(scene, patterns),
|
|
2396
|
+
reference = _sampleDeckSurface.reference,
|
|
2397
|
+
surface = _sampleDeckSurface.surface;
|
|
2398
|
+
floorRef.current = reference;
|
|
2399
|
+
|
|
2400
|
+
// A fresh surface object rebuilds the collar geometry, so only apply on an actual change.
|
|
2401
|
+
var key = deckSurfaceKey(surface);
|
|
2402
|
+
if (surfaceKeyRef.current === key) return;
|
|
2403
|
+
surfaceKeyRef.current = key;
|
|
2404
|
+
setDeck(surface);
|
|
2405
|
+
if (!surface) return;
|
|
2406
|
+
bootPhase('deck: surface sampled');
|
|
2407
|
+
var _iterator = _createForOfIteratorHelper(describeDeckSurface(surface, ownTextures !== null)),
|
|
2408
|
+
_step;
|
|
2409
|
+
try {
|
|
2410
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
2411
|
+
var line = _step.value;
|
|
2412
|
+
LOG.debug(line);
|
|
2413
|
+
}
|
|
2414
|
+
} catch (err) {
|
|
2415
|
+
_iterator.e(err);
|
|
2416
|
+
} finally {
|
|
2417
|
+
_iterator.f();
|
|
2418
|
+
}
|
|
2419
|
+
}, [scene, patterns, ownTextures]);
|
|
2420
|
+
|
|
2421
|
+
// Arm the sample on every event that can change what is there to sample: the match config and
|
|
2422
|
+
// every mounted deck GLB (the part GLBs land on different frames, and a sample taken from only
|
|
2423
|
+
// the parts present so far would mis-map the rest of the ring). NOT the article: the sample
|
|
2424
|
+
// reads the name-matched deck meshes only, so waiting for the footprint would serialize it
|
|
2425
|
+
// behind the whole article load for nothing.
|
|
2426
|
+
useEffect(function () {
|
|
2427
|
+
if (!enabled) return;
|
|
2428
|
+
acquireDelayRef.current = ACQUIRE_DELAY_FRAMES;
|
|
2429
|
+
}, [enabled, decorVersion, acquireDeck]);
|
|
2430
|
+
|
|
2431
|
+
// Nothing named by `nodeMatch` is in the scene, so no collar can be built for the outline the
|
|
2432
|
+
// article just published — the case where the hold above runs out instead of settling.
|
|
2433
|
+
useEffect(function () {
|
|
2434
|
+
if (!active || deck !== null) return;
|
|
2435
|
+
LOG.debug("[DeckCutoutCollar] footprint active but NO floor mesh matches '".concat(deform.nodeMatch, "'"));
|
|
2436
|
+
}, [active, deck, deform.nodeMatch]);
|
|
2437
|
+
useFrame(function () {
|
|
2438
|
+
if (!enabled) return;
|
|
2439
|
+
if (acquireDelayRef.current >= 0) {
|
|
2440
|
+
if (acquireDelayRef.current === 0) acquireDeck();
|
|
2441
|
+
acquireDelayRef.current -= 1;
|
|
2442
|
+
}
|
|
2443
|
+
var floor = floorRef.current;
|
|
2444
|
+
// The deck GLB was unmounted (URL cleared / swapped) — look again once; the replacement, if
|
|
2445
|
+
// any, re-arms the effect above when it mounts.
|
|
2446
|
+
if (floor && !floor.parent) acquireDeck();
|
|
2447
|
+
var collarMesh = meshRef.current;
|
|
2448
|
+
if (!floor || !collarMesh) return;
|
|
2449
|
+
collarMesh.matrix.copy(floor.matrixWorld);
|
|
2450
|
+
collarMesh.matrixWorldNeedsUpdate = true;
|
|
2451
|
+
});
|
|
2452
|
+
var geometry = useMemo(function () {
|
|
2453
|
+
if (!enabled || deck === null || footprint === null || footprint.length < 3) return null;
|
|
2454
|
+
var built = buildCollarGeometry({
|
|
2455
|
+
footprint: footprint,
|
|
2456
|
+
hole: holeRect,
|
|
2457
|
+
collar: collar,
|
|
2458
|
+
deck: deck,
|
|
2459
|
+
ownMaterial: ownTextures ? collar.material : null
|
|
2460
|
+
});
|
|
2461
|
+
LOG.debug(built.diagnostic);
|
|
2462
|
+
return built.geometry;
|
|
2463
|
+
}, [enabled, footprint, holeRect.centerX, holeRect.centerZ, holeRect.halfExtentX, holeRect.halfExtentZ, collar, deck, ownTextures]);
|
|
2464
|
+
|
|
2465
|
+
// Settle the hold on the first BUILT collar, not on the deck sample: the sample lands as soon as
|
|
2466
|
+
// the deck GLBs mount, long before the article publishes the outline the geometry needs. Ending
|
|
2467
|
+
// the hold there would reveal the scene and let the collar appear afterwards — the exact pop the
|
|
2468
|
+
// hold exists to prevent. This effect runs after the commit that put the collar mesh in the
|
|
2469
|
+
// scene, so the reveal can only follow it.
|
|
2470
|
+
useEffect(function () {
|
|
2471
|
+
if (!geometry || collarGateSettled) return;
|
|
2472
|
+
collarGateSettled = true;
|
|
2473
|
+
bootPhase('deck: first collar geometry');
|
|
2474
|
+
useRTVStore.getState().endSceneLoad(DECK_COLLAR_LOAD_ID);
|
|
2475
|
+
}, [geometry]);
|
|
2476
|
+
useEffect(function () {
|
|
2477
|
+
return function () {
|
|
2478
|
+
return geometry === null || geometry === void 0 ? void 0 : geometry.dispose();
|
|
2479
|
+
};
|
|
2480
|
+
}, [geometry]);
|
|
2481
|
+
|
|
2482
|
+
// The clone is ours to dispose; its textures are shared with the deck
|
|
2483
|
+
// material and must survive (`Material.dispose` leaves them alone).
|
|
2484
|
+
var deckMaterial = (_deck$material = deck === null || deck === void 0 ? void 0 : deck.material) !== null && _deck$material !== void 0 ? _deck$material : null;
|
|
2485
|
+
useEffect(function () {
|
|
2486
|
+
return function () {
|
|
2487
|
+
return deckMaterial === null || deckMaterial === void 0 ? void 0 : deckMaterial.dispose();
|
|
2488
|
+
};
|
|
2489
|
+
}, [deckMaterial]);
|
|
2490
|
+
if (!geometry) return null;
|
|
2491
|
+
// The collar stands in for deck, so it casts like deck does (the deck edge
|
|
2492
|
+
// over the cliff), whether it fills a hole or spans a whole area.
|
|
2493
|
+
if (ownTextures) return /*#__PURE__*/jsx("mesh", {
|
|
2494
|
+
ref: meshRef,
|
|
2495
|
+
geometry: geometry,
|
|
2496
|
+
matrixAutoUpdate: false,
|
|
2497
|
+
receiveShadow: true,
|
|
2498
|
+
castShadow: true,
|
|
2499
|
+
children: /*#__PURE__*/jsx("meshStandardMaterial", {
|
|
2500
|
+
map: ownTextures.map,
|
|
2501
|
+
normalMap: ownTextures.normalMap,
|
|
2502
|
+
roughnessMap: ownTextures.roughnessMap,
|
|
2503
|
+
color: collar.color,
|
|
2504
|
+
roughness: collar.roughness,
|
|
2505
|
+
metalness: 0
|
|
2506
|
+
})
|
|
2507
|
+
});
|
|
2508
|
+
if (deckMaterial) return /*#__PURE__*/jsx("mesh", {
|
|
2509
|
+
ref: meshRef,
|
|
2510
|
+
geometry: geometry,
|
|
2511
|
+
material: deckMaterial,
|
|
2512
|
+
matrixAutoUpdate: false,
|
|
2513
|
+
receiveShadow: true,
|
|
2514
|
+
castShadow: true
|
|
2515
|
+
});
|
|
2516
|
+
return /*#__PURE__*/jsx("mesh", {
|
|
2517
|
+
ref: meshRef,
|
|
2518
|
+
geometry: geometry,
|
|
2519
|
+
matrixAutoUpdate: false,
|
|
2520
|
+
receiveShadow: true,
|
|
2521
|
+
castShadow: true,
|
|
2522
|
+
children: /*#__PURE__*/jsx("meshStandardMaterial", {
|
|
2523
|
+
color: collar.color,
|
|
2524
|
+
roughness: collar.roughness,
|
|
2525
|
+
metalness: 0
|
|
2526
|
+
})
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
function DeckCutoutSubsystem() {
|
|
2531
|
+
return /*#__PURE__*/jsxs(Fragment, {
|
|
2532
|
+
children: [/*#__PURE__*/jsx(DeckCutoutArticleSync, {}), /*#__PURE__*/jsx(DeckCutoutCollar, {})]
|
|
2533
|
+
});
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
export { DeckCutoutSubsystem };
|