@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.
Files changed (408) hide show
  1. package/dist/animationsV2/ClipsRuntime.d.ts +16 -1
  2. package/dist/animationsV2/ClipsRuntime.d.ts.map +1 -1
  3. package/dist/animationsV2/ClipsRuntime.js +144 -25
  4. package/dist/animationsV2/appliers.d.ts +9 -0
  5. package/dist/animationsV2/appliers.d.ts.map +1 -1
  6. package/dist/animationsV2/appliers.js +62 -2
  7. package/dist/animationsV2/clipUtils.d.ts +30 -0
  8. package/dist/animationsV2/clipUtils.d.ts.map +1 -1
  9. package/dist/animationsV2/clipUtils.js +163 -17
  10. package/dist/animationsV2/elementSettings.d.ts +5 -0
  11. package/dist/animationsV2/elementSettings.d.ts.map +1 -1
  12. package/dist/animationsV2/elementSettings.js +6 -3
  13. package/dist/animationsV2/sequenceUtils.js +4 -4
  14. package/dist/animationsV2/types.d.ts +23 -0
  15. package/dist/animationsV2/types.d.ts.map +1 -1
  16. package/dist/api/sceneHandle.js +2 -2
  17. package/dist/components/CreatedObject.d.ts +3 -0
  18. package/dist/components/CreatedObject.d.ts.map +1 -1
  19. package/dist/components/CreatedObject.js +6 -2
  20. package/dist/components/CustomPrimitive.d.ts.map +1 -1
  21. package/dist/components/CustomPrimitive.js +1 -10
  22. package/dist/components/EnvironmentManager.d.ts.map +1 -1
  23. package/dist/components/EnvironmentManager.js +11 -5
  24. package/dist/components/GeometryShape.d.ts +5 -2
  25. package/dist/components/GeometryShape.d.ts.map +1 -1
  26. package/dist/components/GeometryShape.js +71 -10
  27. package/dist/components/LandObject.d.ts.map +1 -1
  28. package/dist/components/LandObject.js +1 -1
  29. package/dist/components/LayoutInstances.d.ts +2 -1
  30. package/dist/components/LayoutInstances.d.ts.map +1 -1
  31. package/dist/components/LayoutInstances.js +39 -6
  32. package/dist/components/LightObject.d.ts.map +1 -1
  33. package/dist/components/LightObject.js +3 -0
  34. package/dist/components/ObjectNode.d.ts.map +1 -1
  35. package/dist/components/ObjectNode.js +62 -13
  36. package/dist/components/OceanBuilder.d.ts +2 -0
  37. package/dist/components/OceanBuilder.d.ts.map +1 -1
  38. package/dist/components/OceanBuilder.js +2 -1
  39. package/dist/components/RenderEffects.d.ts +8 -0
  40. package/dist/components/RenderEffects.d.ts.map +1 -0
  41. package/dist/components/RenderEffects.js +120 -0
  42. package/dist/components/SceneBuilder.d.ts.map +1 -1
  43. package/dist/components/SceneBuilder.js +18 -9
  44. package/dist/components/SportsObject.d.ts +9 -0
  45. package/dist/components/SportsObject.d.ts.map +1 -0
  46. package/dist/components/SportsObject.js +20 -0
  47. package/dist/components/UrbanObject.d.ts +9 -0
  48. package/dist/components/UrbanObject.d.ts.map +1 -0
  49. package/dist/components/UrbanObject.js +28 -0
  50. package/dist/components/VegetationObject.d.ts +9 -0
  51. package/dist/components/VegetationObject.d.ts.map +1 -0
  52. package/dist/components/VegetationObject.js +26 -0
  53. package/dist/components/WaterObject.d.ts +15 -0
  54. package/dist/components/WaterObject.d.ts.map +1 -0
  55. package/dist/components/WaterObject.js +29 -0
  56. package/dist/components/index.d.ts +7 -2
  57. package/dist/components/index.d.ts.map +1 -1
  58. package/dist/components/index.js +6 -1
  59. package/dist/components/land/LandPatch.d.ts +3 -1
  60. package/dist/components/land/LandPatch.d.ts.map +1 -1
  61. package/dist/components/land/LandPatch.js +80 -7
  62. package/dist/components/layout/InstancedLayout.d.ts +19 -0
  63. package/dist/components/layout/InstancedLayout.d.ts.map +1 -0
  64. package/dist/components/layout/InstancedLayout.js +154 -0
  65. package/dist/components/layout/LayoutGrounds.d.ts +19 -0
  66. package/dist/components/layout/LayoutGrounds.d.ts.map +1 -0
  67. package/dist/components/layout/LayoutGrounds.js +104 -0
  68. package/dist/components/layout/LinkProxies.d.ts +5 -0
  69. package/dist/components/layout/LinkProxies.d.ts.map +1 -0
  70. package/dist/components/layout/LinkProxies.js +51 -0
  71. package/dist/components/layout/LinkedSource.d.ts +14 -0
  72. package/dist/components/layout/LinkedSource.d.ts.map +1 -0
  73. package/dist/components/layout/LinkedSource.js +129 -0
  74. package/dist/components/projector/Projector.d.ts +14 -0
  75. package/dist/components/projector/Projector.d.ts.map +1 -0
  76. package/dist/components/projector/Projector.js +44 -0
  77. package/dist/components/projector/index.d.ts +4 -0
  78. package/dist/components/projector/index.d.ts.map +1 -0
  79. package/dist/components/projector/index.js +2 -0
  80. package/dist/components/projector/projectorCore.d.ts +19 -0
  81. package/dist/components/projector/projectorCore.d.ts.map +1 -0
  82. package/dist/components/projector/projectorCore.js +545 -0
  83. package/dist/components/sports/SportsBall.d.ts +9 -0
  84. package/dist/components/sports/SportsBall.d.ts.map +1 -0
  85. package/dist/components/sports/SportsBall.js +23 -0
  86. package/dist/components/sports/SportsCourt.d.ts +9 -0
  87. package/dist/components/sports/SportsCourt.d.ts.map +1 -0
  88. package/dist/components/sports/SportsCourt.js +23 -0
  89. package/dist/components/sports/SportsEquipment.d.ts +9 -0
  90. package/dist/components/sports/SportsEquipment.d.ts.map +1 -0
  91. package/dist/components/sports/SportsEquipment.js +23 -0
  92. package/dist/components/sports/SportsGoal.d.ts +9 -0
  93. package/dist/components/sports/SportsGoal.d.ts.map +1 -0
  94. package/dist/components/sports/SportsGoal.js +23 -0
  95. package/dist/components/sports/core/ball.d.ts +166 -0
  96. package/dist/components/sports/core/ball.d.ts.map +1 -0
  97. package/dist/components/sports/core/ball.js +402 -0
  98. package/dist/components/sports/core/court.d.ts +442 -0
  99. package/dist/components/sports/core/court.d.ts.map +1 -0
  100. package/dist/components/sports/core/court.js +658 -0
  101. package/dist/components/sports/core/equipment.d.ts +103 -0
  102. package/dist/components/sports/core/equipment.d.ts.map +1 -0
  103. package/dist/components/sports/core/equipment.js +403 -0
  104. package/dist/components/sports/core/goal.d.ts +98 -0
  105. package/dist/components/sports/core/goal.d.ts.map +1 -0
  106. package/dist/components/sports/core/goal.js +44 -0
  107. package/dist/components/sports/index.d.ts +11 -0
  108. package/dist/components/sports/index.d.ts.map +1 -0
  109. package/dist/components/sports/index.js +8 -0
  110. package/dist/components/sports/sportsCore.d.ts +195 -0
  111. package/dist/components/sports/sportsCore.d.ts.map +1 -0
  112. package/dist/components/sports/sportsCore.js +758 -0
  113. package/dist/components/sports/useSportsInstance.d.ts +35 -0
  114. package/dist/components/sports/useSportsInstance.d.ts.map +1 -0
  115. package/dist/components/sports/useSportsInstance.js +47 -0
  116. package/dist/components/terrain/TerrainSurface.d.ts.map +1 -1
  117. package/dist/components/terrain/TerrainSurface.js +14 -0
  118. package/dist/components/urban/UrbanBlock.d.ts +9 -0
  119. package/dist/components/urban/UrbanBlock.d.ts.map +1 -0
  120. package/dist/components/urban/UrbanBlock.js +23 -0
  121. package/dist/components/urban/UrbanBuilding.d.ts +9 -0
  122. package/dist/components/urban/UrbanBuilding.d.ts.map +1 -0
  123. package/dist/components/urban/UrbanBuilding.js +23 -0
  124. package/dist/components/urban/UrbanJunction.d.ts +9 -0
  125. package/dist/components/urban/UrbanJunction.d.ts.map +1 -0
  126. package/dist/components/urban/UrbanJunction.js +23 -0
  127. package/dist/components/urban/UrbanPark.d.ts +9 -0
  128. package/dist/components/urban/UrbanPark.d.ts.map +1 -0
  129. package/dist/components/urban/UrbanPark.js +23 -0
  130. package/dist/components/urban/UrbanParkingLot.d.ts +9 -0
  131. package/dist/components/urban/UrbanParkingLot.d.ts.map +1 -0
  132. package/dist/components/urban/UrbanParkingLot.js +23 -0
  133. package/dist/components/urban/UrbanPlaza.d.ts +9 -0
  134. package/dist/components/urban/UrbanPlaza.d.ts.map +1 -0
  135. package/dist/components/urban/UrbanPlaza.js +23 -0
  136. package/dist/components/urban/UrbanRoad.d.ts +9 -0
  137. package/dist/components/urban/UrbanRoad.d.ts.map +1 -0
  138. package/dist/components/urban/UrbanRoad.js +23 -0
  139. package/dist/components/urban/UrbanSignage.d.ts +9 -0
  140. package/dist/components/urban/UrbanSignage.d.ts.map +1 -0
  141. package/dist/components/urban/UrbanSignage.js +23 -0
  142. package/dist/components/urban/UrbanSkyline.d.ts +9 -0
  143. package/dist/components/urban/UrbanSkyline.d.ts.map +1 -0
  144. package/dist/components/urban/UrbanSkyline.js +23 -0
  145. package/dist/components/urban/UrbanStreetFurniture.d.ts +9 -0
  146. package/dist/components/urban/UrbanStreetFurniture.d.ts.map +1 -0
  147. package/dist/components/urban/UrbanStreetFurniture.js +23 -0
  148. package/dist/components/urban/UrbanStreetGrid.d.ts +9 -0
  149. package/dist/components/urban/UrbanStreetGrid.d.ts.map +1 -0
  150. package/dist/components/urban/UrbanStreetGrid.js +34 -0
  151. package/dist/components/urban/UrbanTraffic.d.ts +9 -0
  152. package/dist/components/urban/UrbanTraffic.d.ts.map +1 -0
  153. package/dist/components/urban/UrbanTraffic.js +23 -0
  154. package/dist/components/urban/core/block.d.ts +119 -0
  155. package/dist/components/urban/core/block.d.ts.map +1 -0
  156. package/dist/components/urban/core/block.js +266 -0
  157. package/dist/components/urban/core/building.d.ts +128 -0
  158. package/dist/components/urban/core/building.d.ts.map +1 -0
  159. package/dist/components/urban/core/building.js +741 -0
  160. package/dist/components/urban/core/junction.d.ts +152 -0
  161. package/dist/components/urban/core/junction.d.ts.map +1 -0
  162. package/dist/components/urban/core/junction.js +617 -0
  163. package/dist/components/urban/core/park.d.ts +67 -0
  164. package/dist/components/urban/core/park.d.ts.map +1 -0
  165. package/dist/components/urban/core/park.js +176 -0
  166. package/dist/components/urban/core/parkingLot.d.ts +82 -0
  167. package/dist/components/urban/core/parkingLot.d.ts.map +1 -0
  168. package/dist/components/urban/core/parkingLot.js +284 -0
  169. package/dist/components/urban/core/plaza.d.ts +24 -0
  170. package/dist/components/urban/core/plaza.d.ts.map +1 -0
  171. package/dist/components/urban/core/plaza.js +243 -0
  172. package/dist/components/urban/core/road.d.ts +113 -0
  173. package/dist/components/urban/core/road.d.ts.map +1 -0
  174. package/dist/components/urban/core/road.js +347 -0
  175. package/dist/components/urban/core/signage.d.ts +83 -0
  176. package/dist/components/urban/core/signage.d.ts.map +1 -0
  177. package/dist/components/urban/core/signage.js +209 -0
  178. package/dist/components/urban/core/skyline.d.ts +100 -0
  179. package/dist/components/urban/core/skyline.d.ts.map +1 -0
  180. package/dist/components/urban/core/skyline.js +89 -0
  181. package/dist/components/urban/core/streetFurniture.d.ts +75 -0
  182. package/dist/components/urban/core/streetFurniture.d.ts.map +1 -0
  183. package/dist/components/urban/core/streetFurniture.js +193 -0
  184. package/dist/components/urban/core/streetGrid.d.ts +134 -0
  185. package/dist/components/urban/core/streetGrid.d.ts.map +1 -0
  186. package/dist/components/urban/core/streetGrid.js +422 -0
  187. package/dist/components/urban/core/traffic.d.ts +125 -0
  188. package/dist/components/urban/core/traffic.d.ts.map +1 -0
  189. package/dist/components/urban/core/traffic.js +305 -0
  190. package/dist/components/urban/index.d.ts +29 -0
  191. package/dist/components/urban/index.d.ts.map +1 -0
  192. package/dist/components/urban/index.js +16 -0
  193. package/dist/components/urban/textureSetResolver.d.ts +8 -0
  194. package/dist/components/urban/textureSetResolver.d.ts.map +1 -0
  195. package/dist/components/urban/textureSetResolver.js +16 -0
  196. package/dist/components/urban/urbanCore.d.ts +262 -0
  197. package/dist/components/urban/urbanCore.d.ts.map +1 -0
  198. package/dist/components/urban/urbanCore.js +928 -0
  199. package/dist/components/urban/useUrbanInstance.d.ts +53 -0
  200. package/dist/components/urban/useUrbanInstance.d.ts.map +1 -0
  201. package/dist/components/urban/useUrbanInstance.js +100 -0
  202. package/dist/components/vegetation/VegetationBush.d.ts +9 -0
  203. package/dist/components/vegetation/VegetationBush.d.ts.map +1 -0
  204. package/dist/components/vegetation/VegetationBush.js +23 -0
  205. package/dist/components/vegetation/VegetationCrops.d.ts +9 -0
  206. package/dist/components/vegetation/VegetationCrops.d.ts.map +1 -0
  207. package/dist/components/vegetation/VegetationCrops.js +23 -0
  208. package/dist/components/vegetation/VegetationFlowers.d.ts +9 -0
  209. package/dist/components/vegetation/VegetationFlowers.d.ts.map +1 -0
  210. package/dist/components/vegetation/VegetationFlowers.js +23 -0
  211. package/dist/components/vegetation/VegetationForest.d.ts +9 -0
  212. package/dist/components/vegetation/VegetationForest.d.ts.map +1 -0
  213. package/dist/components/vegetation/VegetationForest.js +23 -0
  214. package/dist/components/vegetation/VegetationFruit.d.ts +9 -0
  215. package/dist/components/vegetation/VegetationFruit.d.ts.map +1 -0
  216. package/dist/components/vegetation/VegetationFruit.js +23 -0
  217. package/dist/components/vegetation/VegetationGrass.d.ts +9 -0
  218. package/dist/components/vegetation/VegetationGrass.d.ts.map +1 -0
  219. package/dist/components/vegetation/VegetationGrass.js +23 -0
  220. package/dist/components/vegetation/VegetationHedge.d.ts +9 -0
  221. package/dist/components/vegetation/VegetationHedge.d.ts.map +1 -0
  222. package/dist/components/vegetation/VegetationHedge.js +23 -0
  223. package/dist/components/vegetation/VegetationIvy.d.ts +9 -0
  224. package/dist/components/vegetation/VegetationIvy.d.ts.map +1 -0
  225. package/dist/components/vegetation/VegetationIvy.js +23 -0
  226. package/dist/components/vegetation/VegetationTree.d.ts +9 -0
  227. package/dist/components/vegetation/VegetationTree.d.ts.map +1 -0
  228. package/dist/components/vegetation/VegetationTree.js +23 -0
  229. package/dist/components/vegetation/VegetationVegetable.d.ts +9 -0
  230. package/dist/components/vegetation/VegetationVegetable.d.ts.map +1 -0
  231. package/dist/components/vegetation/VegetationVegetable.js +23 -0
  232. package/dist/components/vegetation/core/bush.d.ts +1256 -0
  233. package/dist/components/vegetation/core/bush.d.ts.map +1 -0
  234. package/dist/components/vegetation/core/bush.js +376 -0
  235. package/dist/components/vegetation/core/crops.d.ts +1033 -0
  236. package/dist/components/vegetation/core/crops.d.ts.map +1 -0
  237. package/dist/components/vegetation/core/crops.js +378 -0
  238. package/dist/components/vegetation/core/flowers.d.ts +648 -0
  239. package/dist/components/vegetation/core/flowers.d.ts.map +1 -0
  240. package/dist/components/vegetation/core/flowers.js +225 -0
  241. package/dist/components/vegetation/core/forest.d.ts +138 -0
  242. package/dist/components/vegetation/core/forest.d.ts.map +1 -0
  243. package/dist/components/vegetation/core/forest.js +310 -0
  244. package/dist/components/vegetation/core/fruit.d.ts +99 -0
  245. package/dist/components/vegetation/core/fruit.d.ts.map +1 -0
  246. package/dist/components/vegetation/core/fruit.js +77 -0
  247. package/dist/components/vegetation/core/grass.d.ts +103 -0
  248. package/dist/components/vegetation/core/grass.d.ts.map +1 -0
  249. package/dist/components/vegetation/core/grass.js +102 -0
  250. package/dist/components/vegetation/core/hedge.d.ts +193 -0
  251. package/dist/components/vegetation/core/hedge.d.ts.map +1 -0
  252. package/dist/components/vegetation/core/hedge.js +243 -0
  253. package/dist/components/vegetation/core/ivy.d.ts +213 -0
  254. package/dist/components/vegetation/core/ivy.d.ts.map +1 -0
  255. package/dist/components/vegetation/core/ivy.js +173 -0
  256. package/dist/components/vegetation/core/tree.d.ts +1117 -0
  257. package/dist/components/vegetation/core/tree.d.ts.map +1 -0
  258. package/dist/components/vegetation/core/tree.js +408 -0
  259. package/dist/components/vegetation/core/vegetable.d.ts +99 -0
  260. package/dist/components/vegetation/core/vegetable.d.ts.map +1 -0
  261. package/dist/components/vegetation/core/vegetable.js +76 -0
  262. package/dist/components/vegetation/index.d.ts +23 -0
  263. package/dist/components/vegetation/index.d.ts.map +1 -0
  264. package/dist/components/vegetation/index.js +14 -0
  265. package/dist/components/vegetation/produceCore.d.ts +929 -0
  266. package/dist/components/vegetation/produceCore.d.ts.map +1 -0
  267. package/dist/components/vegetation/produceCore.js +723 -0
  268. package/dist/components/vegetation/useVegetationInstance.d.ts +39 -0
  269. package/dist/components/vegetation/useVegetationInstance.d.ts.map +1 -0
  270. package/dist/components/vegetation/useVegetationInstance.js +45 -0
  271. package/dist/components/vegetation/vegetationCore.d.ts +412 -0
  272. package/dist/components/vegetation/vegetationCore.d.ts.map +1 -0
  273. package/dist/components/vegetation/vegetationCore.js +1728 -0
  274. package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts +5 -0
  275. package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts.map +1 -1
  276. package/dist/components/volumetricClouds/VolumetricCloudsPass.js +39 -13
  277. package/dist/components/water/OceanSurface.d.ts.map +1 -1
  278. package/dist/components/water/OceanSurface.js +3 -2
  279. package/dist/components/water/WaterBody.d.ts.map +1 -1
  280. package/dist/components/water/WaterBody.js +31 -10
  281. package/dist/components/water/WaterFountain.d.ts +9 -0
  282. package/dist/components/water/WaterFountain.d.ts.map +1 -0
  283. package/dist/components/water/WaterFountain.js +24 -0
  284. package/dist/components/water/WaterStream.d.ts +9 -0
  285. package/dist/components/water/WaterStream.d.ts.map +1 -0
  286. package/dist/components/water/WaterStream.js +24 -0
  287. package/dist/components/water/WaterWaterfall.d.ts +9 -0
  288. package/dist/components/water/WaterWaterfall.d.ts.map +1 -0
  289. package/dist/components/water/WaterWaterfall.js +24 -0
  290. package/dist/components/water/core/fountain.d.ts +70 -0
  291. package/dist/components/water/core/fountain.d.ts.map +1 -0
  292. package/dist/components/water/core/fountain.js +619 -0
  293. package/dist/components/water/core/stream.d.ts +72 -0
  294. package/dist/components/water/core/stream.d.ts.map +1 -0
  295. package/dist/components/water/core/stream.js +167 -0
  296. package/dist/components/water/core/waterfall.d.ts +78 -0
  297. package/dist/components/water/core/waterfall.d.ts.map +1 -0
  298. package/dist/components/water/core/waterfall.js +325 -0
  299. package/dist/components/water/index.d.ts +8 -0
  300. package/dist/components/water/index.d.ts.map +1 -1
  301. package/dist/components/water/index.js +7 -0
  302. package/dist/components/water/types.d.ts +3 -0
  303. package/dist/components/water/types.d.ts.map +1 -1
  304. package/dist/components/water/useWaterInstance.d.ts +43 -0
  305. package/dist/components/water/useWaterInstance.d.ts.map +1 -0
  306. package/dist/components/water/useWaterInstance.js +89 -0
  307. package/dist/components/water/waterCore.d.ts +179 -0
  308. package/dist/components/water/waterCore.d.ts.map +1 -0
  309. package/dist/components/water/waterCore.js +516 -0
  310. package/dist/constants.d.ts.map +1 -1
  311. package/dist/constants.js +9 -1
  312. package/dist/hooks/index.d.ts +7 -2
  313. package/dist/hooks/index.d.ts.map +1 -1
  314. package/dist/hooks/index.js +5 -2
  315. package/dist/hooks/useActionPatch.d.ts +23 -0
  316. package/dist/hooks/useActionPatch.d.ts.map +1 -0
  317. package/dist/hooks/useActionPatch.js +38 -0
  318. package/dist/hooks/useCloudSun.d.ts.map +1 -1
  319. package/dist/hooks/useCloudSun.js +3 -2
  320. package/dist/hooks/useCommittedInstance.d.ts +6 -0
  321. package/dist/hooks/useCommittedInstance.d.ts.map +1 -0
  322. package/dist/hooks/useCommittedInstance.js +42 -0
  323. package/dist/hooks/useConfigLive.d.ts +1 -1
  324. package/dist/hooks/useConfigLive.d.ts.map +1 -1
  325. package/dist/hooks/useContinuousEffects.d.ts.map +1 -1
  326. package/dist/hooks/useContinuousEffects.js +36 -20
  327. package/dist/hooks/useGridLayout.d.ts +7 -0
  328. package/dist/hooks/useGridLayout.d.ts.map +1 -0
  329. package/dist/hooks/useGridLayout.js +10 -0
  330. package/dist/hooks/useInteractionEffects.d.ts.map +1 -1
  331. package/dist/hooks/useInteractionEffects.js +48 -9
  332. package/dist/hooks/useLightLive.d.ts.map +1 -1
  333. package/dist/hooks/useLightLive.js +2 -0
  334. package/dist/hooks/useLinkStatus.d.ts +7 -0
  335. package/dist/hooks/useLinkStatus.d.ts.map +1 -0
  336. package/dist/hooks/useLinkStatus.js +20 -0
  337. package/dist/hooks/useMaterialApplication.d.ts +6 -1
  338. package/dist/hooks/useMaterialApplication.d.ts.map +1 -1
  339. package/dist/hooks/useMaterialApplication.js +7 -2
  340. package/dist/hooks/useOceanMaterial.d.ts +11 -1
  341. package/dist/hooks/useOceanMaterial.d.ts.map +1 -1
  342. package/dist/hooks/useOceanMaterial.js +185 -27
  343. package/dist/hooks/usePathAnimation.d.ts +8 -0
  344. package/dist/hooks/usePathAnimation.d.ts.map +1 -1
  345. package/dist/hooks/usePathAnimation.js +140 -59
  346. package/dist/hooks/useResolvedLayout.d.ts +10 -0
  347. package/dist/hooks/useResolvedLayout.d.ts.map +1 -0
  348. package/dist/hooks/useResolvedLayout.js +17 -0
  349. package/dist/hooks/useTimeLighting.d.ts +36 -0
  350. package/dist/hooks/useTimeLighting.d.ts.map +1 -0
  351. package/dist/hooks/useTimeLighting.js +67 -0
  352. package/dist/index.d.ts +4 -0
  353. package/dist/index.d.ts.map +1 -1
  354. package/dist/index.js +4 -0
  355. package/dist/operations/catalogue/shaderLayers.d.ts.map +1 -1
  356. package/dist/operations/catalogue/shaderLayers.js +26 -3
  357. package/dist/render/renderLive.d.ts +11 -0
  358. package/dist/render/renderLive.d.ts.map +1 -0
  359. package/dist/render/renderLive.js +57 -0
  360. package/dist/shaders/glsl/oceanCore.glsl.d.ts +2 -2
  361. package/dist/shaders/glsl/oceanCore.glsl.d.ts.map +1 -1
  362. package/dist/shaders/glsl/oceanCore.glsl.js +35 -5
  363. package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts +12 -0
  364. package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts.map +1 -1
  365. package/dist/shaders/ocean/OceanSurfaceMaterial.js +55 -7
  366. package/dist/shaders/terrain/TerrainMaterial.d.ts.map +1 -1
  367. package/dist/shaders/terrain/TerrainMaterial.js +28 -0
  368. package/dist/triggers/kinds/change.js +1 -1
  369. package/dist/utils/composerHostedPass.d.ts +74 -0
  370. package/dist/utils/composerHostedPass.d.ts.map +1 -0
  371. package/dist/utils/composerHostedPass.js +63 -0
  372. package/dist/utils/geometryScript.d.ts +17 -0
  373. package/dist/utils/geometryScript.d.ts.map +1 -0
  374. package/dist/utils/geometryScript.js +92 -0
  375. package/dist/utils/geometryScript.worker.d.ts +2 -0
  376. package/dist/utils/geometryScript.worker.d.ts.map +1 -0
  377. package/dist/utils/geometryScript.worker.js +42 -0
  378. package/dist/utils/groundRegistry.d.ts +11 -0
  379. package/dist/utils/groundRegistry.d.ts.map +1 -0
  380. package/dist/utils/groundRegistry.js +39 -0
  381. package/dist/utils/instancingSupport.d.ts +9 -0
  382. package/dist/utils/instancingSupport.d.ts.map +1 -0
  383. package/dist/utils/instancingSupport.js +13 -0
  384. package/dist/utils/landPatch.d.ts +26 -5
  385. package/dist/utils/landPatch.d.ts.map +1 -1
  386. package/dist/utils/landPatch.js +124 -9
  387. package/dist/utils/layoutPlacement.d.ts +30 -0
  388. package/dist/utils/layoutPlacement.d.ts.map +1 -0
  389. package/dist/utils/layoutPlacement.js +113 -0
  390. package/dist/utils/linkRegistry.d.ts +15 -0
  391. package/dist/utils/linkRegistry.d.ts.map +1 -0
  392. package/dist/utils/linkRegistry.js +11 -0
  393. package/dist/utils/shapes.d.ts +28 -2
  394. package/dist/utils/shapes.d.ts.map +1 -1
  395. package/dist/utils/shapes.js +32 -3
  396. package/dist/utils/sharedParts.d.ts +61 -0
  397. package/dist/utils/sharedParts.d.ts.map +1 -0
  398. package/dist/utils/sharedParts.js +268 -0
  399. package/dist/utils/svgAsset.d.ts +6 -0
  400. package/dist/utils/svgAsset.d.ts.map +1 -0
  401. package/dist/utils/svgAsset.js +46 -0
  402. package/dist/utils/waterLight.d.ts +29 -0
  403. package/dist/utils/waterLight.d.ts.map +1 -0
  404. package/dist/utils/waterLight.js +67 -0
  405. package/dist/utils/waterReflection.d.ts +185 -0
  406. package/dist/utils/waterReflection.d.ts.map +1 -0
  407. package/dist/utils/waterReflection.js +616 -0
  408. package/package.json +6 -4
