@woosh/meep-engine 3.56.0 → 3.57.0

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 (625) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/package.json +1 -1
  3. package/src/DUPLICATION_REVIEW_2026_10_24.md +1072 -0
  4. package/src/core/binary/BitSet.js +1 -1
  5. package/src/core/binary/FLOAT16_MAX.d.ts +2 -0
  6. package/src/core/binary/FLOAT16_MAX.d.ts.map +1 -0
  7. package/src/core/binary/FLOAT16_MAX.js +1 -0
  8. package/src/core/binary/base58/base58_encode.js +2 -2
  9. package/src/core/binary/type/TypeBuilder.js +1 -1
  10. package/src/core/bvh2/bvh3/BVH.js +1 -1
  11. package/src/core/bvh2/bvh3/ebvh_optimize_treelet.d.ts.map +1 -1
  12. package/src/core/bvh2/bvh3/ebvh_optimize_treelet.js +7 -3
  13. package/src/core/bvh2/bvh3/ebvh_update_hierarchy_bounds.d.ts.map +1 -1
  14. package/src/core/bvh2/bvh3/ebvh_update_hierarchy_bounds.js +5 -1
  15. package/src/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.d.ts.map +1 -1
  16. package/src/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.js +195 -246
  17. package/src/core/collection/CuckooFilter.js +1 -1
  18. package/src/core/collection/array/isArrayEqual.d.ts.map +1 -1
  19. package/src/core/collection/array/isArrayEqual.js +33 -58
  20. package/src/core/collection/array/typed/array_buffer_hash.d.ts.map +1 -1
  21. package/src/core/collection/array/typed/array_buffer_hash.js +41 -39
  22. package/src/core/collection/array/typed/float32_array_hash.js +38 -38
  23. package/src/core/collection/array/typed/uint16_array_hash.d.ts +2 -1
  24. package/src/core/collection/array/typed/uint16_array_hash.d.ts.map +1 -1
  25. package/src/core/collection/array/typed/uint16_array_hash.js +30 -29
  26. package/src/core/collection/array/typed/uint32_array_hash.d.ts +2 -1
  27. package/src/core/collection/array/typed/uint32_array_hash.d.ts.map +1 -1
  28. package/src/core/collection/array/typed/uint32_array_hash.js +21 -20
  29. package/src/core/collection/array/typed/uint8_array_hash.d.ts +2 -1
  30. package/src/core/collection/array/typed/uint8_array_hash.d.ts.map +1 -1
  31. package/src/core/collection/array/typed/uint8_array_hash.js +37 -36
  32. package/src/core/collection/array/typed/uint_array_for_count.js +2 -2
  33. package/src/core/collection/list/List.d.ts.map +1 -1
  34. package/src/core/collection/list/List.js +3 -19
  35. package/src/core/collection/table/RowFirstTable.d.ts.map +1 -1
  36. package/src/core/collection/table/RowFirstTable.js +4 -28
  37. package/src/core/debug/matchers/DescribeAs.d.ts.map +1 -1
  38. package/src/core/debug/matchers/DescribeAs.js +62 -64
  39. package/src/core/function/FunctionCompiler.js +4 -4
  40. package/src/core/function/objectsEqual.d.ts.map +1 -1
  41. package/src/core/function/objectsEqual.js +3 -2
  42. package/src/core/geom/2d/bvh/BVH2D.d.ts.map +1 -1
  43. package/src/core/geom/2d/bvh/BVH2D.js +4 -3
  44. package/src/core/geom/2d/convex-hull/convex_hull_fixed_count_2d_relaxation.d.ts +5 -2
  45. package/src/core/geom/2d/convex-hull/convex_hull_fixed_count_2d_relaxation.d.ts.map +1 -1
  46. package/src/core/geom/2d/convex-hull/convex_hull_fixed_count_2d_relaxation.js +49 -11
  47. package/src/core/geom/2d/hash-grid/shg_query_elements_circle.d.ts.map +1 -1
  48. package/src/core/geom/2d/hash-grid/shg_query_elements_circle.js +122 -159
  49. package/src/core/geom/2d/hash-grid/shg_query_elements_line.d.ts.map +1 -1
  50. package/src/core/geom/2d/hash-grid/shg_query_elements_line.js +8 -5
  51. package/src/core/geom/2d/polygon/polygon2_rasterize_coverage.d.ts +18 -0
  52. package/src/core/geom/2d/polygon/polygon2_rasterize_coverage.d.ts.map +1 -0
  53. package/src/core/geom/2d/polygon/polygon2_rasterize_coverage.js +97 -0
  54. package/src/core/geom/2d/r-tree/StaticR2Tree.js +2 -2
  55. package/src/core/geom/2d/triangle/tri2_rasterize_coverage.d.ts +21 -0
  56. package/src/core/geom/2d/triangle/tri2_rasterize_coverage.d.ts.map +1 -0
  57. package/src/core/geom/2d/triangle/tri2_rasterize_coverage.js +54 -0
  58. package/src/core/geom/3d/aabb/aabb3_build_frustum.d.ts.map +1 -1
  59. package/src/core/geom/3d/aabb/aabb3_build_frustum.js +20 -53
  60. package/src/core/geom/3d/aabb/aabb3_score_boxes_SAH.d.ts +1 -1
  61. package/src/core/geom/3d/aabb/aabb3_score_boxes_SAH.d.ts.map +1 -1
  62. package/src/core/geom/3d/aabb/aabb3_score_boxes_SAH.js +11 -42
  63. package/src/core/geom/3d/atlas/pack/atlas_pack_charts.d.ts.map +1 -1
  64. package/src/core/geom/3d/atlas/pack/atlas_pack_charts.js +30 -32
  65. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.d.ts.map +1 -1
  66. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js +1 -5
  67. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.d.ts.map +1 -1
  68. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.js +1 -15
  69. package/src/core/geom/3d/atlas/segment/atlas_merge_charts.d.ts.map +1 -1
  70. package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +105 -116
  71. package/src/core/geom/3d/line/line3_compute_segment_point_distance_sqr.d.ts.map +1 -1
  72. package/src/core/geom/3d/line/line3_compute_segment_point_distance_sqr.js +36 -54
  73. package/src/core/geom/3d/plane/plane3_compute_intersection_objects.d.ts +5 -10
  74. package/src/core/geom/3d/plane/plane3_compute_intersection_objects.d.ts.map +1 -1
  75. package/src/core/geom/3d/plane/plane3_compute_intersection_objects.js +20 -82
  76. package/src/core/geom/3d/plane/plane3_slerp.d.ts.map +1 -1
  77. package/src/core/geom/3d/plane/plane3_slerp.js +87 -102
  78. package/src/core/geom/3d/polygon/polygon3_compute_normal.d.ts +12 -0
  79. package/src/core/geom/3d/polygon/polygon3_compute_normal.d.ts.map +1 -1
  80. package/src/core/geom/3d/polygon/polygon3_compute_normal.js +107 -60
  81. package/src/core/geom/3d/ray/ray3_compute_closest_point.d.ts.map +1 -1
  82. package/src/core/geom/3d/ray/ray3_compute_closest_point.js +37 -43
  83. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  84. package/src/core/geom/3d/shape/ConvexHullShape3D.js +5 -16
  85. package/src/core/geom/3d/shape/MeshShape3D.js +2 -56
  86. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  87. package/src/core/geom/3d/shape/TransformedShape3D.js +10 -7
  88. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  89. package/src/core/geom/3d/shape/UnionShape3D.js +9 -3
  90. package/src/core/geom/3d/shape/json/shape_to_type.js +2 -2
  91. package/src/core/geom/3d/sphere/harmonics/SH3_COEFFICIENTS.js +17 -17
  92. package/src/core/geom/3d/tetrahedra/TetrahedralMesh.d.ts.map +1 -1
  93. package/src/core/geom/3d/tetrahedra/TetrahedralMesh.js +6 -2
  94. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.d.ts.map +1 -1
  95. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +3 -2
  96. package/src/core/geom/3d/tetrahedra/tetrahedron_contains_point.d.ts +20 -2
  97. package/src/core/geom/3d/tetrahedra/tetrahedron_contains_point.d.ts.map +1 -1
  98. package/src/core/geom/3d/tetrahedra/tetrahedron_contains_point.js +52 -37
  99. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.d.ts.map +1 -1
  100. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js +40 -56
  101. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts.map +1 -1
  102. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +251 -260
  103. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_wedge_attribute_quadrics.d.ts.map +1 -1
  104. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_wedge_attribute_quadrics.js +104 -113
  105. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_resolve_t_junctions.d.ts.map +1 -1
  106. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_resolve_t_junctions.js +227 -238
  107. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -1
  108. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +15 -24
  109. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.d.ts.map +1 -1
  110. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.js +31 -115
  111. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_fuse_duplicate_edges.d.ts.map +1 -1
  112. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_fuse_duplicate_edges.js +142 -150
  113. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_replace.d.ts.map +1 -1
  114. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_replace.js +151 -194
  115. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_incenter.d.ts.map +1 -1
  116. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_incenter.js +60 -85
  117. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_distance_field.d.ts.map +1 -1
  118. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_distance_field.js +156 -171
  119. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.d.ts.map +1 -1
  120. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.js +8 -44
  121. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path_polyanya.d.ts.map +1 -1
  122. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path_polyanya.js +2 -7
  123. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.d.ts.map +1 -1
  124. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +3 -2
  125. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_segment_penetrates_surface.d.ts.map +1 -1
  126. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_segment_penetrates_surface.js +128 -133
  127. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.d.ts +8 -14
  128. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.d.ts.map +1 -1
  129. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.js +36 -82
  130. package/src/core/geom/3d/triangle/tri3_ray_intersection.d.ts.map +1 -1
  131. package/src/core/geom/3d/triangle/tri3_ray_intersection.js +7 -144
  132. package/src/core/geom/ConicRay.d.ts.map +1 -1
  133. package/src/core/geom/ConicRay.js +230 -237
  134. package/src/core/localization/LocaleDataset.d.ts +6 -0
  135. package/src/core/localization/LocaleDataset.d.ts.map +1 -1
  136. package/src/core/localization/LocaleDataset.js +48 -37
  137. package/src/core/localization/LocalizationEngine.d.ts.map +1 -1
  138. package/src/core/localization/LocalizationEngine.js +3 -2
  139. package/src/core/math/lookup/ParameterLookupTable.d.ts.map +1 -1
  140. package/src/core/math/lookup/ParameterLookupTable.js +3 -1
  141. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts.map +1 -1
  142. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.js +330 -400
  143. package/src/core/math/spline/spline3_hermite_nearest_point.d.ts +16 -0
  144. package/src/core/math/spline/spline3_hermite_nearest_point.d.ts.map +1 -1
  145. package/src/core/math/spline/spline3_hermite_nearest_point.js +134 -167
  146. package/src/core/model/DebouncedObservedBoolean.d.ts +1 -2
  147. package/src/core/model/DebouncedObservedBoolean.d.ts.map +1 -1
  148. package/src/core/model/DebouncedObservedBoolean.js +1 -1
  149. package/src/core/model/node-graph/node/NodeInstance.d.ts.map +1 -1
  150. package/src/core/model/node-graph/node/NodeInstance.js +2 -7
  151. package/src/core/model/node-graph/node/parameter/NodeParameterDescription.js +1 -1
  152. package/src/core/model/object/optional_equality.d.ts +3 -3
  153. package/src/core/model/object/optional_equality.d.ts.map +1 -1
  154. package/src/core/model/object/optional_equality.js +10 -27
  155. package/src/core/model/object/write_property.js +1 -1
  156. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.js +2 -2
  157. package/src/core/primitives/strings/string_compute_byte_size.d.ts +3 -3
  158. package/src/core/primitives/strings/string_compute_byte_size.d.ts.map +1 -1
  159. package/src/core/primitives/strings/string_compute_byte_size.js +8 -21
  160. package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
  161. package/src/core/process/executor/ConcurrentExecutor.js +14 -2
  162. package/src/engine/achievements/gateway/StorageAchievementGateway.d.ts +6 -3
  163. package/src/engine/achievements/gateway/StorageAchievementGateway.d.ts.map +1 -1
  164. package/src/engine/achievements/gateway/StorageAchievementGateway.js +31 -75
  165. package/src/engine/asset/loaders/SVGAssetLoader.d.ts.map +1 -1
  166. package/src/engine/asset/loaders/SVGAssetLoader.js +33 -31
  167. package/src/engine/control/first-person/math/criticallyDampedSpring.d.ts.map +1 -1
  168. package/src/engine/control/first-person/math/criticallyDampedSpring.js +3 -14
  169. package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
  170. package/src/engine/ecs/EntityComponentDataset.js +4 -1
  171. package/src/engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter.d.ts +0 -2
  172. package/src/engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter.d.ts.map +1 -1
  173. package/src/engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter.js +3 -3
  174. package/src/engine/ecs/terrain/ecs/layers/TerrainLayer.js +2 -2
  175. package/src/engine/graphics/sh3/path_tracer/geometry/sort_triangles_by_distance_to_point.js +1 -1
  176. package/src/engine/graphics/texture/sampler/sampler2d_channel_compute_min_indices.js +1 -1
  177. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
  178. package/src/engine/graphics3/WaterSystem.js +2 -1
  179. package/src/engine/graphics3/prefab/prefab_skin_frame.d.ts.map +1 -1
  180. package/src/engine/graphics3/prefab/prefab_skin_frame.js +1 -12
  181. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts +1 -1
  182. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
  183. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.js +141 -137
  184. package/src/engine/input/ecs/systems/InputControllerSystem.d.ts.map +1 -1
  185. package/src/engine/input/ecs/systems/InputControllerSystem.js +13 -3
  186. package/src/engine/input/ecs/systems/InputSystem.d.ts.map +1 -1
  187. package/src/engine/input/ecs/systems/InputSystem.js +2 -1
  188. package/src/engine/intelligence/behavior/composite/ParallelBehavior.d.ts +1 -1
  189. package/src/engine/intelligence/behavior/composite/ParallelBehavior.d.ts.map +1 -1
  190. package/src/engine/intelligence/behavior/composite/ParallelBehavior.js +4 -1
  191. package/src/engine/intelligence/behavior/decorator/InvertStatusBehavior.d.ts.map +1 -1
  192. package/src/engine/intelligence/behavior/decorator/InvertStatusBehavior.js +3 -1
  193. package/src/engine/intelligence/behavior/decorator/RepeatBehavior.d.ts.map +1 -1
  194. package/src/engine/intelligence/behavior/decorator/RepeatBehavior.js +3 -1
  195. package/src/engine/intelligence/behavior/decorator/RepeatBehaviorSerializationAdapter.d.ts.map +1 -1
  196. package/src/engine/intelligence/behavior/decorator/RepeatBehaviorSerializationAdapter.js +39 -33
  197. package/src/engine/intelligence/behavior/util/ConditionalBehavior.d.ts +5 -0
  198. package/src/engine/intelligence/behavior/util/ConditionalBehavior.d.ts.map +1 -1
  199. package/src/engine/intelligence/behavior/util/ConditionalBehavior.js +93 -75
  200. package/src/engine/intelligence/behavior/util/RotationBehavior.d.ts.map +1 -1
  201. package/src/engine/intelligence/behavior/util/RotationBehavior.js +79 -76
  202. package/src/engine/intelligence/blackboard/BlackboardStack.d.ts +1 -0
  203. package/src/engine/intelligence/blackboard/BlackboardStack.d.ts.map +1 -1
  204. package/src/engine/intelligence/blackboard/BlackboardStack.js +159 -123
  205. package/src/engine/intelligence/mcts/StateNode.d.ts.map +1 -1
  206. package/src/engine/intelligence/mcts/StateNode.js +366 -364
  207. package/src/engine/intelligence/resource/ResourceAllocationSolver.js +1 -1
  208. package/src/engine/navigation/ecs/components/Path.d.ts.map +1 -1
  209. package/src/engine/navigation/ecs/components/Path.js +2 -1
  210. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.d.ts.map +1 -1
  211. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.js +5 -2
  212. package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -1
  213. package/src/engine/physics/body/SolverBodyState.js +2 -6
  214. package/src/engine/physics/ccd/linear_sweep.d.ts.map +1 -1
  215. package/src/engine/physics/ccd/linear_sweep.js +3 -16
  216. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts.map +1 -1
  217. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.js +2 -16
  218. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts +13 -1
  219. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -1
  220. package/src/engine/physics/cloth/ecs/cloth_build_rows.js +15 -4
  221. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  222. package/src/engine/physics/ecs/PhysicsSystem.js +2 -5
  223. package/src/engine/physics/inertia/locked_inverse_inertia.d.ts +12 -0
  224. package/src/engine/physics/inertia/locked_inverse_inertia.d.ts.map +1 -0
  225. package/src/engine/physics/inertia/locked_inverse_inertia.js +22 -0
  226. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  227. package/src/engine/physics/integration/integrate_velocity.js +3 -18
  228. package/src/engine/physics/mls-mpm/MLS_MPM.d.ts +1 -0
  229. package/src/engine/physics/mls-mpm/MLS_MPM.d.ts.map +1 -1
  230. package/src/engine/physics/mls-mpm/MLS_MPM.js +5 -4
  231. package/src/engine/physics/narrowphase/convex_decomposition.d.ts.map +1 -1
  232. package/src/engine/physics/narrowphase/convex_decomposition.js +7 -16
  233. package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -1
  234. package/src/engine/physics/vehicle/RaycastVehicle.js +15 -5
  235. package/src/engine/save/StorageBackedList.d.ts +5 -0
  236. package/src/engine/save/StorageBackedList.d.ts.map +1 -1
  237. package/src/engine/save/StorageBackedList.js +161 -144
  238. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  239. package/src/engine/simulation/Ticker.js +394 -391
  240. package/src/engine/sound/simulation/probe/AcousticProbeField.d.ts.map +1 -1
  241. package/src/engine/sound/simulation/probe/AcousticProbeField.js +6 -19
  242. package/src/engine/sound/simulation/probe/probe_edge_visible.d.ts +15 -0
  243. package/src/engine/sound/simulation/probe/probe_edge_visible.d.ts.map +1 -1
  244. package/src/engine/sound/simulation/probe/probe_edge_visible.js +59 -41
  245. package/src/engine/sound/simulation/probe/probe_refine_connectivity.d.ts.map +1 -1
  246. package/src/engine/sound/simulation/probe/probe_refine_connectivity.js +164 -181
  247. package/src/engine/sound/simulation/render/FoaProbeReverbRenderer.d.ts.map +1 -1
  248. package/src/engine/sound/simulation/render/FoaProbeReverbRenderer.js +20 -5
  249. package/src/format/image/avif/av1/decode/Av1FrameContext.d.ts.map +1 -1
  250. package/src/format/image/avif/av1/decode/Av1FrameContext.js +3 -10
  251. package/src/format/image/avif/av1/decode/reconstruct.d.ts +20 -0
  252. package/src/format/image/avif/av1/decode/reconstruct.d.ts.map +1 -1
  253. package/src/format/image/avif/av1/decode/reconstruct.js +22 -9
  254. package/src/format/image/avif/av1/encode/choose_restoration.js +2 -18
  255. package/src/format/image/avif/av1/encode/quantise.d.ts.map +1 -1
  256. package/src/format/image/avif/av1/encode/quantise.js +1 -55
  257. package/src/format/image/avif/av1/filter/loop_restoration.d.ts +9 -0
  258. package/src/format/image/avif/av1/filter/loop_restoration.d.ts.map +1 -1
  259. package/src/format/image/avif/av1/filter/loop_restoration.js +1 -1
  260. package/src/format/texture/basis/transcode/etc1s_to_bc7.js +2 -33
  261. package/src/format/texture/basis/transcode/fit_endpoints.d.ts +11 -0
  262. package/src/format/texture/basis/transcode/fit_endpoints.d.ts.map +1 -1
  263. package/src/format/texture/basis/transcode/fit_endpoints.js +1 -1
  264. package/src/format/texture/basis/uastc/uastc_decode_block.d.ts.map +1 -1
  265. package/src/format/texture/basis/uastc/uastc_decode_block.js +2 -19
  266. package/src/generation/foliage/ANNUAL_SHOOTS_BRIEF_2026_10_25.md +220 -0
  267. package/src/generation/foliage/CROWN_FILLING_BRIEF_2026_10_26.md +172 -0
  268. package/src/generation/foliage/FOLIAGE_DESIGN.md +165 -15
  269. package/src/generation/foliage/LEAF_ARCHITECTURE_BRIEF_2026_10_26.md +158 -0
  270. package/src/generation/foliage/SHOOT_SYSTEMS_ALL_SPECIES_BRIEF_2026_10_26.md +248 -0
  271. package/src/generation/foliage/SPEEDTREE_COMPARISON_2026_10_24.md +694 -0
  272. package/src/generation/foliage/analysis/CrownOpacity.d.ts +118 -0
  273. package/src/generation/foliage/analysis/CrownOpacity.d.ts.map +1 -0
  274. package/src/generation/foliage/analysis/CrownOpacity.js +182 -0
  275. package/src/generation/foliage/analysis/crown_opacity_frames.d.ts +10 -0
  276. package/src/generation/foliage/analysis/crown_opacity_frames.d.ts.map +1 -0
  277. package/src/generation/foliage/analysis/crown_opacity_frames.js +132 -0
  278. package/src/generation/foliage/analysis/crown_opacity_measure.d.ts +51 -0
  279. package/src/generation/foliage/analysis/crown_opacity_measure.d.ts.map +1 -0
  280. package/src/generation/foliage/analysis/crown_opacity_measure.js +649 -0
  281. package/src/generation/foliage/analysis/foliage_measure_leaf_area.d.ts +66 -0
  282. package/src/generation/foliage/analysis/foliage_measure_leaf_area.d.ts.map +1 -0
  283. package/src/generation/foliage/analysis/foliage_measure_leaf_area.js +192 -0
  284. package/src/generation/foliage/analysis/tree_crown_opacity.d.ts +27 -0
  285. package/src/generation/foliage/analysis/tree_crown_opacity.d.ts.map +1 -0
  286. package/src/generation/foliage/analysis/tree_crown_opacity.js +58 -0
  287. package/src/generation/foliage/animation/build_tree_wind_rig.d.ts +3 -2
  288. package/src/generation/foliage/animation/build_tree_wind_rig.d.ts.map +1 -1
  289. package/src/generation/foliage/animation/build_tree_wind_rig.js +12 -30
  290. package/src/generation/foliage/animation/tree_wind_bend_limit.d.ts +12 -0
  291. package/src/generation/foliage/animation/tree_wind_bend_limit.d.ts.map +1 -0
  292. package/src/generation/foliage/animation/tree_wind_bend_limit.js +13 -0
  293. package/src/generation/foliage/asset/TreeAsset.d.ts +5 -0
  294. package/src/generation/foliage/asset/TreeAsset.d.ts.map +1 -1
  295. package/src/generation/foliage/asset/TreeAsset.js +6 -0
  296. package/src/generation/foliage/asset/generate_tree.d.ts.map +1 -1
  297. package/src/generation/foliage/asset/generate_tree.js +23 -6
  298. package/src/generation/foliage/config/{BranchLevelConfig.d.ts → BranchGeneratorConfig.d.ts} +61 -75
  299. package/src/generation/foliage/config/BranchGeneratorConfig.d.ts.map +1 -0
  300. package/src/generation/foliage/config/{BranchLevelConfig.js → BranchGeneratorConfig.js} +83 -126
  301. package/src/generation/foliage/config/BranchSpawnRule.d.ts +118 -0
  302. package/src/generation/foliage/config/BranchSpawnRule.d.ts.map +1 -0
  303. package/src/generation/foliage/config/BranchSpawnRule.js +217 -0
  304. package/src/generation/foliage/config/FoliageCardType.d.ts +1 -1
  305. package/src/generation/foliage/config/FoliageCardType.js +2 -2
  306. package/src/generation/foliage/config/FoliageConfig.d.ts +23 -45
  307. package/src/generation/foliage/config/FoliageConfig.d.ts.map +1 -1
  308. package/src/generation/foliage/config/FoliageConfig.js +47 -93
  309. package/src/generation/foliage/config/FoliageMaterialConfig.d.ts +12 -20
  310. package/src/generation/foliage/config/FoliageMaterialConfig.d.ts.map +1 -1
  311. package/src/generation/foliage/config/FoliageMaterialConfig.js +261 -279
  312. package/src/generation/foliage/config/FoliageSpawnRule.d.ts +77 -0
  313. package/src/generation/foliage/config/FoliageSpawnRule.d.ts.map +1 -0
  314. package/src/generation/foliage/config/FoliageSpawnRule.js +135 -0
  315. package/src/generation/foliage/config/FoliageTileContent.d.ts +9 -0
  316. package/src/generation/foliage/config/FoliageTileContent.d.ts.map +1 -0
  317. package/src/generation/foliage/config/FoliageTileContent.js +12 -0
  318. package/src/generation/foliage/config/LeafApexShape.d.ts +12 -0
  319. package/src/generation/foliage/config/LeafApexShape.d.ts.map +1 -0
  320. package/src/generation/foliage/config/LeafApexShape.js +17 -0
  321. package/src/generation/foliage/config/LeafArchitecture.d.ts +155 -0
  322. package/src/generation/foliage/config/LeafArchitecture.d.ts.map +1 -0
  323. package/src/generation/foliage/config/LeafArchitecture.js +288 -0
  324. package/src/generation/foliage/config/LeafBaseShape.d.ts +14 -0
  325. package/src/generation/foliage/config/LeafBaseShape.d.ts.map +1 -0
  326. package/src/generation/foliage/config/LeafBaseShape.js +21 -0
  327. package/src/generation/foliage/config/LeafLobation.d.ts +10 -0
  328. package/src/generation/foliage/config/LeafLobation.d.ts.map +1 -0
  329. package/src/generation/foliage/config/LeafLobation.js +14 -0
  330. package/src/generation/foliage/config/PhyllotaxisMode.d.ts +11 -0
  331. package/src/generation/foliage/config/PhyllotaxisMode.d.ts.map +1 -1
  332. package/src/generation/foliage/config/PhyllotaxisMode.js +36 -0
  333. package/src/generation/foliage/config/ShootBudZone.d.ts +105 -0
  334. package/src/generation/foliage/config/ShootBudZone.d.ts.map +1 -0
  335. package/src/generation/foliage/config/ShootBudZone.js +219 -0
  336. package/src/generation/foliage/config/ShootGrowthConfig.d.ts +84 -0
  337. package/src/generation/foliage/config/ShootGrowthConfig.d.ts.map +1 -0
  338. package/src/generation/foliage/config/ShootGrowthConfig.js +146 -0
  339. package/src/generation/foliage/config/ShootSystemRule.d.ts +144 -0
  340. package/src/generation/foliage/config/ShootSystemRule.d.ts.map +1 -0
  341. package/src/generation/foliage/config/ShootSystemRule.js +316 -0
  342. package/src/generation/foliage/config/TreeLODConfig.d.ts +1 -1
  343. package/src/generation/foliage/config/TreeLODConfig.js +1 -1
  344. package/src/generation/foliage/config/TreeSpeciesConfig.d.ts +40 -8
  345. package/src/generation/foliage/config/TreeSpeciesConfig.d.ts.map +1 -1
  346. package/src/generation/foliage/config/TreeSpeciesConfig.js +124 -68
  347. package/src/generation/foliage/config/TreeWindConfig.d.ts +23 -10
  348. package/src/generation/foliage/config/TreeWindConfig.d.ts.map +1 -1
  349. package/src/generation/foliage/config/TreeWindConfig.js +34 -25
  350. package/src/generation/foliage/index.d.ts +19 -2
  351. package/src/generation/foliage/index.js +63 -44
  352. package/src/generation/foliage/material/FoliageAtlasCutout.d.ts +59 -0
  353. package/src/generation/foliage/material/FoliageAtlasCutout.d.ts.map +1 -0
  354. package/src/generation/foliage/material/FoliageAtlasCutout.js +93 -0
  355. package/src/generation/foliage/material/compute_foliage_variant_atlas_layout.d.ts +26 -0
  356. package/src/generation/foliage/material/compute_foliage_variant_atlas_layout.d.ts.map +1 -0
  357. package/src/generation/foliage/material/compute_foliage_variant_atlas_layout.js +63 -0
  358. package/src/generation/foliage/material/foliage_atlas_cutout.d.ts +34 -0
  359. package/src/generation/foliage/material/foliage_atlas_cutout.d.ts.map +1 -0
  360. package/src/generation/foliage/material/foliage_atlas_cutout.js +174 -0
  361. package/src/generation/foliage/material/foliage_tile.d.ts +110 -0
  362. package/src/generation/foliage/material/foliage_tile.d.ts.map +1 -0
  363. package/src/generation/foliage/material/foliage_tile.js +286 -0
  364. package/src/generation/foliage/material/leaf_geometry.d.ts +103 -0
  365. package/src/generation/foliage/material/leaf_geometry.d.ts.map +1 -0
  366. package/src/generation/foliage/material/leaf_geometry.js +528 -0
  367. package/src/generation/foliage/material/make_foliage_material.d.ts +13 -33
  368. package/src/generation/foliage/material/make_foliage_material.d.ts.map +1 -1
  369. package/src/generation/foliage/material/make_foliage_material.js +356 -605
  370. package/src/generation/foliage/mesh/foliage_collect_attachments.d.ts +39 -4
  371. package/src/generation/foliage/mesh/foliage_collect_attachments.d.ts.map +1 -1
  372. package/src/generation/foliage/mesh/foliage_collect_attachments.js +637 -569
  373. package/src/generation/foliage/mesh/interpolate_ring_skin_weights4.d.ts +48 -0
  374. package/src/generation/foliage/mesh/interpolate_ring_skin_weights4.d.ts.map +1 -0
  375. package/src/generation/foliage/mesh/interpolate_ring_skin_weights4.js +232 -0
  376. package/src/generation/foliage/mesh/tree_mesh_build_bark.d.ts.map +1 -1
  377. package/src/generation/foliage/mesh/tree_mesh_build_bark.js +1023 -1049
  378. package/src/generation/foliage/mesh/tree_mesh_build_foliage.d.ts +9 -2
  379. package/src/generation/foliage/mesh/tree_mesh_build_foliage.d.ts.map +1 -1
  380. package/src/generation/foliage/mesh/tree_mesh_build_foliage.js +467 -454
  381. package/src/generation/foliage/presets/make_tree_species_preset.d.ts.map +1 -1
  382. package/src/generation/foliage/presets/make_tree_species_preset.js +1648 -870
  383. package/src/generation/foliage/skeleton/ShootFrameworkHash.d.ts +35 -0
  384. package/src/generation/foliage/skeleton/ShootFrameworkHash.d.ts.map +1 -0
  385. package/src/generation/foliage/skeleton/ShootFrameworkHash.js +129 -0
  386. package/src/generation/foliage/skeleton/ShootGirth.d.ts +51 -0
  387. package/src/generation/foliage/skeleton/ShootGirth.d.ts.map +1 -0
  388. package/src/generation/foliage/skeleton/ShootGirth.js +212 -0
  389. package/src/generation/foliage/skeleton/ShootLightField.d.ts +73 -0
  390. package/src/generation/foliage/skeleton/ShootLightField.d.ts.map +1 -0
  391. package/src/generation/foliage/skeleton/ShootLightField.js +197 -0
  392. package/src/generation/foliage/skeleton/TreeSkeleton.d.ts +106 -3
  393. package/src/generation/foliage/skeleton/TreeSkeleton.d.ts.map +1 -1
  394. package/src/generation/foliage/skeleton/TreeSkeleton.js +217 -39
  395. package/src/generation/foliage/skeleton/flare_lobe_phase_offset.d.ts +13 -0
  396. package/src/generation/foliage/skeleton/flare_lobe_phase_offset.d.ts.map +1 -0
  397. package/src/generation/foliage/skeleton/flare_lobe_phase_offset.js +16 -0
  398. package/src/generation/foliage/skeleton/foliage_card_density.d.ts +71 -0
  399. package/src/generation/foliage/skeleton/foliage_card_density.d.ts.map +1 -0
  400. package/src/generation/foliage/skeleton/foliage_card_density.js +101 -0
  401. package/src/generation/foliage/skeleton/foliage_senescence.d.ts +101 -0
  402. package/src/generation/foliage/skeleton/foliage_senescence.d.ts.map +1 -0
  403. package/src/generation/foliage/skeleton/foliage_senescence.js +149 -0
  404. package/src/generation/foliage/skeleton/foliage_unit_cards.d.ts +76 -0
  405. package/src/generation/foliage/skeleton/foliage_unit_cards.d.ts.map +1 -0
  406. package/src/generation/foliage/skeleton/foliage_unit_cards.js +95 -0
  407. package/src/generation/foliage/skeleton/shoot_demography.d.ts +22 -0
  408. package/src/generation/foliage/skeleton/shoot_demography.d.ts.map +1 -0
  409. package/src/generation/foliage/skeleton/shoot_demography.js +96 -0
  410. package/src/generation/foliage/skeleton/shoot_geometry.d.ts +66 -0
  411. package/src/generation/foliage/skeleton/shoot_geometry.d.ts.map +1 -0
  412. package/src/generation/foliage/skeleton/shoot_geometry.js +109 -0
  413. package/src/generation/foliage/skeleton/shoot_systems_simulate.d.ts +166 -0
  414. package/src/generation/foliage/skeleton/shoot_systems_simulate.d.ts.map +1 -0
  415. package/src/generation/foliage/skeleton/shoot_systems_simulate.js +588 -0
  416. package/src/generation/foliage/skeleton/tree_skeleton_assign_skin_joints.d.ts +28 -12
  417. package/src/generation/foliage/skeleton/tree_skeleton_assign_skin_joints.d.ts.map +1 -1
  418. package/src/generation/foliage/skeleton/tree_skeleton_assign_skin_joints.js +137 -102
  419. package/src/generation/foliage/skeleton/tree_skeleton_compute_joint_mechanics.d.ts +112 -0
  420. package/src/generation/foliage/skeleton/tree_skeleton_compute_joint_mechanics.d.ts.map +1 -0
  421. package/src/generation/foliage/skeleton/tree_skeleton_compute_joint_mechanics.js +408 -0
  422. package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.d.ts.map +1 -1
  423. package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.js +208 -206
  424. package/src/generation/foliage/skeleton/tree_skeleton_grow.d.ts +5 -2
  425. package/src/generation/foliage/skeleton/tree_skeleton_grow.d.ts.map +1 -1
  426. package/src/generation/foliage/skeleton/tree_skeleton_grow.js +2048 -1871
  427. package/src/generation/foliage/skeleton/tree_skeleton_grow_shoots.d.ts +17 -0
  428. package/src/generation/foliage/skeleton/tree_skeleton_grow_shoots.d.ts.map +1 -0
  429. package/src/generation/foliage/skeleton/tree_skeleton_grow_shoots.js +231 -0
  430. package/src/generation/foliage/skeleton/tree_skeleton_read_joint_motion.d.ts +14 -0
  431. package/src/generation/foliage/skeleton/tree_skeleton_read_joint_motion.d.ts.map +1 -0
  432. package/src/generation/foliage/skeleton/tree_skeleton_read_joint_motion.js +26 -0
  433. package/src/generation/foliage/skeleton/tree_skeleton_read_joint_parents.d.ts +10 -0
  434. package/src/generation/foliage/skeleton/tree_skeleton_read_joint_parents.d.ts.map +1 -0
  435. package/src/generation/foliage/skeleton/tree_skeleton_read_joint_parents.js +20 -0
  436. package/src/generation/grid/generation/road/GridTaskGenerateRoads.d.ts.map +1 -1
  437. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +4 -1
  438. package/src/generation/markers/GridCellActionPlaceMarkerGroup.d.ts +1 -0
  439. package/src/generation/markers/GridCellActionPlaceMarkerGroup.d.ts.map +1 -1
  440. package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +98 -86
  441. package/src/shade/SHADER_F16_PROPOSAL_2026_10_24.md +263 -0
  442. package/src/shade/descriptor/WebGPUExtensionType.d.ts +17 -0
  443. package/src/shade/descriptor/WebGPUExtensionType.d.ts.map +1 -1
  444. package/src/shade/descriptor/WebGPUExtensionType.js +31 -1
  445. package/src/shade/playground/foliage_wind/FoliageRigOverlay.d.ts +37 -0
  446. package/src/shade/playground/foliage_wind/FoliageRigOverlay.d.ts.map +1 -0
  447. package/src/shade/playground/foliage_wind/FoliageRigOverlay.js +491 -0
  448. package/src/shade/playground/foliage_wind/foliage_world.d.ts.map +1 -1
  449. package/src/shade/playground/foliage_wind/foliage_world.js +600 -602
  450. package/src/shade/playground/foliage_wind/index.html +6 -0
  451. package/src/shade/playground/foliage_wind/main.js +14 -0
  452. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  453. package/src/shade/renderer/animation/GPUAnimationManager.js +2 -1
  454. package/src/shade/renderer/animation/foliage/FoliageWindJoint.d.ts +2 -1
  455. package/src/shade/renderer/animation/foliage/FoliageWindJoint.d.ts.map +1 -1
  456. package/src/shade/renderer/animation/foliage/FoliageWindJoint.js +2 -1
  457. package/src/shade/renderer/animation/foliage/chunk_foliage_wind_step.d.ts.map +1 -1
  458. package/src/shade/renderer/animation/foliage/chunk_foliage_wind_step.js +4 -2
  459. package/src/shade/renderer/buffer/table/GPUTypedTable.js +1 -1
  460. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.d.ts.map +1 -1
  461. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.js +403 -401
  462. package/src/shade/renderer/camera/OrbitalCameraController.d.ts +1 -62
  463. package/src/shade/renderer/camera/OrbitalCameraController.d.ts.map +1 -1
  464. package/src/shade/renderer/camera/OrbitalCameraController.js +1 -374
  465. package/src/shade/renderer/deferred/indirect/chunk_lpv_2d_cache_sample_irradiance.d.ts.map +1 -1
  466. package/src/shade/renderer/deferred/indirect/chunk_lpv_2d_cache_sample_irradiance.js +3 -0
  467. package/src/shade/renderer/deferred/indirect/chunk_lpv_sample_light.d.ts.map +1 -1
  468. package/src/shade/renderer/deferred/indirect/chunk_lpv_sample_light.js +6 -1
  469. package/src/shade/renderer/dynamic/shader_dynamic_mesh_avboit_splat.js +1 -1
  470. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +2 -2
  471. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts.map +1 -1
  472. package/src/shade/renderer/fluid/GPUFluidCascade.js +6 -3
  473. package/src/shade/renderer/fluid/GPU_FLUID_OCCUPANCY_AND_SOTA_REVIEW_2026_10_26.md +361 -0
  474. package/src/shade/renderer/fluid/coupling/chunk_fluid_cascade_parent.d.ts.map +1 -1
  475. package/src/shade/renderer/fluid/coupling/chunk_fluid_cascade_parent.js +335 -241
  476. package/src/shade/renderer/fluid/coupling/shader_fluid_cascade_ghosts.d.ts.map +1 -1
  477. package/src/shade/renderer/fluid/coupling/shader_fluid_cascade_ghosts.js +108 -101
  478. package/src/shade/renderer/fluid/forcing/chunk_fluid_forcing_query_select.d.ts.map +1 -1
  479. package/src/shade/renderer/fluid/forcing/chunk_fluid_forcing_query_select.js +69 -67
  480. package/src/shade/renderer/fluid/forcing/shader_fluid_forcing_apply.d.ts +7 -6
  481. package/src/shade/renderer/fluid/forcing/shader_fluid_forcing_apply.d.ts.map +1 -1
  482. package/src/shade/renderer/fluid/forcing/shader_fluid_forcing_apply.js +341 -287
  483. package/src/shade/renderer/fluid/mac/FluidMacPasses.d.ts +6 -4
  484. package/src/shade/renderer/fluid/mac/FluidMacPasses.d.ts.map +1 -1
  485. package/src/shade/renderer/fluid/mac/FluidMacPasses.js +427 -400
  486. package/src/shade/renderer/fluid/mac/chunk_fluid_mac_seams.d.ts +10 -5
  487. package/src/shade/renderer/fluid/mac/chunk_fluid_mac_seams.d.ts.map +1 -1
  488. package/src/shade/renderer/fluid/mac/chunk_fluid_mac_seams.js +532 -190
  489. package/src/shade/renderer/fluid/mac/chunk_v3_mac_lattice_corners.d.ts.map +1 -1
  490. package/src/shade/renderer/fluid/mac/chunk_v3_mac_lattice_corners.js +231 -237
  491. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_correct.d.ts +2 -2
  492. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_correct.d.ts.map +1 -1
  493. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_correct.js +282 -245
  494. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_predict.d.ts +1 -3
  495. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_predict.d.ts.map +1 -1
  496. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_predict.js +139 -109
  497. package/src/shade/renderer/fluid/mac/shader_fluid_mac_curl.d.ts +6 -6
  498. package/src/shade/renderer/fluid/mac/shader_fluid_mac_curl.d.ts.map +1 -1
  499. package/src/shade/renderer/fluid/mac/shader_fluid_mac_curl.js +257 -222
  500. package/src/shade/renderer/fluid/mac/shader_fluid_mac_divergence.d.ts +8 -7
  501. package/src/shade/renderer/fluid/mac/shader_fluid_mac_divergence.d.ts.map +1 -1
  502. package/src/shade/renderer/fluid/mac/shader_fluid_mac_divergence.js +119 -92
  503. package/src/shade/renderer/fluid/mac/shader_fluid_mac_gradient.d.ts +6 -9
  504. package/src/shade/renderer/fluid/mac/shader_fluid_mac_gradient.d.ts.map +1 -1
  505. package/src/shade/renderer/fluid/mac/shader_fluid_mac_gradient.js +229 -181
  506. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +17 -7
  507. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts.map +1 -1
  508. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.js +634 -621
  509. package/src/shade/renderer/fluid/pressure/shader_fluid_mg_residual_restrict.d.ts +8 -6
  510. package/src/shade/renderer/fluid/pressure/shader_fluid_mg_residual_restrict.d.ts.map +1 -1
  511. package/src/shade/renderer/fluid/pressure/shader_fluid_mg_residual_restrict.js +166 -149
  512. package/src/shade/renderer/fluid/pressure/shader_fluid_mg_smooth.d.ts +8 -6
  513. package/src/shade/renderer/fluid/pressure/shader_fluid_mg_smooth.d.ts.map +1 -1
  514. package/src/shade/renderer/fluid/pressure/shader_fluid_mg_smooth.js +219 -183
  515. package/src/shade/renderer/fluid/publish/shader_fluid_wind_publish.d.ts.map +1 -1
  516. package/src/shade/renderer/fluid/publish/shader_fluid_wind_publish.js +142 -122
  517. package/src/shade/renderer/geometry/meshlet/COMPACTION_PLAN_2026_09_16.md +674 -663
  518. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts +1 -1
  519. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
  520. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +63 -29
  521. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.d.ts.map +1 -1
  522. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/geometry_to_meshlets_meshoptimizer.js +77 -87
  523. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/meshopt_clusterizer.d.ts.map +1 -1
  524. package/src/shade/renderer/geometry/meshlet/build/meshoptimizer/meshopt_clusterizer.js +4 -3
  525. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.d.ts +11 -11
  526. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.d.ts.map +1 -1
  527. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.js +17 -19
  528. package/src/shade/renderer/global_illumination/lpv/graph_bake_irradiance_lookup_2d.js +4 -4
  529. package/src/shade/renderer/global_illumination/probe/octahedral/sample/chunk_lpv_mesh_interpolate_radiance_via_atlas.d.ts.map +1 -1
  530. package/src/shade/renderer/global_illumination/probe/octahedral/sample/chunk_lpv_mesh_interpolate_radiance_via_atlas.js +5 -1
  531. package/src/shade/renderer/loader/gltf/load_gltf.d.ts.map +1 -1
  532. package/src/shade/renderer/loader/gltf/load_gltf.js +11 -35
  533. package/src/shade/renderer/loader/usd/load_usd.d.ts.map +1 -1
  534. package/src/shade/renderer/loader/usd/load_usd.js +6 -19
  535. package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts.map +1 -1
  536. package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.js +199 -193
  537. package/src/shade/renderer/particles/shaders/shader_particle_avboit_splat.d.ts.map +1 -1
  538. package/src/shade/renderer/particles/shaders/shader_particle_avboit_splat.js +151 -143
  539. package/src/shade/renderer/postprocess/bloom/chunk_karis_blur4x4_sample.d.ts.map +1 -1
  540. package/src/shade/renderer/postprocess/bloom/chunk_karis_blur4x4_sample.js +11 -5
  541. package/src/shade/renderer/postprocess/denoise/graph_atorus_denoise_luma.d.ts.map +1 -1
  542. package/src/shade/renderer/postprocess/denoise/graph_atorus_denoise_luma.js +6 -18
  543. package/src/shade/renderer/postprocess/gtao/GTAO.js +1 -1
  544. package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.d.ts.map +1 -1
  545. package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.js +3 -5
  546. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.js +1 -1
  547. package/src/shade/renderer/postprocess/nss/chunk_nss_luma_derivative.d.ts.map +1 -1
  548. package/src/shade/renderer/postprocess/nss/chunk_nss_luma_derivative.js +7 -3
  549. package/src/shade/renderer/postprocess/nss/shader_nss_postprocess.d.ts.map +1 -1
  550. package/src/shade/renderer/postprocess/nss/shader_nss_postprocess.js +4 -2
  551. package/src/shade/renderer/postprocess/nss/shader_nss_preprocess.d.ts.map +1 -1
  552. package/src/shade/renderer/postprocess/nss/shader_nss_preprocess.js +4 -1
  553. package/src/shade/renderer/postprocess/ssr/downsample_karis.d.ts.map +1 -1
  554. package/src/shade/renderer/postprocess/ssr/downsample_karis.js +6 -3
  555. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.d.ts.map +1 -1
  556. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.js +4 -3
  557. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader.d.ts.map +1 -1
  558. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader.js +4 -1
  559. package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.d.ts.map +1 -1
  560. package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.js +4 -2
  561. package/src/shade/renderer/postprocess/taa/chunk_taa_tonemap.d.ts.map +1 -1
  562. package/src/shade/renderer/postprocess/taa/chunk_taa_tonemap.js +35 -39
  563. package/src/shade/renderer/postprocess/taa/chunk_taa_tonemap_inv.d.ts.map +1 -1
  564. package/src/shade/renderer/postprocess/taa/chunk_taa_tonemap_inv.js +20 -17
  565. package/src/shade/renderer/postprocess/taa/shader_taa.d.ts.map +1 -1
  566. package/src/shade/renderer/postprocess/taa/shader_taa.js +6 -3
  567. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.d.ts.map +1 -1
  568. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.js +3 -1
  569. package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.js +1 -1
  570. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_splat_slice.js +2 -2
  571. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +1 -1
  572. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_integrate.d.ts.map +1 -1
  573. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_integrate.js +92 -83
  574. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.js +1 -1
  575. package/src/shade/renderer/shader/ShaderDescriptor.d.ts.map +1 -1
  576. package/src/shade/renderer/shader/ShaderDescriptor.js +12 -4
  577. package/src/shade/renderer/shader/chunk/bvh/chunk_bvh_query_aabb_cooperative.d.ts.map +1 -1
  578. package/src/shade/renderer/shader/chunk/bvh/chunk_bvh_query_aabb_cooperative.js +294 -286
  579. package/src/shade/renderer/shader/chunk/light/lpv/chunk_lpv_interpolate_probe_irradiance8.js +5 -5
  580. package/src/shade/renderer/shader/chunk/light/lpv/chunk_lpv_mesh_interpolate_probe.d.ts.map +1 -1
  581. package/src/shade/renderer/shader/chunk/light/lpv/chunk_lpv_mesh_interpolate_probe.js +5 -3
  582. package/src/shade/renderer/shader/chunk/light/lpv/chunk_lpv_mesh_interpolate_probe_irradiance.d.ts.map +1 -1
  583. package/src/shade/renderer/shader/chunk/light/lpv/chunk_lpv_mesh_interpolate_probe_irradiance.js +9 -6
  584. package/src/shade/renderer/shader/chunk/light/lpv/chunk_lpv_sample_sh3_at.d.ts.map +1 -1
  585. package/src/shade/renderer/shader/chunk/light/lpv/chunk_lpv_sample_sh3_at.js +33 -38
  586. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
  587. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +690 -675
  588. package/src/shade/renderer/shader/type/WebGPUArray.d.ts +3 -0
  589. package/src/shade/renderer/shader/type/WebGPUArray.d.ts.map +1 -1
  590. package/src/shade/renderer/shader/type/WebGPUArray.js +185 -167
  591. package/src/shade/renderer/shader/type/WebGPUStruct.d.ts.map +1 -1
  592. package/src/shade/renderer/shader/type/WebGPUStruct.js +461 -455
  593. package/src/shade/renderer/shader/type/serde/read_gpu_typed_buffer.d.ts.map +1 -1
  594. package/src/shade/renderer/shader/type/serde/read_gpu_typed_buffer.js +335 -211
  595. package/src/shade/renderer/shader/type/serde/write_gpu_typed_buffer.d.ts.map +1 -1
  596. package/src/shade/renderer/shader/type/serde/write_gpu_typed_buffer.js +329 -221
  597. package/src/shade/renderer/shader/type/type_has_f16.d.ts +12 -0
  598. package/src/shade/renderer/shader/type/type_has_f16.d.ts.map +1 -0
  599. package/src/shade/renderer/shader/type/type_has_f16.js +66 -0
  600. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
  601. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +16 -2
  602. package/src/shade/renderer/volumetrics/taa/shader_volumetrics_taa.d.ts.map +1 -1
  603. package/src/shade/renderer/volumetrics/taa/shader_volumetrics_taa.js +5 -2
  604. package/src/shade/renderer/water/shader_water_avboit.js +1 -1
  605. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts.map +1 -1
  606. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +260 -23
  607. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts +6 -7
  608. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  609. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +269 -69
  610. package/src/shade/wgsl/validate_wgsl_source.d.ts.map +1 -1
  611. package/src/shade/wgsl/validate_wgsl_source.js +27 -1
  612. package/src/view/controller/controls/ListController.d.ts.map +1 -1
  613. package/src/view/controller/controls/ListController.js +118 -113
  614. package/src/view/controller/controls/Vector1Control.js +97 -97
  615. package/src/view/elements/image/SvgImageView.d.ts +1 -0
  616. package/src/view/elements/image/SvgImageView.d.ts.map +1 -1
  617. package/src/view/elements/image/SvgImageView.js +92 -90
  618. package/src/view/minimap/dom/MinimapTerrainView.d.ts +1 -0
  619. package/src/view/minimap/dom/MinimapTerrainView.d.ts.map +1 -1
  620. package/src/view/minimap/dom/MinimapTerrainView.js +151 -147
  621. package/src/view/tooltip/gml/TooltipParser.js +2 -2
  622. package/src/engine/network/PriorityFetch.d.ts +0 -59
  623. package/src/engine/network/PriorityFetch.d.ts.map +0 -1
  624. package/src/engine/network/PriorityFetch.js +0 -193
  625. package/src/generation/foliage/config/BranchLevelConfig.d.ts.map +0 -1
