@woosh/meep-engine 3.52.0 → 3.53.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 (370) hide show
  1. package/package.json +1 -1
  2. package/src/engine/ecs/animation/Animation.d.ts +4 -25
  3. package/src/engine/ecs/animation/Animation.d.ts.map +1 -1
  4. package/src/engine/ecs/animation/Animation.js +9 -41
  5. package/src/engine/ecs/ik/IKGoalSystem.d.ts +4 -5
  6. package/src/engine/ecs/ik/IKGoalSystem.d.ts.map +1 -1
  7. package/src/engine/ecs/ik/IKGoalSystem.js +4 -6
  8. package/src/engine/graphics/ecs/mesh/assetTypeByPath.js +1 -1
  9. package/src/engine/graphics3/AnimationGraphSystem.d.ts +9 -22
  10. package/src/engine/graphics3/AnimationGraphSystem.d.ts.map +1 -1
  11. package/src/engine/graphics3/AnimationGraphSystem.js +58 -203
  12. package/src/engine/graphics3/AnimationSystem.d.ts +7 -18
  13. package/src/engine/graphics3/AnimationSystem.d.ts.map +1 -1
  14. package/src/engine/graphics3/AnimationSystem.js +62 -243
  15. package/src/engine/graphics3/FoliageWindSystem.d.ts +67 -0
  16. package/src/engine/graphics3/FoliageWindSystem.d.ts.map +1 -0
  17. package/src/engine/graphics3/FoliageWindSystem.js +257 -0
  18. package/src/engine/graphics3/GPUFluidSystem.d.ts +7 -0
  19. package/src/engine/graphics3/GPUFluidSystem.d.ts.map +1 -1
  20. package/src/engine/graphics3/GPUFluidSystem.js +39 -1
  21. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +0 -1
  22. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
  23. package/src/engine/graphics3/GPUParticleEmitterSystem.js +19 -5
  24. package/src/engine/graphics3/animation/AnimationClips.d.ts +107 -13
  25. package/src/engine/graphics3/animation/AnimationClips.d.ts.map +1 -1
  26. package/src/engine/graphics3/animation/AnimationClips.js +207 -10
  27. package/src/engine/graphics3/animation/AnimationClipsSink.d.ts +89 -0
  28. package/src/engine/graphics3/animation/AnimationClipsSink.d.ts.map +1 -0
  29. package/src/engine/graphics3/animation/AnimationClipsSink.js +205 -0
  30. package/src/engine/graphics3/animation/FoliageWindRigs.d.ts +43 -0
  31. package/src/engine/graphics3/animation/FoliageWindRigs.d.ts.map +1 -0
  32. package/src/engine/graphics3/animation/FoliageWindRigs.js +69 -0
  33. package/src/engine/graphics3/animation/write_clip_playback.d.ts +17 -11
  34. package/src/engine/graphics3/animation/write_clip_playback.d.ts.map +1 -1
  35. package/src/engine/graphics3/animation/write_clip_playback.js +36 -27
  36. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts.map +1 -1
  37. package/src/engine/graphics3/decal/GPUDecalRenderer.js +8 -6
  38. package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
  39. package/src/engine/graphics3/decal/shader_decal_composite.js +11 -5
  40. package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts +7 -7
  41. package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts.map +1 -1
  42. package/src/engine/graphics3/pose/collect_entity_playbacks.js +14 -15
  43. package/src/engine/graphics3/prefab/Prefab.d.ts +6 -0
  44. package/src/engine/graphics3/prefab/Prefab.d.ts.map +1 -1
  45. package/src/engine/graphics3/prefab/Prefab.js +169 -162
  46. package/src/engine/graphics3/prefab/prefab_compile.d.ts.map +1 -1
  47. package/src/engine/graphics3/prefab/prefab_compile.js +369 -334
  48. package/src/engine/graphics3/prefab/prefab_instantiate.d.ts.map +1 -1
  49. package/src/engine/graphics3/prefab/prefab_instantiate.js +43 -2
  50. package/src/engine/graphics3/terrain/chunk_terrain_material_surface.js +2 -2
  51. package/src/engine/physics/ecs/ContactProbeSystem.d.ts +2 -3
  52. package/src/engine/physics/ecs/ContactProbeSystem.d.ts.map +1 -1
  53. package/src/engine/physics/ecs/ContactProbeSystem.js +2 -4
  54. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  55. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +4 -4
  56. package/src/engine/sound/simulation/README.md +1 -1
  57. package/src/engine/sound/simulation/probe/bakeProbeField.d.ts +22 -8
  58. package/src/engine/sound/simulation/probe/bakeProbeField.d.ts.map +1 -1
  59. package/src/engine/sound/simulation/probe/bakeProbeField.js +60 -15
  60. package/src/engine/sound/simulation/probe/bakeProbeReflectors.d.ts.map +1 -1
  61. package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +19 -23
  62. package/src/engine/sound/simulation/probe/probe_sdf_grid.d.ts +63 -17
  63. package/src/engine/sound/simulation/probe/probe_sdf_grid.d.ts.map +1 -1
  64. package/src/engine/sound/simulation/probe/probe_sdf_grid.js +67 -17
  65. package/src/generation/foliage/FOLIAGE_DESIGN.md +318 -0
  66. package/src/generation/foliage/FOLIAGE_PLAN.md +198 -0
  67. package/src/generation/foliage/animation/build_tree_wind_rig.d.ts +20 -0
  68. package/src/generation/foliage/animation/build_tree_wind_rig.d.ts.map +1 -0
  69. package/src/generation/foliage/animation/build_tree_wind_rig.js +108 -0
  70. package/src/generation/foliage/asset/TreeAsset.d.ts +86 -0
  71. package/src/generation/foliage/asset/TreeAsset.d.ts.map +1 -0
  72. package/src/generation/foliage/asset/TreeAsset.js +111 -0
  73. package/src/generation/foliage/asset/TreeLODAsset.d.ts +115 -0
  74. package/src/generation/foliage/asset/TreeLODAsset.d.ts.map +1 -0
  75. package/src/generation/foliage/asset/TreeLODAsset.js +346 -0
  76. package/src/generation/foliage/asset/generate_tree.d.ts +15 -0
  77. package/src/generation/foliage/asset/generate_tree.d.ts.map +1 -0
  78. package/src/generation/foliage/asset/generate_tree.js +93 -0
  79. package/src/generation/foliage/asset/place_tree.d.ts +82 -0
  80. package/src/generation/foliage/asset/place_tree.d.ts.map +1 -0
  81. package/src/generation/foliage/asset/place_tree.js +194 -0
  82. package/src/generation/foliage/config/BarkMaterialConfig.d.ts +120 -0
  83. package/src/generation/foliage/config/BarkMaterialConfig.d.ts.map +1 -0
  84. package/src/generation/foliage/config/BarkMaterialConfig.js +250 -0
  85. package/src/generation/foliage/config/BranchLevelConfig.d.ts +288 -0
  86. package/src/generation/foliage/config/BranchLevelConfig.d.ts.map +1 -0
  87. package/src/generation/foliage/config/BranchLevelConfig.js +536 -0
  88. package/src/generation/foliage/config/CrownConfig.d.ts +100 -0
  89. package/src/generation/foliage/config/CrownConfig.d.ts.map +1 -0
  90. package/src/generation/foliage/config/CrownConfig.js +186 -0
  91. package/src/generation/foliage/config/CrownEnvelopeType.d.ts +16 -0
  92. package/src/generation/foliage/config/CrownEnvelopeType.d.ts.map +1 -0
  93. package/src/generation/foliage/config/CrownEnvelopeType.js +17 -0
  94. package/src/generation/foliage/config/FoliageCardType.d.ts +12 -0
  95. package/src/generation/foliage/config/FoliageCardType.d.ts.map +1 -0
  96. package/src/generation/foliage/config/FoliageCardType.js +18 -0
  97. package/src/generation/foliage/config/FoliageConfig.d.ts +151 -0
  98. package/src/generation/foliage/config/FoliageConfig.d.ts.map +1 -0
  99. package/src/generation/foliage/config/FoliageConfig.js +283 -0
  100. package/src/generation/foliage/config/FoliageMaterialConfig.d.ts +90 -0
  101. package/src/generation/foliage/config/FoliageMaterialConfig.d.ts.map +1 -0
  102. package/src/generation/foliage/config/FoliageMaterialConfig.js +181 -0
  103. package/src/generation/foliage/config/PhyllotaxisMode.d.ts +13 -0
  104. package/src/generation/foliage/config/PhyllotaxisMode.d.ts.map +1 -0
  105. package/src/generation/foliage/config/PhyllotaxisMode.js +20 -0
  106. package/src/generation/foliage/config/TreeLODConfig.d.ts +87 -0
  107. package/src/generation/foliage/config/TreeLODConfig.d.ts.map +1 -0
  108. package/src/generation/foliage/config/TreeLODConfig.js +211 -0
  109. package/src/generation/foliage/config/TreePreset.d.ts +16 -0
  110. package/src/generation/foliage/config/TreePreset.d.ts.map +1 -0
  111. package/src/generation/foliage/config/TreePreset.js +17 -0
  112. package/src/generation/foliage/config/TreeSpeciesConfig.d.ts +109 -0
  113. package/src/generation/foliage/config/TreeSpeciesConfig.d.ts.map +1 -0
  114. package/src/generation/foliage/config/TreeSpeciesConfig.js +400 -0
  115. package/src/generation/foliage/config/TreeWindConfig.d.ts +84 -0
  116. package/src/generation/foliage/config/TreeWindConfig.d.ts.map +1 -0
  117. package/src/generation/foliage/config/TreeWindConfig.js +157 -0
  118. package/src/generation/foliage/index.d.ts +38 -0
  119. package/src/generation/foliage/index.d.ts.map +1 -0
  120. package/src/generation/foliage/index.js +43 -0
  121. package/src/generation/foliage/material/make_bark_material.d.ts +37 -0
  122. package/src/generation/foliage/material/make_bark_material.d.ts.map +1 -0
  123. package/src/generation/foliage/material/make_bark_material.js +802 -0
  124. package/src/generation/foliage/material/make_foliage_material.d.ts +14 -0
  125. package/src/generation/foliage/material/make_foliage_material.d.ts.map +1 -0
  126. package/src/generation/foliage/material/make_foliage_material.js +359 -0
  127. package/src/generation/foliage/mesh/branch_collar_project.d.ts +42 -0
  128. package/src/generation/foliage/mesh/branch_collar_project.d.ts.map +1 -0
  129. package/src/generation/foliage/mesh/branch_collar_project.js +203 -0
  130. package/src/generation/foliage/mesh/branch_ring_triangulate.d.ts +35 -0
  131. package/src/generation/foliage/mesh/branch_ring_triangulate.d.ts.map +1 -0
  132. package/src/generation/foliage/mesh/branch_ring_triangulate.js +72 -0
  133. package/src/generation/foliage/mesh/foliage_collect_attachments.d.ts +80 -0
  134. package/src/generation/foliage/mesh/foliage_collect_attachments.d.ts.map +1 -0
  135. package/src/generation/foliage/mesh/foliage_collect_attachments.js +502 -0
  136. package/src/generation/foliage/mesh/tree_mesh_apply_canopy_ao.d.ts +19 -0
  137. package/src/generation/foliage/mesh/tree_mesh_apply_canopy_ao.d.ts.map +1 -0
  138. package/src/generation/foliage/mesh/tree_mesh_apply_canopy_ao.js +150 -0
  139. package/src/generation/foliage/mesh/tree_mesh_build_bark.d.ts +55 -0
  140. package/src/generation/foliage/mesh/tree_mesh_build_bark.d.ts.map +1 -0
  141. package/src/generation/foliage/mesh/tree_mesh_build_bark.js +1047 -0
  142. package/src/generation/foliage/mesh/tree_mesh_build_foliage.d.ts +21 -0
  143. package/src/generation/foliage/mesh/tree_mesh_build_foliage.d.ts.map +1 -0
  144. package/src/generation/foliage/mesh/tree_mesh_build_foliage.js +415 -0
  145. package/src/generation/foliage/presets/make_tree_species_preset.d.ts +13 -0
  146. package/src/generation/foliage/presets/make_tree_species_preset.d.ts.map +1 -0
  147. package/src/generation/foliage/presets/make_tree_species_preset.js +837 -0
  148. package/src/generation/foliage/skeleton/TreeSkeleton.d.ts +443 -0
  149. package/src/generation/foliage/skeleton/TreeSkeleton.d.ts.map +1 -0
  150. package/src/generation/foliage/skeleton/TreeSkeleton.js +1100 -0
  151. package/src/generation/foliage/skeleton/crown_envelope_evaluate.d.ts +27 -0
  152. package/src/generation/foliage/skeleton/crown_envelope_evaluate.d.ts.map +1 -0
  153. package/src/generation/foliage/skeleton/crown_envelope_evaluate.js +121 -0
  154. package/src/generation/foliage/skeleton/spine_frame_double_reflection.d.ts +32 -0
  155. package/src/generation/foliage/skeleton/spine_frame_double_reflection.d.ts.map +1 -0
  156. package/src/generation/foliage/skeleton/spine_frame_double_reflection.js +87 -0
  157. package/src/generation/foliage/skeleton/tree_skeleton_assign_skin_joints.d.ts +33 -0
  158. package/src/generation/foliage/skeleton/tree_skeleton_assign_skin_joints.d.ts.map +1 -0
  159. package/src/generation/foliage/skeleton/tree_skeleton_assign_skin_joints.js +205 -0
  160. package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.d.ts +14 -0
  161. package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.d.ts.map +1 -0
  162. package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.js +179 -0
  163. package/src/generation/foliage/skeleton/tree_skeleton_grow.d.ts +18 -0
  164. package/src/generation/foliage/skeleton/tree_skeleton_grow.d.ts.map +1 -0
  165. package/src/generation/foliage/skeleton/tree_skeleton_grow.js +1104 -0
  166. package/src/shade/playground/additive_blending/index.html +197 -0
  167. package/src/shade/playground/additive_blending/main.d.ts +2 -0
  168. package/src/shade/playground/additive_blending/main.d.ts.map +1 -0
  169. package/src/shade/playground/additive_blending/main.js +529 -0
  170. package/src/shade/playground/foliage_wind/README.md +18 -0
  171. package/src/shade/playground/foliage_wind/foliage_world.d.ts +127 -0
  172. package/src/shade/playground/foliage_wind/foliage_world.d.ts.map +1 -0
  173. package/src/shade/playground/foliage_wind/foliage_world.js +593 -0
  174. package/src/shade/playground/foliage_wind/index.html +254 -0
  175. package/src/shade/playground/foliage_wind/main.d.ts +2 -0
  176. package/src/shade/playground/foliage_wind/main.d.ts.map +1 -0
  177. package/src/shade/playground/foliage_wind/main.js +263 -0
  178. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  179. package/src/shade/renderer/Renderer.js +45 -16
  180. package/src/shade/renderer/animation/ADDITIVE_BLENDING_PLAN.md +145 -0
  181. package/src/shade/renderer/animation/ANIMATION_DATABASE_SPEC.d.ts +9 -1
  182. package/src/shade/renderer/animation/ANIMATION_DATABASE_SPEC.d.ts.map +1 -1
  183. package/src/shade/renderer/animation/ANIMATION_DATABASE_SPEC.js +53 -1
  184. package/src/shade/renderer/animation/ANIMATION_FOLIAGE_JOINT_STRUCT.d.ts +8 -0
  185. package/src/shade/renderer/animation/ANIMATION_FOLIAGE_JOINT_STRUCT.d.ts.map +1 -0
  186. package/src/shade/renderer/animation/ANIMATION_FOLIAGE_JOINT_STRUCT.js +71 -0
  187. package/src/shade/renderer/animation/ANIMATION_FOLIAGE_RIG_STRUCT.d.ts +11 -0
  188. package/src/shade/renderer/animation/ANIMATION_FOLIAGE_RIG_STRUCT.d.ts.map +1 -0
  189. package/src/shade/renderer/animation/ANIMATION_FOLIAGE_RIG_STRUCT.js +32 -0
  190. package/src/shade/renderer/animation/GPUAnimationManager.d.ts +30 -3
  191. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  192. package/src/shade/renderer/animation/GPUAnimationManager.js +532 -79
  193. package/src/shade/renderer/animation/POSE_ACCUMULATOR_LAYOUT.d.ts +19 -12
  194. package/src/shade/renderer/animation/POSE_ACCUMULATOR_LAYOUT.d.ts.map +1 -1
  195. package/src/shade/renderer/animation/POSE_ACCUMULATOR_LAYOUT.js +20 -13
  196. package/src/shade/renderer/animation/ShadeAnimationClip.d.ts +35 -8
  197. package/src/shade/renderer/animation/ShadeAnimationClip.d.ts.map +1 -1
  198. package/src/shade/renderer/animation/ShadeAnimationClip.js +433 -2
  199. package/src/shade/renderer/animation/chunk_pose_accumulator.d.ts.map +1 -1
  200. package/src/shade/renderer/animation/chunk_pose_accumulator.js +40 -32
  201. package/src/shade/renderer/animation/foliage/FoliageWindJoint.d.ts +97 -0
  202. package/src/shade/renderer/animation/foliage/FoliageWindJoint.d.ts.map +1 -0
  203. package/src/shade/renderer/animation/foliage/FoliageWindJoint.js +161 -0
  204. package/src/shade/renderer/animation/foliage/FoliageWindRig.d.ts +65 -0
  205. package/src/shade/renderer/animation/foliage/FoliageWindRig.d.ts.map +1 -0
  206. package/src/shade/renderer/animation/foliage/FoliageWindRig.js +89 -0
  207. package/src/shade/renderer/animation/foliage/chunk_foliage_wind_step.d.ts +19 -0
  208. package/src/shade/renderer/animation/foliage/chunk_foliage_wind_step.d.ts.map +1 -0
  209. package/src/shade/renderer/animation/foliage/chunk_foliage_wind_step.js +143 -0
  210. package/src/shade/renderer/animation/foliage/shader_foliage_wind_apply.d.ts +14 -0
  211. package/src/shade/renderer/animation/foliage/shader_foliage_wind_apply.d.ts.map +1 -0
  212. package/src/shade/renderer/animation/foliage/shader_foliage_wind_apply.js +116 -0
  213. package/src/shade/renderer/animation/pose/pose_evaluate_local.d.ts.map +1 -1
  214. package/src/shade/renderer/animation/pose/pose_evaluate_local.js +39 -29
  215. package/src/shade/renderer/animation/shader_animation_pose_resolve.js +37 -37
  216. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +2 -2
  217. package/src/shade/renderer/extension/RenderExtension.d.ts +7 -0
  218. package/src/shade/renderer/extension/RenderExtension.d.ts.map +1 -1
  219. package/src/shade/renderer/extension/RenderExtension.js +9 -0
  220. package/src/shade/renderer/extension/RenderExtensionRegistry.d.ts +6 -0
  221. package/src/shade/renderer/extension/RenderExtensionRegistry.d.ts.map +1 -1
  222. package/src/shade/renderer/extension/RenderExtensionRegistry.js +15 -0
  223. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +7 -0
  224. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts.map +1 -1
  225. package/src/shade/renderer/fluid/GPUFluidCascade.js +15 -0
  226. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +7 -0
  227. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts.map +1 -1
  228. package/src/shade/renderer/fluid/GPUFluidCascadeStack.js +11 -0
  229. package/src/shade/renderer/fluid/coupling/chunk_fluid_cascade_parent.d.ts.map +1 -1
  230. package/src/shade/renderer/fluid/coupling/chunk_fluid_cascade_parent.js +51 -48
  231. package/src/shade/renderer/fluid/mac/FluidMacPasses.d.ts +6 -0
  232. package/src/shade/renderer/fluid/mac/FluidMacPasses.d.ts.map +1 -1
  233. package/src/shade/renderer/fluid/mac/FluidMacPasses.js +16 -0
  234. package/src/shade/renderer/fluid/mac/chunk_fluid_mac_seams.d.ts.map +1 -1
  235. package/src/shade/renderer/fluid/mac/chunk_fluid_mac_seams.js +15 -3
  236. package/src/shade/renderer/fluid/mac/chunk_v3_mac_lattice_corners.d.ts.map +1 -1
  237. package/src/shade/renderer/fluid/mac/chunk_v3_mac_lattice_corners.js +102 -96
  238. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_correct.js +12 -12
  239. package/src/shade/renderer/fluid/mac/shader_fluid_mac_advect_predict.js +12 -12
  240. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +7 -0
  241. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts.map +1 -1
  242. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.js +16 -0
  243. package/src/shade/renderer/fluid/publish/FluidWindPublish.d.ts +7 -0
  244. package/src/shade/renderer/fluid/publish/FluidWindPublish.d.ts.map +1 -1
  245. package/src/shade/renderer/fluid/publish/FluidWindPublish.js +10 -0
  246. package/src/shade/renderer/geometry/bvh/GPUBLASRefitBatch.js +2 -2
  247. package/src/shade/renderer/loader/SceneBundle.d.ts +7 -0
  248. package/src/shade/renderer/loader/SceneBundle.d.ts.map +1 -1
  249. package/src/shade/renderer/loader/SceneBundle.js +119 -111
  250. package/src/shade/renderer/material/standard/fragment_gbuffer.d.ts +4 -0
  251. package/src/shade/renderer/material/standard/fragment_gbuffer.d.ts.map +1 -1
  252. package/src/shade/renderer/material/standard/fragment_gbuffer.js +6 -5
  253. package/src/shade/renderer/object_property/chunk_write_object_property_node3d.d.ts +8 -7
  254. package/src/shade/renderer/object_property/chunk_write_object_property_node3d.d.ts.map +1 -1
  255. package/src/shade/renderer/object_property/chunk_write_object_property_node3d.js +9 -8
  256. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  257. package/src/shade/renderer/particles/GPUParticleSystem.js +3 -1
  258. package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts.map +1 -1
  259. package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.js +9 -4
  260. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader.d.ts.map +1 -1
  261. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader.js +9 -7
  262. package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment.d.ts.map +1 -1
  263. package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment.js +15 -7
  264. package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.d.ts.map +1 -1
  265. package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.js +6 -4
  266. package/src/shade/renderer/rasterize/native/oit/oit_resolve_fragment_body.js +3 -3
  267. package/src/shade/renderer/rasterize/native/oit/shader_oit_generate_moments.d.ts.map +1 -1
  268. package/src/shade/renderer/rasterize/native/oit/shader_oit_generate_moments.js +8 -5
  269. package/src/shade/renderer/rasterize/native/viz/viz_rasterization_alpha_tested_pass_descriptor.d.ts.map +1 -1
  270. package/src/shade/renderer/rasterize/native/viz/viz_rasterization_alpha_tested_pass_descriptor.js +4 -3
  271. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +1 -1
  272. package/src/shade/renderer/texture/SAMPLER_ANISOTROPIC_CLAMP.d.ts +9 -0
  273. package/src/shade/renderer/texture/SAMPLER_ANISOTROPIC_CLAMP.d.ts.map +1 -0
  274. package/src/shade/renderer/texture/SAMPLER_ANISOTROPIC_CLAMP.js +17 -0
  275. package/src/shade/renderer/texture/SAMPLER_LINEAR_UPSCALE.d.ts +8 -0
  276. package/src/shade/renderer/texture/SAMPLER_LINEAR_UPSCALE.d.ts.map +1 -1
  277. package/src/shade/renderer/texture/SAMPLER_LINEAR_UPSCALE.js +28 -20
  278. package/src/shade/renderer/texture/SAMPLER_TRILINEAR_CLAMP.d.ts +8 -0
  279. package/src/shade/renderer/texture/SAMPLER_TRILINEAR_CLAMP.d.ts.map +1 -0
  280. package/src/shade/renderer/texture/SAMPLER_TRILINEAR_CLAMP.js +15 -0
  281. package/src/shade/renderer/view/chunk_view_mip_bias.d.ts +21 -0
  282. package/src/shade/renderer/view/chunk_view_mip_bias.d.ts.map +1 -0
  283. package/src/shade/renderer/view/chunk_view_mip_bias.js +44 -0
  284. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +1 -1
  285. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  286. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +105 -26
  287. package/src/engine/graphics/composit/CompositingStages.d.ts +0 -6
  288. package/src/engine/graphics/composit/CompositingStages.d.ts.map +0 -1
  289. package/src/engine/graphics/composit/CompositingStages.js +0 -8
  290. package/src/engine/graphics/geometry/buffered/computeBufferAttributeHash.d.ts +0 -7
  291. package/src/engine/graphics/geometry/buffered/computeBufferAttributeHash.d.ts.map +0 -1
  292. package/src/engine/graphics/geometry/buffered/computeBufferAttributeHash.js +0 -24
  293. package/src/engine/graphics/geometry/buffered/computeGeometryBoundingSphereMiniball.d.ts +0 -8
  294. package/src/engine/graphics/geometry/buffered/computeGeometryBoundingSphereMiniball.d.ts.map +0 -1
  295. package/src/engine/graphics/geometry/buffered/computeGeometryBoundingSphereMiniball.js +0 -17
  296. package/src/engine/graphics/geometry/buffered/computeGeometryEquality.d.ts +0 -8
  297. package/src/engine/graphics/geometry/buffered/computeGeometryEquality.d.ts.map +0 -1
  298. package/src/engine/graphics/geometry/buffered/computeGeometryEquality.js +0 -60
  299. package/src/engine/graphics/geometry/buffered/computeGeometryHash.d.ts +0 -7
  300. package/src/engine/graphics/geometry/buffered/computeGeometryHash.d.ts.map +0 -1
  301. package/src/engine/graphics/geometry/buffered/computeGeometryHash.js +0 -26
  302. package/src/engine/graphics/geometry/buffered/compute_buffer_geometry_byte_size.d.ts +0 -7
  303. package/src/engine/graphics/geometry/buffered/compute_buffer_geometry_byte_size.d.ts.map +0 -1
  304. package/src/engine/graphics/geometry/buffered/compute_buffer_geometry_byte_size.js +0 -23
  305. package/src/engine/graphics/geometry/instancing/geometry_copy.d.ts +0 -7
  306. package/src/engine/graphics/geometry/instancing/geometry_copy.d.ts.map +0 -1
  307. package/src/engine/graphics/geometry/instancing/geometry_copy.js +0 -15
  308. package/src/engine/graphics/geometry/instancing/rewriteMaterial.d.ts +0 -2
  309. package/src/engine/graphics/geometry/instancing/rewriteMaterial.d.ts.map +0 -1
  310. package/src/engine/graphics/geometry/instancing/rewriteMaterial.js +0 -57
  311. package/src/engine/graphics/geometry/ribbon/Quad.d.ts +0 -96
  312. package/src/engine/graphics/geometry/ribbon/Quad.d.ts.map +0 -1
  313. package/src/engine/graphics/geometry/ribbon/Quad.js +0 -213
  314. package/src/engine/graphics/geometry/ribbon/copyAttributeV3.d.ts +0 -9
  315. package/src/engine/graphics/geometry/ribbon/copyAttributeV3.d.ts.map +0 -1
  316. package/src/engine/graphics/geometry/ribbon/copyAttributeV3.js +0 -12
  317. package/src/engine/graphics/geometry/ribbon/copyAttributeValue.d.ts +0 -10
  318. package/src/engine/graphics/geometry/ribbon/copyAttributeValue.d.ts.map +0 -1
  319. package/src/engine/graphics/geometry/ribbon/copyAttributeValue.js +0 -17
  320. package/src/engine/graphics/geometry/ribbon/equalAttributeV3.d.ts +0 -10
  321. package/src/engine/graphics/geometry/ribbon/equalAttributeV3.d.ts.map +0 -1
  322. package/src/engine/graphics/geometry/ribbon/equalAttributeV3.js +0 -13
  323. package/src/engine/graphics/geometry/ribbon/equalAttributeValue.d.ts +0 -11
  324. package/src/engine/graphics/geometry/ribbon/equalAttributeValue.d.ts.map +0 -1
  325. package/src/engine/graphics/geometry/ribbon/equalAttributeValue.js +0 -24
  326. package/src/engine/graphics/geometry/ribbon/rotateRibbon.d.ts +0 -6
  327. package/src/engine/graphics/geometry/ribbon/rotateRibbon.d.ts.map +0 -1
  328. package/src/engine/graphics/geometry/ribbon/rotateRibbon.js +0 -37
  329. package/src/engine/graphics/geometry/ribbon/updateTipPosition.d.ts +0 -9
  330. package/src/engine/graphics/geometry/ribbon/updateTipPosition.d.ts.map +0 -1
  331. package/src/engine/graphics/geometry/ribbon/updateTipPosition.js +0 -52
  332. package/src/engine/graphics/geometry/skining/computeSkinnedMeshVertices.d.ts +0 -8
  333. package/src/engine/graphics/geometry/skining/computeSkinnedMeshVertices.d.ts.map +0 -1
  334. package/src/engine/graphics/geometry/skining/computeSkinnedMeshVertices.js +0 -54
  335. package/src/engine/graphics/render/RenderPassType.d.ts +0 -6
  336. package/src/engine/graphics/render/RenderPassType.d.ts.map +0 -1
  337. package/src/engine/graphics/render/RenderPassType.js +0 -8
  338. package/src/engine/graphics/render/layers/RenderLayer.d.ts +0 -78
  339. package/src/engine/graphics/render/layers/RenderLayer.d.ts.map +0 -1
  340. package/src/engine/graphics/render/layers/RenderLayer.js +0 -95
  341. package/src/engine/graphics/render/layers/RenderLayerManager.d.ts +0 -72
  342. package/src/engine/graphics/render/layers/RenderLayerManager.d.ts.map +0 -1
  343. package/src/engine/graphics/render/layers/RenderLayerManager.js +0 -160
  344. package/src/engine/graphics/render/layers/RenderLayerManagerState.d.ts +0 -18
  345. package/src/engine/graphics/render/layers/RenderLayerManagerState.d.ts.map +0 -1
  346. package/src/engine/graphics/render/layers/RenderLayerManagerState.js +0 -48
  347. package/src/engine/graphics/render/layers/RenderLayerState.d.ts +0 -18
  348. package/src/engine/graphics/render/layers/RenderLayerState.d.ts.map +0 -1
  349. package/src/engine/graphics/render/layers/RenderLayerState.js +0 -27
  350. package/src/engine/graphics/render/layers/RenderLayerUtils.d.ts +0 -8
  351. package/src/engine/graphics/render/layers/RenderLayerUtils.d.ts.map +0 -1
  352. package/src/engine/graphics/render/layers/RenderLayerUtils.js +0 -103
  353. package/src/engine/graphics/render/make_bvh_visibility_builder.d.ts +0 -10
  354. package/src/engine/graphics/render/make_bvh_visibility_builder.d.ts.map +0 -1
  355. package/src/engine/graphics/render/make_bvh_visibility_builder.js +0 -64
  356. package/src/engine/graphics/render/view/CameraViewFlags.d.ts +0 -7
  357. package/src/engine/graphics/render/view/CameraViewFlags.d.ts.map +0 -1
  358. package/src/engine/graphics/render/view/CameraViewFlags.js +0 -9
  359. package/src/engine/graphics/render/visibility/IncrementalDeltaSet.d.ts +0 -143
  360. package/src/engine/graphics/render/visibility/IncrementalDeltaSet.d.ts.map +0 -1
  361. package/src/engine/graphics/render/visibility/IncrementalDeltaSet.js +0 -328
  362. package/src/engine/graphics/render/visibility/VisibilityComputer.d.ts +0 -39
  363. package/src/engine/graphics/render/visibility/VisibilityComputer.d.ts.map +0 -1
  364. package/src/engine/graphics/render/visibility/VisibilityComputer.js +0 -80
  365. package/src/engine/graphics/render/visibility/VisibilityFilter.d.ts +0 -29
  366. package/src/engine/graphics/render/visibility/VisibilityFilter.d.ts.map +0 -1
  367. package/src/engine/graphics/render/visibility/VisibilityFilter.js +0 -37
  368. package/src/engine/graphics3/animation/write_graph_playback.d.ts +0 -24
  369. package/src/engine/graphics3/animation/write_graph_playback.d.ts.map +0 -1
  370. package/src/engine/graphics3/animation/write_graph_playback.js +0 -48