@@ -0,0 +1,758 @@
1
+ // Emitted from Claude outputs/sports-lab/common.mjs by gen_family.mjs — edit the lab module and re-emit.
2
+ // Shared core of the sports objects (lab). Emitted as packages/viewer/src/components/sports/sportsCore.ts.
3
+ //
4
+ // A sports object is a solid prop standing in the scene — a court, a ball, a piece of equipment, a
5
+ // goal — built at REAL size in metres (a tennis court is 23.77 × 10.97 m, a football 22 cm across;
6
+ // the transform scale starts at 1 and only places / resizes it). Everything is lit by the scene's
7
+ // lights like a mesh and casts and receives shadows: every material is a three.js lit material
8
+ // (MeshStandardMaterial) with the prop's own code injected through onBeforeCompile —
9
+ // fragment procedural surfaces (acrylic, clay, wood planks, grass, ice, leather, felt, rubber,
10
+ // brushed metal, carbon weave), court markings and ball seams as anti-aliased signed
11
+ // distances (fwidth), wear as fbm scuffs where the play is, seams and dimples as bumps
12
+ // from screen-space derivatives (no tangent frames);
13
+ // vertex nets sag by a catenary (uniform-driven), ball panels puff out between the seams.
14
+ // Colour slots every variant shares: color (the main surface), secondaryColor (lines / second
15
+ // panel / grip / net), accentColor (seams, paint, bands). Knobs describe the prop, never move it.
16
+ import * as THREE from 'three';
17
+ import { MAX_LAMPS, lampRegistry, registerLamp, unregisterLamp, touchLamps, setLamps, placeLamps, createLampUniforms, gatherLamps, createSunUniforms, nightFromSun, applySun, GLSL_COMMON, } from '../urban/urbanCore.js';
18
+ // Night lighting is the urban family's (one lamp list, one night factor): re-exported for the variants.
19
+ export { MAX_LAMPS, lampRegistry, registerLamp, unregisterLamp, touchLamps, setLamps, placeLamps, createLampUniforms, gatherLamps, createSunUniforms, nightFromSun, applySun, GLSL_COMMON, };
20
+ export const DEG_S = Math.PI / 180;
21
+ export const QUALITY_S = { low: 0.5, medium: 1, high: 2 };
22
+ export function mulberry32(a) {
23
+ return function () {
24
+ a |= 0;
25
+ a = (a + 0x6d2b79f5) | 0;
26
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
27
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
28
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
29
+ };
30
+ }
31
+ export function setColor(uniform, v) {
32
+ if (v === undefined || v === null)
33
+ return;
34
+ if (v.isColor)
35
+ uniform.value.copy(v);
36
+ else
37
+ uniform.value.set(v);
38
+ }
39
+ export function injectUniforms(shader, u) { for (const k in u)
40
+ if (u[k] && typeof u[k] === 'object' && 'value' in u[k])
41
+ shader.uniforms[k] = u[k]; }
42
+ // ---------------------------------------------------------------------------- uniforms / config
43
+ /** Uniforms every sports object has (shared by all of its materials). */
44
+ export function createCommonUniforms() {
45
+ return {
46
+ uTime: { value: 0 },
47
+ uColor: { value: new THREE.Color('#ffffff') },
48
+ uSecondaryColor: { value: new THREE.Color('#ffffff') },
49
+ uAccentColor: { value: new THREE.Color('#ffffff') },
50
+ uWear: { value: 0.2 },
51
+ uGloss: { value: 1 },
52
+ uOpacity: { value: 1 },
53
+ uSeed: { value: 0 },
54
+ };
55
+ }
56
+ /** The base knobs every sports object shares (push point, part 1). `mats` = every lit material of the object. */
57
+ export function applyCommonConfig(u, c, mats = undefined) {
58
+ setColor(u.uColor, c.color);
59
+ setColor(u.uSecondaryColor, c.secondaryColor);
60
+ setColor(u.uAccentColor, c.accentColor);
61
+ if (c.wear !== undefined)
62
+ u.uWear.value = c.wear;
63
+ if (c.gloss !== undefined)
64
+ u.uGloss.value = c.gloss;
65
+ if (c.seed !== undefined)
66
+ u.uSeed.value = (c.seed % 1000) * 0.731;
67
+ if (c.opacity !== undefined) {
68
+ u.uOpacity.value = c.opacity;
69
+ if (mats)
70
+ for (const m of mats) {
71
+ m.opacity = c.opacity;
72
+ m.transparent = c.opacity < 0.999 || !!m.userData.alwaysTransparent;
73
+ }
74
+ }
75
+ }
76
+ /** Per-frame bookkeeping shared by every object. */
77
+ export function updateCommon(u, dt) { u.uTime.value += dt; }
78
+ // ---------------------------------------------------------------------------- GLSL: shared pieces
79
+ export const SPORT_UNIFORMS_GLSL = /* glsl */ `
80
+ uniform float uTime, uWear, uGloss, uOpacity, uSeed;
81
+ uniform vec3 uColor, uSecondaryColor, uAccentColor;
82
+ varying vec3 vObj;
83
+ varying vec3 vWorldPos;
84
+ `;
85
+ export const NOISE_GLSL_S = /* glsl */ `
86
+ float shash12(vec2 p) { vec3 q = fract(vec3(p.xyx) * 0.1031); q += dot(q, q.yzx + 33.33); return fract((q.x + q.y) * q.z); }
87
+ float shash13(vec3 p) { p = fract(p * 0.1031); p += dot(p, p.zyx + 31.32); return fract((p.x + p.y) * p.z); }
88
+ float snoise2(vec2 p) {
89
+ vec2 i = floor(p); vec2 f = fract(p); f = f * f * (3.0 - 2.0 * f);
90
+ return mix(mix(shash12(i), shash12(i + vec2(1, 0)), f.x), mix(shash12(i + vec2(0, 1)), shash12(i + vec2(1, 1)), f.x), f.y);
91
+ }
92
+ float snoise3(vec3 p) {
93
+ vec3 i = floor(p); vec3 f = fract(p); f = f * f * (3.0 - 2.0 * f);
94
+ return mix(mix(mix(shash13(i), shash13(i + vec3(1, 0, 0)), f.x), mix(shash13(i + vec3(0, 1, 0)), shash13(i + vec3(1, 1, 0)), f.x), f.y),
95
+ mix(mix(shash13(i + vec3(0, 0, 1)), shash13(i + vec3(1, 0, 1)), f.x), mix(shash13(i + vec3(0, 1, 1)), shash13(i + vec3(1, 1, 1)), f.x), f.y), f.z);
96
+ }
97
+ float sfbm2(vec2 p) { return (0.5 * snoise2(p) + 0.25 * snoise2(p * 2.03 + 7.1) + 0.125 * snoise2(p * 4.07 + 3.3)) / 0.875; }
98
+ float sfbm3(vec3 p) { return (0.5 * snoise3(p) + 0.25 * snoise3(p * 2.03 + 7.1) + 0.125 * snoise3(p * 4.07 + 3.3)) / 0.875; }
99
+ // anti-aliased line coverage of a signed distance d (metres) for a line of width w
100
+ float aaLine(float d, float w) { float fw = fwidth(d) * 0.75 + 1e-5; return 1.0 - smoothstep(w * 0.5 - fw, w * 0.5 + fw, abs(d)); }
101
+ float aaStep(float d) { float fw = fwidth(d) * 0.75 + 1e-5; return smoothstep(-fw, fw, d); }
102
+ float sdSeg(vec2 p, vec2 a, vec2 b) { vec2 pa = p - a, ba = b - a; float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); return length(pa - ba * h); }
103
+ float sdBox2(vec2 p, vec2 b) { vec2 d = abs(p) - b; return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); }
104
+ // distance to the outline of a box / circle / arc (for line marking)
105
+ float lnBox(vec2 p, vec2 c, vec2 hb) { return abs(sdBox2(p - c, hb)); }
106
+ float lnCircle(vec2 p, vec2 c, float r) { return abs(length(p - c) - r); }
107
+ // arc of a circle between angles a0..a1 (radians, counter-clockwise from +x); outside the span, distance to the end points
108
+ float lnArc(vec2 p, vec2 c, float r, float a0, float a1) {
109
+ vec2 q = p - c; float a = atan(q.y, q.x);
110
+ float mid = 0.5 * (a0 + a1), hw = 0.5 * (a1 - a0);
111
+ float da = mod(a - mid + 3.14159265, 6.2831853) - 3.14159265;
112
+ if (abs(da) <= hw) return abs(length(q) - r);
113
+ vec2 e0 = c + r * vec2(cos(a0), sin(a0)), e1 = c + r * vec2(cos(a1), sin(a1));
114
+ return min(length(p - e0), length(p - e1));
115
+ }
116
+ `;
117
+ export const WEAR_GLSL = /* glsl */ `
118
+ // Scuffs: two fbm layers thresholded by uWear, 0 pristine .. 1 heavily worn; focus (0..1) raises the wear
119
+ // where the play is (a key, a baseline, the sweet spot) so it never looks like uniform noise.
120
+ float scuffs(vec3 p, float scale, float focus) {
121
+ float n = sfbm3(p * scale + uSeed * 7.0) * 0.65 + sfbm3(p * scale * 4.3 + 13.0) * 0.35;
122
+ float t = 1.0 - clamp(uWear * (0.55 + 0.9 * focus), 0.0, 1.0);
123
+ return smoothstep(t - 0.05, t + 0.3, n) * clamp(uWear * 2.0, 0.0, 1.0);
124
+ }
125
+ // a height field as a bump, from screen-space derivatives (no tangent frame needed)
126
+ vec3 bumpNormal(vec3 normal, float h) {
127
+ vec3 dpdx = dFdx(-vViewPosition), dpdy = dFdy(-vViewPosition);
128
+ float dhx = dFdx(h), dhy = dFdy(h);
129
+ vec3 r1 = cross(dpdy, normal), r2 = cross(normal, dpdx);
130
+ float det = dot(dpdx, r1);
131
+ vec3 grad = (r1 * dhx + r2 * dhy) / max(abs(det), 1e-7) * sign(det);
132
+ return normalize(normal - grad);
133
+ }
134
+ `;
135
+ const COMMON_VERT_PARS = /* glsl */ `
136
+ varying vec3 vObj;
137
+ varying vec3 vWorldPos;
138
+ `;
139
+ /**
140
+ * A lit material (MeshStandardMaterial) with the prop's code injected, plus its depth twin for
141
+ * shadows (same vertex displacement, same alpha cut). Hooks (GLSL strings):
142
+ * pars fragment declarations (functions, uniforms of `own`)
143
+ * color sets `diffuseColor.rgb` (and `.a` for cut-outs); runs at map_fragment
144
+ * rough expression for roughnessFactor (default o.roughness)
145
+ * metal expression for metalnessFactor
146
+ * normal code that may reassign `normal` (after normal_fragment_maps)
147
+ * emissive code adding to totalEmissiveRadiance
148
+ * vPars / vCode vertex declarations / code after begin_vertex (may edit `transformed`, `objectNormal` is already read)
149
+ * depthBias [factor, units] polygon offset for a surface laid on other ground surfaces
150
+ * Every material gets vObj (object-space position) and vWorldPos.
151
+ */
152
+ export function litMaterial(u, o) {
153
+ const own = o.own || {};
154
+ const mat = new THREE.MeshStandardMaterial({
155
+ color: 0xffffff, roughness: o.roughness ?? 0.7, metalness: o.metalness ?? 0, envMapIntensity: o.envMapIntensity ?? 0.6,
156
+ side: o.side ?? THREE.FrontSide, transparent: !!o.transparent, alphaTest: o.alphaTest ?? 0, depthWrite: o.depthWrite ?? true,
157
+ });
158
+ if (o.transparent)
159
+ mat.userData.alwaysTransparent = true;
160
+ // A ground surface laid on other ground surfaces (a court on a plaza, a park lawn, a road) sits only
161
+ // centimetres above them; urban paving already pulls itself toward the camera in depth (polygon offset
162
+ // -2 / -4) to beat the terrain, so at a distance it would beat the court too. `depthBias` [factor, units]
163
+ // pulls this surface further forward — depth only, nothing moves on screen.
164
+ if (o.depthBias) {
165
+ mat.polygonOffset = true;
166
+ mat.polygonOffsetFactor = o.depthBias[0];
167
+ mat.polygonOffsetUnits = o.depthBias[1];
168
+ }
169
+ mat.defines = { USE_UV: '' };
170
+ mat.userData.uniforms = own;
171
+ const vert = (shader) => {
172
+ injectUniforms(shader, u);
173
+ injectUniforms(shader, own);
174
+ shader.vertexShader = shader.vertexShader
175
+ .replace('#include <common>', `#include <common>\n${COMMON_VERT_PARS}\n${SPORT_UNIFORMS_GLSL.replace(/varying[^\n]*\n/g, '')}\n${o.vPars || ''}`)
176
+ .replace('#include <begin_vertex>', `#include <begin_vertex>\n vObj = position;\n ${o.vCode || ''}\n vWorldPos = (modelMatrix * vec4(transformed, 1.0)).xyz;`);
177
+ };
178
+ mat.onBeforeCompile = (shader) => {
179
+ vert(shader);
180
+ shader.fragmentShader = shader.fragmentShader
181
+ .replace('#include <common>', `#include <common>\n${SPORT_UNIFORMS_GLSL}\n${NOISE_GLSL_S}\n${WEAR_GLSL}\n${o.pars || ''}`)
182
+ .replace('#include <map_fragment>', `${o.color || ''}\n float propRough = ${o.rough ? '(' + o.rough + ')' : 'roughness'};\n float propMetal = ${o.metal ? '(' + o.metal + ')' : 'metalness'};`)
183
+ .replace('#include <roughnessmap_fragment>', '#include <roughnessmap_fragment>\n roughnessFactor = clamp(propRough, 0.03, 1.0);')
184
+ .replace('#include <metalnessmap_fragment>', '#include <metalnessmap_fragment>\n metalnessFactor = propMetal;')
185
+ .replace('#include <normal_fragment_maps>', `#include <normal_fragment_maps>\n ${o.normal || ''}`)
186
+ .replace('#include <emissivemap_fragment>', `#include <emissivemap_fragment>\n ${o.emissive || ''}`);
187
+ };
188
+ mat.customProgramCacheKey = () => 'sport-' + (o.name || 'lit');
189
+ const depth = new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking, side: o.side ?? THREE.FrontSide });
190
+ depth.defines = { USE_UV: '' };
191
+ if (o.alphaTest || o.transparent)
192
+ depth.alphaTest = o.depthAlphaTest ?? 0.5;
193
+ depth.onBeforeCompile = (shader) => {
194
+ vert(shader);
195
+ if (o.depthAlpha) {
196
+ shader.fragmentShader = shader.fragmentShader
197
+ .replace('#include <common>', `#include <common>\n${SPORT_UNIFORMS_GLSL}\n${NOISE_GLSL_S}\n${o.pars || ''}`)
198
+ .replace('#include <map_fragment>', `${o.depthAlpha}`);
199
+ }
200
+ };
201
+ depth.customProgramCacheKey = () => 'sport-' + (o.name || 'lit') + '-depth';
202
+ return { material: mat, depth };
203
+ }
204
+ /** A prop mesh: casts and receives shadows, picks normally (props are clickable scene objects). */
205
+ export function propMesh(geo, pair, name = '', opts = undefined) {
206
+ const o = Object.assign({ cast: true, receive: true, cull: true }, opts);
207
+ const mesh = new THREE.Mesh(geo, pair.material);
208
+ mesh.customDepthMaterial = pair.depth;
209
+ mesh.castShadow = o.cast;
210
+ mesh.receiveShadow = o.receive;
211
+ mesh.frustumCulled = o.cull;
212
+ mesh.name = name || '';
213
+ return mesh;
214
+ }
215
+ export function disposePair(pair) { pair.material.dispose(); pair.depth.dispose(); }
216
+ // ---------------------------------------------------------------------------- geometry helpers
217
+ /** Concatenate geometries (made non-indexed) that share a material. */
218
+ export function mergeGeos(list) {
219
+ const parts = list.map((g) => (g.index ? g.toNonIndexed() : g));
220
+ let n = 0;
221
+ for (const g of parts)
222
+ n += g.attributes.position.count;
223
+ const pos = new Float32Array(n * 3), nrm = new Float32Array(n * 3), uv = new Float32Array(n * 2);
224
+ let off = 0;
225
+ for (const g of parts) {
226
+ const c = g.attributes.position.count;
227
+ pos.set(g.attributes.position.array, off * 3);
228
+ if (g.attributes.normal)
229
+ nrm.set(g.attributes.normal.array, off * 3);
230
+ if (g.attributes.uv)
231
+ uv.set(g.attributes.uv.array, off * 2);
232
+ off += c;
233
+ }
234
+ const out = new THREE.BufferGeometry();
235
+ out.setAttribute('position', new THREE.BufferAttribute(pos, 3));
236
+ out.setAttribute('normal', new THREE.BufferAttribute(nrm, 3));
237
+ out.setAttribute('uv', new THREE.BufferAttribute(uv, 2));
238
+ for (const g of parts)
239
+ if (!list.includes(g))
240
+ g.dispose();
241
+ out.computeBoundingSphere();
242
+ out.computeBoundingBox();
243
+ return out;
244
+ }
245
+ /** Apply a matrix (position / rotation / scale) to a geometry in place and return it. */
246
+ export function placed(geo, pos = undefined, rotEuler = undefined, scale = undefined) {
247
+ const m = new THREE.Matrix4();
248
+ const q = new THREE.Quaternion();
249
+ if (rotEuler)
250
+ q.setFromEuler(new THREE.Euler(rotEuler[0] || 0, rotEuler[1] || 0, rotEuler[2] || 0));
251
+ m.compose(new THREE.Vector3(...(pos || [0, 0, 0])), q, new THREE.Vector3(...(scale || [1, 1, 1])));
252
+ geo.applyMatrix4(m);
253
+ return geo;
254
+ }
255
+ /** Lathe from a profile of [radius, y] pairs (bottom → top). */
256
+ export function lathe(profile, segs = 32) {
257
+ const pts = profile.map(([r, y]) => new THREE.Vector2(r, y));
258
+ const g = new THREE.LatheGeometry(pts, segs || 32);
259
+ return g;
260
+ }
261
+ /** A tube along points (metres), round section. */
262
+ export function tube(points, radius, segs = 32, radial = 10, closed = false) {
263
+ const curve = new THREE.CatmullRomCurve3(points.map((p) => new THREE.Vector3(...p)), !!closed, 'catmullrom', 0.5);
264
+ return new THREE.TubeGeometry(curve, segs || 32, radius, radial || 10, !!closed);
265
+ }
266
+ /** A straight cylinder between two points. */
267
+ export function bar(a, b, r, radial = 12) {
268
+ const A = new THREE.Vector3(...a), B = new THREE.Vector3(...b);
269
+ const len = A.distanceTo(B);
270
+ const g = new THREE.CylinderGeometry(r, r, len, radial || 12, 1);
271
+ const mid = A.clone().add(B).multiplyScalar(0.5);
272
+ const dir = B.clone().sub(A).normalize();
273
+ const q = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir);
274
+ g.applyQuaternion(q);
275
+ g.translate(mid.x, mid.y, mid.z);
276
+ return g;
277
+ }
278
+ export function box(w, h, d, pos = undefined, rot = undefined) { return placed(new THREE.BoxGeometry(w, h, d), pos, rot); }
279
+ // ---------------------------------------------------------------------------- the material library
280
+ /**
281
+ * The surfaces equipment, goals and fixtures are made of, one shader with a per-part kind:
282
+ * 0 paint smooth coat with a faint orange peel, chips where worn
283
+ * 1 wood grain along the part's own long axis (uGrainAxis), ash / maple tones, scuffed where worn
284
+ * 2 metal brushed (anisotropic streaks along the axis), fingerprints where worn, metallic
285
+ * 3 rubber matte with a fine stipple, lighter where worn
286
+ * 4 leather pebble grain bump, darker creases, polished where handled
287
+ * 5 carbon twill weave, glossy clear coat
288
+ * 6 plastic glossy, slight flow lines
289
+ * 7 fabric felt / cloth, soft nap (no specular)
290
+ * Colour: `slot` 0 color · 1 secondaryColor · 2 accentColor · 3 the part's own uTint (natural material colour).
291
+ * A multiplier uTint always applies (white = as is).
292
+ */
293
+ export const PART_KINDS = { paint: 0, wood: 1, metal: 2, rubber: 3, leather: 4, carbon: 5, plastic: 6, fabric: 7 };
294
+ export function partMaterial(u, o = undefined) {
295
+ const opt = Object.assign({ kind: 'paint', slot: 3, tint: '#cccccc', roughness: 0.5, metalness: 0, axis: [1, 0, 0], scale: 1 }, o);
296
+ const own = {
297
+ uKind: { value: PART_KINDS[opt.kind] ?? 0 },
298
+ uSlot: { value: opt.slot },
299
+ uTint: { value: new THREE.Color(opt.tint) },
300
+ uGrainAxis: { value: new THREE.Vector3(...opt.axis).normalize() },
301
+ uPartScale: { value: opt.scale },
302
+ };
303
+ const pair = litMaterial(u, {
304
+ name: 'part-' + opt.kind,
305
+ own, roughness: opt.roughness, metalness: opt.metalness, envMapIntensity: opt.kind === 'metal' ? 1.0 : 0.6,
306
+ pars: /* glsl */ `
307
+ uniform float uKind, uSlot, uPartScale;
308
+ uniform vec3 uTint, uGrainAxis;
309
+ vec3 slotColor() {
310
+ vec3 c = uSlot < 0.5 ? uColor : uSlot < 1.5 ? uSecondaryColor : uSlot < 2.5 ? uAccentColor : uTint;
311
+ return uSlot < 2.5 ? c * uTint : c;
312
+ }
313
+ // coordinates along / across the part's grain axis, in metres
314
+ vec2 grainUV(vec3 p) {
315
+ vec3 a = uGrainAxis; vec3 b = normalize(abs(a.y) < 0.9 ? cross(a, vec3(0, 1, 0)) : cross(a, vec3(1, 0, 0)));
316
+ vec3 c = cross(a, b);
317
+ return vec2(dot(p, a), dot(p, b) + dot(p, c) * 0.37);
318
+ }
319
+ float partHeight; float partRough; float partMetalF; vec3 partCol;
320
+ void partSurface(vec3 p) {
321
+ vec3 base = slotColor();
322
+ vec2 g = grainUV(p) / uPartScale;
323
+ float worn = scuffs(p, 28.0 / uPartScale, 0.6);
324
+ partHeight = 0.0; partRough = roughness; partMetalF = metalness; partCol = base;
325
+ if (uKind < 0.5) { // paint: orange peel, chips show primer
326
+ float peel = snoise3(p * 900.0) * 0.5 + 0.5;
327
+ partHeight = peel * 0.00004;
328
+ partCol = mix(base, base * 0.55 + 0.2, worn * 0.8);
329
+ partRough = mix(0.25, 0.6, worn) + peel * 0.05;
330
+ } else if (uKind < 1.5) { // wood: rings as long streaks along the axis, pores, darker where oiled by hands
331
+ float ring = sfbm2(vec2(g.x * 6.0, g.y * 90.0));
332
+ float pores = snoise2(vec2(g.x * 400.0, g.y * 1400.0));
333
+ float grain = smoothstep(0.35, 0.75, ring) * 0.7 + pores * 0.15;
334
+ partCol = base * mix(vec3(1.08, 1.03, 0.95), vec3(0.72, 0.6, 0.48), grain);
335
+ partCol = mix(partCol, partCol * 0.6, worn * 0.7);
336
+ partHeight = grain * 0.00006;
337
+ partRough = mix(0.55, 0.85, grain) + worn * 0.1;
338
+ } else if (uKind < 2.5) { // metal: brushed streaks, dull where handled
339
+ float streak = snoise2(vec2(g.x * 60.0, g.y * 2500.0));
340
+ partCol = base * (0.92 + 0.16 * streak);
341
+ partRough = mix(0.28, 0.5, streak * 0.5 + worn * 0.5);
342
+ partMetalF = 1.0 - worn * 0.3;
343
+ partHeight = streak * 0.00002;
344
+ } else if (uKind < 3.5) { // rubber: stipple, polished where worn
345
+ float st = snoise3(p * 1500.0);
346
+ partCol = mix(base, base * 1.6 + 0.08, worn * 0.5);
347
+ partHeight = st * 0.00005;
348
+ partRough = mix(0.9, 0.55, worn);
349
+ } else if (uKind < 4.5) { // leather: pebble grain, creases
350
+ float peb = snoise3(p * 700.0) * 0.6 + snoise3(p * 2100.0) * 0.4;
351
+ float crease = smoothstep(0.55, 0.75, sfbm3(p * 90.0 + 3.0));
352
+ partCol = base * (0.85 + 0.3 * peb) * (1.0 - 0.25 * crease);
353
+ partCol = mix(partCol, partCol * 1.25, worn * 0.5);
354
+ partHeight = peb * 0.00012;
355
+ partRough = mix(0.65, 0.35, worn) - 0.1 * crease;
356
+ } else if (uKind < 5.5) { // carbon: 2x2 twill weave under a clear coat
357
+ vec2 w = g * 700.0;
358
+ float tw = step(0.5, fract((floor(w.x) + floor(w.y)) * 0.5));
359
+ float fib = mix(abs(fract(w.x) - 0.5), abs(fract(w.y) - 0.5), tw) * 2.0;
360
+ partCol = base * mix(0.55, 1.15, 1.0 - fib * fib) ;
361
+ partRough = 0.18 + worn * 0.25;
362
+ partHeight = (1.0 - fib) * 0.00001;
363
+ } else if (uKind < 6.5) { // plastic: glossy with flow lines
364
+ float fl = snoise2(g * vec2(30.0, 400.0));
365
+ partCol = base * (0.96 + 0.08 * fl);
366
+ partCol = mix(partCol, partCol * 0.8 + 0.1, worn * 0.6);
367
+ partRough = 0.22 + worn * 0.3;
368
+ } else { // fabric / felt: soft nap, no shine
369
+ float nap = snoise3(p * 2500.0);
370
+ partCol = base * (0.9 + 0.2 * nap);
371
+ partCol = mix(partCol, partCol * 0.7, worn * 0.5);
372
+ partRough = 1.0;
373
+ partHeight = nap * 0.00008;
374
+ }
375
+ partRough = clamp(partRough / max(uGloss, 0.05), 0.03, 1.0);
376
+ }`,
377
+ color: /* glsl */ `partSurface(vObj);\n diffuseColor.rgb *= partCol;`,
378
+ rough: 'partRough', metal: 'partMetalF',
379
+ normal: /* glsl */ `normal = bumpNormal(normal, partHeight);`,
380
+ });
381
+ return pair;
382
+ }
383
+ // ---------------------------------------------------------------------------- nets
384
+ /**
385
+ * A net: knotted square mesh drawn as anti-aliased threads (alpha-cut, with a translucent far field so a
386
+ * distant net reads as a veil instead of shimmering), on a ruled surface spanned between two frame
387
+ * curves (netRuled). The sag is a uniform (so netTension animates): every vertex moves along its own
388
+ * aSag (direction × metres, set at build: out of the goal, down for a roof) by
389
+ * uSag × 4s(1−s) × profile(t) profile: hanging (bottom free) 1−t · held on both edges 4t(1−t)
390
+ * where s runs along the frame curves and t from the first curve to the second.
391
+ */
392
+ export function netMaterial(u, o = undefined) {
393
+ const opt = Object.assign({ mesh: 0.12, thread: 0.004, sag: 0.1, sagMode: 1, slot: 1 }, o);
394
+ const own = {
395
+ uMesh: { value: opt.mesh }, uThread: { value: opt.thread }, uSag: { value: opt.sag }, uSagMode: { value: opt.sagMode },
396
+ uNetSlot: { value: opt.slot },
397
+ };
398
+ const pars = /* glsl */ `
399
+ uniform float uMesh, uThread, uNetSlot;
400
+ float netCover(vec2 m) {
401
+ // square mesh with knots at the crossings; m in metres
402
+ vec2 c = m / uMesh;
403
+ vec2 d = abs(fract(c) - 0.5) * uMesh; // distance to the nearest thread in each direction
404
+ float w = uThread;
405
+ float fw = max(fwidth(m.x), fwidth(m.y)) * 0.7 + 1e-5;
406
+ float tx = 1.0 - smoothstep(w * 0.5 - fw, w * 0.5 + fw, d.x);
407
+ float ty = 1.0 - smoothstep(w * 0.5 - fw, w * 0.5 + fw, d.y);
408
+ float knot = 1.0 - smoothstep(w * 0.9 - fw, w * 0.9 + fw, length(d));
409
+ float cover = max(max(tx, ty), knot);
410
+ // far field: when a cell is a few pixels the threads can't be resolved — fade to the mean coverage
411
+ float far = smoothstep(0.08, 0.5, fw / uMesh);
412
+ // two thread directions plus knots, seen a little obliquely: reads denser than the bare 2w/mesh
413
+ float mean = clamp(2.6 * w / uMesh, 0.0, 0.85);
414
+ return mix(cover, mean, far);
415
+ }`;
416
+ const pair = litMaterial(u, {
417
+ name: 'net', own, roughness: 0.85, metalness: 0, side: THREE.DoubleSide, transparent: true, alphaTest: 0.02, depthWrite: true,
418
+ vPars: /* glsl */ `uniform float uSag, uSagMode; attribute vec2 aT; attribute vec3 aSag;`,
419
+ vCode: /* glsl */ `
420
+ {
421
+ float across = 4.0 * aT.x * (1.0 - aT.x);
422
+ float prof = uSagMode < 0.5 ? (1.0 - aT.y) : 4.0 * aT.y * (1.0 - aT.y);
423
+ transformed += aSag * (uSag * across * prof);
424
+ }`,
425
+ pars,
426
+ color: /* glsl */ `
427
+ float cover = netCover(vUv);
428
+ vec3 nc = uNetSlot < 0.5 ? uColor : uNetSlot < 1.5 ? uSecondaryColor : uAccentColor;
429
+ float dirt = scuffs(vWorldPos, 3.0, 0.3);
430
+ diffuseColor.rgb *= mix(nc, nc * 0.6, dirt * 0.6);
431
+ diffuseColor.a *= cover;`,
432
+ depthAlpha: /* glsl */ `diffuseColor.a *= netCover(vUv);`,
433
+ rough: '0.9',
434
+ });
435
+ pair.material.userData.net = true;
436
+ return pair;
437
+ }
438
+ /**
439
+ * A net surface spanned between two curves A(s) and B(s), s in 0..1 (both [x, y, z] in metres): the
440
+ * vertex at (s, t) is A(s)·(1−t) + B(s)·t. uv in metres (arc length along the mid curve, distance
441
+ * across) so the mesh cells stay square on any trapezoid or curved strip. sag(s) → [x, y, z]: the
442
+ * direction × metres this vertex bellies at netTension 0.
443
+ */
444
+ export function netRuled(A, B, nS, nT, sag) {
445
+ const pos = [], uv = [], at = [], sg = [], idx = [];
446
+ // arc length of the mid curve for u
447
+ const mids = [], acc = [0];
448
+ for (let i = 0; i <= nS; i++) {
449
+ const s = i / nS, a = A(s), b = B(s);
450
+ mids.push([(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2]);
451
+ }
452
+ for (let i = 1; i <= nS; i++)
453
+ acc.push(acc[i - 1] + Math.hypot(mids[i][0] - mids[i - 1][0], mids[i][1] - mids[i - 1][1], mids[i][2] - mids[i - 1][2]));
454
+ for (let i = 0; i <= nS; i++) {
455
+ const s = i / nS, a = A(s), b = B(s), g = sag(s);
456
+ const span = Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
457
+ for (let j = 0; j <= nT; j++) {
458
+ const t = j / nT;
459
+ pos.push(a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t);
460
+ uv.push(acc[i], span * t);
461
+ at.push(s, t);
462
+ sg.push(g[0], g[1], g[2]);
463
+ }
464
+ }
465
+ for (let i = 0; i < nS; i++)
466
+ for (let j = 0; j < nT; j++) {
467
+ const k = i * (nT + 1) + j;
468
+ idx.push(k, k + nT + 1, k + 1, k + 1, k + nT + 1, k + nT + 2);
469
+ }
470
+ const g = new THREE.BufferGeometry();
471
+ g.setAttribute('position', new THREE.Float32BufferAttribute(pos, 3));
472
+ g.setAttribute('uv', new THREE.Float32BufferAttribute(uv, 2));
473
+ g.setAttribute('aT', new THREE.Float32BufferAttribute(at, 2));
474
+ g.setAttribute('aSag', new THREE.Float32BufferAttribute(sg, 3));
475
+ g.setIndex(idx);
476
+ g.computeVertexNormals();
477
+ g.computeBoundingSphere();
478
+ g.computeBoundingBox();
479
+ return g;
480
+ }
481
+ const line3 = (p, q) => (s) => [p[0] + (q[0] - p[0]) * s, p[1] + (q[1] - p[1]) * s, p[2] + (q[2] - p[2]) * s];
482
+ /** One net mesh = one net material (each net has its own sag share and mode). */
483
+ export function netMesh(geo, pair) {
484
+ const m = new THREE.Mesh(geo, pair.material);
485
+ m.customDepthMaterial = pair.depth;
486
+ m.castShadow = true;
487
+ m.receiveShadow = false;
488
+ return m;
489
+ }
490
+ // ---------------------------------------------------------------------------- goals (shared by `goal` and a court's fixtures)
491
+ export const GOAL_KINDS = ['footballGoal', 'futsalGoal', 'handballGoal', 'hockeyNet', 'basketballHoop', 'volleyballNet', 'tennisNet', 'badmintonNet', 'rugbyPosts', 'americanUprights'];
492
+ /** Natural colours of every goal / net kind, [posts / frame, mesh, tapes / pads / board frame], and its default mesh size (m). */
493
+ export const GOAL_COLORS = {
494
+ footballGoal: { colors: ['#f4f4f2', '#f2f2f2', '#f4f4f2'], mesh: 0.12 },
495
+ futsalGoal: { colors: ['#f4f4f2', '#f2f2f2', '#f4f4f2'], mesh: 0.1 },
496
+ handballGoal: { colors: ['#f4f4f2', '#f2f2f2', '#d13b2a'], mesh: 0.1 },
497
+ hockeyNet: { colors: ['#d12f2f', '#f2f2f2', '#f2f2f2'], mesh: 0.045 },
498
+ basketballHoop: { colors: ['#b9bcc0', '#f2f2f2', '#f2f2f2'], mesh: 0.06 },
499
+ volleyballNet: { colors: ['#b9bcc0', '#1a1a1a', '#f2f2f2'], mesh: 0.1 },
500
+ tennisNet: { colors: ['#2a2a2a', '#1a1a1a', '#f2f2f2'], mesh: 0.045 },
501
+ badmintonNet: { colors: ['#2a2a2a', '#4a2a1e', '#f2f2f2'], mesh: 0.019 },
502
+ pickleballNet: { colors: ['#2a2a2a', '#1a1a1a', '#f2f2f2'], mesh: 0.04 },
503
+ padelNet: { colors: ['#2a2a2a', '#1a1a1a', '#f2f2f2'], mesh: 0.045 },
504
+ tableTennisNet: { colors: ['#2a2a2a', '#1f3f86', '#f2f2f2'], mesh: 0.012 },
505
+ rugbyPosts: { colors: ['#f4f4f2', '#f2f2f2', '#1d4f9c'], mesh: 0.1 },
506
+ americanUprights: { colors: ['#f2c230', '#f2f2f2', '#1d4f9c'], mesh: 0.1 },
507
+ };
508
+ /**
509
+ * Builds a goal / net / hoop at the origin (the goal line, the net's centre, the hoop's base on the
510
+ * ground), facing +z (the play side). Returns { object, mats, nets, apply(cfg), dispose }.
511
+ * Dimensions are the rulebook ones. Materials: posts = slot 0 (color), nets = slot 1, pads / boards /
512
+ * bands = slot 2.
513
+ */
514
+ export function buildGoal(kind, u, o = undefined) {
515
+ const opt = Object.assign({ mesh: undefined, net: true, quality: 1, sag: 0.3 }, o);
516
+ const object = new THREE.Group();
517
+ const mats = [], nets = [], geos = [];
518
+ const paint = (slot, kindName = undefined, extra = undefined) => { const p = partMaterial(u, Object.assign({ kind: kindName || 'paint', slot, tint: '#ffffff', roughness: 0.35 }, extra)); mats.push(p); return p; };
519
+ const addMesh = (geo, pair, name = '') => { geos.push(geo); const m = propMesh(geo, pair, name); object.add(m); return m; };
520
+ // np: { mesh, thread, k (this net's share of the sag at netTension 0) }; mode 0 hanging (bottom free), 1 held on all edges
521
+ const addNet = (geo, np, mode = undefined) => {
522
+ if (!opt.net) {
523
+ geo.dispose();
524
+ return null;
525
+ }
526
+ const k = np.k ?? 1;
527
+ const pair = netMaterial(u, { mesh: np.mesh, thread: np.thread, sag: opt.sag * k, sagMode: mode ?? 1, slot: 1 });
528
+ mats.push(pair);
529
+ geos.push(geo);
530
+ const m = netMesh(geo, pair);
531
+ m.userData.sagK = k;
532
+ object.add(m);
533
+ nets.push(m);
534
+ return m;
535
+ };
536
+ const R = 10 + Math.round(6 * opt.quality);
537
+ const nq = (len) => Math.max(4, Math.round(len * 4 * opt.quality)); // net segments for a length
538
+ /**
539
+ * The nets of a box goal (football, futsal, handball), spanned between the frame's own bars:
540
+ * posts (±W/2, 0..H, 0), crossbar at H, ground bars to the back bar (x, 0, −D), top-rear bar at
541
+ * (x, Hb, −D), stanchions from the crossbar corners down to it. Back and roof belly out / down,
542
+ * the sides belly outward; every edge sits on a bar.
543
+ */
544
+ const boxGoalNets = (W, H, D, Hb, np) => {
545
+ const x0 = -W / 2, x1 = W / 2;
546
+ addNet(netRuled(line3([x0, 0, -D], [x1, 0, -D]), line3([x0, Hb, -D], [x1, Hb, -D]), nq(W), nq(Hb), () => [0, 0, -0.35]), np, 1); // back
547
+ addNet(netRuled(line3([x0, H, 0], [x1, H, 0]), line3([x0, Hb, -D], [x1, Hb, -D]), nq(W), nq(D), () => [0, -0.25, 0]), np, 1); // roof
548
+ for (const x of [x0, x1]) {
549
+ const out = Math.sign(x) * 0.2;
550
+ addNet(netRuled(line3([x, 0, 0], [x, 0, -D]), line3([x, H, 0], [x, Hb, -D]), nq(D), nq(H), () => [out, 0, 0]), np, 1); // side
551
+ }
552
+ };
553
+ const boxGoalFrame = (W, H, D, Hb, r) => {
554
+ const rb = r * 0.5;
555
+ return [
556
+ bar([-W / 2, rb, 0], [-W / 2, rb, -D], rb, 8), bar([W / 2, rb, 0], [W / 2, rb, -D], rb, 8), bar([-W / 2, rb, -D], [W / 2, rb, -D], rb, 8),
557
+ bar([-W / 2, Hb, -D], [W / 2, Hb, -D], rb, 8), bar([-W / 2, rb, -D], [-W / 2, Hb, -D], rb, 8), bar([W / 2, rb, -D], [W / 2, Hb, -D], rb, 8),
558
+ bar([-W / 2, H, 0], [-W / 2, Hb, -D], rb, 8), bar([W / 2, H, 0], [W / 2, Hb, -D], rb, 8),
559
+ ];
560
+ };
561
+ const frameGoal = (W, H, D, r, postPair, meshSize) => {
562
+ const Hb = H * 0.8;
563
+ const parts = [bar([-W / 2, 0, 0], [-W / 2, H + r, 0], r, R), bar([W / 2, 0, 0], [W / 2, H + r, 0], r, R), bar([-W / 2 - r, H + r, 0], [W / 2 + r, H + r, 0], r, R)];
564
+ addMesh(mergeGeos(parts.concat(boxGoalFrame(W, H, D, Hb, r))), postPair, 'frame');
565
+ boxGoalNets(W, H, D, Hb, { mesh: meshSize, thread: Math.min(0.005, meshSize * 0.045) });
566
+ };
567
+ /**
568
+ * A net across a court, along x at z = 0 (the court builder turns it across the court). n:
569
+ * posts distance between the posts (m) netW net length (≤ posts; cables run on to the posts)
570
+ * top height of the top of the net at the posts dip how much lower it is at the centre (tennis 1.07 → 0.914)
571
+ * depth net depth (top to bottom edge) grounded the bottom edge rests on the ground (tennis, pickleball, padel)
572
+ * band top tape height strap centre strap (tennis, pickleball, padel)
573
+ * antennae volleyball antennae + side bands at ±antennae
574
+ * The top edge, its tape and the mesh follow the dip as a parabola between the posts.
575
+ */
576
+ const netOnPosts = (n, meshSize, postPair, bandPair) => {
577
+ const L = n.posts, W = n.netW ?? n.posts, Htop = n.top, dip = n.dip ?? 0, D = n.depth ?? n.top, bandH = n.band, postR = n.postR;
578
+ const topAt = (x) => Htop - dip * (1 - (2 * x / W) ** 2); // x in [-W/2, W/2]
579
+ const bottomAt = (x) => (n.grounded ? 0 : topAt(x) - D);
580
+ addMesh(mergeGeos([bar([-L / 2, 0, 0], [-L / 2, Htop + 0.03, 0], postR, R), bar([L / 2, 0, 0], [L / 2, Htop + 0.03, 0], postR, R)]), postPair, 'posts');
581
+ // cables from the net's ends to the posts (only when the net is shorter than the post span)
582
+ const cables = [];
583
+ if (L - W > 0.05)
584
+ for (const sx of [-1, 1]) {
585
+ cables.push(bar([sx * W / 2, Htop - bandH / 2, 0], [sx * L / 2, Htop - bandH / 2, 0], 0.003, 6));
586
+ if (!n.grounded)
587
+ cables.push(bar([sx * W / 2, Htop - D + 0.02, 0], [sx * L / 2, Htop - D + 0.02, 0], 0.003, 6));
588
+ }
589
+ if (cables.length)
590
+ addMesh(mergeGeos(cables), postPair, 'cables');
591
+ // tapes: the top band follows the dip (short boxes along the parabola), a bottom band on hanging nets, the centre strap
592
+ const tape = (yAt, h, thick) => {
593
+ const seg = 32, parts = [];
594
+ for (let i = 0; i < seg; i++) {
595
+ const x0 = -W / 2 + W * i / seg, x1 = -W / 2 + W * (i + 1) / seg;
596
+ const y0 = yAt(x0), y1 = yAt(x1), len = Math.hypot(x1 - x0, y1 - y0);
597
+ parts.push(box(len + 0.002, h, thick, [(x0 + x1) / 2, (y0 + y1) / 2 - h / 2, 0], [0, 0, Math.atan2(y1 - y0, x1 - x0)]));
598
+ }
599
+ return mergeGeos(parts);
600
+ };
601
+ const bands = [tape(topAt, bandH, 0.012)];
602
+ if (!n.grounded)
603
+ bands.push(tape((x) => bottomAt(x) + bandH * 0.6, bandH * 0.6, 0.01));
604
+ if (n.strap)
605
+ bands.push(box(0.05, topAt(0), 0.014, [0, topAt(0) / 2, 0]));
606
+ if (n.antennae)
607
+ for (const sx of [-1, 1])
608
+ bands.push(box(0.05, D, 0.013, [sx * n.antennae, Htop - D / 2, 0]));
609
+ addMesh(mergeGeos(bands), bandPair, 'bands');
610
+ if (n.antennae) {
611
+ // 1.8 m fibreglass rods, 0.8 m above the net, 10 cm red / white stripes
612
+ const red = [], white = [];
613
+ for (const sx of [-1, 1])
614
+ for (let i = 0; i < 18; i++) {
615
+ const y0 = Htop - D + i * 0.1, y1 = y0 + 0.1;
616
+ (i % 2 ? white : red).push(bar([sx * (n.antennae + 0.03), y0, 0], [sx * (n.antennae + 0.03), y1, 0], 0.005, 8));
617
+ }
618
+ addMesh(mergeGeos(red), paint(3, 'plastic', { tint: '#d13b2a', roughness: 0.4 }), 'antennaRed');
619
+ addMesh(mergeGeos(white), paint(3, 'plastic', { tint: '#f2f2f2', roughness: 0.4 }), 'antennaWhite');
620
+ }
621
+ if (opt.net) {
622
+ // the mesh: from the bottom edge to just under the top tape, along x at z = 0; the top follows
623
+ // the dip (grounded: stretched, the bottom stays on the ground; hanging: shifted down)
624
+ const h0 = (n.grounded ? Htop : D) - bandH * 0.5;
625
+ const y0 = n.grounded ? 0 : Htop - D;
626
+ const drop = (x) => dip * (1 - (2 * x / W) ** 2);
627
+ const A = (s) => { const x = -W / 2 + W * s; return [x, n.grounded ? 0 : y0 - drop(x), 0]; };
628
+ const B = (s) => { const x = -W / 2 + W * s; return [x, (n.grounded ? h0 : y0 + h0) - drop(x), 0]; };
629
+ addNet(netRuled(A, B, nq(W), nq(h0), () => [0, 0, -0.08 * Math.min(h0, 1)]), { mesh: meshSize, thread: n.thread ?? Math.min(0.0035, meshSize * 0.07), k: 0.6 }, 1);
630
+ }
631
+ };
632
+ if (kind === 'footballGoal')
633
+ frameGoal(7.32, 2.44, 2.0, 0.06, paint(0, 'paint', { tint: '#ffffff', roughness: 0.3 }), opt.mesh ?? 0.12);
634
+ else if (kind === 'futsalGoal')
635
+ frameGoal(3.0, 2.0, 1.0, 0.04, paint(0, 'paint', { tint: '#ffffff', roughness: 0.3 }), opt.mesh ?? 0.1);
636
+ else if (kind === 'handballGoal') {
637
+ // 3 × 2 m, posts and crossbar painted in 0.2 m bands of two colours (two meshes)
638
+ const W = 3, H = 2, D = 1.0, r = 0.04;
639
+ const p0 = paint(0, 'paint', { tint: '#ffffff' }), p1 = paint(2, 'paint', { tint: '#ffffff' });
640
+ const segsA = [], segsB = [];
641
+ const stripe = (a, b) => { const len = Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]); const n = Math.max(1, Math.round(len / 0.2)); for (let i = 0; i < n; i++) {
642
+ const t0 = i / n, t1 = (i + 1) / n;
643
+ const pa = a.map((v, k) => v + (b[k] - v) * t0), pb = a.map((v, k) => v + (b[k] - v) * t1);
644
+ (i % 2 ? segsB : segsA).push(bar(pa, pb, r, R));
645
+ } };
646
+ stripe([-W / 2, 0, 0], [-W / 2, H + r, 0]);
647
+ stripe([W / 2, 0, 0], [W / 2, H + r, 0]);
648
+ stripe([-W / 2 - r, H + r, 0], [W / 2 + r, H + r, 0]);
649
+ addMesh(mergeGeos(segsA), p0, 'frameA');
650
+ addMesh(mergeGeos(segsB), p1, 'frameB');
651
+ addMesh(mergeGeos(boxGoalFrame(W, H, D, H * 0.8, r)), p0, 'back');
652
+ boxGoalNets(W, H, D, H * 0.8, { mesh: opt.mesh ?? 0.1, thread: 0.0045 });
653
+ }
654
+ else if (kind === 'hockeyNet') {
655
+ // NHL: 1.83 wide, 1.22 high, 1.12 deep. Posts and crossbar at the goal line; a base frame on the ice
656
+ // curving back to its deepest point; an upper frame at 0.55 H over the same curve; the net spans
657
+ // crossbar → upper frame (roof), upper frame → base (back) and post → frame (the two sides).
658
+ const W = 1.83, H = 1.22, D = 1.12, r = 0.025, Hu = H * 0.55;
659
+ const zb = (s) => -D * (0.55 + 0.45 * Math.sin(Math.PI * s)); // depth of the curved back at s
660
+ const base = (s) => [-W / 2 + W * s, r, zb(s)], upper = (s) => [-W / 2 + W * s, Hu, zb(s)];
661
+ const curvePts = (f) => { const p = []; for (let i = 0; i <= 16; i++)
662
+ p.push(f(i / 16)); return p; };
663
+ const red = paint(0, 'paint', { tint: '#ffffff', roughness: 0.3 });
664
+ const fr = [tube(curvePts(base), r, 32, 8), tube(curvePts(upper), r * 0.8, 32, 8),
665
+ bar([-W / 2, 0, 0], [-W / 2, H, 0], r, R), bar([W / 2, 0, 0], [W / 2, H, 0], r, R), bar([-W / 2 - r, H, 0], [W / 2 + r, H, 0], r, R)];
666
+ for (const sx of [-1, 1]) {
667
+ fr.push(bar([sx * W / 2, r, 0], [sx * W / 2, r, -0.55 * D], r, 8)); // base side bar on the ice
668
+ fr.push(bar([sx * W / 2, r, -0.55 * D], [sx * W / 2, Hu, -0.55 * D], r * 0.8, 8)); // upright at the back corner
669
+ fr.push(bar([sx * W / 2, H, 0], [sx * W / 2, Hu, -0.55 * D], r * 0.8, 8)); // top side bar
670
+ }
671
+ fr.push(bar([0, H, 0], [0, Hu, -D], r * 0.6, 8));
672
+ addMesh(mergeGeos(fr), red, 'frame');
673
+ const np = { mesh: opt.mesh ?? 0.045, thread: 0.0025, k: 0.5 };
674
+ addNet(netRuled(base, upper, nq(W * 1.3), nq(Hu), (s) => { const x = -W / 2 + W * s, z = zb(s) + 0.3 * D, l = Math.hypot(x, z) || 1; return [0.15 * x / l, 0, 0.15 * z / l]; }), np, 1); // back, out of the goal
675
+ addNet(netRuled(line3([-W / 2, H, 0], [W / 2, H, 0]), upper, nq(W), nq(D * 0.8), () => [0, -0.12, 0]), np, 1); // roof
676
+ for (const sx of [-1, 1])
677
+ addNet(netRuled(line3([sx * W / 2, 0, 0], [sx * W / 2, 0, -0.55 * D]), line3([sx * W / 2, H, 0], [sx * W / 2, Hu, -0.55 * D]), nq(0.6), nq(H), () => [sx * 0.1, 0, 0]), np, 1); // sides
678
+ }
679
+ else if (kind === 'basketballHoop') {
680
+ // backboard 1.8 × 1.05, rim r 0.2286 at 3.05 m, 0.15 m in front of the board; pole 1.2 m behind the baseline with a padded base
681
+ const rimR = 0.2286, rimY = 3.05, bw = 1.8, bh = 1.05, bz = -0.15 - rimR;
682
+ const pole = paint(0, 'metal', { tint: '#dddddd', roughness: 0.35, metalness: 1 });
683
+ const pad = paint(2, 'rubber', { tint: '#ffffff', roughness: 0.9 });
684
+ const glass = paint(3, 'plastic', { tint: '#dfe8ee', roughness: 0.15 });
685
+ const rimP = paint(3, 'paint', { tint: '#e8521f', roughness: 0.4 });
686
+ const pz = bz - 1.2;
687
+ addMesh(mergeGeos([bar([0, 0, pz], [0, rimY + 0.6, pz], 0.08, R), bar([0, rimY + 0.5, pz], [0, rimY + 0.35, bz - 0.03], 0.04, 10), bar([0, rimY - 0.25, pz], [0, rimY - 0.2, bz - 0.03], 0.04, 10)]), pole, 'pole');
688
+ addMesh(box(0.5, 1.8, 0.5, [0, 0.9, pz]), pad, 'pad');
689
+ addMesh(mergeGeos([box(bw, bh, 0.02, [0, rimY + bh / 2 - 0.3, bz - 0.02])]), glass, 'board');
690
+ // board frame + inner rectangle (accent)
691
+ const frameP = paint(2, 'paint', { tint: '#ffffff', roughness: 0.4 });
692
+ addMesh(mergeGeos([box(bw, 0.05, 0.03, [0, rimY + bh - 0.3, bz - 0.01]), box(bw, 0.05, 0.03, [0, rimY - 0.3, bz - 0.01]), box(0.05, bh, 0.03, [-bw / 2, rimY + bh / 2 - 0.3, bz - 0.01]), box(0.05, bh, 0.03, [bw / 2, rimY + bh / 2 - 0.3, bz - 0.01]),
693
+ box(0.59, 0.03, 0.03, [0, rimY + 0.45, bz]), box(0.03, 0.45, 0.03, [-0.295, rimY + 0.225, bz]), box(0.03, 0.45, 0.03, [0.295, rimY + 0.225, bz])]), frameP, 'boardFrame');
694
+ const ring = new THREE.TorusGeometry(rimR, 0.01, 10, 48);
695
+ ring.rotateX(Math.PI / 2);
696
+ ring.translate(0, rimY, 0);
697
+ addMesh(mergeGeos([ring, box(0.12, 0.08, 0.15, [0, rimY - 0.03, bz + 0.07])]), rimP, 'rim');
698
+ if (opt.net) {
699
+ // the net: a cone of 12 cords, drawn as a lathe with the net shader in polar coordinates
700
+ const np = netMaterial(u, { mesh: opt.mesh ?? 0.06, thread: 0.004, sag: 0, slot: 1 });
701
+ mats.push(np);
702
+ const g = lathe([[rimR * 0.62, rimY - 0.42], [rimR * 0.78, rimY - 0.3], [rimR * 0.95, rimY - 0.12], [rimR, rimY - 0.01]], 36);
703
+ const uv = g.attributes.uv;
704
+ for (let i = 0; i < uv.count; i++)
705
+ uv.setXY(i, uv.getX(i) * rimR * 6.2832, uv.getY(i) * 0.42);
706
+ g.setAttribute('aT', new THREE.Float32BufferAttribute(new Float32Array(uv.count * 2), 2));
707
+ g.setAttribute('aSag', new THREE.Float32BufferAttribute(new Float32Array(uv.count * 3), 3));
708
+ geos.push(g);
709
+ const m = netMesh(g, np);
710
+ m.userData.sagK = 0;
711
+ object.add(m);
712
+ nets.push(m);
713
+ }
714
+ }
715
+ // posts: slot 0, mesh: slot 1, tapes: slot 2 — natural colours in GOAL_COLORS
716
+ else if (kind === 'volleyballNet')
717
+ netOnPosts({ posts: 11.0, netW: 10.0, top: 2.43, depth: 1.0, band: 0.07, postR: 0.05, antennae: 4.5 }, opt.mesh ?? 0.1, paint(0, 'metal', { tint: '#ffffff', roughness: 0.4, metalness: 1 }), paint(2, 'fabric', { tint: '#ffffff' }));
718
+ else if (kind === 'tennisNet')
719
+ netOnPosts({ posts: 12.8, top: 1.07, dip: 0.156, band: 0.06, postR: 0.04, grounded: true, strap: true, thread: 0.003 }, opt.mesh ?? 0.045, paint(0, 'metal', { tint: '#ffffff', roughness: 0.5, metalness: 0.6 }), paint(2, 'fabric', { tint: '#ffffff' }));
720
+ else if (kind === 'badmintonNet')
721
+ netOnPosts({ posts: 6.1, top: 1.55, dip: 0.026, depth: 0.76, band: 0.075, postR: 0.02 }, opt.mesh ?? 0.019, paint(0, 'metal', { tint: '#ffffff', roughness: 0.5, metalness: 0.6 }), paint(2, 'fabric', { tint: '#ffffff' }));
722
+ else if (kind === 'pickleballNet')
723
+ netOnPosts({ posts: 6.71, top: 0.914, dip: 0.05, band: 0.05, postR: 0.03, grounded: true, strap: true }, opt.mesh ?? 0.04, paint(0, 'metal', { tint: '#ffffff', roughness: 0.5, metalness: 0.6 }), paint(2, 'fabric', { tint: '#ffffff' }));
724
+ else if (kind === 'padelNet')
725
+ netOnPosts({ posts: 10.0, top: 0.92, dip: 0.04, band: 0.06, postR: 0.04, grounded: true, strap: true }, opt.mesh ?? 0.045, paint(0, 'metal', { tint: '#ffffff', roughness: 0.5, metalness: 0.6 }), paint(2, 'fabric', { tint: '#ffffff' }));
726
+ else if (kind === 'tableTennisNet')
727
+ netOnPosts({ posts: 1.83, top: 0.1525, band: 0.015, postR: 0.008, grounded: true, thread: 0.0012 }, opt.mesh ?? 0.012, paint(0, 'metal', { tint: '#ffffff', roughness: 0.5, metalness: 0.6 }), paint(2, 'fabric', { tint: '#ffffff' }));
728
+ else if (kind === 'rugbyPosts' || kind === 'americanUprights') {
729
+ const american = kind === 'americanUprights';
730
+ const W = american ? 5.64 : 5.6, cb = american ? 3.05 : 3.0, top = american ? 10.7 : 11.0, r = american ? 0.06 : 0.07;
731
+ const p = paint(0, 'paint', { tint: '#ffffff', roughness: 0.35 });
732
+ const parts = [];
733
+ if (american) {
734
+ // gooseneck: one pole 1.8 m behind, curving up to the crossbar
735
+ parts.push(bar([0, 0, -1.8], [0, cb - 1.0, -1.8], 0.12, R), tube([[0, cb - 1.0, -1.8], [0, cb - 0.3, -1.5], [0, cb, -0.6], [0, cb, 0]], 0.1, 16, 10));
736
+ parts.push(bar([-W / 2, cb, 0], [W / 2, cb, 0], r, R), bar([-W / 2, cb, 0], [-W / 2, top, 0], r * 0.8, R), bar([W / 2, cb, 0], [W / 2, top, 0], r * 0.8, R));
737
+ }
738
+ else {
739
+ parts.push(bar([-W / 2, 0, 0], [-W / 2, top, 0], r, R), bar([W / 2, 0, 0], [W / 2, top, 0], r, R), bar([-W / 2, cb, 0], [W / 2, cb, 0], r * 0.9, R));
740
+ }
741
+ addMesh(mergeGeos(parts), p, 'posts');
742
+ const padP = paint(2, 'rubber', { tint: '#ffffff', roughness: 0.9 });
743
+ if (american)
744
+ addMesh(box(0.5, 1.9, 0.5, [0, 0.95, -1.8]), padP, 'pad');
745
+ else
746
+ addMesh(mergeGeos([bar([-W / 2, 0, 0], [-W / 2, 1.9, 0], 0.16, 12), bar([W / 2, 0, 0], [W / 2, 1.9, 0], 0.16, 12)]), padP, 'pads');
747
+ }
748
+ const apply = (cfg) => {
749
+ for (const m of nets) {
750
+ const uu = m.material.userData.uniforms;
751
+ if (!uu)
752
+ continue;
753
+ if (cfg.netTension !== undefined)
754
+ uu.uSag.value = (1 - cfg.netTension) * (m.userData.sagK ?? 1);
755
+ }
756
+ };
757
+ return { object, mats, nets, apply, dispose: () => { geos.forEach((g) => g.dispose()); mats.forEach(disposePair); } };
758
+ }