@3driseai/3drise-viewer 0.1.42 → 0.1.44
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/dist/animationsV2/ClipsRuntime.d.ts +16 -1
- package/dist/animationsV2/ClipsRuntime.d.ts.map +1 -1
- package/dist/animationsV2/ClipsRuntime.js +144 -25
- package/dist/animationsV2/appliers.d.ts +9 -0
- package/dist/animationsV2/appliers.d.ts.map +1 -1
- package/dist/animationsV2/appliers.js +62 -2
- package/dist/animationsV2/clipUtils.d.ts +30 -0
- package/dist/animationsV2/clipUtils.d.ts.map +1 -1
- package/dist/animationsV2/clipUtils.js +163 -17
- package/dist/animationsV2/elementSettings.d.ts +5 -0
- package/dist/animationsV2/elementSettings.d.ts.map +1 -1
- package/dist/animationsV2/elementSettings.js +6 -3
- package/dist/animationsV2/sequenceUtils.js +4 -4
- package/dist/animationsV2/types.d.ts +23 -0
- package/dist/animationsV2/types.d.ts.map +1 -1
- package/dist/api/sceneHandle.js +2 -2
- package/dist/components/CreatedObject.d.ts +3 -0
- package/dist/components/CreatedObject.d.ts.map +1 -1
- package/dist/components/CreatedObject.js +6 -2
- package/dist/components/CustomPrimitive.d.ts.map +1 -1
- package/dist/components/CustomPrimitive.js +1 -10
- package/dist/components/EnvironmentManager.d.ts.map +1 -1
- package/dist/components/EnvironmentManager.js +11 -5
- package/dist/components/GeometryShape.d.ts +5 -2
- package/dist/components/GeometryShape.d.ts.map +1 -1
- package/dist/components/GeometryShape.js +71 -10
- package/dist/components/LandObject.d.ts.map +1 -1
- package/dist/components/LandObject.js +1 -1
- package/dist/components/LayoutInstances.d.ts +2 -1
- package/dist/components/LayoutInstances.d.ts.map +1 -1
- package/dist/components/LayoutInstances.js +39 -6
- package/dist/components/LightObject.d.ts.map +1 -1
- package/dist/components/LightObject.js +3 -0
- package/dist/components/ObjectNode.d.ts.map +1 -1
- package/dist/components/ObjectNode.js +62 -13
- package/dist/components/OceanBuilder.d.ts +2 -0
- package/dist/components/OceanBuilder.d.ts.map +1 -1
- package/dist/components/OceanBuilder.js +2 -1
- package/dist/components/RenderEffects.d.ts +8 -0
- package/dist/components/RenderEffects.d.ts.map +1 -0
- package/dist/components/RenderEffects.js +120 -0
- package/dist/components/SceneBuilder.d.ts.map +1 -1
- package/dist/components/SceneBuilder.js +18 -9
- package/dist/components/SportsObject.d.ts +9 -0
- package/dist/components/SportsObject.d.ts.map +1 -0
- package/dist/components/SportsObject.js +20 -0
- package/dist/components/UrbanObject.d.ts +9 -0
- package/dist/components/UrbanObject.d.ts.map +1 -0
- package/dist/components/UrbanObject.js +28 -0
- package/dist/components/VegetationObject.d.ts +9 -0
- package/dist/components/VegetationObject.d.ts.map +1 -0
- package/dist/components/VegetationObject.js +26 -0
- package/dist/components/WaterObject.d.ts +15 -0
- package/dist/components/WaterObject.d.ts.map +1 -0
- package/dist/components/WaterObject.js +29 -0
- package/dist/components/index.d.ts +7 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +6 -1
- package/dist/components/land/LandPatch.d.ts +3 -1
- package/dist/components/land/LandPatch.d.ts.map +1 -1
- package/dist/components/land/LandPatch.js +80 -7
- package/dist/components/layout/InstancedLayout.d.ts +19 -0
- package/dist/components/layout/InstancedLayout.d.ts.map +1 -0
- package/dist/components/layout/InstancedLayout.js +154 -0
- package/dist/components/layout/LayoutGrounds.d.ts +19 -0
- package/dist/components/layout/LayoutGrounds.d.ts.map +1 -0
- package/dist/components/layout/LayoutGrounds.js +104 -0
- package/dist/components/layout/LinkProxies.d.ts +5 -0
- package/dist/components/layout/LinkProxies.d.ts.map +1 -0
- package/dist/components/layout/LinkProxies.js +51 -0
- package/dist/components/layout/LinkedSource.d.ts +14 -0
- package/dist/components/layout/LinkedSource.d.ts.map +1 -0
- package/dist/components/layout/LinkedSource.js +129 -0
- package/dist/components/projector/Projector.d.ts +14 -0
- package/dist/components/projector/Projector.d.ts.map +1 -0
- package/dist/components/projector/Projector.js +44 -0
- package/dist/components/projector/index.d.ts +4 -0
- package/dist/components/projector/index.d.ts.map +1 -0
- package/dist/components/projector/index.js +2 -0
- package/dist/components/projector/projectorCore.d.ts +19 -0
- package/dist/components/projector/projectorCore.d.ts.map +1 -0
- package/dist/components/projector/projectorCore.js +545 -0
- package/dist/components/sports/SportsBall.d.ts +9 -0
- package/dist/components/sports/SportsBall.d.ts.map +1 -0
- package/dist/components/sports/SportsBall.js +23 -0
- package/dist/components/sports/SportsCourt.d.ts +9 -0
- package/dist/components/sports/SportsCourt.d.ts.map +1 -0
- package/dist/components/sports/SportsCourt.js +23 -0
- package/dist/components/sports/SportsEquipment.d.ts +9 -0
- package/dist/components/sports/SportsEquipment.d.ts.map +1 -0
- package/dist/components/sports/SportsEquipment.js +23 -0
- package/dist/components/sports/SportsGoal.d.ts +9 -0
- package/dist/components/sports/SportsGoal.d.ts.map +1 -0
- package/dist/components/sports/SportsGoal.js +23 -0
- package/dist/components/sports/core/ball.d.ts +166 -0
- package/dist/components/sports/core/ball.d.ts.map +1 -0
- package/dist/components/sports/core/ball.js +402 -0
- package/dist/components/sports/core/court.d.ts +442 -0
- package/dist/components/sports/core/court.d.ts.map +1 -0
- package/dist/components/sports/core/court.js +658 -0
- package/dist/components/sports/core/equipment.d.ts +103 -0
- package/dist/components/sports/core/equipment.d.ts.map +1 -0
- package/dist/components/sports/core/equipment.js +403 -0
- package/dist/components/sports/core/goal.d.ts +98 -0
- package/dist/components/sports/core/goal.d.ts.map +1 -0
- package/dist/components/sports/core/goal.js +44 -0
- package/dist/components/sports/index.d.ts +11 -0
- package/dist/components/sports/index.d.ts.map +1 -0
- package/dist/components/sports/index.js +8 -0
- package/dist/components/sports/sportsCore.d.ts +195 -0
- package/dist/components/sports/sportsCore.d.ts.map +1 -0
- package/dist/components/sports/sportsCore.js +758 -0
- package/dist/components/sports/useSportsInstance.d.ts +35 -0
- package/dist/components/sports/useSportsInstance.d.ts.map +1 -0
- package/dist/components/sports/useSportsInstance.js +47 -0
- package/dist/components/terrain/TerrainSurface.d.ts.map +1 -1
- package/dist/components/terrain/TerrainSurface.js +14 -0
- package/dist/components/urban/UrbanBlock.d.ts +9 -0
- package/dist/components/urban/UrbanBlock.d.ts.map +1 -0
- package/dist/components/urban/UrbanBlock.js +23 -0
- package/dist/components/urban/UrbanBuilding.d.ts +9 -0
- package/dist/components/urban/UrbanBuilding.d.ts.map +1 -0
- package/dist/components/urban/UrbanBuilding.js +23 -0
- package/dist/components/urban/UrbanJunction.d.ts +9 -0
- package/dist/components/urban/UrbanJunction.d.ts.map +1 -0
- package/dist/components/urban/UrbanJunction.js +23 -0
- package/dist/components/urban/UrbanPark.d.ts +9 -0
- package/dist/components/urban/UrbanPark.d.ts.map +1 -0
- package/dist/components/urban/UrbanPark.js +23 -0
- package/dist/components/urban/UrbanParkingLot.d.ts +9 -0
- package/dist/components/urban/UrbanParkingLot.d.ts.map +1 -0
- package/dist/components/urban/UrbanParkingLot.js +23 -0
- package/dist/components/urban/UrbanPlaza.d.ts +9 -0
- package/dist/components/urban/UrbanPlaza.d.ts.map +1 -0
- package/dist/components/urban/UrbanPlaza.js +23 -0
- package/dist/components/urban/UrbanRoad.d.ts +9 -0
- package/dist/components/urban/UrbanRoad.d.ts.map +1 -0
- package/dist/components/urban/UrbanRoad.js +23 -0
- package/dist/components/urban/UrbanSignage.d.ts +9 -0
- package/dist/components/urban/UrbanSignage.d.ts.map +1 -0
- package/dist/components/urban/UrbanSignage.js +23 -0
- package/dist/components/urban/UrbanSkyline.d.ts +9 -0
- package/dist/components/urban/UrbanSkyline.d.ts.map +1 -0
- package/dist/components/urban/UrbanSkyline.js +23 -0
- package/dist/components/urban/UrbanStreetFurniture.d.ts +9 -0
- package/dist/components/urban/UrbanStreetFurniture.d.ts.map +1 -0
- package/dist/components/urban/UrbanStreetFurniture.js +23 -0
- package/dist/components/urban/UrbanStreetGrid.d.ts +9 -0
- package/dist/components/urban/UrbanStreetGrid.d.ts.map +1 -0
- package/dist/components/urban/UrbanStreetGrid.js +34 -0
- package/dist/components/urban/UrbanTraffic.d.ts +9 -0
- package/dist/components/urban/UrbanTraffic.d.ts.map +1 -0
- package/dist/components/urban/UrbanTraffic.js +23 -0
- package/dist/components/urban/core/block.d.ts +119 -0
- package/dist/components/urban/core/block.d.ts.map +1 -0
- package/dist/components/urban/core/block.js +266 -0
- package/dist/components/urban/core/building.d.ts +128 -0
- package/dist/components/urban/core/building.d.ts.map +1 -0
- package/dist/components/urban/core/building.js +741 -0
- package/dist/components/urban/core/junction.d.ts +152 -0
- package/dist/components/urban/core/junction.d.ts.map +1 -0
- package/dist/components/urban/core/junction.js +617 -0
- package/dist/components/urban/core/park.d.ts +67 -0
- package/dist/components/urban/core/park.d.ts.map +1 -0
- package/dist/components/urban/core/park.js +176 -0
- package/dist/components/urban/core/parkingLot.d.ts +82 -0
- package/dist/components/urban/core/parkingLot.d.ts.map +1 -0
- package/dist/components/urban/core/parkingLot.js +284 -0
- package/dist/components/urban/core/plaza.d.ts +24 -0
- package/dist/components/urban/core/plaza.d.ts.map +1 -0
- package/dist/components/urban/core/plaza.js +243 -0
- package/dist/components/urban/core/road.d.ts +113 -0
- package/dist/components/urban/core/road.d.ts.map +1 -0
- package/dist/components/urban/core/road.js +347 -0
- package/dist/components/urban/core/signage.d.ts +83 -0
- package/dist/components/urban/core/signage.d.ts.map +1 -0
- package/dist/components/urban/core/signage.js +209 -0
- package/dist/components/urban/core/skyline.d.ts +100 -0
- package/dist/components/urban/core/skyline.d.ts.map +1 -0
- package/dist/components/urban/core/skyline.js +89 -0
- package/dist/components/urban/core/streetFurniture.d.ts +75 -0
- package/dist/components/urban/core/streetFurniture.d.ts.map +1 -0
- package/dist/components/urban/core/streetFurniture.js +193 -0
- package/dist/components/urban/core/streetGrid.d.ts +134 -0
- package/dist/components/urban/core/streetGrid.d.ts.map +1 -0
- package/dist/components/urban/core/streetGrid.js +422 -0
- package/dist/components/urban/core/traffic.d.ts +125 -0
- package/dist/components/urban/core/traffic.d.ts.map +1 -0
- package/dist/components/urban/core/traffic.js +305 -0
- package/dist/components/urban/index.d.ts +29 -0
- package/dist/components/urban/index.d.ts.map +1 -0
- package/dist/components/urban/index.js +16 -0
- package/dist/components/urban/textureSetResolver.d.ts +8 -0
- package/dist/components/urban/textureSetResolver.d.ts.map +1 -0
- package/dist/components/urban/textureSetResolver.js +16 -0
- package/dist/components/urban/urbanCore.d.ts +262 -0
- package/dist/components/urban/urbanCore.d.ts.map +1 -0
- package/dist/components/urban/urbanCore.js +928 -0
- package/dist/components/urban/useUrbanInstance.d.ts +53 -0
- package/dist/components/urban/useUrbanInstance.d.ts.map +1 -0
- package/dist/components/urban/useUrbanInstance.js +100 -0
- package/dist/components/vegetation/VegetationBush.d.ts +9 -0
- package/dist/components/vegetation/VegetationBush.d.ts.map +1 -0
- package/dist/components/vegetation/VegetationBush.js +23 -0
- package/dist/components/vegetation/VegetationCrops.d.ts +9 -0
- package/dist/components/vegetation/VegetationCrops.d.ts.map +1 -0
- package/dist/components/vegetation/VegetationCrops.js +23 -0
- package/dist/components/vegetation/VegetationFlowers.d.ts +9 -0
- package/dist/components/vegetation/VegetationFlowers.d.ts.map +1 -0
- package/dist/components/vegetation/VegetationFlowers.js +23 -0
- package/dist/components/vegetation/VegetationForest.d.ts +9 -0
- package/dist/components/vegetation/VegetationForest.d.ts.map +1 -0
- package/dist/components/vegetation/VegetationForest.js +23 -0
- package/dist/components/vegetation/VegetationFruit.d.ts +9 -0
- package/dist/components/vegetation/VegetationFruit.d.ts.map +1 -0
- package/dist/components/vegetation/VegetationFruit.js +23 -0
- package/dist/components/vegetation/VegetationGrass.d.ts +9 -0
- package/dist/components/vegetation/VegetationGrass.d.ts.map +1 -0
- package/dist/components/vegetation/VegetationGrass.js +23 -0
- package/dist/components/vegetation/VegetationHedge.d.ts +9 -0
- package/dist/components/vegetation/VegetationHedge.d.ts.map +1 -0
- package/dist/components/vegetation/VegetationHedge.js +23 -0
- package/dist/components/vegetation/VegetationIvy.d.ts +9 -0
- package/dist/components/vegetation/VegetationIvy.d.ts.map +1 -0
- package/dist/components/vegetation/VegetationIvy.js +23 -0
- package/dist/components/vegetation/VegetationTree.d.ts +9 -0
- package/dist/components/vegetation/VegetationTree.d.ts.map +1 -0
- package/dist/components/vegetation/VegetationTree.js +23 -0
- package/dist/components/vegetation/VegetationVegetable.d.ts +9 -0
- package/dist/components/vegetation/VegetationVegetable.d.ts.map +1 -0
- package/dist/components/vegetation/VegetationVegetable.js +23 -0
- package/dist/components/vegetation/core/bush.d.ts +1256 -0
- package/dist/components/vegetation/core/bush.d.ts.map +1 -0
- package/dist/components/vegetation/core/bush.js +376 -0
- package/dist/components/vegetation/core/crops.d.ts +1033 -0
- package/dist/components/vegetation/core/crops.d.ts.map +1 -0
- package/dist/components/vegetation/core/crops.js +378 -0
- package/dist/components/vegetation/core/flowers.d.ts +648 -0
- package/dist/components/vegetation/core/flowers.d.ts.map +1 -0
- package/dist/components/vegetation/core/flowers.js +225 -0
- package/dist/components/vegetation/core/forest.d.ts +138 -0
- package/dist/components/vegetation/core/forest.d.ts.map +1 -0
- package/dist/components/vegetation/core/forest.js +310 -0
- package/dist/components/vegetation/core/fruit.d.ts +99 -0
- package/dist/components/vegetation/core/fruit.d.ts.map +1 -0
- package/dist/components/vegetation/core/fruit.js +77 -0
- package/dist/components/vegetation/core/grass.d.ts +103 -0
- package/dist/components/vegetation/core/grass.d.ts.map +1 -0
- package/dist/components/vegetation/core/grass.js +102 -0
- package/dist/components/vegetation/core/hedge.d.ts +193 -0
- package/dist/components/vegetation/core/hedge.d.ts.map +1 -0
- package/dist/components/vegetation/core/hedge.js +243 -0
- package/dist/components/vegetation/core/ivy.d.ts +213 -0
- package/dist/components/vegetation/core/ivy.d.ts.map +1 -0
- package/dist/components/vegetation/core/ivy.js +173 -0
- package/dist/components/vegetation/core/tree.d.ts +1117 -0
- package/dist/components/vegetation/core/tree.d.ts.map +1 -0
- package/dist/components/vegetation/core/tree.js +408 -0
- package/dist/components/vegetation/core/vegetable.d.ts +99 -0
- package/dist/components/vegetation/core/vegetable.d.ts.map +1 -0
- package/dist/components/vegetation/core/vegetable.js +76 -0
- package/dist/components/vegetation/index.d.ts +23 -0
- package/dist/components/vegetation/index.d.ts.map +1 -0
- package/dist/components/vegetation/index.js +14 -0
- package/dist/components/vegetation/produceCore.d.ts +929 -0
- package/dist/components/vegetation/produceCore.d.ts.map +1 -0
- package/dist/components/vegetation/produceCore.js +723 -0
- package/dist/components/vegetation/useVegetationInstance.d.ts +39 -0
- package/dist/components/vegetation/useVegetationInstance.d.ts.map +1 -0
- package/dist/components/vegetation/useVegetationInstance.js +45 -0
- package/dist/components/vegetation/vegetationCore.d.ts +412 -0
- package/dist/components/vegetation/vegetationCore.d.ts.map +1 -0
- package/dist/components/vegetation/vegetationCore.js +1728 -0
- package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts +5 -0
- package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts.map +1 -1
- package/dist/components/volumetricClouds/VolumetricCloudsPass.js +39 -13
- package/dist/components/water/OceanSurface.d.ts.map +1 -1
- package/dist/components/water/OceanSurface.js +3 -2
- package/dist/components/water/WaterBody.d.ts.map +1 -1
- package/dist/components/water/WaterBody.js +31 -10
- package/dist/components/water/WaterFountain.d.ts +9 -0
- package/dist/components/water/WaterFountain.d.ts.map +1 -0
- package/dist/components/water/WaterFountain.js +24 -0
- package/dist/components/water/WaterStream.d.ts +9 -0
- package/dist/components/water/WaterStream.d.ts.map +1 -0
- package/dist/components/water/WaterStream.js +24 -0
- package/dist/components/water/WaterWaterfall.d.ts +9 -0
- package/dist/components/water/WaterWaterfall.d.ts.map +1 -0
- package/dist/components/water/WaterWaterfall.js +24 -0
- package/dist/components/water/core/fountain.d.ts +70 -0
- package/dist/components/water/core/fountain.d.ts.map +1 -0
- package/dist/components/water/core/fountain.js +619 -0
- package/dist/components/water/core/stream.d.ts +72 -0
- package/dist/components/water/core/stream.d.ts.map +1 -0
- package/dist/components/water/core/stream.js +167 -0
- package/dist/components/water/core/waterfall.d.ts +78 -0
- package/dist/components/water/core/waterfall.d.ts.map +1 -0
- package/dist/components/water/core/waterfall.js +325 -0
- package/dist/components/water/index.d.ts +8 -0
- package/dist/components/water/index.d.ts.map +1 -1
- package/dist/components/water/index.js +7 -0
- package/dist/components/water/types.d.ts +3 -0
- package/dist/components/water/types.d.ts.map +1 -1
- package/dist/components/water/useWaterInstance.d.ts +43 -0
- package/dist/components/water/useWaterInstance.d.ts.map +1 -0
- package/dist/components/water/useWaterInstance.js +89 -0
- package/dist/components/water/waterCore.d.ts +179 -0
- package/dist/components/water/waterCore.d.ts.map +1 -0
- package/dist/components/water/waterCore.js +516 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +9 -1
- package/dist/hooks/index.d.ts +7 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +5 -2
- package/dist/hooks/useActionPatch.d.ts +23 -0
- package/dist/hooks/useActionPatch.d.ts.map +1 -0
- package/dist/hooks/useActionPatch.js +38 -0
- package/dist/hooks/useCloudSun.d.ts.map +1 -1
- package/dist/hooks/useCloudSun.js +3 -2
- package/dist/hooks/useCommittedInstance.d.ts +6 -0
- package/dist/hooks/useCommittedInstance.d.ts.map +1 -0
- package/dist/hooks/useCommittedInstance.js +42 -0
- package/dist/hooks/useConfigLive.d.ts +1 -1
- package/dist/hooks/useConfigLive.d.ts.map +1 -1
- package/dist/hooks/useContinuousEffects.d.ts.map +1 -1
- package/dist/hooks/useContinuousEffects.js +36 -20
- package/dist/hooks/useGridLayout.d.ts +7 -0
- package/dist/hooks/useGridLayout.d.ts.map +1 -0
- package/dist/hooks/useGridLayout.js +10 -0
- package/dist/hooks/useInteractionEffects.d.ts.map +1 -1
- package/dist/hooks/useInteractionEffects.js +48 -9
- package/dist/hooks/useLightLive.d.ts.map +1 -1
- package/dist/hooks/useLightLive.js +2 -0
- package/dist/hooks/useLinkStatus.d.ts +7 -0
- package/dist/hooks/useLinkStatus.d.ts.map +1 -0
- package/dist/hooks/useLinkStatus.js +20 -0
- package/dist/hooks/useMaterialApplication.d.ts +6 -1
- package/dist/hooks/useMaterialApplication.d.ts.map +1 -1
- package/dist/hooks/useMaterialApplication.js +7 -2
- package/dist/hooks/useOceanMaterial.d.ts +11 -1
- package/dist/hooks/useOceanMaterial.d.ts.map +1 -1
- package/dist/hooks/useOceanMaterial.js +185 -27
- package/dist/hooks/usePathAnimation.d.ts +8 -0
- package/dist/hooks/usePathAnimation.d.ts.map +1 -1
- package/dist/hooks/usePathAnimation.js +140 -59
- package/dist/hooks/useResolvedLayout.d.ts +10 -0
- package/dist/hooks/useResolvedLayout.d.ts.map +1 -0
- package/dist/hooks/useResolvedLayout.js +17 -0
- package/dist/hooks/useTimeLighting.d.ts +36 -0
- package/dist/hooks/useTimeLighting.d.ts.map +1 -0
- package/dist/hooks/useTimeLighting.js +67 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/operations/catalogue/shaderLayers.d.ts.map +1 -1
- package/dist/operations/catalogue/shaderLayers.js +26 -3
- package/dist/render/renderLive.d.ts +11 -0
- package/dist/render/renderLive.d.ts.map +1 -0
- package/dist/render/renderLive.js +57 -0
- package/dist/shaders/glsl/oceanCore.glsl.d.ts +2 -2
- package/dist/shaders/glsl/oceanCore.glsl.d.ts.map +1 -1
- package/dist/shaders/glsl/oceanCore.glsl.js +35 -5
- package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts +12 -0
- package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts.map +1 -1
- package/dist/shaders/ocean/OceanSurfaceMaterial.js +55 -7
- package/dist/shaders/terrain/TerrainMaterial.d.ts.map +1 -1
- package/dist/shaders/terrain/TerrainMaterial.js +28 -0
- package/dist/triggers/kinds/change.js +1 -1
- package/dist/utils/composerHostedPass.d.ts +74 -0
- package/dist/utils/composerHostedPass.d.ts.map +1 -0
- package/dist/utils/composerHostedPass.js +63 -0
- package/dist/utils/geometryScript.d.ts +17 -0
- package/dist/utils/geometryScript.d.ts.map +1 -0
- package/dist/utils/geometryScript.js +92 -0
- package/dist/utils/geometryScript.worker.d.ts +2 -0
- package/dist/utils/geometryScript.worker.d.ts.map +1 -0
- package/dist/utils/geometryScript.worker.js +42 -0
- package/dist/utils/groundRegistry.d.ts +11 -0
- package/dist/utils/groundRegistry.d.ts.map +1 -0
- package/dist/utils/groundRegistry.js +39 -0
- package/dist/utils/instancingSupport.d.ts +9 -0
- package/dist/utils/instancingSupport.d.ts.map +1 -0
- package/dist/utils/instancingSupport.js +13 -0
- package/dist/utils/landPatch.d.ts +26 -5
- package/dist/utils/landPatch.d.ts.map +1 -1
- package/dist/utils/landPatch.js +124 -9
- package/dist/utils/layoutPlacement.d.ts +30 -0
- package/dist/utils/layoutPlacement.d.ts.map +1 -0
- package/dist/utils/layoutPlacement.js +113 -0
- package/dist/utils/linkRegistry.d.ts +15 -0
- package/dist/utils/linkRegistry.d.ts.map +1 -0
- package/dist/utils/linkRegistry.js +11 -0
- package/dist/utils/shapes.d.ts +28 -2
- package/dist/utils/shapes.d.ts.map +1 -1
- package/dist/utils/shapes.js +32 -3
- package/dist/utils/sharedParts.d.ts +61 -0
- package/dist/utils/sharedParts.d.ts.map +1 -0
- package/dist/utils/sharedParts.js +268 -0
- package/dist/utils/svgAsset.d.ts +6 -0
- package/dist/utils/svgAsset.d.ts.map +1 -0
- package/dist/utils/svgAsset.js +46 -0
- package/dist/utils/waterLight.d.ts +29 -0
- package/dist/utils/waterLight.d.ts.map +1 -0
- package/dist/utils/waterLight.js +67 -0
- package/dist/utils/waterReflection.d.ts +185 -0
- package/dist/utils/waterReflection.d.ts.map +1 -0
- package/dist/utils/waterReflection.js +616 -0
- package/package.json +6 -4
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// urbanCore — the shared core of the urban family (type 'urban': building, block, road, streetFurniture, skyline, plaza, junction, traffic, signage, parkingLot, park, streetGrid)
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// ONE util imported by every urban component (emitted from Claude outputs/urban-lab/common.mjs —
|
|
5
|
+
// edit there and re-emit, so the lab and the repo stay the same code).
|
|
6
|
+
//
|
|
7
|
+
// What is in here and why:
|
|
8
|
+
// lamp list Street lamps carry no THREE light. Every lamp (a streetFurniture lamp, a
|
|
9
|
+
// road's own lamps, later lamp layouts) registers its world position, colour,
|
|
10
|
+
// radius and intensity in one registry. Surfaces the family owns (road, paving,
|
|
11
|
+
// facades) GATHER the lamps that reach their bounds into a bounded uniform array
|
|
12
|
+
// (MAX_LAMPS) and light themselves in their fragment shader. Adding a lamp
|
|
13
|
+
// recompiles nothing; the registry may hold 256+, a surface loops over <= 48.
|
|
14
|
+
// sun / night One sun block shared by every urban shader (direction, beam colour, sky and
|
|
15
|
+
// ground ambient) and a night factor derived from the sun's height; in the app
|
|
16
|
+
// the component fills it from useCloudSun, the lab from its hour slider.
|
|
17
|
+
// textures A texture set from the library ({ set, maps, tileSize }) -> colour / normal /
|
|
18
|
+
// roughness textures plus the colour map's linear mean, so a knob colour decides
|
|
19
|
+
// the albedo and the scan only adds grain (the terrain's rule).
|
|
20
|
+
// lamp fixture One generative lamp-post geometry used by streetFurniture (kind lamp) and by
|
|
21
|
+
// a road's own lamps, with a vertex flag for the emissive head.
|
|
22
|
+
import * as THREE from 'three';
|
|
23
|
+
export const MAX_LAMPS = 48;
|
|
24
|
+
export function mulberry32(a) {
|
|
25
|
+
return function () {
|
|
26
|
+
a |= 0;
|
|
27
|
+
a = (a + 0x6d2b79f5) | 0;
|
|
28
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
29
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
30
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export const clamp01 = (x) => Math.min(1, Math.max(0, x));
|
|
34
|
+
// ---------------------------------------------------------------------------- lamp registry
|
|
35
|
+
// A lamp: { id, position: THREE.Vector3 (world), color: THREE.Color, radius (world), intensity, on,
|
|
36
|
+
// baseRadius (the object's own metres), scale (the object's world scale) }.
|
|
37
|
+
// Components own their entries (register on mount, place them with placeLamps when their
|
|
38
|
+
// matrixWorld changes, unregister on unmount). `version` bumps on every change so a consumer knows to regather.
|
|
39
|
+
// A lamp scales with its object like everything else in the scene: its reach is baseRadius × scale
|
|
40
|
+
// and its light falls off over the object's own metres (distance ÷ scale), so a city scaled down to
|
|
41
|
+
// a model looks the same, only smaller.
|
|
42
|
+
export const lampRegistry = { lamps: [], version: 0 };
|
|
43
|
+
export function registerLamp(lamp) {
|
|
44
|
+
if (lamp.baseRadius === undefined)
|
|
45
|
+
lamp.baseRadius = lamp.radius;
|
|
46
|
+
if (lamp.scale === undefined)
|
|
47
|
+
lamp.scale = 1;
|
|
48
|
+
lampRegistry.lamps.push(lamp);
|
|
49
|
+
lampRegistry.version++;
|
|
50
|
+
return lamp;
|
|
51
|
+
}
|
|
52
|
+
export function unregisterLamp(lamp) {
|
|
53
|
+
const i = lampRegistry.lamps.indexOf(lamp);
|
|
54
|
+
if (i >= 0) {
|
|
55
|
+
lampRegistry.lamps.splice(i, 1);
|
|
56
|
+
lampRegistry.version++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function touchLamps() { lampRegistry.version++; }
|
|
60
|
+
/**
|
|
61
|
+
* Set lamps' colour / intensity / radius (undefined = leave it) and bump the lamp list ONLY when a
|
|
62
|
+
* value really changed. An object's apply runs every frame while any of its knobs animates (the
|
|
63
|
+
* config driver pushes the whole merged config); a bump per frame makes every lamp-lit surface in
|
|
64
|
+
* the scene re-gather and every lamp field re-bake each frame (a city fell to 7 fps). Returns
|
|
65
|
+
* whether anything changed.
|
|
66
|
+
*/
|
|
67
|
+
const _lampColor = new THREE.Color();
|
|
68
|
+
export function setLamps(lamps, color = undefined, intensity = undefined, radius = undefined) {
|
|
69
|
+
let changed = false;
|
|
70
|
+
if (color !== undefined)
|
|
71
|
+
_lampColor.set(color);
|
|
72
|
+
for (const L of lamps) {
|
|
73
|
+
if (color !== undefined && !L.color.equals(_lampColor)) {
|
|
74
|
+
L.color.copy(_lampColor);
|
|
75
|
+
changed = true;
|
|
76
|
+
}
|
|
77
|
+
if (intensity !== undefined && L.intensity !== intensity) {
|
|
78
|
+
L.intensity = intensity;
|
|
79
|
+
changed = true;
|
|
80
|
+
}
|
|
81
|
+
if (radius !== undefined && L.baseRadius !== radius) {
|
|
82
|
+
L.baseRadius = radius;
|
|
83
|
+
L.radius = radius * (L.scale || 1);
|
|
84
|
+
changed = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (changed)
|
|
88
|
+
touchLamps();
|
|
89
|
+
return changed;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Place lamps through an object's world matrix: each lamp at its local head (`locals[i]`), its reach
|
|
93
|
+
* and falloff scaled by the matrix's scale. Call when the matrix changed, then touchLamps().
|
|
94
|
+
*/
|
|
95
|
+
export function placeLamps(lamps, locals, matrixWorld) {
|
|
96
|
+
const s = matrixWorld.getMaxScaleOnAxis() || 1;
|
|
97
|
+
for (let i = 0; i < lamps.length; i++) {
|
|
98
|
+
const L = lamps[i];
|
|
99
|
+
L.position.copy(locals[i]).applyMatrix4(matrixWorld);
|
|
100
|
+
if (L.baseRadius === undefined)
|
|
101
|
+
L.baseRadius = L.radius;
|
|
102
|
+
L.scale = s;
|
|
103
|
+
L.radius = L.baseRadius * s;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
let _blankField = null;
|
|
107
|
+
function blankFieldTexture() {
|
|
108
|
+
if (!_blankField) {
|
|
109
|
+
_blankField = new THREE.DataTexture(new Uint8Array(4), 1, 1, THREE.RGBAFormat);
|
|
110
|
+
_blankField.needsUpdate = true;
|
|
111
|
+
}
|
|
112
|
+
return _blankField;
|
|
113
|
+
}
|
|
114
|
+
export function createLampUniforms() {
|
|
115
|
+
const pos = [], col = [];
|
|
116
|
+
const aim = [];
|
|
117
|
+
for (let i = 0; i < MAX_LAMPS; i++) {
|
|
118
|
+
pos.push(new THREE.Vector4(0, 0, 0, 1));
|
|
119
|
+
col.push(new THREE.Vector4(1, 1, 1, 0));
|
|
120
|
+
aim.push(new THREE.Vector4(0, 0, 0, 0));
|
|
121
|
+
}
|
|
122
|
+
// uLampTop: the highest point any gathered lamp can reach (world y); fragments above it skip the loop.
|
|
123
|
+
// uLampField*: a baked top-down lamp texture (createLampField) used INSTEAD of the list when on.
|
|
124
|
+
// uLampAims: per lamp (aim direction xyz, cos of the beam's outer half-angle); w > 0 a projector — an
|
|
125
|
+
// aimed long-throw floodlight (stadium towers); w <= 0 a street lamp (an omni pool under the head), -w
|
|
126
|
+
// its object's world scale (its falloff is over the object's own metres).
|
|
127
|
+
return {
|
|
128
|
+
uLamps: { value: pos }, uLampColors: { value: col }, uLampAims: { value: aim }, uLampCount: { value: 0 }, uLampTop: { value: 0 },
|
|
129
|
+
uLampField: { value: blankFieldTexture() }, uLampFieldRect: { value: new THREE.Vector4(0, 0, 1, 1) }, uLampFieldOn: { value: 0 },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const _d = new THREE.Vector3(), _a = new THREE.Vector3();
|
|
133
|
+
/**
|
|
134
|
+
* Fill a surface's lamp uniforms with the registry lamps whose pool reaches `bounds`
|
|
135
|
+
* (a world Box3), nearest first when more than MAX_LAMPS qualify. Positions are WORLD
|
|
136
|
+
* space, like the shaders' vP (pass invWorld only for a shader that lights in local space).
|
|
137
|
+
*/
|
|
138
|
+
export function gatherLamps(u, bounds, invWorld = null) {
|
|
139
|
+
const list = lampRegistry.lamps;
|
|
140
|
+
const hits = [];
|
|
141
|
+
for (let i = 0; i < list.length; i++) {
|
|
142
|
+
const L = list[i];
|
|
143
|
+
if (!L.on)
|
|
144
|
+
continue;
|
|
145
|
+
bounds.clampPoint(L.position, _d);
|
|
146
|
+
const dist = _d.distanceTo(L.position);
|
|
147
|
+
if (dist < L.radius)
|
|
148
|
+
hits.push([dist, L]);
|
|
149
|
+
}
|
|
150
|
+
if (hits.length > MAX_LAMPS)
|
|
151
|
+
hits.sort((a, b) => a[0] - b[0]);
|
|
152
|
+
const n = Math.min(hits.length, MAX_LAMPS);
|
|
153
|
+
let top = -1e9;
|
|
154
|
+
for (let i = 0; i < n; i++) {
|
|
155
|
+
const L = hits[i][1];
|
|
156
|
+
_d.copy(L.position);
|
|
157
|
+
if (invWorld)
|
|
158
|
+
_d.applyMatrix4(invWorld);
|
|
159
|
+
u.uLamps.value[i].set(_d.x, _d.y, _d.z, L.radius);
|
|
160
|
+
u.uLampColors.value[i].set(L.color.r, L.color.g, L.color.b, L.intensity);
|
|
161
|
+
if (u.uLampAims) {
|
|
162
|
+
if (L.aim) {
|
|
163
|
+
_a.copy(L.aim);
|
|
164
|
+
if (invWorld)
|
|
165
|
+
_a.transformDirection(invWorld);
|
|
166
|
+
u.uLampAims.value[i].set(_a.x, _a.y, _a.z, L.spread || 0.8);
|
|
167
|
+
}
|
|
168
|
+
else
|
|
169
|
+
u.uLampAims.value[i].set(0, 0, 0, -(L.scale || 1));
|
|
170
|
+
}
|
|
171
|
+
top = Math.max(top, _d.y + L.radius);
|
|
172
|
+
}
|
|
173
|
+
u.uLampCount.value = n;
|
|
174
|
+
u.uLampTop.value = n ? top : -1e9;
|
|
175
|
+
return n;
|
|
176
|
+
}
|
|
177
|
+
// ---------------------------------------------------------------------------- lamp field
|
|
178
|
+
// For a large flat set of surfaces (a whole street grid) a per-surface list of 48 lamps does
|
|
179
|
+
// not fit, and it forces one material per piece. A lamp field is the lamps' light on the
|
|
180
|
+
// ground, baked top-down into one texture over the set's world rectangle: every surface
|
|
181
|
+
// shares it, so the pieces can merge into a few meshes, and the fragment pays one fetch
|
|
182
|
+
// instead of a 48-lamp loop. Stored sqrt-encoded in RGBA8 (fine in the dark tails, no
|
|
183
|
+
// float-filtering extension needed). Walls get a share by their normal (see lampLight).
|
|
184
|
+
const FIELD_MAX = 768, FIELD_TEXEL = 0.5, FIELD_RANGE = 4; // max side, finest metres per texel, stored range
|
|
185
|
+
export function createLampField() {
|
|
186
|
+
const texture = new THREE.DataTexture(new Uint8Array(4), 1, 1, THREE.RGBAFormat);
|
|
187
|
+
texture.magFilter = THREE.LinearFilter;
|
|
188
|
+
texture.minFilter = THREE.LinearFilter;
|
|
189
|
+
texture.generateMipmaps = false;
|
|
190
|
+
texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;
|
|
191
|
+
return { texture, rect: new THREE.Vector4(0, 0, 1, 1), acc: null, w: 0, h: 0 };
|
|
192
|
+
}
|
|
193
|
+
/** Point a material's lamp uniforms at a field (the uniforms take the field's texture and rect objects). */
|
|
194
|
+
export function attachLampField(u, field) {
|
|
195
|
+
if (!u || !u.uLampField)
|
|
196
|
+
return;
|
|
197
|
+
u.uLampField.value = field.texture;
|
|
198
|
+
u.uLampFieldRect.value = field.rect;
|
|
199
|
+
u.uLampFieldOn.value = 1;
|
|
200
|
+
}
|
|
201
|
+
/** Bake every registry lamp whose pool reaches `bounds` (world Box3) into the field. Same falloff as lampLight, receiver facing up. */
|
|
202
|
+
export function bakeLampField(field, bounds) {
|
|
203
|
+
const x0 = bounds.min.x - 2, z0 = bounds.min.z - 2;
|
|
204
|
+
const ex = bounds.max.x - bounds.min.x + 4, ez = bounds.max.z - bounds.min.z + 4;
|
|
205
|
+
// the finest texel follows the lamps' scale (a city scaled down keeps its pools sharp)
|
|
206
|
+
let sMin = Infinity;
|
|
207
|
+
for (const L of lampRegistry.lamps)
|
|
208
|
+
if (L.on)
|
|
209
|
+
sMin = Math.min(sMin, L.scale || 1);
|
|
210
|
+
const texel = Math.max(FIELD_TEXEL * (Number.isFinite(sMin) ? sMin : 1), Math.max(ex, ez) / FIELD_MAX);
|
|
211
|
+
const w = Math.max(2, Math.ceil(ex / texel)), h = Math.max(2, Math.ceil(ez / texel));
|
|
212
|
+
if (!field.acc || field.w !== w || field.h !== h) {
|
|
213
|
+
field.acc = new Float32Array(w * h * 3);
|
|
214
|
+
field.texture.dispose();
|
|
215
|
+
field.texture.image = { data: new Uint8Array(w * h * 4), width: w, height: h };
|
|
216
|
+
field.w = w;
|
|
217
|
+
field.h = h;
|
|
218
|
+
}
|
|
219
|
+
else
|
|
220
|
+
field.acc.fill(0);
|
|
221
|
+
// Texel centres sit at x0 + (i + 0.5) * texel; the shader maps world xz to uv with the same rect.
|
|
222
|
+
field.rect.set(x0, z0, w * texel, h * texel);
|
|
223
|
+
const acc = field.acc, gy = bounds.min.y + 0.05;
|
|
224
|
+
for (const L of lampRegistry.lamps) {
|
|
225
|
+
if (!L.on || L.intensity <= 0)
|
|
226
|
+
continue;
|
|
227
|
+
const p = L.position, R = L.radius;
|
|
228
|
+
if (p.x < x0 - R || p.z < z0 - R || p.x > x0 + w * texel + R || p.z > z0 + h * texel + R)
|
|
229
|
+
continue;
|
|
230
|
+
const i0 = Math.max(0, Math.floor((p.x - R - x0) / texel)), i1 = Math.min(w - 1, Math.ceil((p.x + R - x0) / texel));
|
|
231
|
+
const j0 = Math.max(0, Math.floor((p.z - R - z0) / texel)), j1 = Math.min(h - 1, Math.ceil((p.z + R - z0) / texel));
|
|
232
|
+
const dy = p.y - gy, cr = L.color.r * L.intensity * 4, cg = L.color.g * L.intensity * 4, cb = L.color.b * L.intensity * 4;
|
|
233
|
+
const fs = 0.06 / ((L.scale || 1) * (L.scale || 1)); // falloff over the object's own metres
|
|
234
|
+
const A = L.aim, c0 = L.spread || 0.8, c1 = c0 + (1 - c0) * 0.45;
|
|
235
|
+
for (let j = j0; j <= j1; j++) {
|
|
236
|
+
const dz = p.z - (z0 + (j + 0.5) * texel);
|
|
237
|
+
for (let i = i0; i <= i1; i++) {
|
|
238
|
+
const dx = p.x - (x0 + (i + 0.5) * texel);
|
|
239
|
+
const r2 = dx * dx + dy * dy + dz * dz, r = Math.sqrt(r2), x = r / R;
|
|
240
|
+
if (x >= 1)
|
|
241
|
+
continue;
|
|
242
|
+
let win = 1 - x * x * x * x;
|
|
243
|
+
win *= win;
|
|
244
|
+
const ly = dy / Math.max(r, 1e-3);
|
|
245
|
+
if (A) {
|
|
246
|
+
const dc = -(A.x * dx + A.y * dy + A.z * dz) / Math.max(r, 1e-3);
|
|
247
|
+
const ts = clamp01((dc - c0) / (c1 - c0)), spot = ts * ts * (3 - 2 * ts);
|
|
248
|
+
const kp = win * spot * (0.35 + 0.65 * Math.max(ly, 0)) / (1 + x * x * 3);
|
|
249
|
+
if (kp > 0) {
|
|
250
|
+
const o = (j * w + i) * 3;
|
|
251
|
+
acc[o] += cr * kp;
|
|
252
|
+
acc[o + 1] += cg * kp;
|
|
253
|
+
acc[o + 2] += cb * kp;
|
|
254
|
+
}
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const t = clamp01((ly - 0.1) / 0.6), cone = t * t * (3 - 2 * t);
|
|
258
|
+
const k = win * Math.max(ly, 0) * cone / (1 + r2 * fs);
|
|
259
|
+
if (k <= 0)
|
|
260
|
+
continue;
|
|
261
|
+
const o = (j * w + i) * 3;
|
|
262
|
+
acc[o] += cr * k;
|
|
263
|
+
acc[o + 1] += cg * k;
|
|
264
|
+
acc[o + 2] += cb * k;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const data = field.texture.image.data;
|
|
269
|
+
for (let i = 0, n = w * h; i < n; i++) {
|
|
270
|
+
data[i * 4] = Math.round(255 * Math.sqrt(Math.min(acc[i * 3] / FIELD_RANGE, 1)));
|
|
271
|
+
data[i * 4 + 1] = Math.round(255 * Math.sqrt(Math.min(acc[i * 3 + 1] / FIELD_RANGE, 1)));
|
|
272
|
+
data[i * 4 + 2] = Math.round(255 * Math.sqrt(Math.min(acc[i * 3 + 2] / FIELD_RANGE, 1)));
|
|
273
|
+
data[i * 4 + 3] = 255;
|
|
274
|
+
}
|
|
275
|
+
field.texture.needsUpdate = true;
|
|
276
|
+
}
|
|
277
|
+
// ---------------------------------------------------------------------------- sun block
|
|
278
|
+
export function createSunUniforms() {
|
|
279
|
+
return {
|
|
280
|
+
uSunDir: { value: new THREE.Vector3(0.35, 0.6, 0.72).normalize() },
|
|
281
|
+
uSunColor: { value: new THREE.Color(1.0, 0.95, 0.88) },
|
|
282
|
+
uSkyAmbient: { value: new THREE.Color(0.35, 0.42, 0.55) },
|
|
283
|
+
uGroundAmbient: { value: new THREE.Color(0.18, 0.16, 0.14) },
|
|
284
|
+
uNight: { value: 0 },
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
/** Night factor from the sun: lights come on through civil dusk (sun 2 deg above to 6 deg below). */
|
|
288
|
+
export function nightFromSun(sunDirY) {
|
|
289
|
+
return clamp01((0.035 - sunDirY) / (0.035 + 0.105));
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* sun = { direction, color, skyAmbient, groundAmbient } (the useCloudSun state) and the
|
|
293
|
+
* object's lighting mode: 'auto' follows the sun, 'day' / 'night' force it.
|
|
294
|
+
*/
|
|
295
|
+
export function applySun(u, sun, lighting = 'auto') {
|
|
296
|
+
u.uSunDir.value.copy(sun.direction);
|
|
297
|
+
u.uSunColor.value.copy(sun.color);
|
|
298
|
+
u.uSkyAmbient.value.copy(sun.skyAmbient);
|
|
299
|
+
u.uGroundAmbient.value.copy(sun.groundAmbient);
|
|
300
|
+
const n = lighting === 'night' ? 1 : lighting === 'day' ? 0 : nightFromSun(sun.direction.y);
|
|
301
|
+
u.uNight.value = n;
|
|
302
|
+
}
|
|
303
|
+
// ---------------------------------------------------------------------------- GLSL chunks
|
|
304
|
+
export const GLSL_COMMON = /* glsl */ `
|
|
305
|
+
uniform vec3 uSunDir;
|
|
306
|
+
uniform vec3 uSunColor;
|
|
307
|
+
uniform vec3 uSkyAmbient;
|
|
308
|
+
uniform vec3 uGroundAmbient;
|
|
309
|
+
uniform float uNight;
|
|
310
|
+
uniform vec4 uLamps[${MAX_LAMPS}];
|
|
311
|
+
uniform vec4 uLampColors[${MAX_LAMPS}];
|
|
312
|
+
uniform vec4 uLampAims[${MAX_LAMPS}];
|
|
313
|
+
uniform int uLampCount;
|
|
314
|
+
uniform float uLampTop;
|
|
315
|
+
uniform sampler2D uLampField;
|
|
316
|
+
uniform vec4 uLampFieldRect;
|
|
317
|
+
uniform float uLampFieldOn;
|
|
318
|
+
|
|
319
|
+
float hash11(float p) { p = fract(p * 0.1031); p *= p + 33.33; p *= p + p; return fract(p); }
|
|
320
|
+
float hash21(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * 0.1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); }
|
|
321
|
+
float vnoise21(vec2 p) { vec2 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f); return mix(mix(hash21(i), hash21(i + vec2(1.0, 0.0)), f.x), mix(hash21(i + vec2(0.0, 1.0)), hash21(i + vec2(1.0, 1.0)), f.x), f.y); }
|
|
322
|
+
float hash31(vec3 p) { p = fract(p * 0.1031); p += dot(p, p.zyx + 31.32); return fract((p.x + p.y) * p.z); }
|
|
323
|
+
|
|
324
|
+
// Irradiance from the gathered lamps. Each lamp is a downward-facing head: a smooth
|
|
325
|
+
// window to its radius, inverse-square falloff softened near the head, a cosine cone so
|
|
326
|
+
// it pools on the ground and only grazes walls, Lambert on the receiver. Scaled by the
|
|
327
|
+
// night factor so pools fade in with dusk.
|
|
328
|
+
vec3 lampLight(vec3 P, vec3 N) {
|
|
329
|
+
vec3 acc = vec3(0.0);
|
|
330
|
+
if (uNight < 0.002) return acc;
|
|
331
|
+
// A baked lamp field (a street grid): the ground's light at this xz, decoded; walls take a share.
|
|
332
|
+
if (uLampFieldOn > 0.5) {
|
|
333
|
+
vec3 e = texture2D(uLampField, (P.xz - uLampFieldRect.xy) / uLampFieldRect.zw).rgb;
|
|
334
|
+
return e * e * ${FIELD_RANGE}.0 * (0.3 + 0.7 * max(N.y, 0.0)) * uNight;
|
|
335
|
+
}
|
|
336
|
+
// Above the reach of every gathered lamp (most of a facade) the loop is skipped.
|
|
337
|
+
if (P.y > uLampTop) return acc;
|
|
338
|
+
for (int i = 0; i < ${MAX_LAMPS}; i++) {
|
|
339
|
+
if (i >= uLampCount) break;
|
|
340
|
+
vec4 L = uLamps[i];
|
|
341
|
+
vec3 d = L.xyz - P;
|
|
342
|
+
float r2 = dot(d, d);
|
|
343
|
+
float r = sqrt(r2);
|
|
344
|
+
vec3 l = d / max(r, 1e-3);
|
|
345
|
+
float x = r / L.w;
|
|
346
|
+
float win = clamp(1.0 - x * x * x * x, 0.0, 1.0);
|
|
347
|
+
win *= win;
|
|
348
|
+
float ndl = max(dot(N, l), 0.0);
|
|
349
|
+
vec4 A = uLampAims[i];
|
|
350
|
+
if (A.w > 0.0) {
|
|
351
|
+
// a projector: an aimed beam (soft edge from the outer cone in), throw nearly flat over its
|
|
352
|
+
// reach, so a few towers light a whole stadium evenly; walls facing it take light too
|
|
353
|
+
float spot = smoothstep(A.w, mix(A.w, 1.0, 0.45), dot(-l, A.xyz));
|
|
354
|
+
acc += uLampColors[i].rgb * (uLampColors[i].a * win * spot * (0.35 + 0.65 * ndl) * 4.0 / (1.0 + x * x * 3.0));
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
float cone = smoothstep(0.1, 0.7, l.y);
|
|
358
|
+
float ls = A.w < 0.0 ? -A.w : 1.0; // the lamp's object scale: falloff over its own metres
|
|
359
|
+
// Intensity 1 puts ~0.75 irradiance on the ground under an 8 m head, a tenth of that
|
|
360
|
+
// 14 m along the kerb: street-lamp contrast, not a wash.
|
|
361
|
+
acc += uLampColors[i].rgb * (uLampColors[i].a * win * ndl * cone * 4.0 / (1.0 + r2 * 0.06 / (ls * ls)));
|
|
362
|
+
}
|
|
363
|
+
return acc * uNight;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Sun + sky + ground + lamps on a rough dielectric. Radiance units: a 0.5 albedo at noon
|
|
367
|
+
// lands near 0.9, so NoToneMapping keeps it under 1.
|
|
368
|
+
vec3 shadeSurface(vec3 albedo, vec3 N, vec3 P, vec3 V, float rough, float ao) {
|
|
369
|
+
float ndl = max(dot(N, uSunDir), 0.0);
|
|
370
|
+
vec3 sky = uSkyAmbient * (0.55 + 0.45 * N.y);
|
|
371
|
+
vec3 gnd = uGroundAmbient * (0.5 - 0.5 * N.y);
|
|
372
|
+
vec3 col = albedo * (uSunColor * ndl + (sky + gnd) * ao);
|
|
373
|
+
vec3 H = normalize(uSunDir + V);
|
|
374
|
+
float spec = pow(max(dot(N, H), 0.0), mix(90.0, 6.0, rough)) * (1.0 - rough) * 0.35 * ndl;
|
|
375
|
+
col += uSunColor * spec;
|
|
376
|
+
col += albedo * lampLight(P, N);
|
|
377
|
+
return col;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Box-filtered coverage of the band [a, b] over this pixel's footprint on x: exact
|
|
381
|
+
// anti-aliasing at the edge AND the right average when the band is thinner than a pixel
|
|
382
|
+
// (a far facade blends to the mean of glass and spandrel instead of sparkling).
|
|
383
|
+
float bandAA(float x, float a, float b) {
|
|
384
|
+
float w = max(fwidth(x), 1e-5);
|
|
385
|
+
float lo = max(a, x - 0.5 * w), hi = min(b, x + 0.5 * w);
|
|
386
|
+
return clamp((hi - lo) / w, 0.0, 1.0);
|
|
387
|
+
}
|
|
388
|
+
`;
|
|
389
|
+
export const GLSL_FOG_PARS = /* glsl */ `#include <fog_pars_fragment>`;
|
|
390
|
+
export const GLSL_FOG_VERT_PARS = /* glsl */ `#include <fog_pars_vertex>`;
|
|
391
|
+
/** Shared tail: output in the viewer's convention (NoToneMapping, sRGB out, scene fog). */
|
|
392
|
+
export const GLSL_OUT = /* glsl */ `
|
|
393
|
+
gl_FragColor = vec4(col, 1.0);
|
|
394
|
+
#include <tonemapping_fragment>
|
|
395
|
+
#include <colorspace_fragment>
|
|
396
|
+
#include <fog_fragment>
|
|
397
|
+
`;
|
|
398
|
+
// ---------------------------------------------------------------------------- textures
|
|
399
|
+
const _texCache = new Map();
|
|
400
|
+
/**
|
|
401
|
+
* maps: { color?, normal?, roughness?, ao? } URLs (a library set's maps). Returns
|
|
402
|
+
* { color, normal, roughness, ao, mean: THREE.Color, ready: Promise }. Textures repeat;
|
|
403
|
+
* the colour map is sRGB. `mean` is the colour map's linear mean, filled when it loads.
|
|
404
|
+
*/
|
|
405
|
+
export function loadTextureSet(maps) {
|
|
406
|
+
const key = JSON.stringify(maps || {});
|
|
407
|
+
if (_texCache.has(key))
|
|
408
|
+
return _texCache.get(key);
|
|
409
|
+
const loader = new THREE.TextureLoader();
|
|
410
|
+
loader.setCrossOrigin('anonymous');
|
|
411
|
+
const out = { color: null, normal: null, roughness: null, ao: null, mean: new THREE.Color(0.5, 0.5, 0.5), ready: null };
|
|
412
|
+
let pending = 4, finish = null;
|
|
413
|
+
out.ready = new Promise((resolve) => { finish = resolve; });
|
|
414
|
+
const done = () => { if (--pending === 0 && finish)
|
|
415
|
+
finish(out); };
|
|
416
|
+
const prep = (t, srgb) => {
|
|
417
|
+
t.wrapS = t.wrapT = THREE.RepeatWrapping;
|
|
418
|
+
t.anisotropy = 8;
|
|
419
|
+
if (srgb)
|
|
420
|
+
t.colorSpace = THREE.SRGBColorSpace;
|
|
421
|
+
return t;
|
|
422
|
+
};
|
|
423
|
+
const load = (url, srgb, onLoad) => {
|
|
424
|
+
if (!url) {
|
|
425
|
+
done();
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
loader.load(url, (t) => { onLoad(prep(t, srgb)); done(); }, undefined, () => done());
|
|
429
|
+
};
|
|
430
|
+
load(maps && maps.color, true, (t) => {
|
|
431
|
+
out.color = t;
|
|
432
|
+
const image = t.image;
|
|
433
|
+
if (image && typeof document !== 'undefined') {
|
|
434
|
+
// Linear mean of the colour map (16 x 16 downsample), so albedo = knob colour * tex / mean.
|
|
435
|
+
const c = document.createElement('canvas');
|
|
436
|
+
c.width = c.height = 16;
|
|
437
|
+
const g = c.getContext('2d');
|
|
438
|
+
try {
|
|
439
|
+
g.drawImage(image, 0, 0, 16, 16);
|
|
440
|
+
const d = g.getImageData(0, 0, 16, 16).data;
|
|
441
|
+
let r = 0, gg = 0, b = 0;
|
|
442
|
+
const lin = (v) => { v /= 255; return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); };
|
|
443
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
444
|
+
r += lin(d[i]);
|
|
445
|
+
gg += lin(d[i + 1]);
|
|
446
|
+
b += lin(d[i + 2]);
|
|
447
|
+
}
|
|
448
|
+
const n = d.length / 4;
|
|
449
|
+
out.mean.setRGB(Math.max(r / n, 0.02), Math.max(gg / n, 0.02), Math.max(b / n, 0.02));
|
|
450
|
+
}
|
|
451
|
+
catch (e) { /* cross-origin without CORS: keep the neutral mean */ }
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
load(maps && maps.normal, false, (t) => { out.normal = t; });
|
|
455
|
+
load(maps && maps.roughness, false, (t) => { out.roughness = t; });
|
|
456
|
+
load(maps && maps.ao, false, (t) => { out.ao = t; });
|
|
457
|
+
_texCache.set(key, out);
|
|
458
|
+
return out;
|
|
459
|
+
}
|
|
460
|
+
const _white = (() => { const t = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1); t.needsUpdate = true; return t; })();
|
|
461
|
+
const _flatNormal = (() => { const t = new THREE.DataTexture(new Uint8Array([128, 128, 255, 255]), 1, 1); t.needsUpdate = true; return t; })();
|
|
462
|
+
export function createTextureUniforms(prefix) {
|
|
463
|
+
const u = {};
|
|
464
|
+
u[prefix + 'Map'] = { value: _white };
|
|
465
|
+
u[prefix + 'Normal'] = { value: _flatNormal };
|
|
466
|
+
u[prefix + 'Rough'] = { value: _white };
|
|
467
|
+
u[prefix + 'Mean'] = { value: new THREE.Color(1, 1, 1) };
|
|
468
|
+
u[prefix + 'Tile'] = { value: 2 };
|
|
469
|
+
u[prefix + 'Has'] = { value: 0 };
|
|
470
|
+
u[prefix + 'NormalStrength'] = { value: 1 };
|
|
471
|
+
return u;
|
|
472
|
+
}
|
|
473
|
+
/** Point a texture-uniform block at a loaded set (or clear it when `set` is null). */
|
|
474
|
+
export function applyTextureSet(u, prefix, set, tile = 2, normalStrength = 1) {
|
|
475
|
+
const has = !!(set && set.color);
|
|
476
|
+
u[prefix + 'Has'].value = has ? 1 : 0;
|
|
477
|
+
u[prefix + 'Map'].value = has ? set.color : _white;
|
|
478
|
+
u[prefix + 'Normal'].value = set && set.normal ? set.normal : _flatNormal;
|
|
479
|
+
u[prefix + 'Rough'].value = set && set.roughness ? set.roughness : _white;
|
|
480
|
+
u[prefix + 'Mean'].value.copy(has ? set.mean : new THREE.Color(1, 1, 1));
|
|
481
|
+
u[prefix + 'Tile'].value = Math.max(0.05, tile || 2);
|
|
482
|
+
u[prefix + 'NormalStrength'].value = normalStrength;
|
|
483
|
+
}
|
|
484
|
+
/** GLSL for one texture block: sampleTex_<prefix>(uv, N, T, B) -> albedo multiplier, bumped normal, roughness. */
|
|
485
|
+
export function glslTextureBlock(prefix) {
|
|
486
|
+
return /* glsl */ `
|
|
487
|
+
uniform sampler2D ${prefix}Map;
|
|
488
|
+
uniform sampler2D ${prefix}Normal;
|
|
489
|
+
uniform sampler2D ${prefix}Rough;
|
|
490
|
+
uniform vec3 ${prefix}Mean;
|
|
491
|
+
uniform float ${prefix}Tile;
|
|
492
|
+
uniform float ${prefix}Has;
|
|
493
|
+
uniform float ${prefix}NormalStrength;
|
|
494
|
+
// albedoMul: texture / its mean (1.0 without a texture), so the knob colour stays the average.
|
|
495
|
+
vec3 ${prefix}Sample(vec2 uvM, inout vec3 N, vec3 T, vec3 B, out float rough) {
|
|
496
|
+
vec2 uv = uvM / ${prefix}Tile;
|
|
497
|
+
vec3 c = texture2D(${prefix}Map, uv).rgb;
|
|
498
|
+
vec3 albedoMul = mix(vec3(1.0), min(c / ${prefix}Mean, vec3(4.0)), ${prefix}Has);
|
|
499
|
+
vec3 nm = texture2D(${prefix}Normal, uv).xyz * 2.0 - 1.0;
|
|
500
|
+
// Relief fades out as the texture minifies (a bumped normal at sub-texel scale is just speckle).
|
|
501
|
+
float px = fwidth(uv.x) + fwidth(uv.y);
|
|
502
|
+
nm.xy *= ${prefix}NormalStrength * ${prefix}Has * (1.0 - smoothstep(0.004, 0.03, px));
|
|
503
|
+
N = normalize(T * nm.x + B * nm.y + N * max(nm.z, 0.2));
|
|
504
|
+
rough = mix(0.85, texture2D(${prefix}Rough, uv).r, ${prefix}Has);
|
|
505
|
+
return albedoMul;
|
|
506
|
+
}`;
|
|
507
|
+
}
|
|
508
|
+
// ---------------------------------------------------------------------------- paths and ribbons
|
|
509
|
+
/** Sample the polyline: [{ p: Vector3, t: Vector3 (tangent), n: Vector3 (right), s }] */
|
|
510
|
+
/**
|
|
511
|
+
* Sample a polyline ([x, z] metres) into frames every ~1.5 m: [{ p, t (tangent), n (right), s }].
|
|
512
|
+
* `curve` 0 keeps the corners, 1 rounds the path (Catmull-Rom tension); `closed` loops it.
|
|
513
|
+
*/
|
|
514
|
+
export function samplePath(points, curve = 0.5, closed = false) {
|
|
515
|
+
const pts = (points || []).filter((p) => Array.isArray(p) && p.length >= 2).map((p) => new THREE.Vector3(p[0], 0, p[1]));
|
|
516
|
+
if (pts.length < 2)
|
|
517
|
+
return [];
|
|
518
|
+
let path;
|
|
519
|
+
if (pts.length === 2 && !closed)
|
|
520
|
+
path = new THREE.LineCurve3(pts[0], pts[1]);
|
|
521
|
+
else
|
|
522
|
+
path = new THREE.CatmullRomCurve3(pts, closed, 'catmullrom', 0.5 * Math.min(1, Math.max(0, curve)));
|
|
523
|
+
const len = path.getLength();
|
|
524
|
+
const n = Math.max(2, Math.ceil(len / 1.5));
|
|
525
|
+
const spaced = path.getSpacedPoints(n);
|
|
526
|
+
const out = [];
|
|
527
|
+
let s = 0;
|
|
528
|
+
for (let i = 0; i <= n; i++) {
|
|
529
|
+
const p = spaced[i];
|
|
530
|
+
if (i > 0)
|
|
531
|
+
s += p.distanceTo(spaced[i - 1]);
|
|
532
|
+
const a = closed ? spaced[(i - 1 + n) % n] : spaced[Math.max(0, i - 1)], b = closed ? spaced[(i + 1) % n] : spaced[Math.min(n, i + 1)];
|
|
533
|
+
const t = new THREE.Vector3().subVectors(b, a);
|
|
534
|
+
t.y = 0;
|
|
535
|
+
t.normalize();
|
|
536
|
+
const nrm = new THREE.Vector3(t.z, 0, -t.x); // right of travel
|
|
537
|
+
out.push({ p, t, n: nrm, s });
|
|
538
|
+
}
|
|
539
|
+
return out;
|
|
540
|
+
}
|
|
541
|
+
// ---------------------------------------------------------------------------- geometry
|
|
542
|
+
/** A ribbon between offsets ta..tb (across) at heights ya..yb along the sampled path; uv = (s, t) or (s, y) for faces. */
|
|
543
|
+
export function ribbon(samples, ta, tb, ya, yb, vertical = false, facing = 1) {
|
|
544
|
+
// vertical: a wall at t = ta (= tb) from ya to yb; `facing` +1 = normal away from the road (+n side), -1 = toward it.
|
|
545
|
+
const n = samples.length;
|
|
546
|
+
const pos = new Float32Array(n * 2 * 3), nor = new Float32Array(n * 2 * 3), st = new Float32Array(n * 2 * 2);
|
|
547
|
+
const idx = [];
|
|
548
|
+
const A = new THREE.Vector3(), Bv = new THREE.Vector3(), C = new THREE.Vector3(), fn = new THREE.Vector3(), want = new THREE.Vector3();
|
|
549
|
+
for (let i = 0; i < n; i++) {
|
|
550
|
+
const S = samples[i];
|
|
551
|
+
const ax = S.p.x + S.n.x * ta, az = S.p.z + S.n.z * ta;
|
|
552
|
+
const bx = S.p.x + S.n.x * tb, bz = S.p.z + S.n.z * tb;
|
|
553
|
+
pos.set([ax, ya, az, bx, yb, bz], i * 6);
|
|
554
|
+
if (vertical) {
|
|
555
|
+
const sgn = (Math.sign(ta) || 1) * facing;
|
|
556
|
+
nor.set([S.n.x * sgn, 0, S.n.z * sgn, S.n.x * sgn, 0, S.n.z * sgn], i * 6);
|
|
557
|
+
st.set([S.s, ya, S.s, yb], i * 4);
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
nor.set([0, 1, 0, 0, 1, 0], i * 6);
|
|
561
|
+
st.set([S.s, ta, S.s, tb], i * 4);
|
|
562
|
+
}
|
|
563
|
+
if (i < n - 1) {
|
|
564
|
+
const a = i * 2;
|
|
565
|
+
// Wind each quad so its geometric normal agrees with the stored one.
|
|
566
|
+
idx.push(a, a + 2, a + 1, a + 1, a + 2, a + 3);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
// Fix the winding per strip from the first quad.
|
|
570
|
+
if (n > 1) {
|
|
571
|
+
A.fromArray(pos, 0);
|
|
572
|
+
Bv.fromArray(pos, 6);
|
|
573
|
+
C.fromArray(pos, 3);
|
|
574
|
+
fn.subVectors(Bv, A).cross(C.clone().sub(A));
|
|
575
|
+
want.fromArray(nor, 0);
|
|
576
|
+
if (fn.dot(want) < 0)
|
|
577
|
+
for (let k = 0; k < idx.length; k += 3) {
|
|
578
|
+
const t = idx[k + 1];
|
|
579
|
+
idx[k + 1] = idx[k + 2];
|
|
580
|
+
idx[k + 2] = t;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const g = new THREE.BufferGeometry();
|
|
584
|
+
g.setAttribute('position', new THREE.BufferAttribute(pos, 3));
|
|
585
|
+
g.setAttribute('normal', new THREE.BufferAttribute(nor, 3));
|
|
586
|
+
g.setAttribute('aUV', new THREE.BufferAttribute(st, 2));
|
|
587
|
+
g.setIndex(idx);
|
|
588
|
+
return g;
|
|
589
|
+
}
|
|
590
|
+
// ---------------------------------------------------------------------------- geometry merge
|
|
591
|
+
/**
|
|
592
|
+
* Merge indexed or non-indexed geometries into one (position, normal + the named float/vec
|
|
593
|
+
* attributes, missing ones filled with `fill[name]` or 0). Disposes the inputs.
|
|
594
|
+
*/
|
|
595
|
+
export function mergeGeometries(list, attrs = [], fill = undefined) {
|
|
596
|
+
let count = 0;
|
|
597
|
+
for (const g of list)
|
|
598
|
+
count += g.getAttribute('position').count;
|
|
599
|
+
const pos = new Float32Array(count * 3), nor = new Float32Array(count * 3);
|
|
600
|
+
const extra = {};
|
|
601
|
+
for (const [name, size] of attrs)
|
|
602
|
+
extra[name] = new Float32Array(count * size);
|
|
603
|
+
const idx = [];
|
|
604
|
+
let o = 0;
|
|
605
|
+
for (const g of list) {
|
|
606
|
+
const P = g.getAttribute('position'), N = g.getAttribute('normal');
|
|
607
|
+
pos.set(P.array, o * 3);
|
|
608
|
+
nor.set(N.array, o * 3);
|
|
609
|
+
for (const [name, size] of attrs) {
|
|
610
|
+
const a = g.getAttribute(name);
|
|
611
|
+
if (a)
|
|
612
|
+
extra[name].set(a.array, o * size);
|
|
613
|
+
else {
|
|
614
|
+
const f = (fill && fill[name] !== undefined) ? fill[name] : (g.userData[name] !== undefined ? g.userData[name] : 0);
|
|
615
|
+
for (let i = 0; i < P.count * size; i++)
|
|
616
|
+
extra[name][o * size + i] = f;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (g.index)
|
|
620
|
+
for (let i = 0; i < g.index.count; i++)
|
|
621
|
+
idx.push(g.index.getX(i) + o);
|
|
622
|
+
else
|
|
623
|
+
for (let i = 0; i < P.count; i++)
|
|
624
|
+
idx.push(i + o);
|
|
625
|
+
o += P.count;
|
|
626
|
+
g.dispose();
|
|
627
|
+
}
|
|
628
|
+
const m = new THREE.BufferGeometry();
|
|
629
|
+
m.setAttribute('position', new THREE.BufferAttribute(pos, 3));
|
|
630
|
+
m.setAttribute('normal', new THREE.BufferAttribute(nor, 3));
|
|
631
|
+
for (const [name, size] of attrs)
|
|
632
|
+
m.setAttribute(name, new THREE.BufferAttribute(extra[name], size));
|
|
633
|
+
m.setIndex(idx);
|
|
634
|
+
m.computeBoundingBox();
|
|
635
|
+
m.computeBoundingSphere();
|
|
636
|
+
return m;
|
|
637
|
+
}
|
|
638
|
+
// ---------------------------------------------------------------------------- merge by material
|
|
639
|
+
/**
|
|
640
|
+
* Merge every mesh under `roots` into as few meshes as there are materials (instanced meshes of
|
|
641
|
+
* the same geometry into one InstancedMesh), added to `group` in its local space, lit by a baked
|
|
642
|
+
* lamp `field`. Used by objects built from other variants' cores (a street grid, a block's
|
|
643
|
+
* streets): ~200 pieces become ~10 draw calls. A merged mesh draws with the FIRST piece's
|
|
644
|
+
* material — the pieces' knob uniforms all receive the same config, so any one stands for all;
|
|
645
|
+
* a per-piece difference must be a vertex attribute (the junction's aTee / aRound), never a
|
|
646
|
+
* uniform. A material can keep itself apart with material.userData.mergeKey (a lawn island).
|
|
647
|
+
* The originals leave the scene graph; their cores still own and dispose them. Returns the
|
|
648
|
+
* merged meshes (dispose their geometry; the materials are the pieces').
|
|
649
|
+
*/
|
|
650
|
+
export function mergeByMaterial(group, roots, field) {
|
|
651
|
+
const inv = new THREE.Matrix4(), im = new THREE.Matrix4(), col = new THREE.Color();
|
|
652
|
+
group.updateMatrixWorld(true);
|
|
653
|
+
inv.copy(group.matrixWorld).invert();
|
|
654
|
+
const sets = new Map();
|
|
655
|
+
for (const root of roots) {
|
|
656
|
+
const meshes = [];
|
|
657
|
+
root.traverse((o) => { if (o.isMesh)
|
|
658
|
+
meshes.push(o); });
|
|
659
|
+
for (const m of meshes) {
|
|
660
|
+
const mat = m.material, g = m.geometry;
|
|
661
|
+
let sig = '';
|
|
662
|
+
if (m.isInstancedMesh) {
|
|
663
|
+
if (!g.boundingBox)
|
|
664
|
+
g.computeBoundingBox();
|
|
665
|
+
sig = g.getAttribute('position').count + ':' + g.boundingBox.max.y.toFixed(3) + ':' + !!m.instanceColor;
|
|
666
|
+
}
|
|
667
|
+
const key = (m.isInstancedMesh ? 'I' : 'M') + '|' + mat.vertexShader + '|' + mat.fragmentShader + '|' + Object.keys(g.attributes).sort().join(',')
|
|
668
|
+
+ '|' + mat.polygonOffsetFactor + ',' + mat.polygonOffsetUnits + '|' + m.renderOrder + '|' + sig + '|' + (mat.userData.mergeKey || '');
|
|
669
|
+
let set = sets.get(key);
|
|
670
|
+
if (!set)
|
|
671
|
+
sets.set(key, (set = { first: m, list: [] }));
|
|
672
|
+
set.list.push({ mesh: m, rel: new THREE.Matrix4().multiplyMatrices(inv, m.matrixWorld) });
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const merged = [];
|
|
676
|
+
for (const set of sets.values()) {
|
|
677
|
+
const first = set.first;
|
|
678
|
+
let mesh;
|
|
679
|
+
if (first.isInstancedMesh) {
|
|
680
|
+
let n = 0;
|
|
681
|
+
for (const e of set.list)
|
|
682
|
+
n += e.mesh.count;
|
|
683
|
+
mesh = new THREE.InstancedMesh(first.geometry.clone(), first.material, n);
|
|
684
|
+
let o = 0;
|
|
685
|
+
for (const e of set.list)
|
|
686
|
+
for (let i = 0; i < e.mesh.count; i++, o++) {
|
|
687
|
+
e.mesh.getMatrixAt(i, im);
|
|
688
|
+
im.premultiply(e.rel);
|
|
689
|
+
mesh.setMatrixAt(o, im);
|
|
690
|
+
if (e.mesh.instanceColor) {
|
|
691
|
+
e.mesh.getColorAt(i, col);
|
|
692
|
+
mesh.setColorAt(o, col);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
696
|
+
if (mesh.instanceColor)
|
|
697
|
+
mesh.instanceColor.needsUpdate = true;
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
const geos = set.list.map((e) => e.mesh.geometry.clone().applyMatrix4(e.rel));
|
|
701
|
+
const extra = Object.keys(geos[0].attributes).filter((a) => a !== 'position' && a !== 'normal').map((a) => [a, geos[0].getAttribute(a).itemSize]);
|
|
702
|
+
mesh = new THREE.Mesh(mergeGeometries(geos, extra), first.material);
|
|
703
|
+
}
|
|
704
|
+
mesh.frustumCulled = false;
|
|
705
|
+
mesh.renderOrder = first.renderOrder;
|
|
706
|
+
mesh.castShadow = first.castShadow;
|
|
707
|
+
mesh.receiveShadow = first.receiveShadow;
|
|
708
|
+
if (field)
|
|
709
|
+
attachLampField(first.material.uniforms, field);
|
|
710
|
+
group.add(mesh);
|
|
711
|
+
merged.push(mesh);
|
|
712
|
+
}
|
|
713
|
+
for (const set of sets.values())
|
|
714
|
+
for (const e of set.list)
|
|
715
|
+
if (e.mesh.parent)
|
|
716
|
+
e.mesh.parent.remove(e.mesh);
|
|
717
|
+
return merged;
|
|
718
|
+
}
|
|
719
|
+
// ---------------------------------------------------------------------------- lamp fixture geometry
|
|
720
|
+
/**
|
|
721
|
+
* A street lamp as one BufferGeometry: tapered pole, a curved arm, a flat head whose
|
|
722
|
+
* underside is the emissive panel. Attributes: position, normal, aEmissive (1 on the panel).
|
|
723
|
+
* Base at the origin, head at (armReach, height, 0) facing -y. Pure function of its params.
|
|
724
|
+
*/
|
|
725
|
+
export function buildLampGeometry(p) {
|
|
726
|
+
const height = p.height || 7, reach = p.armReach === undefined ? 1.6 : p.armReach;
|
|
727
|
+
const headW = p.headWidth || 0.9, headD = p.headDepth || 0.35, headH = 0.16;
|
|
728
|
+
const parts = [];
|
|
729
|
+
const pole = new THREE.CylinderGeometry(0.06, 0.11, height, 10, 1);
|
|
730
|
+
pole.translate(0, height / 2, 0);
|
|
731
|
+
parts.push(pole);
|
|
732
|
+
// Arm: a quarter-circle sweep from the pole top out to the head, 6 tube segments.
|
|
733
|
+
const curve = new THREE.CubicBezierCurve3(new THREE.Vector3(0, height, 0), new THREE.Vector3(0, height + 0.5, 0), new THREE.Vector3(reach * 0.5, height + 0.55, 0), new THREE.Vector3(reach, height + 0.4, 0));
|
|
734
|
+
const arm = new THREE.TubeGeometry(curve, 6, 0.045, 8, false);
|
|
735
|
+
parts.push(arm);
|
|
736
|
+
const head = new THREE.BoxGeometry(headW, headH, headD);
|
|
737
|
+
head.translate(reach, height + 0.35, 0);
|
|
738
|
+
parts.push(head);
|
|
739
|
+
// Merge by hand (keeps the lab free of BufferGeometryUtils).
|
|
740
|
+
let count = 0;
|
|
741
|
+
for (const g of parts)
|
|
742
|
+
count += g.getAttribute('position').count;
|
|
743
|
+
const pos = new Float32Array(count * 3), nor = new Float32Array(count * 3), emi = new Float32Array(count);
|
|
744
|
+
const idx = [];
|
|
745
|
+
let o = 0;
|
|
746
|
+
for (const g of parts) {
|
|
747
|
+
const ng = g.index ? g : g;
|
|
748
|
+
const P = ng.getAttribute('position'), N = ng.getAttribute('normal');
|
|
749
|
+
for (let i = 0; i < P.count; i++) {
|
|
750
|
+
pos[(o + i) * 3] = P.getX(i);
|
|
751
|
+
pos[(o + i) * 3 + 1] = P.getY(i);
|
|
752
|
+
pos[(o + i) * 3 + 2] = P.getZ(i);
|
|
753
|
+
nor[(o + i) * 3] = N.getX(i);
|
|
754
|
+
nor[(o + i) * 3 + 1] = N.getY(i);
|
|
755
|
+
nor[(o + i) * 3 + 2] = N.getZ(i);
|
|
756
|
+
// The head's underside (normal -y) is the lit panel.
|
|
757
|
+
emi[o + i] = (g === head && N.getY(i) < -0.5) ? 1 : 0;
|
|
758
|
+
}
|
|
759
|
+
if (ng.index)
|
|
760
|
+
for (let i = 0; i < ng.index.count; i++)
|
|
761
|
+
idx.push(ng.index.getX(i) + o);
|
|
762
|
+
else
|
|
763
|
+
for (let i = 0; i < P.count; i++)
|
|
764
|
+
idx.push(i + o);
|
|
765
|
+
o += P.count;
|
|
766
|
+
g.dispose();
|
|
767
|
+
}
|
|
768
|
+
const geo = new THREE.BufferGeometry();
|
|
769
|
+
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
|
|
770
|
+
geo.setAttribute('normal', new THREE.BufferAttribute(nor, 3));
|
|
771
|
+
geo.setAttribute('aEmissive', new THREE.BufferAttribute(emi, 1));
|
|
772
|
+
geo.setAttribute('aAccent', new THREE.BufferAttribute(new Float32Array(count), 1));
|
|
773
|
+
geo.setIndex(idx);
|
|
774
|
+
geo.userData.head = new THREE.Vector3(reach, height + 0.27, 0);
|
|
775
|
+
return geo;
|
|
776
|
+
}
|
|
777
|
+
// ---------------------------------------------------------------------------- paving material
|
|
778
|
+
// A textured hard surface lit by the shared sun block and the lamp list: sidewalks, kerbs,
|
|
779
|
+
// a block's plot, a plaza. Geometry carries aUV in METRES (so one tile size means the same
|
|
780
|
+
// everywhere); the normal picks the tangent frame.
|
|
781
|
+
export function createPavingUniforms() {
|
|
782
|
+
return Object.assign(createSunUniforms(), createLampUniforms(), createTextureUniforms('uPav'), {
|
|
783
|
+
uPavColor: { value: new THREE.Color('#9b9792') },
|
|
784
|
+
uPavRoughness: { value: 0.9 },
|
|
785
|
+
uGrassMix: { value: 0 }, // 1 = lawn look (no texture, noise-broken green)
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
export const PAVING_VERT = /* glsl */ `
|
|
789
|
+
attribute vec2 aUV;
|
|
790
|
+
varying vec3 vN; varying vec3 vP; varying vec2 vUVm; varying vec3 vLocalN;
|
|
791
|
+
${GLSL_FOG_VERT_PARS}
|
|
792
|
+
void main() {
|
|
793
|
+
#ifdef USE_INSTANCING
|
|
794
|
+
mat4 m = modelMatrix * instanceMatrix;
|
|
795
|
+
#else
|
|
796
|
+
mat4 m = modelMatrix;
|
|
797
|
+
#endif
|
|
798
|
+
vec4 wp = m * vec4(position, 1.0);
|
|
799
|
+
vP = wp.xyz;
|
|
800
|
+
vN = normalize(mat3(m) * normal);
|
|
801
|
+
vLocalN = normal;
|
|
802
|
+
vUVm = aUV;
|
|
803
|
+
vec4 mvPosition = viewMatrix * wp;
|
|
804
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
805
|
+
#include <fog_vertex>
|
|
806
|
+
}`;
|
|
807
|
+
export const PAVING_FRAG = /* glsl */ `
|
|
808
|
+
precision highp float;
|
|
809
|
+
${GLSL_COMMON}
|
|
810
|
+
${glslTextureBlock('uPav')}
|
|
811
|
+
uniform vec3 uPavColor; uniform float uPavRoughness; uniform float uGrassMix;
|
|
812
|
+
varying vec3 vN; varying vec3 vP; varying vec2 vUVm; varying vec3 vLocalN;
|
|
813
|
+
${GLSL_FOG_PARS}
|
|
814
|
+
void main() {
|
|
815
|
+
vec3 N = normalize(vN);
|
|
816
|
+
vec3 V = normalize(cameraPosition - vP);
|
|
817
|
+
vec3 T = abs(vLocalN.y) > 0.9 ? vec3(1.0, 0.0, 0.0) : normalize(cross(vec3(0.0, 1.0, 0.0), N));
|
|
818
|
+
vec3 B = abs(vLocalN.y) > 0.9 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
|
|
819
|
+
float rough;
|
|
820
|
+
vec3 Nb = N;
|
|
821
|
+
vec3 mul = uPavSample(vUVm, Nb, T, B, rough);
|
|
822
|
+
rough = mix(rough, uPavRoughness, 0.5);
|
|
823
|
+
// Large-scale tone variation so a long sidewalk is not one repeated tile.
|
|
824
|
+
float macro = 1.0 + (0.2 * vnoise21(vUVm / 9.0) - 0.1) * uPavHas;
|
|
825
|
+
vec3 albedo = uPavColor * mul * macro;
|
|
826
|
+
// Lawn: no texture, two greens broken by a cell hash and a soft gradient.
|
|
827
|
+
// Lawn: two greens broken by cells and a soft gradient, tinted by the colour knob (neutral at #4f7a2e).
|
|
828
|
+
vec3 lawn = mix(vec3(0.16, 0.30, 0.09), vec3(0.28, 0.42, 0.14), hash21(floor(vUVm * 1.7)) * 0.6 + 0.2 * sin(vUVm.x * 0.7) * cos(vUVm.y * 0.5)) * (uPavColor / vec3(0.31, 0.48, 0.18));
|
|
829
|
+
albedo = mix(albedo, lawn, uGrassMix);
|
|
830
|
+
vec3 col = shadeSurface(albedo, Nb, vP, V, mix(rough, 0.95, uGrassMix), 1.0);
|
|
831
|
+
${GLSL_OUT}
|
|
832
|
+
}`;
|
|
833
|
+
/**
|
|
834
|
+
* Depth options for the family's flat ground surfaces (asphalt, paving, plaza slabs). They sit
|
|
835
|
+
* centimetres above the scene's terrain or a ground plane; without an offset the depth buffer
|
|
836
|
+
* cannot tell them apart at distance and they flicker as the camera moves (z-fighting).
|
|
837
|
+
* A polygon offset pulls them toward the camera in depth only — nothing moves on screen.
|
|
838
|
+
*/
|
|
839
|
+
export const SURFACE_DEPTH = { polygonOffset: true, polygonOffsetFactor: -2, polygonOffsetUnits: -4 };
|
|
840
|
+
export function createPavingMaterial(u) {
|
|
841
|
+
return new THREE.ShaderMaterial({ uniforms: Object.assign(THREE.UniformsUtils.clone(THREE.UniformsLib.fog), u), vertexShader: PAVING_VERT, fragmentShader: PAVING_FRAG, fog: true, ...SURFACE_DEPTH });
|
|
842
|
+
}
|
|
843
|
+
// ---------------------------------------------------------------------------- fixture material
|
|
844
|
+
// Painted metal lit by the shared sun block, with the emissive head panel: lit at night (uNight),
|
|
845
|
+
// a glowing disc brighter than 1 so bloom (render task) can pick it up. One material for every
|
|
846
|
+
// fixture of an object; instanced or not.
|
|
847
|
+
export function createFixtureUniforms() {
|
|
848
|
+
return Object.assign(createSunUniforms(), createLampUniforms(), {
|
|
849
|
+
uPaint: { value: new THREE.Color('#2b2f33') },
|
|
850
|
+
uAccent: { value: new THREE.Color('#8a5a2b') },
|
|
851
|
+
uLampColor: { value: new THREE.Color('#ffd9a0') },
|
|
852
|
+
uLampGlow: { value: 1 },
|
|
853
|
+
uRough: { value: 0.55 },
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
export const FIXTURE_VERT = /* glsl */ `
|
|
857
|
+
attribute float aEmissive;
|
|
858
|
+
attribute float aAccent;
|
|
859
|
+
varying vec3 vN; varying vec3 vP; varying float vE; varying float vA; varying vec3 vIC;
|
|
860
|
+
${GLSL_FOG_VERT_PARS}
|
|
861
|
+
void main() {
|
|
862
|
+
vec3 n = normal;
|
|
863
|
+
#ifdef USE_INSTANCING
|
|
864
|
+
mat4 m = modelMatrix * instanceMatrix;
|
|
865
|
+
#else
|
|
866
|
+
mat4 m = modelMatrix;
|
|
867
|
+
#endif
|
|
868
|
+
// Per-instance paint (InstancedMesh.instanceColor): cars in a parking lot, else white.
|
|
869
|
+
vIC = vec3(1.0);
|
|
870
|
+
#ifdef USE_INSTANCING_COLOR
|
|
871
|
+
vIC = instanceColor;
|
|
872
|
+
#endif
|
|
873
|
+
vec4 wp = m * vec4(position, 1.0);
|
|
874
|
+
vP = wp.xyz;
|
|
875
|
+
vN = normalize(mat3(m) * n);
|
|
876
|
+
vE = aEmissive;
|
|
877
|
+
vA = aAccent;
|
|
878
|
+
vec4 mvPosition = viewMatrix * wp;
|
|
879
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
880
|
+
#include <fog_vertex>
|
|
881
|
+
}`;
|
|
882
|
+
export const FIXTURE_FRAG = /* glsl */ `
|
|
883
|
+
precision highp float;
|
|
884
|
+
${GLSL_COMMON}
|
|
885
|
+
uniform vec3 uPaint; uniform vec3 uAccent; uniform vec3 uLampColor; uniform float uLampGlow; uniform float uRough;
|
|
886
|
+
varying vec3 vN; varying vec3 vP; varying float vE; varying float vA; varying vec3 vIC;
|
|
887
|
+
${GLSL_FOG_PARS}
|
|
888
|
+
void main() {
|
|
889
|
+
vec3 N = normalize(vN);
|
|
890
|
+
vec3 V = normalize(cameraPosition - vP);
|
|
891
|
+
vec3 base = mix(uPaint * vIC, uAccent, vA);
|
|
892
|
+
vec3 col = shadeSurface(base, N, vP, V, mix(uRough, 0.8, vA), 1.0);
|
|
893
|
+
// The panel: dim diffuser by day, a bright warm disc at night (over 1.0 on purpose, for bloom).
|
|
894
|
+
vec3 panel = mix(uPaint * 1.6, uLampColor * (1.0 + 2.2 * uLampGlow), uNight);
|
|
895
|
+
col = mix(col, panel, vE);
|
|
896
|
+
float alpha = 1.0;
|
|
897
|
+
#ifdef GLASS
|
|
898
|
+
// Shelter glass: a faint tint plus a Fresnel sky reflection, mostly see-through.
|
|
899
|
+
float fres = 0.04 + 0.96 * pow(1.0 - abs(dot(N, V)), 5.0);
|
|
900
|
+
col = uAccent * 0.08 * (uSkyAmbient + uSunColor * 0.3) + (uSkyAmbient * 2.0 + uSunColor * 0.1) * fres * 0.8;
|
|
901
|
+
alpha = 0.18 + 0.7 * fres;
|
|
902
|
+
#endif
|
|
903
|
+
gl_FragColor = vec4(col, alpha);
|
|
904
|
+
#include <tonemapping_fragment>
|
|
905
|
+
#include <colorspace_fragment>
|
|
906
|
+
#include <fog_fragment>
|
|
907
|
+
}`;
|
|
908
|
+
// ---------------------------------------------------------------------------- low-poly car
|
|
909
|
+
// The family's crowd car (parked in a parkingLot, driving in traffic): one geometry, ~80 triangles,
|
|
910
|
+
// in the fixture material with per-car paint from CAR_PALETTE. A car MODEL from the library is still
|
|
911
|
+
// the better hero car; these are the crowd.
|
|
912
|
+
export const CAR_PALETTE = ['#e8e8e8', '#cfd2d6', '#1d1f22', '#6c7076', '#9b1b1b', '#1f3f7a', '#2f4f3a', '#b9b1a0', '#4a4a4a', '#e3e3e3'];
|
|
913
|
+
/** A low-poly car as one geometry, facing +x, base at y = 0: body, cabin (accent = dark glass), four wheels (accent). */
|
|
914
|
+
export function buildCarGeometry() {
|
|
915
|
+
const parts = [];
|
|
916
|
+
const box = (w, h, d, x, y, z, accent) => { const g = new THREE.BoxGeometry(w, h, d); g.translate(x, y, z); g.userData.aAccent = accent; g.userData.aEmissive = 0; parts.push(g); };
|
|
917
|
+
box(4.4, 0.55, 1.8, 0, 0.55, 0, 0); // lower body
|
|
918
|
+
box(4.0, 0.35, 1.7, -0.1, 0.98, 0, 0); // upper body
|
|
919
|
+
box(2.3, 0.6, 1.62, -0.25, 1.42, 0, 1); // cabin (dark)
|
|
920
|
+
for (const sx of [-1, 1])
|
|
921
|
+
for (const sz of [-1, 1])
|
|
922
|
+
box(0.64, 0.64, 0.3, sx * 1.4, 0.32, sz * 0.82, 1);
|
|
923
|
+
return mergeGeometries(parts, [['aEmissive', 1], ['aAccent', 1]]);
|
|
924
|
+
}
|
|
925
|
+
export function createFixtureMaterial(u, glass = false) {
|
|
926
|
+
return new THREE.ShaderMaterial({ uniforms: Object.assign(THREE.UniformsUtils.clone(THREE.UniformsLib.fog), u), vertexShader: FIXTURE_VERT, fragmentShader: FIXTURE_FRAG, fog: true,
|
|
927
|
+
defines: glass ? { GLASS: 1 } : {}, transparent: !!glass, depthWrite: !glass, side: glass ? THREE.DoubleSide : THREE.FrontSide });
|
|
928
|
+
}
|