@@ -0,0 +1,1104 @@
1
+ import { PointHashGrid3 } from "../../../core/geom/3d/hash-grid/PointHashGrid3.js";
2
+ import { GOLDEN_RATIO } from "../../../core/math/GOLDEN_RATIO.js";
3
+ import { PI2 } from "../../../core/math/PI2.js";
4
+ import { clamp } from "../../../core/math/clamp.js";
5
+ import { lerp } from "../../../core/math/lerp.js";
6
+ import { curl_noise_3d } from "../../../core/math/noise/curl_noise_3d.js";
7
+ import { seededRandom } from "../../../core/math/random/seededRandom.js";
8
+ import { CrownEnvelopeType } from "../config/CrownEnvelopeType.js";
9
+ import { PhyllotaxisMode } from "../config/PhyllotaxisMode.js";
10
+ import { TreeSpeciesConfig } from "../config/TreeSpeciesConfig.js";
11
+ import { TreeSkeleton } from "./TreeSkeleton.js";
12
+ import { crown_envelope_evaluate, crown_envelope_sdf } from "./crown_envelope_evaluate.js";
13
+ import { spine_frame_double_reflection } from "./spine_frame_double_reflection.js";
14
+ import { tree_skeleton_compute_pipe_radii } from "./tree_skeleton_compute_pipe_radii.js";
15
+
16
+ const GOLDEN_ANGLE = PI2 / (GOLDEN_RATIO * GOLDEN_RATIO); // ~2.399963229728653 rad (137.50776 deg)
17
+
18
+ const SCRATCH_CURL = [0, 0, 0];
19
+ const SCRATCH_RMF = new Float64Array(6);
20
+ const SCRATCH_POS = new Float32Array(3);
21
+ const SCRATCH_TAN = new Float32Array(3);
22
+ const SCRATCH_P0 = new Float32Array(3);
23
+ const SCRATCH_T0 = new Float32Array(3);
24
+ const SCRATCH_R0 = new Float32Array(3);
25
+ const SCRATCH_M_PREV = new Float32Array(3);
26
+ const SCRATCH_M_NEXT = new Float32Array(3);
27
+ const SCRATCH_GUIDE_P0 = new Float32Array(3);
28
+ const SCRATCH_GUIDE_P1 = new Float32Array(3);
29
+ const SCRATCH_GUIDE_T = new Float32Array(3);
30
+
31
+ /**
32
+ * Evaluates the trunk guide curve position `out_pos` and unit tangent `out_tan` at normalized
33
+ * parameter `t_norm` in [0, 1]. Defaults to the vertical upward plumb line `(0, t_norm * trunk_len, 0)`
34
+ * when `trunk_guide` is null or omitted.
35
+ *
36
+ * @param {Float32Array|number[]} out_pos
37
+ * @param {Float32Array|number[]} out_tan
38
+ * @param {Array<ArrayLike<number>>|Function|null|undefined} trunk_guide
39
+ * @param {number} t_norm
40
+ * @param {number} trunk_len
41
+ */
42
+ function evaluate_trunk_guide(out_pos, out_tan, trunk_guide, t_norm, trunk_len) {
43
+ const t = clamp(t_norm, 0.0, 1.0);
44
+ if (!trunk_guide) {
45
+ out_pos[0] = 0.0;
46
+ out_pos[1] = t * trunk_len;
47
+ out_pos[2] = 0.0;
48
+ out_tan[0] = 0.0;
49
+ out_tan[1] = 1.0;
50
+ out_tan[2] = 0.0;
51
+ return;
52
+ }
53
+
54
+ const sample_pt = (u, dst) => {
55
+ if (typeof trunk_guide === "function") {
56
+ trunk_guide(u, dst);
57
+ return;
58
+ }
59
+ if (Array.isArray(trunk_guide) && trunk_guide.length >= 2) {
60
+ const n = trunk_guide.length - 1;
61
+ const su = clamp(u, 0.0, 1.0) * n;
62
+ const idx = Math.min(n - 1, Math.floor(su));
63
+ const frac = su - idx;
64
+ const a = trunk_guide[idx];
65
+ const b = trunk_guide[idx + 1];
66
+ dst[0] = lerp(a[0] ?? 0.0, b[0] ?? 0.0, frac);
67
+ dst[1] = lerp(a[1] ?? 0.0, b[1] ?? 0.0, frac);
68
+ dst[2] = lerp(a[2] ?? 0.0, b[2] ?? 0.0, frac);
69
+ return;
70
+ }
71
+ dst[0] = 0.0;
72
+ dst[1] = u * trunk_len;
73
+ dst[2] = 0.0;
74
+ };
75
+
76
+ sample_pt(t, out_pos);
77
+ const eps = 0.01;
78
+ const t_a = Math.max(0.0, t - eps);
79
+ const t_b = Math.min(1.0, t + eps);
80
+ const pa = [0, 0, 0];
81
+ const pb = [0, 0, 0];
82
+ sample_pt(t_a, pa);
83
+ sample_pt(t_b, pb);
84
+ let tx = pb[0] - pa[0];
85
+ let ty = pb[1] - pa[1];
86
+ let tz = pb[2] - pa[2];
87
+ const tl = Math.hypot(tx, ty, tz);
88
+ if (tl > 1e-8) {
89
+ out_tan[0] = tx / tl;
90
+ out_tan[1] = ty / tl;
91
+ out_tan[2] = tz / tl;
92
+ } else {
93
+ out_tan[0] = 0.0;
94
+ out_tan[1] = 1.0;
95
+ out_tan[2] = 0.0;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Rotates unit vector `(vx, vy, vz)` around unit axis `(ax, ay, az)` by `angle` radians (Rodrigues' formula).
101
+ * @param {number[]|Float64Array} out
102
+ * @param {number} vx
103
+ * @param {number} vy
104
+ * @param {number} vz
105
+ * @param {number} ax
106
+ * @param {number} ay
107
+ * @param {number} az
108
+ * @param {number} angle
109
+ */
110
+ function rotate_around_axis(out, vx, vy, vz, ax, ay, az, angle) {
111
+ const c = Math.cos(angle);
112
+ const s = Math.sin(angle);
113
+ const dot = vx * ax + vy * ay + vz * az;
114
+ const omc = 1.0 - c;
115
+
116
+ const cx = ay * vz - az * vy;
117
+ const cy = az * vx - ax * vz;
118
+ const cz = ax * vy - ay * vx;
119
+
120
+ let rx = vx * c + cx * s + ax * dot * omc;
121
+ let ry = vy * c + cy * s + ay * dot * omc;
122
+ let rz = vz * c + cz * s + az * dot * omc;
123
+
124
+ const len = Math.sqrt(rx * rx + ry * ry + rz * rz);
125
+ if (len > 1e-12) {
126
+ const inv = 1.0 / len;
127
+ rx *= inv;
128
+ ry *= inv;
129
+ rz *= inv;
130
+ }
131
+ out[0] = rx;
132
+ out[1] = ry;
133
+ out[2] = rz;
134
+ }
135
+
136
+ /**
137
+ * Generates a directed botanical `TreeSkeleton` from a `TreeSpeciesConfig`, combining:
138
+ * - Multi-level parametric Cubic Hermite spine generation (`BranchLevelConfig`, `AnimationCurve` profiles)
139
+ * - Botanical phyllotaxis (`SpiralGolden`, `AlternateDistichous`, `Tristichous`, `Opposite`, `OppositeDecussate`, `Whorled`)
140
+ * - Gravitropism (toward `-Y`), phototropism (toward `+Y` and outward crown exterior), and 3D `curl_noise_3d` gnarl
141
+ * - Dichotomous / sympodial trunk & bough splitting
142
+ * - Space colonization crown competition via `PointHashGrid3`
143
+ * - Rotation-Minimizing Frames (`spine_frame_double_reflection`)
144
+ * - Pipe Model cross-section radius accumulation (`tree_skeleton_compute_pipe_radii`)
145
+ *
146
+ * @param {TreeSpeciesConfig|Object} [config]
147
+ * @param {TreeSkeleton} [out]
148
+ * @returns {TreeSkeleton}
149
+ */
150
+ export function tree_skeleton_grow(config = new TreeSpeciesConfig(), out = new TreeSkeleton()) {
151
+ const species = config && config.isTreeSpeciesConfig ? config : TreeSpeciesConfig.from(config);
152
+ out.clear();
153
+
154
+ const rng = seededRandom(species.seed | 0);
155
+ // Seed-derived 3D noise offset so unseeded Simplex/Curl fields are unique and deterministic per seed
156
+ const noise_ox = (rng() - 0.5) * 1000.0;
157
+ const noise_oy = (rng() - 0.5) * 1000.0;
158
+ const noise_oz = (rng() - 0.5) * 1000.0;
159
+
160
+ const crown = species.crown;
161
+ const crown_cy = crown.base_height + crown.height * 0.5;
162
+ out.crown_center[0] = 0.0;
163
+ out.crown_center[1] = crown_cy;
164
+ out.crown_center[2] = 0.0;
165
+
166
+ out.crown_radii[0] = Math.max(0.25, crown.radius);
167
+ out.crown_radii[1] = Math.max(0.25, crown.height * 0.5);
168
+ out.crown_radii[2] = Math.max(0.25, crown.radius);
169
+
170
+ // Build Space Colonization attraction point cloud + PointHashGrid3 once
171
+ const use_space_col = crown.space_colonization_weight > 0.0 && crown.attraction_point_count > 0;
172
+ const attr_count = use_space_col ? (crown.attraction_point_count | 0) : 0;
173
+ const attr_positions = new Float32Array(attr_count * 3);
174
+ const attr_alive = new Uint8Array(attr_count);
175
+ let attr_grid = null;
176
+
177
+ if (use_space_col) {
178
+ let placed = 0;
179
+ let attempts = 0;
180
+ const max_attempts = attr_count * 12;
181
+ while (placed < attr_count && attempts < max_attempts) {
182
+ attempts++;
183
+ const u_h = rng();
184
+ const env_r = crown.radius * crown_envelope_evaluate(crown.envelope_type, u_h);
185
+ const rx = (rng() * 2.0 - 1.0) * env_r;
186
+ const rz = (rng() * 2.0 - 1.0) * env_r;
187
+ if (rx * rx + rz * rz <= env_r * env_r) {
188
+ const ry = crown.base_height + u_h * crown.height;
189
+ attr_positions[placed * 3] = rx;
190
+ attr_positions[placed * 3 + 1] = ry;
191
+ attr_positions[placed * 3 + 2] = rz;
192
+ attr_alive[placed] = 1;
193
+ placed++;
194
+ }
195
+ }
196
+ while (placed < attr_count) {
197
+ const theta = rng() * PI2;
198
+ const u_h = rng();
199
+ const r = Math.sqrt(rng()) * crown.radius * crown_envelope_evaluate(crown.envelope_type, u_h);
200
+ attr_positions[placed * 3] = Math.cos(theta) * r;
201
+ attr_positions[placed * 3 + 1] = crown.base_height + u_h * crown.height;
202
+ attr_positions[placed * 3 + 2] = Math.sin(theta) * r;
203
+ attr_alive[placed] = 1;
204
+ placed++;
205
+ }
206
+
207
+ attr_grid = new PointHashGrid3();
208
+ attr_grid.build(attr_positions, attr_count, Math.max(0.25, crown.attraction_radius));
209
+ }
210
+
211
+ const r_influence_sq = crown.attraction_radius * crown.attraction_radius;
212
+ const r_kill_sq = crown.kill_radius * crown.kill_radius;
213
+
214
+ // Queue of branches to grow in level-order (guarantees parent branches & vertices precede children)
215
+ const queue = [];
216
+
217
+ const lvl0 = species.levels[0];
218
+ if (!lvl0) {
219
+ return out;
220
+ }
221
+
222
+ const trunk_len = Math.max(0.2, lvl0.length * (1.0 + (rng() * 2.0 - 1.0) * lvl0.length_variance));
223
+ const trunk_base_r = Math.max(0.01, trunk_len * lvl0.radius_ratio);
224
+
225
+ evaluate_trunk_guide(SCRATCH_GUIDE_P0, SCRATCH_GUIDE_T, species.trunk_guide, 0.0, trunk_len);
226
+
227
+ // Apply subtle initial basal trunk lean around the guide tangent
228
+ const max_lean = lvl0.phyllotaxis_mode === PhyllotaxisMode.Whorled ? 0.025 : 0.06;
229
+ const lean_ang = clamp((lvl0.basal_lean ?? 0.02) * (0.75 + 0.5 * rng()), 0.0, max_lean);
230
+ const lean_az = rng() * PI2;
231
+ const init_tx = clamp(SCRATCH_GUIDE_T[0] + Math.sin(lean_ang) * Math.cos(lean_az), -0.95, 0.95);
232
+ const init_ty = Math.max(0.2, SCRATCH_GUIDE_T[1] * Math.cos(lean_ang));
233
+ const init_tz = clamp(SCRATCH_GUIDE_T[2] + Math.sin(lean_ang) * Math.sin(lean_az), -0.95, 0.95);
234
+ const init_t_len = Math.hypot(init_tx, init_ty, init_tz) || 1.0;
235
+ const norm_tx = init_tx / init_t_len;
236
+ const norm_ty = init_ty / init_t_len;
237
+ const norm_tz = init_tz / init_t_len;
238
+
239
+ // Orthonormal initial R perpendicular to norm_T
240
+ let init_rx = 1.0 - norm_tx * norm_tx;
241
+ let init_ry = -norm_ty * norm_tx;
242
+ let init_rz = -norm_tz * norm_tx;
243
+ const init_r_len = Math.hypot(init_rx, init_ry, init_rz) || 1.0;
244
+ init_rx /= init_r_len;
245
+ init_ry /= init_r_len;
246
+ init_rz /= init_r_len;
247
+
248
+ queue.push({
249
+ level: 0,
250
+ parent_branch_id: -1,
251
+ parent_edge_id: -1,
252
+ parent_attach_t: 0.0,
253
+ start_vertex: -1,
254
+ px: SCRATCH_GUIDE_P0[0],
255
+ py: SCRATCH_GUIDE_P0[1],
256
+ pz: SCRATCH_GUIDE_P0[2],
257
+ tx: norm_tx,
258
+ ty: norm_ty,
259
+ tz: norm_tz,
260
+ rx: init_rx,
261
+ ry: init_ry,
262
+ rz: init_rz,
263
+ length: trunk_len,
264
+ base_radius: trunk_base_r,
265
+ start_seg: 0,
266
+ total_segs: Math.max(1, lvl0.segments),
267
+ initial_arc_s: 0.0,
268
+ has_split: false
269
+ });
270
+
271
+ const rot_scratch = [0, 0, 0];
272
+
273
+ for (let q_idx = 0; q_idx < queue.length; q_idx++) {
274
+ const job = queue[q_idx];
275
+ const level = job.level;
276
+ const lvl_cfg = species.levels[level];
277
+ if (!lvl_cfg) continue;
278
+
279
+ const phase_hash = rng();
280
+ const N_seg = job.total_segs;
281
+ const remaining_segs = N_seg - job.start_seg;
282
+ if (remaining_segs <= 0) continue;
283
+
284
+ const step_len = job.length / N_seg;
285
+
286
+ let cur_px = job.px;
287
+ let cur_py = job.py;
288
+ let cur_pz = job.pz;
289
+ let cur_tx = job.tx;
290
+ let cur_ty = job.ty;
291
+ let cur_tz = job.tz;
292
+ let cur_rx = job.rx;
293
+ let cur_ry = job.ry;
294
+ let cur_rz = job.rz;
295
+ let cur_arc_s = job.initial_arc_s;
296
+
297
+ // Compute binormal S = T x R
298
+ let cur_sx = cur_ty * cur_rz - cur_tz * cur_ry;
299
+ let cur_sy = cur_tz * cur_rx - cur_tx * cur_rz;
300
+ let cur_sz = cur_tx * cur_ry - cur_ty * cur_rx;
301
+
302
+ let prev_v = job.start_vertex;
303
+ let branch_id = -1;
304
+
305
+ if (prev_v < 0) {
306
+ const t0 = job.start_seg / N_seg;
307
+ const r0_profile = Math.max(0.05, lvl_cfg.radius_profile.evaluate(t0));
308
+ const r0 = Math.max(0.002, job.base_radius * Math.max(1.0 - lvl_cfg.taper * t0, 0.05) * r0_profile);
309
+
310
+ branch_id = out.add_branch(
311
+ 0,
312
+ level,
313
+ job.parent_branch_id,
314
+ job.parent_edge_id,
315
+ job.parent_attach_t,
316
+ job.length,
317
+ r0,
318
+ phase_hash
319
+ );
320
+ prev_v = out.add_node(
321
+ cur_px, cur_py, cur_pz,
322
+ cur_tx, cur_ty, cur_tz,
323
+ cur_rx, cur_ry, cur_rz,
324
+ r0,
325
+ branch_id,
326
+ level,
327
+ job.parent_branch_id,
328
+ t0,
329
+ cur_arc_s,
330
+ phase_hash
331
+ );
332
+ if (job.attach_parent_vertex !== undefined && job.attach_parent_vertex >= 0) {
333
+ out.connect_nodes(job.attach_parent_vertex, prev_v);
334
+ }
335
+ out.branch_root_vertex[branch_id] = prev_v;
336
+ out.branch_tip_vertex[branch_id] = prev_v;
337
+ } else {
338
+ const prev_r = out.node_read_radius(prev_v);
339
+ branch_id = out.add_branch(
340
+ prev_v,
341
+ level,
342
+ job.parent_branch_id,
343
+ job.parent_edge_id,
344
+ job.parent_attach_t,
345
+ job.length,
346
+ prev_r,
347
+ phase_hash
348
+ );
349
+ }
350
+
351
+ const branch_edges = [];
352
+ const branch_edge_t0 = [];
353
+ const branch_edge_t1 = [];
354
+ const branch_vertices = [prev_v];
355
+
356
+ // Pre-schedule phyllotactic child attachment parameters so the parent spine kinks sympodially at lateral emissions
357
+ const next_level_preview = level + 1;
358
+ const has_children = next_level_preview < species.levels.length && lvl_cfg.child_count > 0;
359
+ const span_fraction_est = job.child_span_fraction ?? Math.max(0.25, (N_seg - job.start_seg) / Math.max(1, N_seg));
360
+ const planned_child_count = has_children
361
+ ? (span_fraction_est < 0.99 ? Math.max(1, Math.round(lvl_cfg.child_count * span_fraction_est)) : lvl_cfg.child_count)
362
+ : 0;
363
+ const bare_est = clamp(lvl_cfg.bare_length_ratio, 0.0, 0.92);
364
+ const t_min_est = Math.max(bare_est, job.start_seg / N_seg);
365
+ const base_phi = rng() * PI2;
366
+ const scheduled_children = [];
367
+
368
+ if (planned_child_count > 0 && 1.0 > t_min_est + 1e-3) {
369
+ for (let c = 0; c < planned_child_count; c++) {
370
+ let u_c = (c + 0.5) / planned_child_count;
371
+ if (lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.Opposite
372
+ || lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.OppositeDecussate) {
373
+ const pair_idx = c >> 1;
374
+ const pair_count = Math.max(1, Math.ceil(planned_child_count / 2));
375
+ u_c = (pair_idx + 0.5) / pair_count;
376
+ } else if (lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.Whorled) {
377
+ const w_size = Math.max(1, lvl_cfg.whorl_count | 0);
378
+ const whorl_idx = Math.floor(c / w_size);
379
+ const whorl_total = Math.max(1, Math.ceil(planned_child_count / w_size));
380
+ u_c = (whorl_idx + 0.5) / whorl_total;
381
+ }
382
+
383
+ const t_att = clamp(
384
+ lerp(t_min_est, 0.96, u_c) + (rng() - 0.5) * (0.3 / planned_child_count),
385
+ t_min_est,
386
+ 0.98
387
+ );
388
+
389
+ let phi_c = base_phi;
390
+ switch (lvl_cfg.phyllotaxis_mode) {
391
+ case PhyllotaxisMode.AlternateDistichous:
392
+ phi_c += c * Math.PI;
393
+ break;
394
+ case PhyllotaxisMode.Tristichous:
395
+ phi_c += c * (PI2 / 3.0);
396
+ break;
397
+ case PhyllotaxisMode.Opposite:
398
+ phi_c += (c & 1) * Math.PI;
399
+ break;
400
+ case PhyllotaxisMode.OppositeDecussate:
401
+ phi_c += (c & 1) * Math.PI + (c >> 1) * (Math.PI * 0.5);
402
+ break;
403
+ case PhyllotaxisMode.Whorled: {
404
+ const w_size = Math.max(1, lvl_cfg.whorl_count | 0);
405
+ const w_idx = Math.floor(c / w_size);
406
+ const sub_idx = c % w_size;
407
+ phi_c += (sub_idx * PI2) / w_size + (w_idx * Math.PI) / w_size;
408
+ break;
409
+ }
410
+ case PhyllotaxisMode.SpiralGolden:
411
+ default:
412
+ phi_c += c * GOLDEN_ANGLE;
413
+ break;
414
+ }
415
+ phi_c += (rng() * 2.0 - 1.0) * lvl_cfg.azimuth_jitter;
416
+ scheduled_children.push({ t_attach: t_att, phi: phi_c });
417
+ }
418
+ }
419
+
420
+ const k_symp = lvl_cfg.nodal_kink_strength ?? 0.45;
421
+ const zig_base = lvl_cfg.internode_zigzag ?? 0.06;
422
+ const tip_up = lvl_cfg.tip_upcurl ?? 0.18;
423
+
424
+ for (let s = job.start_seg + 1; s <= N_seg; s++) {
425
+ const t_prev = (s - 1) / N_seg;
426
+ const t_curr = s / N_seg;
427
+
428
+ // 1. Intrinsic S-curve pitch curvature (zero-mean higher harmonic on trunk so trunk weaves rather than leaning)
429
+ const s_wave = level === 0
430
+ ? (lvl_cfg.curvature * Math.sin(PI2 * 1.5 * t_curr + phase_hash * PI2))
431
+ : (lvl_cfg.curve_back !== 0.0
432
+ ? (lvl_cfg.curvature * Math.cos(Math.PI * t_curr) + lvl_cfg.curve_back * Math.sin(PI2 * t_curr))
433
+ : lvl_cfg.curvature);
434
+ const d_pitch = s_wave / Math.max(2, N_seg * 0.65);
435
+
436
+ if (Math.abs(d_pitch) > 1e-6) {
437
+ rotate_around_axis(rot_scratch, cur_tx, cur_ty, cur_tz, cur_sx, cur_sy, cur_sz, d_pitch);
438
+ cur_tx = rot_scratch[0];
439
+ cur_ty = rot_scratch[1];
440
+ cur_tz = rot_scratch[2];
441
+ rotate_around_axis(rot_scratch, cur_rx, cur_ry, cur_rz, cur_sx, cur_sy, cur_sz, d_pitch);
442
+ cur_rx = rot_scratch[0];
443
+ cur_ry = rot_scratch[1];
444
+ cur_rz = rot_scratch[2];
445
+ }
446
+
447
+ // 2. Helical spiral twist around T
448
+ if (Math.abs(lvl_cfg.spiral_twist) > 1e-6) {
449
+ const d_twist = lvl_cfg.spiral_twist / N_seg;
450
+ rotate_around_axis(rot_scratch, cur_rx, cur_ry, cur_rz, cur_tx, cur_ty, cur_tz, d_twist);
451
+ cur_rx = rot_scratch[0];
452
+ cur_ry = rot_scratch[1];
453
+ cur_rz = rot_scratch[2];
454
+ cur_sx = cur_ty * cur_rz - cur_tz * cur_ry;
455
+ cur_sy = cur_tz * cur_rx - cur_tx * cur_rz;
456
+ cur_sz = cur_tx * cur_ry - cur_ty * cur_rx;
457
+ }
458
+
459
+ // 2b. Sympodial Nodal Kinking at scheduled lateral child emissions + Internodal Zig-Zag
460
+ let kink_rx = 0.0;
461
+ let kink_ry = 0.0;
462
+ let kink_rz = 0.0;
463
+
464
+ if (k_symp > 0.0 && scheduled_children.length > 0 && lvl_cfg.phyllotaxis_mode !== PhyllotaxisMode.Whorled) {
465
+ for (let c = 0; c < scheduled_children.length; c++) {
466
+ const sc = scheduled_children[c];
467
+ if (sc.t_attach >= t_prev && sc.t_attach <= t_curr) {
468
+ // Recoil away from child branch emission azimuth phi
469
+ const recoil_phi = sc.phi + Math.PI;
470
+ const c_recoil = Math.cos(recoil_phi);
471
+ const s_recoil = Math.sin(recoil_phi);
472
+ const amp = clamp(0.18 * k_symp * Math.sin(lvl_cfg.insertion_angle), 0.0, 0.32);
473
+ kink_rx += (c_recoil * cur_rx + s_recoil * cur_sx) * amp;
474
+ kink_ry += (c_recoil * cur_ry + s_recoil * cur_sy) * amp;
475
+ kink_rz += (c_recoil * cur_rz + s_recoil * cur_sz) * amp;
476
+ }
477
+ }
478
+ }
479
+
480
+ if (zig_base > 0.0) {
481
+ // Alternate by PI on trunk so consecutive internode kinks balance around the plumb line
482
+ const zig_step = (level === 0 || lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.AlternateDistichous)
483
+ ? Math.PI
484
+ : GOLDEN_ANGLE;
485
+ const zig_phi = base_phi + s * zig_step + (rng() - 0.5) * 0.55;
486
+ const zig_amp = zig_base * (0.65 + 0.70 * rng()) * (0.65 + 0.35 * t_curr);
487
+ const cz = Math.cos(zig_phi);
488
+ const sz = Math.sin(zig_phi);
489
+ kink_rx += (cz * cur_rx + sz * cur_sx) * zig_amp;
490
+ kink_ry += (cz * cur_ry + sz * cur_sy) * zig_amp;
491
+ kink_rz += (cz * cur_rz + sz * cur_sz) * zig_amp;
492
+ }
493
+
494
+ const kink_mag = Math.hypot(kink_rx, kink_ry, kink_rz);
495
+ if (kink_mag > 1e-6) {
496
+ // Rotation axis B = T x (kink_dir) in the (R, S) plane
497
+ const kx = kink_rx / kink_mag;
498
+ const ky = kink_ry / kink_mag;
499
+ const kz = kink_rz / kink_mag;
500
+ let bx = cur_ty * kz - cur_tz * ky;
501
+ let by = cur_tz * kx - cur_tx * kz;
502
+ let bz = cur_tx * ky - cur_ty * kx;
503
+ const b_len = Math.hypot(bx, by, bz);
504
+ if (b_len > 1e-6) {
505
+ bx /= b_len;
506
+ by /= b_len;
507
+ bz /= b_len;
508
+ const clamped_kink = Math.min(0.42, kink_mag);
509
+ rotate_around_axis(rot_scratch, cur_tx, cur_ty, cur_tz, bx, by, bz, clamped_kink);
510
+ cur_tx = rot_scratch[0];
511
+ cur_ty = rot_scratch[1];
512
+ cur_tz = rot_scratch[2];
513
+ rotate_around_axis(rot_scratch, cur_rx, cur_ry, cur_rz, bx, by, bz, clamped_kink);
514
+ cur_rx = rot_scratch[0];
515
+ cur_ry = rot_scratch[1];
516
+ cur_rz = rot_scratch[2];
517
+ cur_sx = cur_ty * cur_rz - cur_tz * cur_ry;
518
+ cur_sy = cur_tz * cur_rx - cur_tx * cur_rz;
519
+ cur_sz = cur_tx * cur_ry - cur_ty * cur_rx;
520
+ }
521
+ }
522
+
523
+ // 3. Biomechanical Tropism steering (Gravitropic sag + Terminal Phototropic Up-Curl + Outward spread)
524
+ let steer_x = 0.0;
525
+ let steer_y = 0.0;
526
+ let steer_z = 0.0;
527
+
528
+ if (level === 0) {
529
+ // Orthotropic upward striving & plumb-line righting reflex along guide curve (default vertical +Y)
530
+ evaluate_trunk_guide(SCRATCH_GUIDE_P0, SCRATCH_GUIDE_T, species.trunk_guide, t_prev, job.length);
531
+ evaluate_trunk_guide(SCRATCH_GUIDE_P1, SCRATCH_GUIDE_T, species.trunk_guide, t_curr, job.length);
532
+ const gtx = SCRATCH_GUIDE_T[0];
533
+ const gty = SCRATCH_GUIDE_T[1];
534
+ const gtz = SCRATCH_GUIDE_T[2];
535
+
536
+ const righting = 1.65 + Math.max(0.0, -lvl_cfg.gravitropism) * 2.5 + Math.max(0.0, lvl_cfg.phototropism);
537
+ steer_x += gtx * righting;
538
+ steer_y += gty * righting;
539
+ steer_z += gtz * righting;
540
+
541
+ // Plumb-line centering force pulling lateral drift back toward the guide curve
542
+ const dx_g = cur_px - SCRATCH_GUIDE_P0[0];
543
+ const dy_g = cur_py - SCRATCH_GUIDE_P0[1];
544
+ const dz_g = cur_pz - SCRATCH_GUIDE_P0[2];
545
+ const dot_g = dx_g * gtx + dy_g * gty + dz_g * gtz;
546
+ const lat_x = dx_g - dot_g * gtx;
547
+ const lat_y = dy_g - dot_g * gty;
548
+ const lat_z = dz_g - dot_g * gtz;
549
+ const plumb_k = (lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.Whorled ? 5.2 : 3.4) / Math.max(1.0, job.length);
550
+ steer_x -= lat_x * plumb_k * N_seg * 0.5;
551
+ steer_y -= lat_y * plumb_k * N_seg * 0.5;
552
+ steer_z -= lat_z * plumb_k * N_seg * 0.5;
553
+ } else {
554
+ if (lvl_cfg.gravitropism !== 0.0 || tip_up !== 0.0) {
555
+ if (crown.envelope_type === CrownEnvelopeType.Weeping && level >= 2) {
556
+ steer_y -= lvl_cfg.gravitropism * (0.4 + 1.2 * t_curr);
557
+ } else {
558
+ // Cantilever sag in mid-span followed by phototropic up-curl near branch tips
559
+ const sag_term = lvl_cfg.gravitropism * 3.0 * t_curr * Math.pow(Math.max(0.0, 1.0 - t_curr), 0.75);
560
+ const up_term = tip_up * Math.pow(t_curr, 1.5);
561
+ steer_y += up_term - sag_term;
562
+ }
563
+ }
564
+
565
+ if (lvl_cfg.phototropism !== 0.0) {
566
+ const raw_sky_mix = clamp(crown.sky_phototropism_mix ?? 0.6, 0.0, 1.0);
567
+ const sky_mix = level === 1 ? raw_sky_mix * 0.45 : raw_sky_mix;
568
+ const horiz_r = Math.sqrt(cur_px * cur_px + cur_pz * cur_pz);
569
+ const out_x = horiz_r > 1e-4 ? cur_px / horiz_r : cur_rx;
570
+ const out_z = horiz_r > 1e-4 ? cur_pz / horiz_r : cur_rz;
571
+
572
+ steer_x += lvl_cfg.phototropism * (1.0 - sky_mix) * out_x;
573
+ steer_y += lvl_cfg.phototropism * sky_mix;
574
+ steer_z += lvl_cfg.phototropism * (1.0 - sky_mix) * out_z;
575
+ }
576
+ }
577
+
578
+ // 4. 2-Octave 3D Curl Noise gnarl scaled properly by sqrt(6 / N_seg)
579
+ if (lvl_cfg.gnarl_strength > 0.0) {
580
+ const freq = lvl_cfg.gnarl_frequency;
581
+ curl_noise_3d(
582
+ SCRATCH_CURL,
583
+ cur_px * freq + noise_ox + level * 17.3,
584
+ cur_py * freq + noise_oy + level * 31.7,
585
+ cur_pz * freq + noise_oz + level * 47.1
586
+ );
587
+ const c1x = SCRATCH_CURL[0], c1y = SCRATCH_CURL[1], c1z = SCRATCH_CURL[2];
588
+ curl_noise_3d(
589
+ SCRATCH_CURL,
590
+ cur_px * freq * 2.15 + noise_ox + 53.1 + phase_hash * 29.3,
591
+ cur_py * freq * 2.15 + noise_oy + 79.3,
592
+ cur_pz * freq * 2.15 + noise_oz + 113.7
593
+ );
594
+ const cx_tot = 0.68 * c1x + 0.32 * SCRATCH_CURL[0];
595
+ const cy_tot = 0.68 * c1y + 0.32 * SCRATCH_CURL[1];
596
+ const cz_tot = 0.68 * c1z + 0.32 * SCRATCH_CURL[2];
597
+
598
+ const gnarl_scale = lvl_cfg.gnarl_strength * Math.sqrt(6.0 / Math.max(2, N_seg));
599
+ // Project curl vector perpendicular to T so it steers direction cleanly
600
+ const dot_ct = cx_tot * cur_tx + cy_tot * cur_ty + cz_tot * cur_tz;
601
+ steer_x += (cx_tot - dot_ct * cur_tx) * gnarl_scale * 2.2;
602
+ steer_y += (cy_tot - dot_ct * cur_ty) * gnarl_scale * 1.6;
603
+ steer_z += (cz_tot - dot_ct * cur_tz) * gnarl_scale * 2.2;
604
+ }
605
+
606
+ // 5. Space colonization attraction steering (for branches in crown, level >= 1)
607
+ if (use_space_col && level >= 1 && attr_grid !== null) {
608
+ let attr_vx = 0.0;
609
+ let attr_vy = 0.0;
610
+ let attr_vz = 0.0;
611
+ let attr_hits = 0;
612
+
613
+ attr_grid.forEachNear(cur_px, cur_py, cur_pz, (idx) => {
614
+ if (attr_alive[idx] === 0) return;
615
+ const ax = attr_positions[idx * 3] - cur_px;
616
+ const ay = attr_positions[idx * 3 + 1] - cur_py;
617
+ const az = attr_positions[idx * 3 + 2] - cur_pz;
618
+ const d2 = ax * ax + ay * ay + az * az;
619
+ if (d2 <= r_kill_sq) {
620
+ attr_alive[idx] = 0;
621
+ } else if (d2 <= r_influence_sq && d2 > 1e-8) {
622
+ const inv_d = 1.0 / Math.sqrt(d2);
623
+ attr_vx += ax * inv_d;
624
+ attr_vy += ay * inv_d;
625
+ attr_vz += az * inv_d;
626
+ attr_hits++;
627
+ }
628
+ });
629
+
630
+ if (attr_hits > 0) {
631
+ const attr_len = Math.sqrt(attr_vx * attr_vx + attr_vy * attr_vy + attr_vz * attr_vz);
632
+ if (attr_len > 1e-6) {
633
+ const w_sc = crown.space_colonization_weight / attr_len;
634
+ steer_x += attr_vx * w_sc;
635
+ steer_y += attr_vy * w_sc;
636
+ steer_z += attr_vz * w_sc;
637
+ }
638
+ }
639
+ }
640
+
641
+ // 6. Crown envelope soft containment for lateral branches (level >= 1)
642
+ let seg_len = step_len;
643
+ if (level >= 1 && crown.prune_ratio > 0.0) {
644
+ const probe_x = cur_px + cur_tx * step_len;
645
+ const probe_y = cur_py + cur_ty * step_len;
646
+ const probe_z = cur_pz + cur_tz * step_len;
647
+ const sdf = crown_envelope_sdf(probe_x, probe_y, probe_z, crown);
648
+ if (sdf > 0.0) {
649
+ // Gently steer back toward crown center and shorten segment near boundary
650
+ const to_cx = -probe_x;
651
+ const to_cy = crown_cy - probe_y;
652
+ const to_cz = -probe_z;
653
+ const to_c_len = Math.sqrt(to_cx * to_cx + to_cy * to_cy + to_cz * to_cz);
654
+ if (to_c_len > 1e-5) {
655
+ const pull = crown.prune_ratio * 0.45 / to_c_len;
656
+ steer_x += to_cx * pull;
657
+ steer_y += to_cy * pull;
658
+ steer_z += to_cz * pull;
659
+ }
660
+ seg_len *= Math.max(0.35, 1.0 - crown.prune_ratio * 0.5);
661
+ }
662
+ }
663
+
664
+ // Apply steering to tangent and normalize
665
+ const steer_div = Math.max(2.0, Math.sqrt(N_seg) * 1.35);
666
+ let next_tx = cur_tx + steer_x / steer_div;
667
+ let next_ty = cur_ty + steer_y / steer_div;
668
+ let next_tz = cur_tz + steer_z / steer_div;
669
+ const t_len = Math.sqrt(next_tx * next_tx + next_ty * next_ty + next_tz * next_tz);
670
+ if (t_len > 1e-12) {
671
+ next_tx /= t_len;
672
+ next_ty /= t_len;
673
+ next_tz /= t_len;
674
+ } else {
675
+ next_tx = cur_tx;
676
+ next_ty = cur_ty;
677
+ next_tz = cur_tz;
678
+ }
679
+
680
+ let next_px = cur_px + 0.5 * (cur_tx + next_tx) * seg_len;
681
+ let next_py = cur_py + 0.5 * (cur_ty + next_ty) * seg_len;
682
+ let next_pz = cur_pz + 0.5 * (cur_tz + next_tz) * seg_len;
683
+
684
+ // Orthotropic plumb-tube containment on trunk (level === 0):
685
+ // Preserves local 3D nodal kinks and serpentine sweep while guaranteeing the trunk never drifts sideways
686
+ if (level === 0 && branch_id === 0) {
687
+ const gx = SCRATCH_GUIDE_P1[0];
688
+ const gy = SCRATCH_GUIDE_P1[1];
689
+ const gz = SCRATCH_GUIDE_P1[2];
690
+ const gtx = SCRATCH_GUIDE_T[0];
691
+ const gty = SCRATCH_GUIDE_T[1];
692
+ const gtz = SCRATCH_GUIDE_T[2];
693
+
694
+ const dx = next_px - gx;
695
+ const dy = next_py - gy;
696
+ const dz = next_pz - gz;
697
+ const dot_g = dx * gtx + dy * gty + dz * gtz;
698
+ const off_x = dx - dot_g * gtx;
699
+ const off_y = dy - dot_g * gty;
700
+ const off_z = dz - dot_g * gtz;
701
+ const off_r = Math.hypot(off_x, off_y, off_z);
702
+
703
+ const is_conifer = lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.Whorled;
704
+ const max_drift_frac = is_conifer ? 0.032 : 0.058;
705
+ // Envelope rises from 0 at root, peaks in mid-trunk (allowing 3D sweep/crookedness), and tapers near tip
706
+ const env = Math.sin(Math.PI * Math.min(0.92, t_curr * 0.88)) * (1.0 - 0.38 * t_curr * t_curr);
707
+ const max_r = job.length * max_drift_frac * Math.max(0.18, env);
708
+
709
+ if (off_r > max_r && off_r > 1e-6) {
710
+ const scale_r = max_r / off_r;
711
+ next_px = gx + dot_g * gtx + off_x * scale_r;
712
+ next_py = gy + dot_g * gty + off_y * scale_r;
713
+ next_pz = gz + dot_g * gtz + off_z * scale_r;
714
+
715
+ let ch_x = next_px - cur_px;
716
+ let ch_y = next_py - cur_py;
717
+ let ch_z = next_pz - cur_pz;
718
+ const ch_l = Math.hypot(ch_x, ch_y, ch_z);
719
+ if (ch_l > 1e-6) {
720
+ next_tx = ch_x / ch_l;
721
+ next_ty = ch_y / ch_l;
722
+ next_tz = ch_z / ch_l;
723
+ }
724
+ }
725
+ }
726
+
727
+ // Transport RMF via Wang et al. Double Reflection
728
+ spine_frame_double_reflection(
729
+ SCRATCH_RMF, 0,
730
+ cur_px, cur_py, cur_pz,
731
+ cur_tx, cur_ty, cur_tz,
732
+ cur_rx, cur_ry, cur_rz,
733
+ next_px, next_py, next_pz,
734
+ next_tx, next_ty, next_tz
735
+ );
736
+ let next_rx = SCRATCH_RMF[0];
737
+ let next_ry = SCRATCH_RMF[1];
738
+ let next_rz = SCRATCH_RMF[2];
739
+ cur_sx = SCRATCH_RMF[3];
740
+ cur_sy = SCRATCH_RMF[4];
741
+ cur_sz = SCRATCH_RMF[5];
742
+
743
+ cur_arc_s += seg_len;
744
+
745
+ const r_prof = Math.max(0.04, lvl_cfg.radius_profile.evaluate(t_curr));
746
+ const r_curr = Math.max(
747
+ 0.0015,
748
+ job.base_radius * Math.max(1.0 - lvl_cfg.taper * t_curr, 0.05) * r_prof
749
+ );
750
+
751
+ const curr_v = out.add_node(
752
+ next_px, next_py, next_pz,
753
+ next_tx, next_ty, next_tz,
754
+ next_rx, next_ry, next_rz,
755
+ r_curr,
756
+ branch_id,
757
+ level,
758
+ job.parent_branch_id,
759
+ t_curr,
760
+ cur_arc_s,
761
+ phase_hash
762
+ );
763
+
764
+ const edge_id = out.add_segment(
765
+ prev_v,
766
+ curr_v,
767
+ cur_tx * seg_len,
768
+ cur_ty * seg_len,
769
+ cur_tz * seg_len,
770
+ next_tx * seg_len,
771
+ next_ty * seg_len,
772
+ next_tz * seg_len,
773
+ seg_len,
774
+ t_prev === 0.0 ? job.parent_attach_t : t_prev
775
+ );
776
+
777
+ branch_edges.push(edge_id);
778
+ branch_edge_t0.push(t_prev);
779
+ branch_edge_t1.push(t_curr);
780
+ branch_vertices.push(curr_v);
781
+
782
+ out.branch_tip_vertex[branch_id] = curr_v;
783
+ out.branch_length[branch_id] = cur_arc_s - job.initial_arc_s;
784
+
785
+ // Check for dichotomous / sympodial splitting at interior segments
786
+ const min_split_seg = level === 0
787
+ ? Math.max(2, Math.floor(N_seg * 0.5))
788
+ : Math.max(1, Math.floor(N_seg * 0.25));
789
+ if (
790
+ !job.has_split
791
+ && lvl_cfg.split_probability > 0.0
792
+ && s >= min_split_seg
793
+ && s <= Math.floor(N_seg * 0.75)
794
+ && rng() < lvl_cfg.split_probability
795
+ ) {
796
+ job.has_split = true;
797
+ const fork_count = Math.max(2, lvl_cfg.split_count | 0);
798
+ const split_ang = lvl_cfg.split_angle;
799
+ const base_az = rng() * PI2;
800
+
801
+ // Preserve pre-split orthonormal triad (T, R, S) before deflecting fork 0
802
+ const pre_tx = next_tx, pre_ty = next_ty, pre_tz = next_tz;
803
+ const pre_rx = next_rx, pre_ry = next_ry, pre_rz = next_rz;
804
+ const pre_sx = cur_sx, pre_sy = cur_sy, pre_sz = cur_sz;
805
+
806
+ // Current branch deflects as fork 0: rotate both T and R around bend_axis so R remains orthogonal to T
807
+ const fork0_deflect = level === 0 ? split_ang * 0.22 : split_ang * 0.5;
808
+ const bend_axis_x = Math.cos(base_az) * pre_rx + Math.sin(base_az) * pre_sx;
809
+ const bend_axis_y = Math.cos(base_az) * pre_ry + Math.sin(base_az) * pre_sy;
810
+ const bend_axis_z = Math.cos(base_az) * pre_rz + Math.sin(base_az) * pre_sz;
811
+ rotate_around_axis(
812
+ rot_scratch,
813
+ pre_tx, pre_ty, pre_tz,
814
+ bend_axis_x, bend_axis_y, bend_axis_z,
815
+ fork0_deflect
816
+ );
817
+ next_tx = rot_scratch[0];
818
+ next_ty = rot_scratch[1];
819
+ next_tz = rot_scratch[2];
820
+
821
+ rotate_around_axis(
822
+ rot_scratch,
823
+ pre_rx, pre_ry, pre_rz,
824
+ bend_axis_x, bend_axis_y, bend_axis_z,
825
+ fork0_deflect
826
+ );
827
+ next_rx = rot_scratch[0];
828
+ next_ry = rot_scratch[1];
829
+ next_rz = rot_scratch[2];
830
+
831
+ cur_sx = next_ty * next_rz - next_tz * next_ry;
832
+ cur_sy = next_tz * next_rx - next_tx * next_rz;
833
+ cur_sz = next_tx * next_ry - next_ty * next_rx;
834
+
835
+ // Spawn fork 1..fork_count-1 as continuation branches with their own root vertex connected to curr_v
836
+ const rem_segs = Math.max(1, N_seg - s);
837
+ const rem_fraction = Math.max(0.15, rem_segs / Math.max(1, N_seg));
838
+ for (let f = 1; f < fork_count; f++) {
839
+ const f_az = base_az + (f * PI2) / fork_count;
840
+ const fax = Math.cos(f_az) * pre_rx + Math.sin(f_az) * pre_sx;
841
+ const fay = Math.cos(f_az) * pre_ry + Math.sin(f_az) * pre_sy;
842
+ const faz = Math.cos(f_az) * pre_rz + Math.sin(f_az) * pre_sz;
843
+
844
+ const f_tan = [0, 0, 0];
845
+ rotate_around_axis(f_tan, pre_tx, pre_ty, pre_tz, fax, fay, faz, split_ang * 0.65);
846
+
847
+ const f_r = [0, 0, 0];
848
+ rotate_around_axis(f_r, pre_rx, pre_ry, pre_rz, fax, fay, faz, split_ang * 0.65);
849
+
850
+ queue.push({
851
+ level,
852
+ parent_branch_id: branch_id,
853
+ parent_edge_id: edge_id,
854
+ parent_attach_t: t_curr,
855
+ attach_parent_vertex: curr_v,
856
+ start_vertex: -1,
857
+ px: next_px,
858
+ py: next_py,
859
+ pz: next_pz,
860
+ tx: f_tan[0],
861
+ ty: f_tan[1],
862
+ tz: f_tan[2],
863
+ rx: f_r[0],
864
+ ry: f_r[1],
865
+ rz: f_r[2],
866
+ length: Math.max(0.15, job.length * rem_fraction),
867
+ base_radius: r_curr * 0.78,
868
+ start_seg: 0,
869
+ total_segs: rem_segs,
870
+ initial_arc_s: cur_arc_s,
871
+ has_split: true,
872
+ child_span_fraction: rem_fraction
873
+ });
874
+ }
875
+ }
876
+
877
+ cur_px = next_px;
878
+ cur_py = next_py;
879
+ cur_pz = next_pz;
880
+ cur_tx = next_tx;
881
+ cur_ty = next_ty;
882
+ cur_tz = next_tz;
883
+ cur_rx = next_rx;
884
+ cur_ry = next_ry;
885
+ cur_rz = next_rz;
886
+ prev_v = curr_v;
887
+ }
888
+
889
+ // Smooth or preserve crisp interior Hermite tangents across consecutive edges of this branch
890
+ // (preserving branch_edges[0]'s m0 so continuation forks keep their exact fork departure tangent)
891
+ if (branch_edges.length >= 2) {
892
+ const kink_sharp = clamp(lvl_cfg.kink_sharpness ?? 0.5, 0.0, 0.92);
893
+ for (let k = 1; k < branch_edges.length; k++) {
894
+ const e_prev = branch_edges[k - 1];
895
+ const e_next = branch_edges[k];
896
+ const v_mid = branch_vertices[k];
897
+ const l_prev = Math.max(1e-5, out.segment_length(e_prev));
898
+ const l_next = Math.max(1e-5, out.segment_length(e_next));
899
+
900
+ out.segment_read_m1(SCRATCH_M_PREV, 0, e_prev);
901
+ out.segment_read_m0(SCRATCH_M_NEXT, 0, e_next);
902
+
903
+ const t_in_x = SCRATCH_M_PREV[0] / l_prev;
904
+ const t_in_y = SCRATCH_M_PREV[1] / l_prev;
905
+ const t_in_z = SCRATCH_M_PREV[2] / l_prev;
906
+ const t_out_x = SCRATCH_M_NEXT[0] / l_next;
907
+ const t_out_y = SCRATCH_M_NEXT[1] / l_next;
908
+ const t_out_z = SCRATCH_M_NEXT[2] / l_next;
909
+
910
+ let tx = t_in_x + t_out_x;
911
+ let ty = t_in_y + t_out_y;
912
+ let tz = t_in_z + t_out_z;
913
+ const t_len = Math.sqrt(tx * tx + ty * ty + tz * tz);
914
+ if (t_len > 1e-12) {
915
+ tx /= t_len;
916
+ ty /= t_len;
917
+ tz /= t_len;
918
+
919
+ out.node_write_tangent(v_mid, tx, ty, tz);
920
+
921
+ // Blend between smooth C1 average tangent and crisp sympodial kink tangents
922
+ let m1x = lerp(tx, t_in_x, kink_sharp);
923
+ let m1y = lerp(ty, t_in_y, kink_sharp);
924
+ let m1z = lerp(tz, t_in_z, kink_sharp);
925
+ const m1_l = Math.sqrt(m1x * m1x + m1y * m1y + m1z * m1z);
926
+ if (m1_l > 1e-12) { m1x /= m1_l; m1y /= m1_l; m1z /= m1_l; }
927
+
928
+ let m0x = lerp(tx, t_out_x, kink_sharp);
929
+ let m0y = lerp(ty, t_out_y, kink_sharp);
930
+ let m0z = lerp(tz, t_out_z, kink_sharp);
931
+ const m0_l = Math.sqrt(m0x * m0x + m0y * m0y + m0z * m0z);
932
+ if (m0_l > 1e-12) { m0x /= m0_l; m0y /= m0_l; m0z /= m0_l; }
933
+
934
+ out.segment_write_m1(e_prev, m1x * l_prev, m1y * l_prev, m1z * l_prev);
935
+ out.segment_write_m0(e_next, m0x * l_next, m0y * l_next, m0z * l_next);
936
+ }
937
+ }
938
+ }
939
+
940
+ // Spawn lateral child branches at level + 1 using pre-scheduled attachments
941
+ const next_level = level + 1;
942
+ if (scheduled_children.length > 0 && branch_edges.length > 0) {
943
+ const next_cfg = species.levels[next_level];
944
+ const t_max = branch_edge_t1[branch_edge_t1.length - 1];
945
+
946
+ for (let c = 0; c < scheduled_children.length; c++) {
947
+ const sc = scheduled_children[c];
948
+ const t_attach = clamp(sc.t_attach, branch_edge_t0[0], t_max * 0.985);
949
+ const phi = sc.phi;
950
+
951
+ // Find parent edge containing t_attach
952
+ let seg_idx = 0;
953
+ for (let i = 0; i < branch_edges.length; i++) {
954
+ if (t_attach >= branch_edge_t0[i] && t_attach <= branch_edge_t1[i]) {
955
+ seg_idx = i;
956
+ break;
957
+ }
958
+ }
959
+
960
+ const parent_e = branch_edges[seg_idx];
961
+ const t0_e = branch_edge_t0[seg_idx];
962
+ const t1_e = branch_edge_t1[seg_idx];
963
+ const local_u = clamp((t_attach - t0_e) / Math.max(1e-5, t1_e - t0_e), 0.0, 1.0);
964
+
965
+ out.evaluate_segment_position(SCRATCH_POS, 0, parent_e, local_u);
966
+ out.evaluate_segment_tangent(SCRATCH_TAN, 0, parent_e, local_u);
967
+
968
+ const p_v0 = branch_vertices[seg_idx];
969
+ const p_v1 = branch_vertices[seg_idx + 1];
970
+ const r_p0 = out.node_read_radius(p_v0);
971
+ const r_p1 = out.node_read_radius(p_v1);
972
+ const parent_r_at_attach = lerp(r_p0, r_p1, local_u);
973
+
974
+ // Transport parent RMF R vector from p_v0 along parent_e to SCRATCH_POS at local_u
975
+ out.node_read_position(SCRATCH_P0, 0, p_v0);
976
+ out.node_read_tangent(SCRATCH_T0, 0, p_v0);
977
+ out.node_read_frame_r(SCRATCH_R0, 0, p_v0);
978
+ const p_rx = SCRATCH_R0[0];
979
+ const p_ry = SCRATCH_R0[1];
980
+ const p_rz = SCRATCH_R0[2];
981
+
982
+ spine_frame_double_reflection(
983
+ SCRATCH_RMF, 0,
984
+ SCRATCH_P0[0], SCRATCH_P0[1], SCRATCH_P0[2],
985
+ SCRATCH_T0[0], SCRATCH_T0[1], SCRATCH_T0[2],
986
+ p_rx, p_ry, p_rz,
987
+ SCRATCH_POS[0], SCRATCH_POS[1], SCRATCH_POS[2],
988
+ SCRATCH_TAN[0], SCRATCH_TAN[1], SCRATCH_TAN[2]
989
+ );
990
+ const prx = SCRATCH_RMF[0], pry = SCRATCH_RMF[1], prz = SCRATCH_RMF[2];
991
+ const psx = SCRATCH_RMF[3], psy = SCRATCH_RMF[4], psz = SCRATCH_RMF[5];
992
+
993
+ // Radial direction D_rad in parent cross-section plane
994
+ const cos_p = Math.cos(phi);
995
+ const sin_p = Math.sin(phi);
996
+ let drx = cos_p * prx + sin_p * psx;
997
+ let dry = cos_p * pry + sin_p * psy;
998
+ let drz = cos_p * prz + sin_p * psz;
999
+
1000
+ // Plagiotropic flattening on lateral branches (non-weeping): prevent child shoots
1001
+ // from pointing straight downward from the underside of boughs into the lower trunk
1002
+ if (level >= 1 && crown.envelope_type !== CrownEnvelopeType.Weeping) {
1003
+ dry = dry < 0.0 ? dry * 0.15 + 0.08 : dry * 0.75 + 0.12;
1004
+ const dot_tp = drx * SCRATCH_TAN[0] + dry * SCRATCH_TAN[1] + drz * SCRATCH_TAN[2];
1005
+ drx -= dot_tp * SCRATCH_TAN[0];
1006
+ dry -= dot_tp * SCRATCH_TAN[1];
1007
+ drz -= dot_tp * SCRATCH_TAN[2];
1008
+ const d_len = Math.sqrt(drx * drx + dry * dry + drz * drz);
1009
+ if (d_len > 1e-6) {
1010
+ drx /= d_len;
1011
+ dry /= d_len;
1012
+ drz /= d_len;
1013
+ } else {
1014
+ drx = cos_p * prx + sin_p * psx;
1015
+ dry = cos_p * pry + sin_p * psy;
1016
+ drz = cos_p * prz + sin_p * psz;
1017
+ }
1018
+ }
1019
+
1020
+ // Insertion polar angle alpha from parent tangent T_p toward D_rad
1021
+ const alpha_scale = lvl_cfg.insertion_angle_profile.evaluate(t_attach);
1022
+ const alpha = clamp(
1023
+ lvl_cfg.insertion_angle * alpha_scale + (rng() * 2.0 - 1.0) * lvl_cfg.insertion_angle_variation,
1024
+ 0.1,
1025
+ Math.PI * 0.85
1026
+ );
1027
+
1028
+ const cos_a = Math.cos(alpha);
1029
+ const sin_a = Math.sin(alpha);
1030
+ let ctx = cos_a * SCRATCH_TAN[0] + sin_a * drx;
1031
+ let cty = cos_a * SCRATCH_TAN[1] + sin_a * dry;
1032
+ let ctz = cos_a * SCRATCH_TAN[2] + sin_a * drz;
1033
+ const ct_len = Math.sqrt(ctx * ctx + cty * cty + ctz * ctz);
1034
+ if (ct_len > 1e-12) {
1035
+ ctx /= ct_len;
1036
+ cty /= ct_len;
1037
+ ctz /= ct_len;
1038
+ }
1039
+
1040
+ // Initial RMF radial vector for child branch:
1041
+ // R_c = cos(alpha) * D_rad - sin(alpha) * T_p is exact unit length and orthogonal
1042
+ // to T_c = cos(alpha) * T_p + sin(alpha) * D_rad for all alpha in [0, pi],
1043
+ // avoiding any singularity at alpha = pi/2.
1044
+ let crx = cos_a * drx - sin_a * SCRATCH_TAN[0];
1045
+ let cry = cos_a * dry - sin_a * SCRATCH_TAN[1];
1046
+ let crz = cos_a * drz - sin_a * SCRATCH_TAN[2];
1047
+ const cr_dot = crx * ctx + cry * cty + crz * ctz;
1048
+ crx -= cr_dot * ctx;
1049
+ cry -= cr_dot * cty;
1050
+ crz -= cr_dot * ctz;
1051
+ const cr_len = Math.sqrt(crx * crx + cry * cry + crz * crz);
1052
+ if (cr_len > 1e-12) {
1053
+ crx /= cr_len;
1054
+ cry /= cr_len;
1055
+ crz /= cr_len;
1056
+ }
1057
+
1058
+ // Compute child branch length modulated by length_profile and crown envelope at mid-span height
1059
+ const est_mid_y = SCRATCH_POS[1] + Math.max(0.0, cty) * next_cfg.length * 0.5;
1060
+ const h_norm = clamp((est_mid_y - crown.base_height) / Math.max(0.1, crown.height), 0.0, 1.0);
1061
+ const env_scale = level === 0
1062
+ ? crown_envelope_evaluate(crown.envelope_type, h_norm)
1063
+ : lerp(1.0, crown_envelope_evaluate(crown.envelope_type, h_norm), 0.5);
1064
+ const prof_scale = Math.max(0.1, next_cfg.length_profile.evaluate(t_attach));
1065
+ const var_scale = 1.0 + (rng() * 2.0 - 1.0) * next_cfg.length_variance;
1066
+ const child_len = Math.max(0.15, next_cfg.length * prof_scale * env_scale * var_scale);
1067
+
1068
+ const child_base_r = Math.min(
1069
+ parent_r_at_attach * 0.85,
1070
+ Math.max(0.003, child_len * next_cfg.radius_ratio)
1071
+ );
1072
+
1073
+ queue.push({
1074
+ level: next_level,
1075
+ parent_branch_id: branch_id,
1076
+ parent_edge_id: parent_e,
1077
+ parent_attach_t: t_attach,
1078
+ attach_parent_vertex: p_v0,
1079
+ start_vertex: -1,
1080
+ px: SCRATCH_POS[0],
1081
+ py: SCRATCH_POS[1],
1082
+ pz: SCRATCH_POS[2],
1083
+ tx: ctx,
1084
+ ty: cty,
1085
+ tz: ctz,
1086
+ rx: crx,
1087
+ ry: cry,
1088
+ rz: crz,
1089
+ length: child_len,
1090
+ base_radius: child_base_r,
1091
+ start_seg: 0,
1092
+ total_segs: Math.max(1, next_cfg.segments),
1093
+ initial_arc_s: 0.0,
1094
+ has_split: false
1095
+ });
1096
+ }
1097
+ }
1098
+ }
1099
+
1100
+ // Bottom-up Pipe Model radius calculation & blending
1101
+ tree_skeleton_compute_pipe_radii(out, species);
1102
+
1103
+ return out;
1104
+ }