@@ -1,1049 +1,1023 @@
1
- import { PI2 } from "../../../core/math/PI2.js";
2
- import { clamp } from "../../../core/math/clamp.js";
3
- import { lerp } from "../../../core/math/lerp.js";
4
- import { Attribute } from "../../../shade/renderer/geometry/Attribute.js";
5
- import { Geometry } from "../../../shade/renderer/geometry/Geometry.js";
6
- import {
7
- geometry_obtain_attribute,
8
- geometry_obtain_index
9
- } from "../../../shade/renderer/geometry/geometry_obtain_attribute.js";
10
- import { StandardAttributes } from "../../../shade/renderer/geometry/StandardAttributes.js";
11
- import { BranchLevelConfig } from "../config/BranchLevelConfig.js";
12
- import { TreeLODConfig } from "../config/TreeLODConfig.js";
13
- import { TreeSpeciesConfig } from "../config/TreeSpeciesConfig.js";
14
- import { evaluate_bark_relief } from "../material/make_bark_material.js";
15
- import { spine_frame_double_reflection } from "../skeleton/spine_frame_double_reflection.js";
16
- import { branch_collar_project } from "./branch_collar_project.js";
17
- import { branch_apex_triangulate, branch_ring_triangulate } from "./branch_ring_triangulate.js";
18
- import { tree_mesh_apply_canopy_ao } from "./tree_mesh_apply_canopy_ao.js";
19
-
20
- const DEFAULT_LEVEL_CONFIG = new BranchLevelConfig();
21
-
22
- const SCRATCH_P0 = new Float32Array(3);
23
- const SCRATCH_P1 = new Float32Array(3);
24
- const SCRATCH_T0 = new Float32Array(3);
25
- const SCRATCH_V_TAN = new Float32Array(3);
26
- const SCRATCH_R0 = new Float32Array(3);
27
- const SCRATCH_SPINE0 = new Float32Array(3);
28
- const SCRATCH_SPINE1 = new Float32Array(3);
29
- const SCRATCH_RMF = new Float64Array(6);
30
- const SCRATCH_PARENT_P = new Float32Array(3);
31
- const SCRATCH_PARENT_T = new Float32Array(3);
32
- const SCRATCH_COLLAR_POS = new Float32Array(3);
33
- const SCRATCH_COLLAR_NORM = new Float32Array(3);
34
- const SCRATCH_RELIEF = new Float32Array(4);
35
-
36
- /**
37
- * Deterministic [0, 1) hash for procedural knot/burl placement on branch `b`.
38
- * @param {number} seed
39
- * @param {number} b
40
- * @param {number} k
41
- * @param {number} salt
42
- * @returns {number}
43
- */
44
- function knot_rand01(seed, b, k, salt) {
45
- let h = (seed ^ Math.imul(b + 1, 0x9E3779B1) ^ Math.imul(k + 1, 0x85EBCA6B) ^ Math.imul(salt, 0xC2B2AE35)) | 0;
46
- h = Math.imul(h ^ (h >>> 16), 0x7FEB352D);
47
- h = Math.imul(h ^ (h >>> 15), 0x846CA68B);
48
- h = (h ^ (h >>> 16)) >>> 0;
49
- return h / 4294967296.0;
50
- }
51
-
52
- /**
53
- * Helper to obtain a `Uint16Array` attribute on `Geometry` for `StandardAttributes.SkinningJoints`.
54
- * @param {Geometry} geometry
55
- * @param {number} count vertex count
56
- * @returns {Uint16Array}
57
- */
58
- export function geometry_obtain_joints_attribute(geometry, count) {
59
- const existing = geometry.getAttribute(StandardAttributes.SkinningJoints);
60
- if (existing === undefined) {
61
- const attr = Attribute.from(new Uint16Array(count * 4), 4, StandardAttributes.SkinningJoints);
62
- geometry.addAttribute(attr);
63
- return /** @type {Uint16Array} */ (attr.data);
64
- }
65
- if (existing.count !== count || !(existing.data instanceof Uint16Array)) {
66
- existing.data = new Uint16Array(count * 4);
67
- existing.count = count;
68
- }
69
- existing.needsUpdate = true;
70
- return /** @type {Uint16Array} */ (existing.data);
71
- }
72
-
73
- /**
74
- * Sweeps the Cubic Hermite branch spines of a `TreeSkeleton` into a single manifold, seamless-UV
75
- * bark `Geometry` with root buttress lobes, longitudinal bark flutes, welded lateral branch collars,
76
- * canopy ambient occlusion, and 4-joint skeletal wind skinning attributes.
77
- *
78
- * @param {import("../skeleton/TreeSkeleton.js").TreeSkeleton} skeleton
79
- * @param {TreeSpeciesConfig} [config]
80
- * @param {TreeLODConfig} [lod_config]
81
- * @param {import("../../../shade/renderer/geometry/Geometry.js").Geometry} [out]
82
- * @returns {Geometry}
83
- */
84
- export function tree_mesh_build_bark(
85
- skeleton,
86
- config = new TreeSpeciesConfig(),
87
- lod_config = new TreeLODConfig(),
88
- out = new Geometry(),
89
- only_rings = false
90
- ) {
91
- const species = config && config.isTreeSpeciesConfig ? config : TreeSpeciesConfig.from(config);
92
- const lod_cfg = lod_config && lod_config.isTreeLODConfig ? lod_config : TreeLODConfig.from(lod_config);
93
-
94
- const branch_count = skeleton.branch_count;
95
-
96
- const radial_scale = clamp(lod_cfg.radial_scale ?? 1.0, 0.2, 2.0);
97
- const long_scale = clamp(lod_cfg.longitudinal_scale ?? 1.0, 0.2, 4.0);
98
- const max_level = lod_cfg.max_branch_level ?? 3;
99
- const min_radius = lod_cfg.min_branch_radius ?? 0.0;
100
- const texel_density = Math.max(0.1, species.bark_material.texel_density ?? 1.2);
101
- const twist_uv = species.bark_material.twist_uv ?? 0.0;
102
-
103
- // Collect retained branches and their resampled longitudinal cross-sectional rings
104
- const branch_plans = [];
105
- let total_vertices = 0;
106
- let total_indices = 0;
107
-
108
- for (let b = 0; b < branch_count; b++) {
109
- const level = skeleton.branch_level[b];
110
- const base_r = skeleton.branch_base_radius[b];
111
- if (level > max_level) continue;
112
- if (level > 0 && base_r < min_radius) continue;
113
-
114
- const lvl_cfg = (species.levels && species.levels.length > 0)
115
- ? (species.levels[Math.min(level, species.levels.length - 1)] || DEFAULT_LEVEL_CONFIG)
116
- : DEFAULT_LEVEL_CONFIG;
117
- const m_max = Math.max(3, Math.round(lvl_cfg.radial_segments_max * radial_scale));
118
- const m_min = Math.max(3, Math.min(m_max, Math.round(lvl_cfg.radial_segments_min * radial_scale)));
119
-
120
- // Walk the node/segment spine of branch b via `skeleton.graph`
121
- const nodes = [];
122
- const edges = [];
123
- skeleton.collect_branch_chain(b, nodes, edges);
124
-
125
- const E = edges.length;
126
- if (E < 1) continue;
127
-
128
- const sample_steps = Math.max(1, Math.round(E * long_scale));
129
- const ring_count = sample_steps + 1;
130
- const ring_m = new Int32Array(ring_count);
131
- let branch_verts = 0;
132
- let branch_tris = 0;
133
-
134
- for (let i = 0; i <= sample_steps; i++) {
135
- const u_branch = (i / sample_steps) * E;
136
- const edge_idx = Math.min(E - 1, Math.floor(u_branch));
137
- const local_u = i === sample_steps ? 1.0 : (u_branch - edge_idx);
138
-
139
- const r0 = skeleton.node_read_radius(nodes[edge_idx]);
140
- const r1 = skeleton.node_read_radius(nodes[edge_idx + 1]);
141
- const r_i = lerp(r0, r1, local_u);
142
-
143
- const ratio = base_r > 1e-5 ? clamp(r_i / base_r, 0.0, 1.0) : 1.0;
144
- const m_i = Math.max(3, Math.round(lerp(m_min, m_max, Math.pow(ratio, 0.7))));
145
- ring_m[i] = m_i;
146
- branch_verts += (m_i + 1);
147
- if (i > 0) {
148
- branch_tris += (ring_m[i - 1] + m_i);
149
- }
150
- }
151
-
152
- // Terminal apex cone cap (m_tip vertices, m_tip triangles)
153
- const m_tip = ring_m[sample_steps];
154
- branch_verts += m_tip;
155
- branch_tris += m_tip;
156
-
157
- branch_plans.push({
158
- branch_id: b,
159
- level,
160
- base_r,
161
- lvl_cfg,
162
- nodes,
163
- edges,
164
- sample_steps,
165
- ring_m
166
- });
167
-
168
- total_vertices += branch_verts;
169
- total_indices += branch_tris * 3;
170
- }
171
-
172
- const is_empty_fallback = branch_plans.length === 0 || total_vertices === 0 || total_indices === 0;
173
- if (is_empty_fallback) {
174
- if (only_rings) {
175
- return /** @type {any} */ (new Map());
176
- }
177
- total_vertices = 4;
178
- total_indices = 6;
179
- }
180
-
181
- const positions = geometry_obtain_attribute(out, StandardAttributes.Position, 3, total_vertices);
182
- const normals = geometry_obtain_attribute(out, StandardAttributes.Normal, 3, total_vertices);
183
- const uv0 = geometry_obtain_attribute(out, StandardAttributes.TextureCoordinates0, 2, total_vertices);
184
- const uv1 = geometry_obtain_attribute(out, StandardAttributes.TextureCoordinates1, 2, total_vertices);
185
- const colors = geometry_obtain_attribute(out, StandardAttributes.Color, 3, total_vertices);
186
- const joints = geometry_obtain_joints_attribute(out, total_vertices);
187
- const weights = geometry_obtain_attribute(out, StandardAttributes.SkinningWeights, 4, total_vertices);
188
- const indices = geometry_obtain_index(out, total_indices);
189
-
190
- if (is_empty_fallback) {
191
- // Populate valid non-degenerate fallback quad so ensureTangents() and MeshoptClusterizer always succeed
192
- const quad_pos = [
193
- -0.02, 0.0, -0.02,
194
- 0.02, 0.0, -0.02,
195
- 0.02, 0.04, 0.02,
196
- -0.02, 0.04, 0.02
197
- ];
198
- const quad_uv = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
199
- for (let k = 0; k < 4; k++) {
200
- positions[k * 3] = quad_pos[k * 3];
201
- positions[k * 3 + 1] = quad_pos[k * 3 + 1];
202
- positions[k * 3 + 2] = quad_pos[k * 3 + 2];
203
- normals[k * 3] = 0.0;
204
- normals[k * 3 + 1] = 1.0;
205
- normals[k * 3 + 2] = 0.0;
206
- uv0[k * 2] = quad_uv[k * 2];
207
- uv0[k * 2 + 1] = quad_uv[k * 2 + 1];
208
- uv1[k * 2] = 0.0;
209
- uv1[k * 2 + 1] = 0.0;
210
- colors[k * 3] = 1.0;
211
- colors[k * 3 + 1] = 1.0;
212
- colors[k * 3 + 2] = 1.0;
213
- joints[k * 4] = 0;
214
- joints[k * 4 + 1] = 0;
215
- joints[k * 4 + 2] = 0;
216
- joints[k * 4 + 3] = 0;
217
- weights[k * 4] = 1.0;
218
- weights[k * 4 + 1] = 0.0;
219
- weights[k * 4 + 2] = 0.0;
220
- weights[k * 4 + 3] = 0.0;
221
- }
222
- indices[0] = 0;
223
- indices[1] = 1;
224
- indices[2] = 2;
225
- indices[3] = 0;
226
- indices[4] = 2;
227
- indices[5] = 3;
228
-
229
- out.name = `${species.name || "tree"}_bark_lod${lod_cfg.level}`;
230
- out.ensureBounds();
231
- out.ensureTangents();
232
- return out;
233
- }
234
-
235
- let v_cursor = 0;
236
- let i_cursor = 0;
237
- const meshed_branch_rings = new Map();
238
-
239
- for (let p_idx = 0; p_idx < branch_plans.length; p_idx++) {
240
- const plan = branch_plans[p_idx];
241
- const b = plan.branch_id;
242
- const level = plan.level;
243
- const base_r = plan.base_r;
244
- const lvl_cfg = plan.lvl_cfg;
245
- const nodes = plan.nodes;
246
- const edges = plan.edges;
247
- const E = edges.length;
248
- const sample_steps = plan.sample_steps;
249
- const ring_m = plan.ring_m;
250
-
251
- // Quantized integer circumference wrap count (1 or 2) for seamless bark tiling proportional to stem radius
252
- const k_wrap = Math.max(1, Math.min(2, Math.round(PI2 * base_r * texel_density)));
253
-
254
- // Pre-collect child branch attachment collars on this branch `b` (for parent-stem collar bosses & UV grain flow)
255
- const child_collars = [];
256
- const b_len = Math.max(0.2, skeleton.branch_length[b]);
257
- for (let cb = b + 1; cb < branch_count; cb++) {
258
- if (skeleton.branch_parent[cb] === b) {
259
- const c_root = skeleton.branch_root_vertex[cb];
260
- skeleton.node_read_tangent(SCRATCH_V_TAN, 0, c_root);
261
- const c_r = skeleton.branch_base_radius[cb];
262
- const r_ratio = clamp(c_r / Math.max(1e-4, base_r), 0.15, 0.82);
263
- child_collars.push({
264
- t_c: skeleton.branch_parent_attach_t[cb],
265
- tx_c: SCRATCH_V_TAN[0],
266
- ty_c: SCRATCH_V_TAN[1],
267
- tz_c: SCRATCH_V_TAN[2],
268
- r_ratio,
269
- sigma_t: clamp(1.55 * c_r / b_len, 0.035, 0.13),
270
- sigma_theta: clamp(0.60 * r_ratio + 0.22, 0.28, 0.72)
271
- });
272
- }
273
- }
274
-
275
- // Pre-collect deterministic 3D knots / callus eyes / burls along this branch `b`
276
- const branch_knots = [];
277
- const knot_count = lvl_cfg.knot_count | 0;
278
- const knot_str = lvl_cfg.knot_strength ?? 0.0;
279
- if (knot_count > 0 && knot_str > 0.0) {
280
- const seed_i = species.seed | 0;
281
- for (let k = 0; k < knot_count; k++) {
282
- const t_k = clamp(0.09 + (k + 0.2 + 0.6 * knot_rand01(seed_i, b, k, 1)) * (0.78 / knot_count), 0.08, 0.88);
283
- const theta_k = (k * 2.399963229728653 + knot_rand01(seed_i, b, k, 2) * 1.4) % PI2;
284
- const sigma_t = clamp(0.045 + 0.035 * knot_rand01(seed_i, b, k, 3), 0.04, 0.085);
285
- const sigma_theta = clamp(0.32 + 0.24 * knot_rand01(seed_i, b, k, 4), 0.30, 0.56);
286
- const amp_k = knot_str * (0.80 + 0.55 * knot_rand01(seed_i, b, k, 5));
287
- const is_burl = knot_rand01(seed_i, b, k, 6) > 0.45;
288
- branch_knots.push({ t_k, theta_k, sigma_t, sigma_theta, amp_k, is_burl });
289
- }
290
- }
291
-
292
- // Parent branch attachment context from actual meshed parent frustum
293
- const parent_b = skeleton.branch_parent[b];
294
- const parent_attach_t = skeleton.branch_parent_attach_t[b];
295
- const p_rings = parent_b >= 0 ? meshed_branch_rings.get(parent_b) : undefined;
296
- const has_meshed_parent = b > 0 && p_rings !== undefined && p_rings.length >= 2;
297
-
298
- let parent_px = 0.0, parent_py = 0.0, parent_pz = 0.0;
299
- let parent_tx = 0.0, parent_ty = 1.0, parent_tz = 0.0;
300
- let parent_r = base_r * 1.4;
301
- let parent_v_attach = 0.02;
302
- let parent_taper_slope = 0.0;
303
- let parent_m = 8;
304
- let parent_flute_amp = 0.0;
305
- let parent_z_min = -Infinity;
306
- let parent_z_max = Infinity;
307
- let shift_x = 0.0, shift_y = 0.0, shift_z = 0.0;
308
- let base_j0 = 0, base_j1 = 0, base_j2 = 0, base_j3 = 0;
309
- let base_w0 = 1.0, base_w1 = 0.0, base_w2 = 0.0, base_w3 = 0.0;
310
- let emerge_tx = 0.0, emerge_ty = 1.0, emerge_tz = 0.0;
311
-
312
- if (has_meshed_parent) {
313
- let k_attach = p_rings.length - 2;
314
- for (let k = 0; k < p_rings.length - 1; k++) {
315
- if (parent_attach_t >= p_rings[k].t_norm - 1e-5 && parent_attach_t <= p_rings[k + 1].t_norm + 1e-5) {
316
- k_attach = k;
317
- break;
318
- }
319
- }
320
- const pr_a = p_rings[k_attach];
321
- const pr_b = p_rings[k_attach + 1];
322
- const xi = clamp((parent_attach_t - pr_a.t_norm) / Math.max(1e-5, pr_b.t_norm - pr_a.t_norm), 0.0, 1.0);
323
-
324
- parent_px = lerp(pr_a.cx, pr_b.cx, xi);
325
- parent_py = lerp(pr_a.cy, pr_b.cy, xi);
326
- parent_pz = lerp(pr_a.cz, pr_b.cz, xi);
327
-
328
- const ax = pr_b.cx - pr_a.cx;
329
- const ay = pr_b.cy - pr_a.cy;
330
- const az = pr_b.cz - pr_a.cz;
331
- const parent_seg_len = Math.sqrt(ax * ax + ay * ay + az * az) || 1e-5;
332
- parent_tx = ax / parent_seg_len;
333
- parent_ty = ay / parent_seg_len;
334
- parent_tz = az / parent_seg_len;
335
-
336
- parent_r = lerp(pr_a.r, pr_b.r, xi);
337
- parent_v_attach = lerp(pr_a.v_uv ?? 0.25, pr_b.v_uv ?? 0.25, xi);
338
- parent_taper_slope = (pr_b.r - pr_a.r) / parent_seg_len;
339
- parent_m = Math.min(pr_a.m, pr_b.m);
340
- parent_flute_amp = pr_a.flute_amp;
341
- parent_z_min = -(xi + 0.25) * parent_seg_len;
342
- parent_z_max = (1.0 - xi + 0.25) * parent_seg_len;
343
-
344
- // Translate child spine so its root lies on the exact parent mesh frustum chord
345
- skeleton.node_read_position(SCRATCH_P0, 0, nodes[0]);
346
- shift_x = parent_px - SCRATCH_P0[0];
347
- shift_y = parent_py - SCRATCH_P0[1];
348
- shift_z = parent_pz - SCRATCH_P0[2];
349
-
350
- // Compute emergence chord direction from parent_P to child Ring i = 1 spine center
351
- const u_step1 = (1 / sample_steps) * E;
352
- const edge_idx1 = Math.min(E - 1, Math.floor(u_step1));
353
- const local_u1 = 1 === sample_steps ? 1.0 : (u_step1 - edge_idx1);
354
- skeleton.evaluate_segment_position(SCRATCH_P1, 0, edges[edge_idx1], local_u1);
355
- const p1x = SCRATCH_P1[0] + shift_x;
356
- const p1y = SCRATCH_P1[1] + shift_y;
357
- const p1z = SCRATCH_P1[2] + shift_z;
358
- const emx = p1x - parent_px;
359
- const emy = p1y - parent_py;
360
- const emz = p1z - parent_pz;
361
- const em_len = Math.sqrt(emx * emx + emy * emy + emz * emz);
362
- if (em_len > 1e-6) {
363
- emerge_tx = emx / em_len;
364
- emerge_ty = emy / em_len;
365
- emerge_tz = emz / em_len;
366
- } else {
367
- skeleton.node_read_tangent(SCRATCH_V_TAN, 0, nodes[0]);
368
- emerge_tx = SCRATCH_V_TAN[0];
369
- emerge_ty = SCRATCH_V_TAN[1];
370
- emerge_tz = SCRATCH_V_TAN[2];
371
- }
372
-
373
- // Merge parent frustum skinning weights at xi so child Ring i = 0 deforms identically to parent
374
- const merged_j = [];
375
- const merged_w = [];
376
- const accumulate_joint = (j_id, w_val) => {
377
- if (w_val <= 1e-7) return;
378
- for (let s = 0; s < merged_j.length; s++) {
379
- if (merged_j[s] === j_id) {
380
- merged_w[s] += w_val;
381
- return;
382
- }
383
- }
384
- merged_j.push(j_id);
385
- merged_w.push(w_val);
386
- };
387
- const w_a = 1.0 - xi;
388
- accumulate_joint(pr_a.j0, pr_a.w0 * w_a);
389
- accumulate_joint(pr_a.j1, pr_a.w1 * w_a);
390
- accumulate_joint(pr_a.j2, pr_a.w2 * w_a);
391
- accumulate_joint(pr_a.j3, pr_a.w3 * w_a);
392
- accumulate_joint(pr_b.j0, pr_b.w0 * xi);
393
- accumulate_joint(pr_b.j1, pr_b.w1 * xi);
394
- accumulate_joint(pr_b.j2, pr_b.w2 * xi);
395
- accumulate_joint(pr_b.j3, pr_b.w3 * xi);
396
-
397
- // Sort descending by weight and normalize top 4
398
- for (let a = 0; a < merged_j.length; a++) {
399
- for (let c = a + 1; c < merged_j.length; c++) {
400
- if (merged_w[c] > merged_w[a]) {
401
- const tj = merged_j[a]; merged_j[a] = merged_j[c]; merged_j[c] = tj;
402
- const tw = merged_w[a]; merged_w[a] = merged_w[c]; merged_w[c] = tw;
403
- }
404
- }
405
- }
406
- base_j0 = merged_j[0] ?? 0;
407
- base_j1 = merged_j[1] ?? 0;
408
- base_j2 = merged_j[2] ?? 0;
409
- base_j3 = merged_j[3] ?? 0;
410
- base_w0 = merged_w[0] ?? 1.0;
411
- base_w1 = merged_w[1] ?? 0.0;
412
- base_w2 = merged_w[2] ?? 0.0;
413
- base_w3 = merged_w[3] ?? 0.0;
414
- const w_sum = base_w0 + base_w1 + base_w2 + base_w3;
415
- if (w_sum > 1e-6) {
416
- base_w0 /= w_sum;
417
- base_w1 /= w_sum;
418
- base_w2 /= w_sum;
419
- base_w3 /= w_sum;
420
- } else {
421
- base_w0 = 1.0;
422
- }
423
- }
424
-
425
- let prev_ring_start = -1;
426
- let prev_m = 0;
427
- const current_branch_rings = [];
428
-
429
- // Track continuous RMF along the resampled spine rings
430
- let prev_px = 0.0, prev_py = 0.0, prev_pz = 0.0;
431
- let prev_tx = 0.0, prev_ty = 1.0, prev_tz = 0.0;
432
- let cur_rx = 1.0, cur_ry = 0.0, cur_rz = 0.0;
433
- let cur_sx = 0.0, cur_sy = 0.0, cur_sz = 1.0;
434
-
435
- // Keep terminal ring state for apex cap
436
- let tip_px = 0.0, tip_py = 0.0, tip_pz = 0.0;
437
- let tip_tx = 0.0, tip_ty = 1.0, tip_tz = 0.0;
438
- let tip_rx = 1.0, tip_ry = 0.0, tip_rz = 0.0;
439
- let tip_sx = 0.0, tip_sy = 0.0, tip_sz = 1.0;
440
- let tip_r = 0.002;
441
- let tip_arc_s = 0.0;
442
- let tip_v_uv = 0.90;
443
- let tip_wind_sway = 0.0;
444
- let tip_wind_phase = 0.0;
445
- let tip_j0 = 0, tip_j1 = 0, tip_j2 = 0, tip_j3 = 0;
446
- let tip_w0 = 1.0, tip_w1 = 0.0, tip_w2 = 0.0, tip_w3 = 0.0;
447
-
448
- // Continuous ontogenetic V range [v_start, v_end] in [0, 1] from trunk base (old bark) to twig tips (young shoot bark)
449
- const num_levels = (species.levels && species.levels.length > 0) ? species.levels.length : 1;
450
- let v_start = 0.02;
451
- let v_end = num_levels > 2 ? 0.62 : (num_levels === 2 ? 0.80 : 0.90);
452
- if (has_meshed_parent) {
453
- v_start = parent_v_attach;
454
- if (level === 1 && num_levels > 2) {
455
- v_end = clamp(lerp(v_start, 0.80, 0.65), v_start + 0.10, 0.84);
456
- } else {
457
- v_end = clamp(lerp(v_start, 0.95, 0.82), Math.max(0.82, v_start + 0.08), 0.97);
458
- }
459
- }
460
-
461
- const max_weld_rings = Math.min(lvl_cfg.collar_weld_rings, Math.max(0, sample_steps - 1));
462
-
463
- for (let i = 0; i <= sample_steps; i++) {
464
- const m_i = ring_m[i];
465
- const ring_start = v_cursor;
466
-
467
- const u_branch = (i / sample_steps) * E;
468
- const edge_idx = Math.min(E - 1, Math.floor(u_branch));
469
- const local_u = i === sample_steps ? 1.0 : (u_branch - edge_idx);
470
- const edge_id = edges[edge_idx];
471
- const v0 = nodes[edge_idx];
472
- const v1 = nodes[edge_idx + 1];
473
-
474
- skeleton.evaluate_segment_position(SCRATCH_P0, 0, edge_id, local_u);
475
- skeleton.evaluate_segment_tangent(SCRATCH_T0, 0, edge_id, local_u);
476
-
477
- const px = SCRATCH_P0[0] + shift_x;
478
- const py = SCRATCH_P0[1] + shift_y;
479
- const pz = SCRATCH_P0[2] + shift_z;
480
- const tx = SCRATCH_T0[0], ty = SCRATCH_T0[1], tz = SCRATCH_T0[2];
481
-
482
- if (i === 0) {
483
- skeleton.node_read_tangent(SCRATCH_V_TAN, 0, v0);
484
- skeleton.node_read_frame_r(SCRATCH_R0, 0, v0);
485
- spine_frame_double_reflection(
486
- SCRATCH_RMF, 0,
487
- px, py, pz,
488
- SCRATCH_V_TAN[0], SCRATCH_V_TAN[1], SCRATCH_V_TAN[2],
489
- SCRATCH_R0[0], SCRATCH_R0[1], SCRATCH_R0[2],
490
- px, py, pz,
491
- tx, ty, tz
492
- );
493
- } else {
494
- spine_frame_double_reflection(
495
- SCRATCH_RMF, 0,
496
- prev_px, prev_py, prev_pz,
497
- prev_tx, prev_ty, prev_tz,
498
- cur_rx, cur_ry, cur_rz,
499
- px, py, pz,
500
- tx, ty, tz
501
- );
502
- }
503
- cur_rx = SCRATCH_RMF[0];
504
- cur_ry = SCRATCH_RMF[1];
505
- cur_rz = SCRATCH_RMF[2];
506
- cur_sx = SCRATCH_RMF[3];
507
- cur_sy = SCRATCH_RMF[4];
508
- cur_sz = SCRATCH_RMF[5];
509
-
510
- prev_px = px;
511
- prev_py = py;
512
- prev_pz = pz;
513
- prev_tx = tx;
514
- prev_ty = ty;
515
- prev_tz = tz;
516
-
517
- const rx = cur_rx, ry = cur_ry, rz = cur_rz;
518
- const sx = cur_sx, sy = cur_sy, sz = cur_sz;
519
-
520
- const r0 = skeleton.node_read_radius(v0);
521
- const r1 = skeleton.node_read_radius(v1);
522
- const r_node = lerp(r0, r1, local_u);
523
-
524
- skeleton.node_read_spine_param(SCRATCH_SPINE0, 0, v0);
525
- skeleton.node_read_spine_param(SCRATCH_SPINE1, 0, v1);
526
- const t_norm = lerp(SCRATCH_SPINE0[0], SCRATCH_SPINE1[0], local_u);
527
- const arc_s = lerp(SCRATCH_SPINE0[1], SCRATCH_SPINE1[1], local_u);
528
- const phase_hash = skeleton.branch_phase_hash[b];
529
-
530
- // Skinning joint indices & weights at this ring (causal upstream-only + <= 4 joints per frustum slice)
531
- let ring_j0 = 0, ring_j1 = 0, ring_j2 = 0, ring_j3 = 0;
532
- let ring_w0 = 1.0, ring_w1 = 0.0, ring_w2 = 0.0, ring_w3 = 0.0;
533
- if (has_meshed_parent) {
534
- const parent_attach_v = skeleton.node_parent_vertex(nodes[0]);
535
- const ancestor_j = parent_attach_v >= 0 ? skeleton.node_read_joint(parent_attach_v, 0) : 0;
536
- const j_branch_root = skeleton.node_read_joint(nodes[0], 0);
537
- const j_v0 = skeleton.node_read_joint(v0, 0);
538
- if (i === 0 || j_v0 === ancestor_j) {
539
- ring_j0 = base_j0;
540
- ring_j1 = base_j1;
541
- ring_j2 = base_j2;
542
- ring_j3 = base_j3;
543
- ring_w0 = base_w0;
544
- ring_w1 = base_w1;
545
- ring_w2 = base_w2;
546
- ring_w3 = base_w3;
547
- } else if (j_v0 === j_branch_root) {
548
- const w_own = clamp(0.85 * t_norm, 0.0, 0.90);
549
- const w_base = 1.0 - w_own;
550
- ring_j0 = j_v0;
551
- ring_j1 = base_j0;
552
- ring_j2 = base_j1;
553
- ring_j3 = 0;
554
- ring_w0 = w_own;
555
- ring_w1 = w_base * base_w0;
556
- ring_w2 = w_base * base_w1;
557
- ring_w3 = 0.0;
558
- const rw_sum = ring_w0 + ring_w1 + ring_w2;
559
- if (rw_sum > 1e-6) {
560
- ring_w0 /= rw_sum;
561
- ring_w1 /= rw_sum;
562
- ring_w2 /= rw_sum;
563
- }
564
- } else {
565
- const w_own = clamp(0.50 + 0.45 * t_norm, 0.0, 0.95);
566
- ring_j0 = j_v0;
567
- ring_j1 = j_branch_root;
568
- ring_j2 = 0;
569
- ring_j3 = 0;
570
- ring_w0 = w_own;
571
- ring_w1 = 1.0 - w_own;
572
- ring_w2 = 0.0;
573
- ring_w3 = 0.0;
574
- }
575
- } else {
576
- const j_primary = skeleton.node_read_joint(v0, 0);
577
- const j_parent = skeleton.node_read_joint(v0, 1);
578
- const w_primary = (v0 === 0 || j_primary === j_parent) ? 1.0 : clamp(0.25 + 0.75 * t_norm, 0.0, 1.0);
579
- ring_j0 = j_primary;
580
- ring_j1 = j_parent;
581
- ring_w0 = w_primary;
582
- ring_w1 = 1.0 - w_primary;
583
- }
584
-
585
- // Collar weld factor: i === 0 of every child branch is always seated into its meshed parent frustum
586
- let weld_factor = 0.0;
587
- if (has_meshed_parent) {
588
- if (i === 0) {
589
- weld_factor = 1.0;
590
- } else if (i <= max_weld_rings && i < sample_steps && t_norm < 0.65) {
591
- const u_collar = 1.0 - i / (max_weld_rings + 1);
592
- weld_factor = u_collar * u_collar;
593
- }
594
- }
595
-
596
- // Root flare factor on trunk/basal branches
597
- const has_flare = lvl_cfg.flare_amplitude > 0.0 && t_norm < lvl_cfg.flare_height_ratio;
598
- let flare_env = 0.0;
599
- if (has_flare) {
600
- const u_f = 1.0 - t_norm / Math.max(1e-4, lvl_cfg.flare_height_ratio);
601
- flare_env = lvl_cfg.flare_amplitude * u_f * u_f;
602
- }
603
-
604
- const unwelded = 1.0 - weld_factor;
605
- const v_base = clamp(lerp(v_start, v_end, Math.pow(t_norm, 0.88)), 0.01, 0.98);
606
- // Smooth terminal shoot rounding over t_norm in (0.88, 1.0] so young apical tips terminate as circular shoots without pleated flutes
607
- const tip_roundness = i === sample_steps ? 0.0 : clamp((1.0 - t_norm) / 0.12, 0.0, 1.0);
608
- const ontogenetic_maturity = (level >= 2 ? clamp(1.0 - 0.72 * ((v_base - 0.55) / 0.45), 0.22, 1.0) : 1.0) * tip_roundness;
609
- const flute_env = lvl_cfg.flute_amplitude * unwelded * ontogenetic_maturity;
610
- const has_flute = flute_env > 1e-5;
611
- const horiz_sin = Math.sqrt(Math.max(0.0, 1.0 - ty * ty));
612
- const oval_ecc = (lvl_cfg.oval_eccentricity ?? 0.0) * unwelded * horiz_sin * tip_roundness;
613
- const bark_disp = (lvl_cfg.bark_displacement ?? 0.0) * unwelded * ontogenetic_maturity;
614
- const parent_collar_amp = (lvl_cfg.parent_collar_bulge ?? 0.0) * unwelded * tip_roundness;
615
-
616
- const wind_sway = clamp((level + t_norm) * 0.25, 0.0, 1.0);
617
- const wind_phase = clamp(phase_hash, 0.0, 1.0);
618
- const collar_ao = 1.0 - 0.16 * weld_factor;
619
-
620
- for (let j = 0; j <= m_i; j++) {
621
- // Ensure exact periodic closure between j=0 and j=m_i
622
- const j_wrapped = j === m_i ? 0 : j;
623
- const theta = (j_wrapped / m_i) * PI2;
624
- const cos_t = Math.cos(theta);
625
- const sin_t = Math.sin(theta);
626
-
627
- // Outward radial unit vector in cross-section plane
628
- const n_rad_x = cos_t * rx + sin_t * sx;
629
- const n_rad_y = cos_t * ry + cos_t * 0.0 + sin_t * sy;
630
- const n_rad_z = cos_t * rz + sin_t * sz;
631
-
632
- // Circumferential tangent unit vector (-sin_t R + cos_t S)
633
- const tan_theta_x = -sin_t * rx + cos_t * sx;
634
- const tan_theta_y = -sin_t * ry + cos_t * sy;
635
- const tan_theta_z = -sin_t * rz + cos_t * sz;
636
-
637
- // Evaluate lobe/flute/oval/collar/knot/bark radius multiplier & UV grain streamline warp
638
- let r_mult = 1.0;
639
- let d_rmult_dtheta = 0.0;
640
- let u_warp = 0.0;
641
- let vert_ao = collar_ao;
642
- let d_tan = 0.0;
643
-
644
- // 1. Phase-locked root buttress lobes flowing continuously into twisting trunk flutes
645
- const theta_flute = theta + 0.32 * arc_s + phase_hash * PI2;
646
- if (has_flare) {
647
- const lobe_phase = lvl_cfg.flare_lobes * theta_flute;
648
- const cos_lobe = Math.cos(lobe_phase);
649
- if (cos_lobe > 0.0) {
650
- const p = lvl_cfg.flare_sharpness;
651
- r_mult += flare_env * Math.pow(cos_lobe, p);
652
- d_rmult_dtheta -= flare_env * p * lvl_cfg.flare_lobes * Math.pow(cos_lobe, Math.max(0.0, p - 1.0)) * Math.sin(lobe_phase);
653
- }
654
- }
655
-
656
- if (has_flute) {
657
- const f_freq = Math.min(4, Math.max(2, lvl_cfg.flute_frequency | 0));
658
- const flute_phase = f_freq * theta_flute;
659
- const f_val = Math.sin(flute_phase);
660
- const f_der = f_freq * Math.cos(flute_phase);
661
- r_mult += flute_env * f_val;
662
- d_rmult_dtheta += flute_env * f_der * 0.45;
663
- }
664
-
665
- // 2. Biomechanical reaction-wood ovalization on inclined/horizontal boughs
666
- if (oval_ecc > 1e-4) {
667
- const oval_term = oval_ecc * (1.35 * (2.0 * n_rad_y * n_rad_y - 1.0) - 0.22 * n_rad_y);
668
- r_mult += oval_term;
669
- d_rmult_dtheta += oval_ecc * (5.4 * n_rad_y - 0.22) * tan_theta_y;
670
- }
671
-
672
- // 3. Parent-stem branch collar swelling bosses & UV grain streamline deflection around child branches
673
- if (parent_collar_amp > 1e-4 && child_collars.length > 0) {
674
- for (let c_idx = 0; c_idx < child_collars.length; c_idx++) {
675
- const cc = child_collars[c_idx];
676
- const dt = t_norm - cc.t_c;
677
- if (Math.abs(dt) < 3.2 * cc.sigma_t) {
678
- const theta_c = Math.atan2(
679
- cc.tx_c * sx + cc.ty_c * sy + cc.tz_c * sz,
680
- cc.tx_c * rx + cc.ty_c * ry + cc.tz_c * rz
681
- );
682
- const dtheta = Math.atan2(Math.sin(theta - theta_c), Math.cos(theta - theta_c));
683
- const sig_t = dt < 0.0 ? cc.sigma_t * 1.25 : cc.sigma_t * 0.90;
684
- const u_t = dt / sig_t;
685
- const u_th = dtheta / cc.sigma_theta;
686
- const dist2 = u_t * u_t + u_th * u_th;
687
- if (dist2 < 9.0) {
688
- const gauss = Math.exp(-0.5 * dist2);
689
- const b_amp = parent_collar_amp * (0.55 + 0.75 * cc.r_ratio);
690
- r_mult += b_amp * gauss;
691
- d_rmult_dtheta += -b_amp * gauss * (u_th / cc.sigma_theta);
692
- const r_uv2 = dist2 + 0.85;
693
- u_warp += 0.008 * unwelded * (u_th / r_uv2) * Math.exp(-0.35 * dist2);
694
- }
695
- }
696
- }
697
- }
698
-
699
- // 4. Localized 3D knots, volcanic callus rims, and burls + UV grain streamline deflection
700
- if (unwelded * tip_roundness > 0.05 && branch_knots.length > 0) {
701
- for (let k_idx = 0; k_idx < branch_knots.length; k_idx++) {
702
- const kn = branch_knots[k_idx];
703
- const dt = t_norm - kn.t_k;
704
- if (Math.abs(dt) < 3.2 * kn.sigma_t) {
705
- const dtheta = Math.atan2(Math.sin(theta - kn.theta_k), Math.cos(theta - kn.theta_k));
706
- const u_t = dt / kn.sigma_t;
707
- const u_th = dtheta / kn.sigma_theta;
708
- const rho2 = u_t * u_t + u_th * u_th;
709
- if (rho2 < 9.0) {
710
- const k_amp = kn.amp_k * unwelded * tip_roundness;
711
- if (kn.is_burl) {
712
- const w_k = Math.exp(-0.55 * rho2);
713
- r_mult += k_amp * w_k;
714
- d_rmult_dtheta += -k_amp * w_k * 1.1 * (u_th / kn.sigma_theta);
715
- } else {
716
- const exp_r = Math.exp(-0.85 * rho2);
717
- const w_rim = (0.30 + 1.90 * rho2) * exp_r;
718
- const dw_drho2 = (1.90 - 0.85 * (0.30 + 1.90 * rho2)) * exp_r;
719
- r_mult += k_amp * w_rim;
720
- d_rmult_dtheta += k_amp * dw_drho2 * (2.0 * u_th / kn.sigma_theta);
721
- if (rho2 < 0.65) {
722
- vert_ao *= (0.72 + 0.28 * (rho2 / 0.65));
723
- }
724
- }
725
- const r_uv2 = rho2 + 0.85;
726
- u_warp += 0.009 * unwelded * (u_th / r_uv2) * Math.exp(-0.35 * rho2);
727
- }
728
- }
729
- }
730
- }
731
-
732
- // 5. 3D Geometric Bark Relief & Coupled Tangential Ridge-Pinch
733
- const u_periodic = k_wrap * (j_wrapped / m_i) + twist_uv * arc_s;
734
- if (bark_disp > 1e-4) {
735
- evaluate_bark_relief(
736
- SCRATCH_RELIEF,
737
- u_periodic + u_warp,
738
- v_base,
739
- species.bark_material,
740
- (species.seed | 0) + b * 19
741
- );
742
- const h_bark = SCRATCH_RELIEF[0];
743
- const dh_du = SCRATCH_RELIEF[1];
744
- u_warp += SCRATCH_RELIEF[2] * unwelded * 0.65;
745
-
746
- r_mult += bark_disp * (h_bark - 0.50) * 1.35;
747
- d_rmult_dtheta += bark_disp * dh_du * 0.15;
748
- }
749
- u_warp = clamp(u_warp, -0.015, 0.015);
750
-
751
- if (weld_factor > 0.0) {
752
- if (i === 0) {
753
- // Asymmetric adaxial (upper crotch) / abaxial (lower support) collar spread at base ring
754
- const align_parent = n_rad_x * parent_tx + n_rad_y * parent_ty + n_rad_z * parent_tz;
755
- const spread = align_parent >= 0.0
756
- ? lvl_cfg.collar_spread_upper * align_parent
757
- : lvl_cfg.collar_spread_lower * (-align_parent);
758
- r_mult += weld_factor * (0.15 + spread);
759
- } else {
760
- // Symmetric collar flare on subsequent rings so Ring i >= 1 stays centered on spine
761
- r_mult += weld_factor * 0.18;
762
- }
763
- }
764
-
765
- const r_local = r_node * Math.max(0.25, r_mult);
766
- let vx = px + r_local * n_rad_x + d_tan * tan_theta_x;
767
- let vy = py + r_local * n_rad_y + d_tan * tan_theta_y;
768
- let vz = pz + r_local * n_rad_z + d_tan * tan_theta_z;
769
-
770
- // Perturb outward normal gently by low-frequency lobe/knot derivative (fine detail comes from normal map)
771
- const slope = clamp(d_rmult_dtheta / Math.max(0.25, r_mult), -0.24, 0.24);
772
-
773
- let vnx = n_rad_x - slope * tan_theta_x;
774
- let vny = n_rad_y - slope * tan_theta_y;
775
- let vnz = n_rad_z - slope * tan_theta_z;
776
- const vn_len = Math.sqrt(vnx * vnx + vny * vny + vnz * vnz);
777
- if (vn_len > 1e-8) {
778
- vnx /= vn_len;
779
- vny /= vn_len;
780
- vnz /= vn_len;
781
- }
782
-
783
- if (weld_factor > 0.0) {
784
- branch_collar_project(
785
- SCRATCH_COLLAR_POS, 0,
786
- SCRATCH_COLLAR_NORM, 0,
787
- vx, vy, vz,
788
- vnx, vny, vnz,
789
- emerge_tx, emerge_ty, emerge_tz,
790
- parent_px, parent_py, parent_pz,
791
- parent_tx, parent_ty, parent_tz,
792
- parent_r,
793
- weld_factor,
794
- lvl_cfg.collar_normal_blend,
795
- parent_m,
796
- parent_flute_amp,
797
- parent_taper_slope,
798
- parent_z_min,
799
- parent_z_max
800
- );
801
- if (i === 0) {
802
- vx = SCRATCH_COLLAR_POS[0];
803
- vy = SCRATCH_COLLAR_POS[1];
804
- vz = SCRATCH_COLLAR_POS[2];
805
- }
806
- vnx = SCRATCH_COLLAR_NORM[0];
807
- vny = SCRATCH_COLLAR_NORM[1];
808
- vnz = SCRATCH_COLLAR_NORM[2];
809
- }
810
-
811
- const v3 = v_cursor * 3;
812
- const v2 = v_cursor * 2;
813
- const v4 = v_cursor * 4;
814
-
815
- positions[v3] = vx;
816
- positions[v3 + 1] = vy;
817
- positions[v3 + 2] = vz;
818
-
819
- normals[v3] = vnx;
820
- normals[v3 + 1] = vny;
821
- normals[v3 + 2] = vnz;
822
-
823
- uv0[v2] = k_wrap * (j / m_i) + twist_uv * arc_s + u_warp;
824
- uv0[v2 + 1] = v_base;
825
-
826
- uv1[v2] = wind_sway;
827
- uv1[v2 + 1] = wind_phase;
828
-
829
- colors[v3] = vert_ao;
830
- colors[v3 + 1] = vert_ao;
831
- colors[v3 + 2] = vert_ao;
832
-
833
- joints[v4] = ring_j0;
834
- joints[v4 + 1] = ring_j1;
835
- joints[v4 + 2] = ring_j2;
836
- joints[v4 + 3] = ring_j3;
837
-
838
- weights[v4] = ring_w0;
839
- weights[v4 + 1] = ring_w1;
840
- weights[v4 + 2] = ring_w2;
841
- weights[v4 + 3] = ring_w3;
842
-
843
- v_cursor++;
844
- }
845
-
846
- // Recenter non-collar-root rings so their vertex centroid lies on the exact spine point (px, py, pz)
847
- // while preserving all asymmetric 3D relief (knots, burls, collar bosses, ovalization, bark plates)
848
- if (i > 0 || !has_meshed_parent) {
849
- let mean_x = 0.0, mean_y = 0.0, mean_z = 0.0;
850
- for (let j = 0; j < m_i; j++) {
851
- const vi3 = (ring_start + j) * 3;
852
- mean_x += positions[vi3];
853
- mean_y += positions[vi3 + 1];
854
- mean_z += positions[vi3 + 2];
855
- }
856
- mean_x /= m_i;
857
- mean_y /= m_i;
858
- mean_z /= m_i;
859
- const corr_x = px - mean_x;
860
- const corr_y = py - mean_y;
861
- const corr_z = pz - mean_z;
862
- for (let j = 0; j <= m_i; j++) {
863
- const vi3 = (ring_start + j) * 3;
864
- positions[vi3] += corr_x;
865
- positions[vi3 + 1] += corr_y;
866
- positions[vi3 + 2] += corr_z;
867
- }
868
- }
869
-
870
- // Record emitted ring centroid and skinning state for child branch attachment
871
- let r_cx = 0.0, r_cy = 0.0, r_cz = 0.0;
872
- for (let j = 0; j < m_i; j++) {
873
- const vi3 = (ring_start + j) * 3;
874
- r_cx += positions[vi3];
875
- r_cy += positions[vi3 + 1];
876
- r_cz += positions[vi3 + 2];
877
- }
878
- r_cx /= m_i;
879
- r_cy /= m_i;
880
- r_cz /= m_i;
881
-
882
- current_branch_rings.push({
883
- cx: r_cx,
884
- cy: r_cy,
885
- cz: r_cz,
886
- r: r_node,
887
- m: m_i,
888
- t_norm,
889
- v_uv: v_base,
890
- flute_amp: lvl_cfg.flute_amplitude ?? 0.0,
891
- j0: ring_j0,
892
- j1: ring_j1,
893
- j2: ring_j2,
894
- j3: ring_j3,
895
- w0: ring_w0,
896
- w1: ring_w1,
897
- w2: ring_w2,
898
- w3: ring_w3
899
- });
900
-
901
- if (prev_ring_start >= 0) {
902
- i_cursor = branch_ring_triangulate(
903
- indices,
904
- i_cursor,
905
- prev_ring_start,
906
- prev_m,
907
- ring_start,
908
- m_i
909
- );
910
- }
911
-
912
- prev_ring_start = ring_start;
913
- prev_m = m_i;
914
-
915
- if (i === sample_steps) {
916
- tip_px = px;
917
- tip_py = py;
918
- tip_pz = pz;
919
- tip_tx = tx;
920
- tip_ty = ty;
921
- tip_tz = tz;
922
- tip_rx = rx;
923
- tip_ry = ry;
924
- tip_rz = rz;
925
- tip_sx = sx;
926
- tip_sy = sy;
927
- tip_sz = sz;
928
- tip_r = r_node;
929
- tip_arc_s = arc_s;
930
- tip_v_uv = v_base;
931
- tip_wind_sway = wind_sway;
932
- tip_wind_phase = wind_phase;
933
- tip_j0 = ring_j0;
934
- tip_j1 = ring_j1;
935
- tip_j2 = ring_j2;
936
- tip_j3 = ring_j3;
937
- tip_w0 = ring_w0;
938
- tip_w1 = ring_w1;
939
- tip_w2 = ring_w2;
940
- tip_w3 = ring_w3;
941
- }
942
- }
943
-
944
- meshed_branch_rings.set(b, current_branch_rings);
945
-
946
- // Close terminal branch tip with a watertight conical shoot-bud apex cap via branch_apex_triangulate
947
- const apex_start = v_cursor;
948
- const m_tip = prev_m;
949
- const cap_ext = tip_r * 1.15;
950
- const cap_arc_s = tip_arc_s + cap_ext;
951
-
952
- for (let j = 0; j < m_tip; j++) {
953
- const theta_mid = ((j + 0.5) / m_tip) * PI2;
954
- const cos_m = Math.cos(theta_mid);
955
- const sin_m = Math.sin(theta_mid);
956
-
957
- const n_rad_x = cos_m * tip_rx + sin_m * tip_sx;
958
- const n_rad_y = cos_m * tip_ry + sin_m * tip_sy;
959
- const n_rad_z = cos_m * tip_rz + sin_m * tip_sz;
960
-
961
- // Outward-tilted shoot-bud cone normal for smooth shading at the branch tip
962
- let anx = 0.62 * tip_tx + 0.78 * n_rad_x;
963
- let any = 0.62 * tip_ty + 0.78 * n_rad_y;
964
- let anz = 0.62 * tip_tz + 0.78 * n_rad_z;
965
- const an_len = Math.sqrt(anx * anx + any * any + anz * anz);
966
- if (an_len > 1e-8) {
967
- anx /= an_len;
968
- any /= an_len;
969
- anz /= an_len;
970
- }
971
-
972
- const v3 = v_cursor * 3;
973
- const v2 = v_cursor * 2;
974
- const v4 = v_cursor * 4;
975
-
976
- positions[v3] = tip_px + cap_ext * tip_tx;
977
- positions[v3 + 1] = tip_py + cap_ext * tip_ty;
978
- positions[v3 + 2] = tip_pz + cap_ext * tip_tz;
979
-
980
- normals[v3] = anx;
981
- normals[v3 + 1] = any;
982
- normals[v3 + 2] = anz;
983
-
984
- uv0[v2] = k_wrap * ((j + 0.5) / m_tip) + twist_uv * cap_arc_s;
985
- uv0[v2 + 1] = clamp(tip_v_uv + 0.01, 0.02, 0.99);
986
-
987
- uv1[v2] = tip_wind_sway;
988
- uv1[v2 + 1] = tip_wind_phase;
989
-
990
- colors[v3] = 1.0;
991
- colors[v3 + 1] = 1.0;
992
- colors[v3 + 2] = 1.0;
993
-
994
- joints[v4] = tip_j0;
995
- joints[v4 + 1] = tip_j1;
996
- joints[v4 + 2] = tip_j2;
997
- joints[v4 + 3] = tip_j3;
998
-
999
- weights[v4] = tip_w0;
1000
- weights[v4 + 1] = tip_w1;
1001
- weights[v4 + 2] = tip_w2;
1002
- weights[v4 + 3] = tip_w3;
1003
-
1004
- v_cursor++;
1005
- }
1006
-
1007
- i_cursor = branch_apex_triangulate(indices, i_cursor, prev_ring_start, m_tip, apex_start);
1008
- }
1009
-
1010
- if (only_rings) {
1011
- return /** @type {any} */ (meshed_branch_rings);
1012
- }
1013
-
1014
- // Apply analytic crown ambient occlusion onto bark vertex colors
1015
- tree_mesh_apply_canopy_ao(out, skeleton.crown_center, skeleton.crown_radii, {
1016
- ao_core_darkening: (species.foliage.ao_core_darkening ?? 0.35) * 0.65,
1017
- normal_puffiness: 0.0,
1018
- preserve_existing_color: true
1019
- });
1020
-
1021
- out.name = `${species.name || "tree"}_bark_lod${lod_cfg.level}`;
1022
- out.ensureBounds();
1023
- out.ensureTangents();
1024
-
1025
- return out;
1026
- }
1027
-
1028
- /**
1029
- * Computes the meshed cross-sectional ring centroids, radii, polygon vertex counts, and 4-slot
1030
- * skinning weights for every retained branch in `skeleton` using the exact same ring synthesis as
1031
- * `tree_mesh_build_bark`.
1032
- *
1033
- * @param {import("../skeleton/TreeSkeleton.js").TreeSkeleton} skeleton
1034
- * @param {TreeSpeciesConfig} [config]
1035
- * @param {TreeLODConfig} [lod_config]
1036
- * @returns {Map<number, Array<{
1037
- * cx: number, cy: number, cz: number,
1038
- * r: number, m: number, t_norm: number, flute_amp: number,
1039
- * j0: number, j1: number, j2: number, j3: number,
1040
- * w0: number, w1: number, w2: number, w3: number
1041
- * }>>}
1042
- */
1043
- export function tree_mesh_compute_branch_rings(
1044
- skeleton,
1045
- config = new TreeSpeciesConfig(),
1046
- lod_config = new TreeLODConfig()
1047
- ) {
1048
- return /** @type {any} */ (tree_mesh_build_bark(skeleton, config, lod_config, new Geometry(), true));
1049
- }
1
+ import { PI2 } from "../../../core/math/PI2.js";
2
+ import { clamp } from "../../../core/math/clamp.js";
3
+ import { lerp } from "../../../core/math/lerp.js";
4
+ import { Attribute } from "../../../shade/renderer/geometry/Attribute.js";
5
+ import { Geometry } from "../../../shade/renderer/geometry/Geometry.js";
6
+ import {
7
+ geometry_obtain_attribute,
8
+ geometry_obtain_index
9
+ } from "../../../shade/renderer/geometry/geometry_obtain_attribute.js";
10
+ import { StandardAttributes } from "../../../shade/renderer/geometry/StandardAttributes.js";
11
+ import { TreeLODConfig } from "../config/TreeLODConfig.js";
12
+ import { TreeSpeciesConfig } from "../config/TreeSpeciesConfig.js";
13
+ import { evaluate_bark_relief } from "../material/make_bark_material.js";
14
+ import { flare_lobe_phase_offset } from "../skeleton/flare_lobe_phase_offset.js";
15
+ import { spine_frame_double_reflection } from "../skeleton/spine_frame_double_reflection.js";
16
+ import { branch_collar_project } from "./branch_collar_project.js";
17
+ import { branch_apex_triangulate, branch_ring_triangulate } from "./branch_ring_triangulate.js";
18
+ import { interpolate_ring_skin_weights4 } from "./interpolate_ring_skin_weights4.js";
19
+ import { tree_skeleton_read_joint_motion } from "../skeleton/tree_skeleton_read_joint_motion.js";
20
+ import { tree_mesh_apply_canopy_ao } from "./tree_mesh_apply_canopy_ao.js";
21
+
22
+
23
+ const SCRATCH_P0 = new Float32Array(3);
24
+ const SCRATCH_P1 = new Float32Array(3);
25
+ const SCRATCH_T0 = new Float32Array(3);
26
+ const SCRATCH_V_TAN = new Float32Array(3);
27
+ const SCRATCH_R0 = new Float32Array(3);
28
+ const SCRATCH_SPINE0 = new Float32Array(3);
29
+ const SCRATCH_SPINE1 = new Float32Array(3);
30
+ const SCRATCH_RMF = new Float64Array(6);
31
+
32
+ /**
33
+ * Height of a living shoot's conical bud cap, as a multiple of the tip radius.
34
+ * @type {number}
35
+ */
36
+ const SHOOT_BUD_HEIGHT = 1.15;
37
+ const SCRATCH_PARENT_P = new Float32Array(3);
38
+ const SCRATCH_PARENT_T = new Float32Array(3);
39
+ const SCRATCH_COLLAR_POS = new Float32Array(3);
40
+ const SCRATCH_COLLAR_NORM = new Float32Array(3);
41
+ const SCRATCH_RELIEF = new Float32Array(4);
42
+ const SCRATCH_JOINTS4 = new Int32Array(4);
43
+ const SCRATCH_WEIGHTS4 = new Float64Array(4);
44
+
45
+ /**
46
+ * Helper to obtain a `Uint16Array` attribute on `Geometry` for `StandardAttributes.SkinningJoints`.
47
+ * @param {Geometry} geometry
48
+ * @param {number} count vertex count
49
+ * @returns {Uint16Array}
50
+ */
51
+ export function geometry_obtain_joints_attribute(geometry, count) {
52
+ const existing = geometry.getAttribute(StandardAttributes.SkinningJoints);
53
+ if (existing === undefined) {
54
+ const attr = Attribute.from(new Uint16Array(count * 4), 4, StandardAttributes.SkinningJoints);
55
+ geometry.addAttribute(attr);
56
+ return /** @type {Uint16Array} */ (attr.data);
57
+ }
58
+ if (existing.count !== count || !(existing.data instanceof Uint16Array)) {
59
+ existing.data = new Uint16Array(count * 4);
60
+ existing.count = count;
61
+ }
62
+ existing.needsUpdate = true;
63
+ return /** @type {Uint16Array} */ (existing.data);
64
+ }
65
+
66
+ /**
67
+ * Sweeps the Cubic Hermite branch spines of a `TreeSkeleton` into a single manifold, seamless-UV
68
+ * bark `Geometry` with root buttress lobes, longitudinal bark flutes, welded lateral branch collars,
69
+ * canopy ambient occlusion, and 4-joint skeletal wind skinning attributes.
70
+ *
71
+ * @param {import("../skeleton/TreeSkeleton.js").TreeSkeleton} skeleton
72
+ * @param {TreeSpeciesConfig} [config]
73
+ * @param {TreeLODConfig} [lod_config]
74
+ * @param {import("../../../shade/renderer/geometry/Geometry.js").Geometry} [out]
75
+ * @returns {Geometry}
76
+ */
77
+ export function tree_mesh_build_bark(
78
+ skeleton,
79
+ config = new TreeSpeciesConfig(),
80
+ lod_config = new TreeLODConfig(),
81
+ out = new Geometry(),
82
+ only_rings = false
83
+ ) {
84
+ const species = config && config.isTreeSpeciesConfig ? config : TreeSpeciesConfig.from(config);
85
+ const lod_cfg = lod_config && lod_config.isTreeLODConfig ? lod_config : TreeLODConfig.from(lod_config);
86
+
87
+ const branch_count = skeleton.branch_count;
88
+
89
+ const radial_scale = clamp(lod_cfg.radial_scale ?? 1.0, 0.2, 2.0);
90
+ const long_scale = clamp(lod_cfg.longitudinal_scale ?? 1.0, 0.2, 4.0);
91
+ const max_level = lod_cfg.max_branch_level ?? 3;
92
+ const min_radius = lod_cfg.min_branch_radius ?? 0.0;
93
+ const texel_density = Math.max(0.1, species.bark_material.texel_density ?? 1.2);
94
+ const twist_uv = species.bark_material.twist_uv ?? 0.0;
95
+
96
+ // Number of branching depths the generator graph can reach (trunk = 1), for the ontogenetic bark V range
97
+ const generator_depth = new Uint8Array(species.branches.length);
98
+ let generator_depth_count = 1;
99
+ // rules are acyclic with source < target, so visiting sources in ascending order settles every depth
100
+ for (let g = 0; g < generator_depth.length; g++) {
101
+ for (let r = 0; r < species.branch_rules.length; r++) {
102
+ const rule = species.branch_rules[r];
103
+ if (rule.source === g) {
104
+ generator_depth[rule.target] = Math.max(generator_depth[rule.target], generator_depth[g] + 1);
105
+ }
106
+ }
107
+ }
108
+ for (let g = 0; g < generator_depth.length; g++) {
109
+ generator_depth_count = Math.max(generator_depth_count, generator_depth[g] + 1);
110
+ }
111
+
112
+ // Collect retained branches and their resampled longitudinal cross-sectional rings
113
+ const branch_plans = [];
114
+ let total_vertices = 0;
115
+ let total_indices = 0;
116
+
117
+ for (let b = 0; b < branch_count; b++) {
118
+ const level = skeleton.branch_level[b];
119
+ const base_r = skeleton.branch_base_radius[b];
120
+ if (level > max_level) continue;
121
+ if (level > 0 && base_r < min_radius) continue;
122
+
123
+ const lvl_cfg = species.branches[skeleton.branch_generator[b]];
124
+ const m_max = Math.max(3, Math.round(lvl_cfg.radial_segments_max * radial_scale));
125
+ const m_min = Math.max(3, Math.min(m_max, Math.round(lvl_cfg.radial_segments_min * radial_scale)));
126
+
127
+ // Walk the node/segment spine of branch b via `skeleton.graph`
128
+ const nodes = [];
129
+ const edges = [];
130
+ skeleton.collect_branch_chain(b, nodes, edges);
131
+
132
+ const E = edges.length;
133
+ if (E < 1) continue;
134
+
135
+ const sample_steps = Math.max(1, Math.round(E * long_scale));
136
+ const ring_count = sample_steps + 1;
137
+ const ring_m = new Int32Array(ring_count);
138
+ let branch_verts = 0;
139
+ let branch_tris = 0;
140
+
141
+ for (let i = 0; i <= sample_steps; i++) {
142
+ const u_branch = (i / sample_steps) * E;
143
+ const edge_idx = Math.min(E - 1, Math.floor(u_branch));
144
+ const local_u = i === sample_steps ? 1.0 : (u_branch - edge_idx);
145
+
146
+ const r0 = skeleton.node_read_radius(nodes[edge_idx]);
147
+ const r1 = skeleton.node_read_radius(nodes[edge_idx + 1]);
148
+ const r_i = lerp(r0, r1, local_u);
149
+
150
+ const ratio = base_r > 1e-5 ? clamp(r_i / base_r, 0.0, 1.0) : 1.0;
151
+ const m_i = Math.max(3, Math.round(lerp(m_min, m_max, Math.pow(ratio, 0.7))));
152
+ ring_m[i] = m_i;
153
+ branch_verts += (m_i + 1);
154
+ if (i > 0) {
155
+ branch_tris += (ring_m[i - 1] + m_i);
156
+ }
157
+ }
158
+
159
+ // Terminal apex cone cap (m_tip vertices, m_tip triangles)
160
+ const m_tip = ring_m[sample_steps];
161
+ branch_verts += m_tip;
162
+ branch_tris += m_tip;
163
+
164
+ branch_plans.push({
165
+ branch_id: b,
166
+ level,
167
+ base_r,
168
+ lvl_cfg,
169
+ nodes,
170
+ edges,
171
+ sample_steps,
172
+ ring_m
173
+ });
174
+
175
+ total_vertices += branch_verts;
176
+ total_indices += branch_tris * 3;
177
+ }
178
+
179
+ const is_empty_fallback = branch_plans.length === 0 || total_vertices === 0 || total_indices === 0;
180
+ if (is_empty_fallback) {
181
+ if (only_rings) {
182
+ return /** @type {any} */ (new Map());
183
+ }
184
+ total_vertices = 4;
185
+ total_indices = 6;
186
+ }
187
+
188
+ const positions = geometry_obtain_attribute(out, StandardAttributes.Position, 3, total_vertices);
189
+ const normals = geometry_obtain_attribute(out, StandardAttributes.Normal, 3, total_vertices);
190
+ const uv0 = geometry_obtain_attribute(out, StandardAttributes.TextureCoordinates0, 2, total_vertices);
191
+ const uv1 = geometry_obtain_attribute(out, StandardAttributes.TextureCoordinates1, 2, total_vertices);
192
+ const colors = geometry_obtain_attribute(out, StandardAttributes.Color, 3, total_vertices);
193
+ const joints = geometry_obtain_joints_attribute(out, total_vertices);
194
+ const weights = geometry_obtain_attribute(out, StandardAttributes.SkinningWeights, 4, total_vertices);
195
+ const indices = geometry_obtain_index(out, total_indices);
196
+
197
+ if (is_empty_fallback) {
198
+ // Populate valid non-degenerate fallback quad so ensureTangents() and MeshoptClusterizer always succeed
199
+ const quad_pos = [
200
+ -0.02, 0.0, -0.02,
201
+ 0.02, 0.0, -0.02,
202
+ 0.02, 0.04, 0.02,
203
+ -0.02, 0.04, 0.02
204
+ ];
205
+ const quad_uv = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
206
+ for (let k = 0; k < 4; k++) {
207
+ positions[k * 3] = quad_pos[k * 3];
208
+ positions[k * 3 + 1] = quad_pos[k * 3 + 1];
209
+ positions[k * 3 + 2] = quad_pos[k * 3 + 2];
210
+ normals[k * 3] = 0.0;
211
+ normals[k * 3 + 1] = 1.0;
212
+ normals[k * 3 + 2] = 0.0;
213
+ uv0[k * 2] = quad_uv[k * 2];
214
+ uv0[k * 2 + 1] = quad_uv[k * 2 + 1];
215
+ uv1[k * 2] = 0.0;
216
+ uv1[k * 2 + 1] = 0.0;
217
+ colors[k * 3] = 1.0;
218
+ colors[k * 3 + 1] = 1.0;
219
+ colors[k * 3 + 2] = 1.0;
220
+ joints[k * 4] = 0;
221
+ joints[k * 4 + 1] = 0;
222
+ joints[k * 4 + 2] = 0;
223
+ joints[k * 4 + 3] = 0;
224
+ weights[k * 4] = 1.0;
225
+ weights[k * 4 + 1] = 0.0;
226
+ weights[k * 4 + 2] = 0.0;
227
+ weights[k * 4 + 3] = 0.0;
228
+ }
229
+ indices[0] = 0;
230
+ indices[1] = 1;
231
+ indices[2] = 2;
232
+ indices[3] = 0;
233
+ indices[4] = 2;
234
+ indices[5] = 3;
235
+
236
+ out.name = `${species.name || "tree"}_bark_lod${lod_cfg.level}`;
237
+ out.ensureBounds();
238
+ out.ensureTangents();
239
+ return out;
240
+ }
241
+
242
+ let v_cursor = 0;
243
+ let i_cursor = 0;
244
+ const meshed_branch_rings = new Map();
245
+ const joint_motion = tree_skeleton_read_joint_motion(skeleton);
246
+
247
+ for (let p_idx = 0; p_idx < branch_plans.length; p_idx++) {
248
+ const plan = branch_plans[p_idx];
249
+ const b = plan.branch_id;
250
+ const level = plan.level;
251
+ const base_r = plan.base_r;
252
+ const lvl_cfg = plan.lvl_cfg;
253
+ const nodes = plan.nodes;
254
+ const edges = plan.edges;
255
+ const E = edges.length;
256
+ const sample_steps = plan.sample_steps;
257
+ const ring_m = plan.ring_m;
258
+
259
+ // Quantized integer circumference wrap count (1 or 2) for seamless bark tiling proportional to stem radius
260
+ const k_wrap = Math.max(1, Math.min(2, Math.round(PI2 * base_r * texel_density)));
261
+
262
+ // Pre-collect child branch attachment collars on this branch `b` (for parent-stem collar bosses & UV grain flow)
263
+ const child_collars = [];
264
+ const b_len = Math.max(0.2, skeleton.branch_length[b]);
265
+ for (let cb = b + 1; cb < branch_count; cb++) {
266
+ if (skeleton.branch_parent[cb] === b) {
267
+ const c_root = skeleton.branch_root_vertex[cb];
268
+ skeleton.node_read_tangent(SCRATCH_V_TAN, 0, c_root);
269
+ const c_r = skeleton.branch_base_radius[cb];
270
+ const r_ratio = clamp(c_r / Math.max(1e-4, base_r), 0.15, 0.82);
271
+ child_collars.push({
272
+ t_c: skeleton.branch_parent_attach_t[cb],
273
+ tx_c: SCRATCH_V_TAN[0],
274
+ ty_c: SCRATCH_V_TAN[1],
275
+ tz_c: SCRATCH_V_TAN[2],
276
+ r_ratio,
277
+ sigma_t: clamp(1.55 * c_r / b_len, 0.035, 0.13),
278
+ sigma_theta: clamp(0.60 * r_ratio + 0.22, 0.28, 0.72)
279
+ });
280
+ }
281
+ }
282
+
283
+ // Knots: the healed scars of the branches this one shed (`TreeSkeleton.add_scar`). Each is the collar the branch
284
+ // had, outliving it as a callus rim around the eye, sized by the branch when it died
285
+ const branch_knots = [];
286
+ const collar_bulge = lvl_cfg.parent_collar_bulge;
287
+ if (collar_bulge > 0.0) {
288
+ for (let k = 0; k < skeleton.scar_count; k++) {
289
+ if (skeleton.scar_branch[k] !== b) continue;
290
+ const d = k * 5;
291
+ const scar_r = skeleton.scar_data[d + 4];
292
+ if (scar_r <= 0.0) continue;
293
+ const r_ratio = clamp(scar_r / Math.max(1e-4, base_r), 0.15, 0.82);
294
+ branch_knots.push({
295
+ t_k: skeleton.scar_data[d],
296
+ tx_k: skeleton.scar_data[d + 1],
297
+ ty_k: skeleton.scar_data[d + 2],
298
+ tz_k: skeleton.scar_data[d + 3],
299
+ sigma_t: clamp(1.55 * scar_r / b_len, 0.035, 0.13),
300
+ sigma_theta: clamp(0.60 * r_ratio + 0.22, 0.28, 0.72),
301
+ amp_k: collar_bulge * (0.55 + 0.75 * r_ratio)
302
+ });
303
+ }
304
+ }
305
+
306
+ // Parent branch attachment context from actual meshed parent frustum
307
+ const parent_b = skeleton.branch_parent[b];
308
+ const parent_attach_t = skeleton.branch_parent_attach_t[b];
309
+ const p_rings = parent_b >= 0 ? meshed_branch_rings.get(parent_b) : undefined;
310
+ const has_meshed_parent = b > 0 && p_rings !== undefined && p_rings.length >= 2;
311
+
312
+ let parent_px = 0.0, parent_py = 0.0, parent_pz = 0.0;
313
+ let parent_tx = 0.0, parent_ty = 1.0, parent_tz = 0.0;
314
+ let parent_r = base_r * 1.4;
315
+ let parent_v_attach = 0.02;
316
+ let parent_taper_slope = 0.0;
317
+ let parent_m = 8;
318
+ let parent_flute_amp = 0.0;
319
+ let parent_z_min = -Infinity;
320
+ let parent_z_max = Infinity;
321
+ let shift_x = 0.0, shift_y = 0.0, shift_z = 0.0;
322
+ let base_j0 = 0, base_j1 = 0, base_j2 = 0, base_j3 = 0;
323
+ let base_w0 = 1.0, base_w1 = 0.0, base_w2 = 0.0, base_w3 = 0.0;
324
+ let emerge_tx = 0.0, emerge_ty = 1.0, emerge_tz = 0.0;
325
+
326
+ if (has_meshed_parent) {
327
+ let k_attach = p_rings.length - 2;
328
+ for (let k = 0; k < p_rings.length - 1; k++) {
329
+ if (parent_attach_t >= p_rings[k].t_norm - 1e-5 && parent_attach_t <= p_rings[k + 1].t_norm + 1e-5) {
330
+ k_attach = k;
331
+ break;
332
+ }
333
+ }
334
+ const pr_a = p_rings[k_attach];
335
+ const pr_b = p_rings[k_attach + 1];
336
+ const xi = clamp((parent_attach_t - pr_a.t_norm) / Math.max(1e-5, pr_b.t_norm - pr_a.t_norm), 0.0, 1.0);
337
+
338
+ parent_px = lerp(pr_a.cx, pr_b.cx, xi);
339
+ parent_py = lerp(pr_a.cy, pr_b.cy, xi);
340
+ parent_pz = lerp(pr_a.cz, pr_b.cz, xi);
341
+
342
+ const ax = pr_b.cx - pr_a.cx;
343
+ const ay = pr_b.cy - pr_a.cy;
344
+ const az = pr_b.cz - pr_a.cz;
345
+ const parent_seg_len = Math.sqrt(ax * ax + ay * ay + az * az) || 1e-5;
346
+ parent_tx = ax / parent_seg_len;
347
+ parent_ty = ay / parent_seg_len;
348
+ parent_tz = az / parent_seg_len;
349
+
350
+ parent_r = lerp(pr_a.r, pr_b.r, xi);
351
+ parent_v_attach = lerp(pr_a.v_uv ?? 0.25, pr_b.v_uv ?? 0.25, xi);
352
+ parent_taper_slope = (pr_b.r - pr_a.r) / parent_seg_len;
353
+ parent_m = Math.min(pr_a.m, pr_b.m);
354
+ parent_flute_amp = pr_a.flute_amp;
355
+ parent_z_min = -(xi + 0.25) * parent_seg_len;
356
+ parent_z_max = (1.0 - xi + 0.25) * parent_seg_len;
357
+
358
+ // Translate child spine so its root lies on the exact parent mesh frustum chord
359
+ skeleton.node_read_position(SCRATCH_P0, 0, nodes[0]);
360
+ shift_x = parent_px - SCRATCH_P0[0];
361
+ shift_y = parent_py - SCRATCH_P0[1];
362
+ shift_z = parent_pz - SCRATCH_P0[2];
363
+
364
+ // Compute emergence chord direction from parent_P to child Ring i = 1 spine center
365
+ const u_step1 = (1 / sample_steps) * E;
366
+ const edge_idx1 = Math.min(E - 1, Math.floor(u_step1));
367
+ const local_u1 = 1 === sample_steps ? 1.0 : (u_step1 - edge_idx1);
368
+ skeleton.evaluate_segment_position(SCRATCH_P1, 0, edges[edge_idx1], local_u1);
369
+ const p1x = SCRATCH_P1[0] + shift_x;
370
+ const p1y = SCRATCH_P1[1] + shift_y;
371
+ const p1z = SCRATCH_P1[2] + shift_z;
372
+ const emx = p1x - parent_px;
373
+ const emy = p1y - parent_py;
374
+ const emz = p1z - parent_pz;
375
+ const em_len = Math.sqrt(emx * emx + emy * emy + emz * emz);
376
+ if (em_len > 1e-6) {
377
+ emerge_tx = emx / em_len;
378
+ emerge_ty = emy / em_len;
379
+ emerge_tz = emz / em_len;
380
+ } else {
381
+ skeleton.node_read_tangent(SCRATCH_V_TAN, 0, nodes[0]);
382
+ emerge_tx = SCRATCH_V_TAN[0];
383
+ emerge_ty = SCRATCH_V_TAN[1];
384
+ emerge_tz = SCRATCH_V_TAN[2];
385
+ }
386
+
387
+ // Merge parent frustum skinning weights at xi so child Ring i = 0 deforms identically to parent
388
+ interpolate_ring_skin_weights4(SCRATCH_JOINTS4, 0, SCRATCH_WEIGHTS4, 0, pr_a, pr_b, xi, joint_motion, parent_px, parent_py, parent_pz);
389
+ base_j0 = SCRATCH_JOINTS4[0];
390
+ base_j1 = SCRATCH_JOINTS4[1];
391
+ base_j2 = SCRATCH_JOINTS4[2];
392
+ base_j3 = SCRATCH_JOINTS4[3];
393
+ base_w0 = SCRATCH_WEIGHTS4[0];
394
+ base_w1 = SCRATCH_WEIGHTS4[1];
395
+ base_w2 = SCRATCH_WEIGHTS4[2];
396
+ base_w3 = SCRATCH_WEIGHTS4[3];
397
+ }
398
+
399
+ let prev_ring_start = -1;
400
+ let prev_m = 0;
401
+ const current_branch_rings = [];
402
+
403
+ // Track continuous RMF along the resampled spine rings
404
+ let prev_px = 0.0, prev_py = 0.0, prev_pz = 0.0;
405
+ let prev_tx = 0.0, prev_ty = 1.0, prev_tz = 0.0;
406
+ let cur_rx = 1.0, cur_ry = 0.0, cur_rz = 0.0;
407
+ let cur_sx = 0.0, cur_sy = 0.0, cur_sz = 1.0;
408
+
409
+ // Keep terminal ring state for apex cap
410
+ let tip_px = 0.0, tip_py = 0.0, tip_pz = 0.0;
411
+ let tip_tx = 0.0, tip_ty = 1.0, tip_tz = 0.0;
412
+ let tip_rx = 1.0, tip_ry = 0.0, tip_rz = 0.0;
413
+ let tip_sx = 0.0, tip_sy = 0.0, tip_sz = 1.0;
414
+ let tip_r = 0.002;
415
+ let tip_arc_s = 0.0;
416
+ let tip_v_uv = 0.90;
417
+ let tip_wind_sway = 0.0;
418
+ let tip_wind_phase = 0.0;
419
+ let tip_j0 = 0, tip_j1 = 0, tip_j2 = 0, tip_j3 = 0;
420
+ let tip_w0 = 1.0, tip_w1 = 0.0, tip_w2 = 0.0, tip_w3 = 0.0;
421
+
422
+ // Continuous ontogenetic V range [v_start, v_end] in [0, 1] from trunk base (old bark) to twig tips (young shoot bark)
423
+ const num_levels = generator_depth_count;
424
+ let v_start = 0.02;
425
+ let v_end = num_levels > 2 ? 0.62 : (num_levels === 2 ? 0.80 : 0.90);
426
+ if (has_meshed_parent) {
427
+ v_start = parent_v_attach;
428
+ if (level === 1 && num_levels > 2) {
429
+ v_end = clamp(lerp(v_start, 0.80, 0.65), v_start + 0.10, 0.84);
430
+ } else {
431
+ v_end = clamp(lerp(v_start, 0.95, 0.82), Math.max(0.82, v_start + 0.08), 0.97);
432
+ }
433
+ }
434
+
435
+ const max_weld_rings = Math.min(lvl_cfg.collar_weld_rings, Math.max(0, sample_steps - 1));
436
+
437
+ for (let i = 0; i <= sample_steps; i++) {
438
+ const m_i = ring_m[i];
439
+ const ring_start = v_cursor;
440
+
441
+ const u_branch = (i / sample_steps) * E;
442
+ const edge_idx = Math.min(E - 1, Math.floor(u_branch));
443
+ const local_u = i === sample_steps ? 1.0 : (u_branch - edge_idx);
444
+ const edge_id = edges[edge_idx];
445
+ const v0 = nodes[edge_idx];
446
+ const v1 = nodes[edge_idx + 1];
447
+
448
+ skeleton.evaluate_segment_position(SCRATCH_P0, 0, edge_id, local_u);
449
+ skeleton.evaluate_segment_tangent(SCRATCH_T0, 0, edge_id, local_u);
450
+
451
+ const px = SCRATCH_P0[0] + shift_x;
452
+ const py = SCRATCH_P0[1] + shift_y;
453
+ const pz = SCRATCH_P0[2] + shift_z;
454
+ const tx = SCRATCH_T0[0], ty = SCRATCH_T0[1], tz = SCRATCH_T0[2];
455
+
456
+ if (i === 0) {
457
+ skeleton.node_read_tangent(SCRATCH_V_TAN, 0, v0);
458
+ skeleton.node_read_frame_r(SCRATCH_R0, 0, v0);
459
+ spine_frame_double_reflection(
460
+ SCRATCH_RMF, 0,
461
+ px, py, pz,
462
+ SCRATCH_V_TAN[0], SCRATCH_V_TAN[1], SCRATCH_V_TAN[2],
463
+ SCRATCH_R0[0], SCRATCH_R0[1], SCRATCH_R0[2],
464
+ px, py, pz,
465
+ tx, ty, tz
466
+ );
467
+ } else {
468
+ spine_frame_double_reflection(
469
+ SCRATCH_RMF, 0,
470
+ prev_px, prev_py, prev_pz,
471
+ prev_tx, prev_ty, prev_tz,
472
+ cur_rx, cur_ry, cur_rz,
473
+ px, py, pz,
474
+ tx, ty, tz
475
+ );
476
+ }
477
+ cur_rx = SCRATCH_RMF[0];
478
+ cur_ry = SCRATCH_RMF[1];
479
+ cur_rz = SCRATCH_RMF[2];
480
+ cur_sx = SCRATCH_RMF[3];
481
+ cur_sy = SCRATCH_RMF[4];
482
+ cur_sz = SCRATCH_RMF[5];
483
+
484
+ prev_px = px;
485
+ prev_py = py;
486
+ prev_pz = pz;
487
+ prev_tx = tx;
488
+ prev_ty = ty;
489
+ prev_tz = tz;
490
+
491
+ const rx = cur_rx, ry = cur_ry, rz = cur_rz;
492
+ const sx = cur_sx, sy = cur_sy, sz = cur_sz;
493
+
494
+ const r0 = skeleton.node_read_radius(v0);
495
+ const r1 = skeleton.node_read_radius(v1);
496
+ const r_node = lerp(r0, r1, local_u);
497
+
498
+ skeleton.node_read_spine_param(SCRATCH_SPINE0, 0, v0);
499
+ skeleton.node_read_spine_param(SCRATCH_SPINE1, 0, v1);
500
+ const t_norm = lerp(SCRATCH_SPINE0[0], SCRATCH_SPINE1[0], local_u);
501
+ const arc_s = lerp(SCRATCH_SPINE0[1], SCRATCH_SPINE1[1], local_u);
502
+ const phase_hash = skeleton.branch_phase_hash[b];
503
+
504
+ // Skinning joint indices & weights at this ring (causal upstream-only + <= 4 joints per frustum slice)
505
+ let ring_j0 = 0, ring_j1 = 0, ring_j2 = 0, ring_j3 = 0;
506
+ let ring_w0 = 1.0, ring_w1 = 0.0, ring_w2 = 0.0, ring_w3 = 0.0;
507
+ if (has_meshed_parent) {
508
+ const parent_attach_v = skeleton.node_parent_vertex(nodes[0]);
509
+ const ancestor_j = parent_attach_v >= 0 ? skeleton.node_read_joint(parent_attach_v, 0) : 0;
510
+ const j_branch_root = skeleton.node_read_joint(nodes[0], 0);
511
+ const j_v0 = skeleton.node_read_joint(v0, 0);
512
+ if (i === 0 || j_v0 === ancestor_j) {
513
+ ring_j0 = base_j0;
514
+ ring_j1 = base_j1;
515
+ ring_j2 = base_j2;
516
+ ring_j3 = base_j3;
517
+ ring_w0 = base_w0;
518
+ ring_w1 = base_w1;
519
+ ring_w2 = base_w2;
520
+ ring_w3 = base_w3;
521
+ } else if (j_v0 === j_branch_root) {
522
+ const w_own = clamp(0.85 * t_norm, 0.0, 0.90);
523
+ const w_base = 1.0 - w_own;
524
+ ring_j0 = j_v0;
525
+ ring_j1 = base_j0;
526
+ ring_j2 = base_j1;
527
+ ring_j3 = 0;
528
+ ring_w0 = w_own;
529
+ ring_w1 = w_base * base_w0;
530
+ ring_w2 = w_base * base_w1;
531
+ ring_w3 = 0.0;
532
+ const rw_sum = ring_w0 + ring_w1 + ring_w2;
533
+ if (rw_sum > 1e-6) {
534
+ ring_w0 /= rw_sum;
535
+ ring_w1 /= rw_sum;
536
+ ring_w2 /= rw_sum;
537
+ }
538
+ } else {
539
+ const w_own = clamp(0.50 + 0.45 * t_norm, 0.0, 0.95);
540
+ ring_j0 = j_v0;
541
+ ring_j1 = j_branch_root;
542
+ ring_j2 = 0;
543
+ ring_j3 = 0;
544
+ ring_w0 = w_own;
545
+ ring_w1 = 1.0 - w_own;
546
+ ring_w2 = 0.0;
547
+ ring_w3 = 0.0;
548
+ }
549
+ } else {
550
+ const j_primary = skeleton.node_read_joint(v0, 0);
551
+ const j_parent = skeleton.node_read_joint(v0, 1);
552
+ const w_primary = (v0 === 0 || j_primary === j_parent) ? 1.0 : clamp(0.25 + 0.75 * t_norm, 0.0, 1.0);
553
+ ring_j0 = j_primary;
554
+ ring_j1 = j_parent;
555
+ ring_w0 = w_primary;
556
+ ring_w1 = 1.0 - w_primary;
557
+ }
558
+
559
+ // Collar weld factor: i === 0 of every child branch is always seated into its meshed parent frustum
560
+ let weld_factor = 0.0;
561
+ if (has_meshed_parent) {
562
+ if (i === 0) {
563
+ weld_factor = 1.0;
564
+ } else if (i <= max_weld_rings && i < sample_steps && t_norm < 0.65) {
565
+ const u_collar = 1.0 - i / (max_weld_rings + 1);
566
+ weld_factor = u_collar * u_collar;
567
+ }
568
+ }
569
+
570
+ // Root flare factor on trunk/basal branches
571
+ const has_flare = lvl_cfg.flare_amplitude > 0.0 && t_norm < lvl_cfg.flare_height_ratio;
572
+ let flare_env = 0.0;
573
+ if (has_flare) {
574
+ const u_f = 1.0 - t_norm / Math.max(1e-4, lvl_cfg.flare_height_ratio);
575
+ flare_env = lvl_cfg.flare_amplitude * u_f * u_f;
576
+ }
577
+
578
+ const unwelded = 1.0 - weld_factor;
579
+ const v_base = clamp(lerp(v_start, v_end, Math.pow(t_norm, 0.88)), 0.01, 0.98);
580
+ // Smooth terminal shoot rounding over t_norm in (0.88, 1.0] so young apical tips terminate as circular shoots without pleated flutes
581
+ const tip_roundness = i === sample_steps ? 0.0 : clamp((1.0 - t_norm) / 0.12, 0.0, 1.0);
582
+ const ontogenetic_maturity = (level >= 2 ? clamp(1.0 - 0.72 * ((v_base - 0.55) / 0.45), 0.22, 1.0) : 1.0) * tip_roundness;
583
+ const flute_env = lvl_cfg.flute_amplitude * unwelded * ontogenetic_maturity;
584
+ const has_flute = flute_env > 1e-5;
585
+ const horiz_sin = Math.sqrt(Math.max(0.0, 1.0 - ty * ty));
586
+ const oval_ecc = (lvl_cfg.oval_eccentricity ?? 0.0) * unwelded * horiz_sin * tip_roundness;
587
+ const bark_disp = (lvl_cfg.bark_displacement ?? 0.0) * unwelded * ontogenetic_maturity;
588
+ const parent_collar_amp = (lvl_cfg.parent_collar_bulge ?? 0.0) * unwelded * tip_roundness;
589
+
590
+ const wind_sway = clamp((level + t_norm) * 0.25, 0.0, 1.0);
591
+ const wind_phase = clamp(phase_hash, 0.0, 1.0);
592
+ const collar_ao = 1.0 - 0.16 * weld_factor;
593
+
594
+ for (let j = 0; j <= m_i; j++) {
595
+ // Ensure exact periodic closure between j=0 and j=m_i
596
+ const j_wrapped = j === m_i ? 0 : j;
597
+ const theta = (j_wrapped / m_i) * PI2;
598
+ const cos_t = Math.cos(theta);
599
+ const sin_t = Math.sin(theta);
600
+
601
+ // Outward radial unit vector in cross-section plane
602
+ const n_rad_x = cos_t * rx + sin_t * sx;
603
+ const n_rad_y = cos_t * ry + cos_t * 0.0 + sin_t * sy;
604
+ const n_rad_z = cos_t * rz + sin_t * sz;
605
+
606
+ // Circumferential tangent unit vector (-sin_t R + cos_t S)
607
+ const tan_theta_x = -sin_t * rx + cos_t * sx;
608
+ const tan_theta_y = -sin_t * ry + cos_t * sy;
609
+ const tan_theta_z = -sin_t * rz + cos_t * sz;
610
+
611
+ // Evaluate lobe/flute/oval/collar/knot/bark radius multiplier & UV grain streamline warp
612
+ let r_mult = 1.0;
613
+ let d_rmult_dtheta = 0.0;
614
+ let u_warp = 0.0;
615
+ let vert_ao = collar_ao;
616
+ let d_tan = 0.0;
617
+
618
+ // 1. Phase-locked root buttress lobes flowing continuously into twisting trunk flutes
619
+ const theta_flute = theta + flare_lobe_phase_offset(arc_s, phase_hash);
620
+ if (has_flare) {
621
+ const lobe_phase = lvl_cfg.flare_lobes * theta_flute;
622
+ const cos_lobe = Math.cos(lobe_phase);
623
+ if (cos_lobe > 0.0) {
624
+ const p = lvl_cfg.flare_sharpness;
625
+ r_mult += flare_env * Math.pow(cos_lobe, p);
626
+ d_rmult_dtheta -= flare_env * p * lvl_cfg.flare_lobes * Math.pow(cos_lobe, Math.max(0.0, p - 1.0)) * Math.sin(lobe_phase);
627
+ }
628
+ }
629
+
630
+ if (has_flute) {
631
+ const f_freq = Math.min(4, Math.max(2, lvl_cfg.flute_frequency | 0));
632
+ const flute_phase = f_freq * theta_flute;
633
+ const f_val = Math.sin(flute_phase);
634
+ const f_der = f_freq * Math.cos(flute_phase);
635
+ r_mult += flute_env * f_val;
636
+ d_rmult_dtheta += flute_env * f_der * 0.45;
637
+ }
638
+
639
+ // 2. Biomechanical reaction-wood ovalization on inclined/horizontal boughs
640
+ if (oval_ecc > 1e-4) {
641
+ const oval_term = oval_ecc * (1.35 * (2.0 * n_rad_y * n_rad_y - 1.0) - 0.22 * n_rad_y);
642
+ r_mult += oval_term;
643
+ d_rmult_dtheta += oval_ecc * (5.4 * n_rad_y - 0.22) * tan_theta_y;
644
+ }
645
+
646
+ // 3. Parent-stem branch collar swelling bosses & UV grain streamline deflection around child branches
647
+ if (parent_collar_amp > 1e-4 && child_collars.length > 0) {
648
+ for (let c_idx = 0; c_idx < child_collars.length; c_idx++) {
649
+ const cc = child_collars[c_idx];
650
+ const dt = t_norm - cc.t_c;
651
+ if (Math.abs(dt) < 3.2 * cc.sigma_t) {
652
+ const theta_c = Math.atan2(
653
+ cc.tx_c * sx + cc.ty_c * sy + cc.tz_c * sz,
654
+ cc.tx_c * rx + cc.ty_c * ry + cc.tz_c * rz
655
+ );
656
+ const dtheta = Math.atan2(Math.sin(theta - theta_c), Math.cos(theta - theta_c));
657
+ const sig_t = dt < 0.0 ? cc.sigma_t * 1.25 : cc.sigma_t * 0.90;
658
+ const u_t = dt / sig_t;
659
+ const u_th = dtheta / cc.sigma_theta;
660
+ const dist2 = u_t * u_t + u_th * u_th;
661
+ if (dist2 < 9.0) {
662
+ const gauss = Math.exp(-0.5 * dist2);
663
+ const b_amp = parent_collar_amp * (0.55 + 0.75 * cc.r_ratio);
664
+ r_mult += b_amp * gauss;
665
+ d_rmult_dtheta += -b_amp * gauss * (u_th / cc.sigma_theta);
666
+ const r_uv2 = dist2 + 0.85;
667
+ u_warp += 0.008 * unwelded * (u_th / r_uv2) * Math.exp(-0.35 * dist2);
668
+ }
669
+ }
670
+ }
671
+ }
672
+
673
+ // 4. Branch scars: volcanic callus rims + UV grain streamline deflection
674
+ if (unwelded * tip_roundness > 0.05 && branch_knots.length > 0) {
675
+ for (let k_idx = 0; k_idx < branch_knots.length; k_idx++) {
676
+ const kn = branch_knots[k_idx];
677
+ const dt = t_norm - kn.t_k;
678
+ if (Math.abs(dt) < 3.2 * kn.sigma_t) {
679
+ const theta_k = Math.atan2(
680
+ kn.tx_k * sx + kn.ty_k * sy + kn.tz_k * sz,
681
+ kn.tx_k * rx + kn.ty_k * ry + kn.tz_k * rz
682
+ );
683
+ const dtheta = Math.atan2(Math.sin(theta - theta_k), Math.cos(theta - theta_k));
684
+ const u_t = dt / kn.sigma_t;
685
+ const u_th = dtheta / kn.sigma_theta;
686
+ const rho2 = u_t * u_t + u_th * u_th;
687
+ if (rho2 < 9.0) {
688
+ const k_amp = kn.amp_k * unwelded * tip_roundness;
689
+ const exp_r = Math.exp(-0.85 * rho2);
690
+ const w_rim = (0.30 + 1.90 * rho2) * exp_r;
691
+ const dw_drho2 = (1.90 - 0.85 * (0.30 + 1.90 * rho2)) * exp_r;
692
+ r_mult += k_amp * w_rim;
693
+ d_rmult_dtheta += k_amp * dw_drho2 * (2.0 * u_th / kn.sigma_theta);
694
+ if (rho2 < 0.65) {
695
+ vert_ao *= (0.72 + 0.28 * (rho2 / 0.65));
696
+ }
697
+ const r_uv2 = rho2 + 0.85;
698
+ u_warp += 0.009 * unwelded * (u_th / r_uv2) * Math.exp(-0.35 * rho2);
699
+ }
700
+ }
701
+ }
702
+ }
703
+
704
+ // 5. 3D Geometric Bark Relief & Coupled Tangential Ridge-Pinch
705
+ const u_periodic = k_wrap * (j_wrapped / m_i) + twist_uv * arc_s;
706
+ if (bark_disp > 1e-4) {
707
+ evaluate_bark_relief(
708
+ SCRATCH_RELIEF,
709
+ u_periodic + u_warp,
710
+ v_base,
711
+ species.bark_material,
712
+ (species.seed | 0) + b * 19
713
+ );
714
+ const h_bark = SCRATCH_RELIEF[0];
715
+ const dh_du = SCRATCH_RELIEF[1];
716
+ u_warp += SCRATCH_RELIEF[2] * unwelded * 0.65;
717
+
718
+ r_mult += bark_disp * (h_bark - 0.50) * 1.35;
719
+ d_rmult_dtheta += bark_disp * dh_du * 0.15;
720
+ }
721
+ u_warp = clamp(u_warp, -0.015, 0.015);
722
+
723
+ if (weld_factor > 0.0) {
724
+ if (i === 0) {
725
+ // Asymmetric adaxial (upper crotch) / abaxial (lower support) collar spread at base ring
726
+ const align_parent = n_rad_x * parent_tx + n_rad_y * parent_ty + n_rad_z * parent_tz;
727
+ const spread = align_parent >= 0.0
728
+ ? lvl_cfg.collar_spread_upper * align_parent
729
+ : lvl_cfg.collar_spread_lower * (-align_parent);
730
+ r_mult += weld_factor * (0.15 + spread);
731
+ } else {
732
+ // Symmetric collar flare on subsequent rings so Ring i >= 1 stays centered on spine
733
+ r_mult += weld_factor * 0.18;
734
+ }
735
+ }
736
+
737
+ const r_local = r_node * Math.max(0.25, r_mult);
738
+ let vx = px + r_local * n_rad_x + d_tan * tan_theta_x;
739
+ let vy = py + r_local * n_rad_y + d_tan * tan_theta_y;
740
+ let vz = pz + r_local * n_rad_z + d_tan * tan_theta_z;
741
+
742
+ // Perturb outward normal gently by low-frequency lobe/knot derivative (fine detail comes from normal map)
743
+ const slope = clamp(d_rmult_dtheta / Math.max(0.25, r_mult), -0.24, 0.24);
744
+
745
+ let vnx = n_rad_x - slope * tan_theta_x;
746
+ let vny = n_rad_y - slope * tan_theta_y;
747
+ let vnz = n_rad_z - slope * tan_theta_z;
748
+ const vn_len = Math.sqrt(vnx * vnx + vny * vny + vnz * vnz);
749
+ if (vn_len > 1e-8) {
750
+ vnx /= vn_len;
751
+ vny /= vn_len;
752
+ vnz /= vn_len;
753
+ }
754
+
755
+ if (weld_factor > 0.0) {
756
+ branch_collar_project(
757
+ SCRATCH_COLLAR_POS, 0,
758
+ SCRATCH_COLLAR_NORM, 0,
759
+ vx, vy, vz,
760
+ vnx, vny, vnz,
761
+ emerge_tx, emerge_ty, emerge_tz,
762
+ parent_px, parent_py, parent_pz,
763
+ parent_tx, parent_ty, parent_tz,
764
+ parent_r,
765
+ weld_factor,
766
+ lvl_cfg.collar_normal_blend,
767
+ parent_m,
768
+ parent_flute_amp,
769
+ parent_taper_slope,
770
+ parent_z_min,
771
+ parent_z_max
772
+ );
773
+ if (i === 0) {
774
+ vx = SCRATCH_COLLAR_POS[0];
775
+ vy = SCRATCH_COLLAR_POS[1];
776
+ vz = SCRATCH_COLLAR_POS[2];
777
+ }
778
+ vnx = SCRATCH_COLLAR_NORM[0];
779
+ vny = SCRATCH_COLLAR_NORM[1];
780
+ vnz = SCRATCH_COLLAR_NORM[2];
781
+ }
782
+
783
+ const v3 = v_cursor * 3;
784
+ const v2 = v_cursor * 2;
785
+ const v4 = v_cursor * 4;
786
+
787
+ positions[v3] = vx;
788
+ positions[v3 + 1] = vy;
789
+ positions[v3 + 2] = vz;
790
+
791
+ normals[v3] = vnx;
792
+ normals[v3 + 1] = vny;
793
+ normals[v3 + 2] = vnz;
794
+
795
+ uv0[v2] = k_wrap * (j / m_i) + twist_uv * arc_s + u_warp;
796
+ uv0[v2 + 1] = v_base;
797
+
798
+ uv1[v2] = wind_sway;
799
+ uv1[v2 + 1] = wind_phase;
800
+
801
+ colors[v3] = vert_ao;
802
+ colors[v3 + 1] = vert_ao;
803
+ colors[v3 + 2] = vert_ao;
804
+
805
+ joints[v4] = ring_j0;
806
+ joints[v4 + 1] = ring_j1;
807
+ joints[v4 + 2] = ring_j2;
808
+ joints[v4 + 3] = ring_j3;
809
+
810
+ weights[v4] = ring_w0;
811
+ weights[v4 + 1] = ring_w1;
812
+ weights[v4 + 2] = ring_w2;
813
+ weights[v4 + 3] = ring_w3;
814
+
815
+ v_cursor++;
816
+ }
817
+
818
+ // Recenter non-collar-root rings so their vertex centroid lies on the exact spine point (px, py, pz)
819
+ // while preserving all asymmetric 3D relief (knots, burls, collar bosses, ovalization, bark plates)
820
+ if (i > 0 || !has_meshed_parent) {
821
+ let mean_x = 0.0, mean_y = 0.0, mean_z = 0.0;
822
+ for (let j = 0; j < m_i; j++) {
823
+ const vi3 = (ring_start + j) * 3;
824
+ mean_x += positions[vi3];
825
+ mean_y += positions[vi3 + 1];
826
+ mean_z += positions[vi3 + 2];
827
+ }
828
+ mean_x /= m_i;
829
+ mean_y /= m_i;
830
+ mean_z /= m_i;
831
+ const corr_x = px - mean_x;
832
+ const corr_y = py - mean_y;
833
+ const corr_z = pz - mean_z;
834
+ for (let j = 0; j <= m_i; j++) {
835
+ const vi3 = (ring_start + j) * 3;
836
+ positions[vi3] += corr_x;
837
+ positions[vi3 + 1] += corr_y;
838
+ positions[vi3 + 2] += corr_z;
839
+ }
840
+ }
841
+
842
+ // Record emitted ring centroid and skinning state for child branch attachment
843
+ let r_cx = 0.0, r_cy = 0.0, r_cz = 0.0;
844
+ for (let j = 0; j < m_i; j++) {
845
+ const vi3 = (ring_start + j) * 3;
846
+ r_cx += positions[vi3];
847
+ r_cy += positions[vi3 + 1];
848
+ r_cz += positions[vi3 + 2];
849
+ }
850
+ r_cx /= m_i;
851
+ r_cy /= m_i;
852
+ r_cz /= m_i;
853
+
854
+ current_branch_rings.push({
855
+ cx: r_cx,
856
+ cy: r_cy,
857
+ cz: r_cz,
858
+ r: r_node,
859
+ m: m_i,
860
+ t_norm,
861
+ v_uv: v_base,
862
+ flute_amp: lvl_cfg.flute_amplitude ?? 0.0,
863
+ j0: ring_j0,
864
+ j1: ring_j1,
865
+ j2: ring_j2,
866
+ j3: ring_j3,
867
+ w0: ring_w0,
868
+ w1: ring_w1,
869
+ w2: ring_w2,
870
+ w3: ring_w3
871
+ });
872
+
873
+ if (prev_ring_start >= 0) {
874
+ i_cursor = branch_ring_triangulate(
875
+ indices,
876
+ i_cursor,
877
+ prev_ring_start,
878
+ prev_m,
879
+ ring_start,
880
+ m_i
881
+ );
882
+ }
883
+
884
+ prev_ring_start = ring_start;
885
+ prev_m = m_i;
886
+
887
+ if (i === sample_steps) {
888
+ tip_px = px;
889
+ tip_py = py;
890
+ tip_pz = pz;
891
+ tip_tx = tx;
892
+ tip_ty = ty;
893
+ tip_tz = tz;
894
+ tip_rx = rx;
895
+ tip_ry = ry;
896
+ tip_rz = rz;
897
+ tip_sx = sx;
898
+ tip_sy = sy;
899
+ tip_sz = sz;
900
+ tip_r = r_node;
901
+ tip_arc_s = arc_s;
902
+ tip_v_uv = v_base;
903
+ tip_wind_sway = wind_sway;
904
+ tip_wind_phase = wind_phase;
905
+ tip_j0 = ring_j0;
906
+ tip_j1 = ring_j1;
907
+ tip_j2 = ring_j2;
908
+ tip_j3 = ring_j3;
909
+ tip_w0 = ring_w0;
910
+ tip_w1 = ring_w1;
911
+ tip_w2 = ring_w2;
912
+ tip_w3 = ring_w3;
913
+ }
914
+ }
915
+
916
+ meshed_branch_rings.set(b, current_branch_rings);
917
+
918
+ // Close terminal branch tip with a watertight apex cap via branch_apex_triangulate: a conical shoot bud on a
919
+ // living branch, a flat break on a dead stub
920
+ const apex_start = v_cursor;
921
+ const m_tip = prev_m;
922
+ const apex_height = skeleton.branch_dead[b] ? 0.0 : SHOOT_BUD_HEIGHT;
923
+ const cap_ext = tip_r * apex_height;
924
+ const cap_arc_s = tip_arc_s + cap_ext;
925
+
926
+ for (let j = 0; j < m_tip; j++) {
927
+ const theta_mid = ((j + 0.5) / m_tip) * PI2;
928
+ const cos_m = Math.cos(theta_mid);
929
+ const sin_m = Math.sin(theta_mid);
930
+
931
+ const n_rad_x = cos_m * tip_rx + sin_m * tip_sx;
932
+ const n_rad_y = cos_m * tip_ry + sin_m * tip_sy;
933
+ const n_rad_z = cos_m * tip_rz + sin_m * tip_sz;
934
+
935
+ // Cone normal for a cap of height apex_height * r over radius r: (T * r + rad * h) normalized
936
+ let anx = tip_tx + apex_height * n_rad_x;
937
+ let any = tip_ty + apex_height * n_rad_y;
938
+ let anz = tip_tz + apex_height * n_rad_z;
939
+ const an_len = Math.sqrt(anx * anx + any * any + anz * anz);
940
+ if (an_len > 1e-8) {
941
+ anx /= an_len;
942
+ any /= an_len;
943
+ anz /= an_len;
944
+ }
945
+
946
+ const v3 = v_cursor * 3;
947
+ const v2 = v_cursor * 2;
948
+ const v4 = v_cursor * 4;
949
+
950
+ positions[v3] = tip_px + cap_ext * tip_tx;
951
+ positions[v3 + 1] = tip_py + cap_ext * tip_ty;
952
+ positions[v3 + 2] = tip_pz + cap_ext * tip_tz;
953
+
954
+ normals[v3] = anx;
955
+ normals[v3 + 1] = any;
956
+ normals[v3 + 2] = anz;
957
+
958
+ uv0[v2] = k_wrap * ((j + 0.5) / m_tip) + twist_uv * cap_arc_s;
959
+ uv0[v2 + 1] = clamp(tip_v_uv + 0.01, 0.02, 0.99);
960
+
961
+ uv1[v2] = tip_wind_sway;
962
+ uv1[v2 + 1] = tip_wind_phase;
963
+
964
+ colors[v3] = 1.0;
965
+ colors[v3 + 1] = 1.0;
966
+ colors[v3 + 2] = 1.0;
967
+
968
+ joints[v4] = tip_j0;
969
+ joints[v4 + 1] = tip_j1;
970
+ joints[v4 + 2] = tip_j2;
971
+ joints[v4 + 3] = tip_j3;
972
+
973
+ weights[v4] = tip_w0;
974
+ weights[v4 + 1] = tip_w1;
975
+ weights[v4 + 2] = tip_w2;
976
+ weights[v4 + 3] = tip_w3;
977
+
978
+ v_cursor++;
979
+ }
980
+
981
+ i_cursor = branch_apex_triangulate(indices, i_cursor, prev_ring_start, m_tip, apex_start);
982
+ }
983
+
984
+ if (only_rings) {
985
+ return /** @type {any} */ (meshed_branch_rings);
986
+ }
987
+
988
+ // Apply analytic crown ambient occlusion onto bark vertex colors
989
+ tree_mesh_apply_canopy_ao(out, skeleton.crown_center, skeleton.crown_radii, {
990
+ ao_core_darkening: (species.foliage.ao_core_darkening ?? 0.35) * 0.65,
991
+ normal_puffiness: 0.0,
992
+ preserve_existing_color: true
993
+ });
994
+
995
+ out.name = `${species.name || "tree"}_bark_lod${lod_cfg.level}`;
996
+ out.ensureBounds();
997
+ out.ensureTangents();
998
+
999
+ return out;
1000
+ }
1001
+
1002
+ /**
1003
+ * Computes the meshed cross-sectional ring centroids, radii, polygon vertex counts, and 4-slot
1004
+ * skinning weights for every retained branch in `skeleton` using the exact same ring synthesis as
1005
+ * `tree_mesh_build_bark`.
1006
+ *
1007
+ * @param {import("../skeleton/TreeSkeleton.js").TreeSkeleton} skeleton
1008
+ * @param {TreeSpeciesConfig} [config]
1009
+ * @param {TreeLODConfig} [lod_config]
1010
+ * @returns {Map<number, Array<{
1011
+ * cx: number, cy: number, cz: number,
1012
+ * r: number, m: number, t_norm: number, flute_amp: number,
1013
+ * j0: number, j1: number, j2: number, j3: number,
1014
+ * w0: number, w1: number, w2: number, w3: number
1015
+ * }>>}
1016
+ */
1017
+ export function tree_mesh_compute_branch_rings(
1018
+ skeleton,
1019
+ config = new TreeSpeciesConfig(),
1020
+ lod_config = new TreeLODConfig()
1021
+ ) {
1022
+ return /** @type {any} */ (tree_mesh_build_bark(skeleton, config, lod_config, new Geometry(), true));
1023
+ }