@woosh/meep-engine 3.53.0 → 3.55.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.
- package/package.json +1 -1
- package/src/core/math/noise/worley_noise_2d.d.ts +14 -0
- package/src/core/math/noise/worley_noise_2d.d.ts.map +1 -0
- package/src/core/math/noise/worley_noise_2d.js +74 -0
- package/src/engine/graphics/texture/atlas/CachingTextureAtlas.d.ts +6 -1
- package/src/engine/graphics/texture/atlas/CachingTextureAtlas.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/CachingTextureAtlas.js +363 -325
- package/src/engine/graphics/texture/atlas/ManagedTextureAtlas.d.ts +93 -14
- package/src/engine/graphics/texture/atlas/ManagedTextureAtlas.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/ManagedTextureAtlas.js +322 -183
- package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.d.ts +9 -0
- package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.js +110 -83
- package/src/engine/graphics/texture/atlas/SpriteAtlas.d.ts +42 -0
- package/src/engine/graphics/texture/atlas/SpriteAtlas.d.ts.map +1 -0
- package/src/engine/{graphics3/decal/DecalAtlas.js → graphics/texture/atlas/SpriteAtlas.js} +202 -194
- package/src/engine/{graphics3/decal/DecalAtlasFill.d.ts → graphics/texture/atlas/SpriteAtlasFill.d.ts} +4 -4
- package/src/engine/graphics/texture/atlas/SpriteAtlasFill.d.ts.map +1 -0
- package/src/engine/graphics/texture/atlas/SpriteAtlasFill.js +30 -0
- package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts +7 -0
- package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/TextureAtlas.js +11 -1
- package/src/engine/{graphics3/decal/decal_atlas_border.d.ts → graphics/texture/atlas/sprite_atlas_border.d.ts} +12 -12
- package/src/engine/graphics/texture/atlas/sprite_atlas_border.d.ts.map +1 -0
- package/src/engine/{graphics3/decal/decal_atlas_border.js → graphics/texture/atlas/sprite_atlas_border.js} +69 -69
- package/src/engine/graphics3/DecalSystem.d.ts.map +1 -1
- package/src/engine/graphics3/DecalSystem.js +447 -450
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +9 -22
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
- package/src/engine/graphics3/GPUParticleEmitterSystem.js +892 -951
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/generation/foliage/FOLIAGE_DESIGN.md +2 -1
- package/src/generation/foliage/config/BarkMaterialConfig.d.ts +4 -3
- package/src/generation/foliage/config/BarkMaterialConfig.d.ts.map +1 -1
- package/src/generation/foliage/config/BarkMaterialConfig.js +6 -6
- package/src/generation/foliage/config/FoliageBarkType.d.ts +11 -0
- package/src/generation/foliage/config/FoliageBarkType.d.ts.map +1 -0
- package/src/generation/foliage/config/FoliageBarkType.js +16 -0
- package/src/generation/foliage/config/FoliageMaterialConfig.d.ts +58 -5
- package/src/generation/foliage/config/FoliageMaterialConfig.d.ts.map +1 -1
- package/src/generation/foliage/config/FoliageMaterialConfig.js +279 -181
- package/src/generation/foliage/index.d.ts +2 -1
- package/src/generation/foliage/index.js +44 -43
- package/src/generation/foliage/material/make_bark_material.d.ts.map +1 -1
- package/src/generation/foliage/material/make_bark_material.js +23 -95
- package/src/generation/foliage/material/make_foliage_material.d.ts +29 -3
- package/src/generation/foliage/material/make_foliage_material.d.ts.map +1 -1
- package/src/generation/foliage/material/make_foliage_material.js +605 -359
- package/src/generation/foliage/mesh/branch_collar_project.d.ts.map +1 -1
- package/src/generation/foliage/mesh/branch_collar_project.js +205 -203
- package/src/generation/foliage/mesh/foliage_collect_attachments.d.ts +15 -0
- package/src/generation/foliage/mesh/foliage_collect_attachments.d.ts.map +1 -1
- package/src/generation/foliage/mesh/foliage_collect_attachments.js +569 -502
- package/src/generation/foliage/mesh/tree_mesh_build_bark.d.ts.map +1 -1
- package/src/generation/foliage/mesh/tree_mesh_build_bark.js +1049 -1047
- package/src/generation/foliage/mesh/tree_mesh_build_foliage.d.ts +2 -1
- package/src/generation/foliage/mesh/tree_mesh_build_foliage.d.ts.map +1 -1
- package/src/generation/foliage/mesh/tree_mesh_build_foliage.js +454 -415
- package/src/generation/foliage/presets/make_tree_species_preset.d.ts.map +1 -1
- package/src/generation/foliage/presets/make_tree_species_preset.js +870 -837
- package/src/generation/foliage/skeleton/crown_envelope_evaluate.js +121 -121
- package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.d.ts +10 -7
- package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.d.ts.map +1 -1
- package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.js +206 -179
- package/src/generation/foliage/skeleton/tree_skeleton_grow.d.ts.map +1 -1
- package/src/generation/foliage/skeleton/tree_skeleton_grow.js +1871 -1104
- package/src/shade/RENDERER_CONTRACT.md +5 -2
- package/src/shade/device/SHADE_DEVICE_FLOOR.d.ts.map +1 -1
- package/src/shade/device/SHADE_DEVICE_FLOOR.js +2 -0
- package/src/shade/playground/foliage_wind/foliage_world.d.ts +6 -0
- package/src/shade/playground/foliage_wind/foliage_world.d.ts.map +1 -1
- package/src/shade/playground/foliage_wind/foliage_world.js +602 -593
- package/src/shade/playground/foliage_wind/index.html +260 -254
- package/src/shade/playground/foliage_wind/main.js +275 -263
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +1 -1
- package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
- package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
- package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
- package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
- package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
- package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +1 -1
- package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +3 -3
- package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
- package/src/shade/renderer/hiz/HierarchicalZBuffer.js +350 -294
- package/src/shade/renderer/hiz/generate_downsampler_code.d.ts +0 -2
- package/src/shade/renderer/hiz/generate_downsampler_code.d.ts.map +1 -1
- package/src/shade/renderer/hiz/generate_downsampler_code.js +99 -100
- package/src/shade/renderer/hiz/hzb_pick_alignment.d.ts +15 -0
- package/src/shade/renderer/hiz/hzb_pick_alignment.d.ts.map +1 -0
- package/src/shade/renderer/hiz/hzb_pick_alignment.js +148 -0
- package/src/shade/renderer/hiz/hzb_plan_reduction_steps.d.ts +28 -0
- package/src/shade/renderer/hiz/hzb_plan_reduction_steps.d.ts.map +1 -0
- package/src/shade/renderer/hiz/hzb_plan_reduction_steps.js +58 -0
- package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +5 -5
- package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
- package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +57 -67
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +3 -3
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +1 -1
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.js +111 -112
- package/src/shade/renderer/hiz/shader_hzb_spd.d.ts +5 -0
- package/src/shade/renderer/hiz/shader_hzb_spd.d.ts.map +1 -0
- package/src/shade/renderer/hiz/shader_hzb_spd.js +160 -0
- package/src/shade/renderer/light/GPULightCollection.d.ts +8 -0
- package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
- package/src/shade/renderer/light/GPULightCollection.js +67 -0
- package/src/shade/renderer/light/environment/FAR_FIELD_IRRADIANCE_SH_RESOURCE.d.ts +7 -0
- package/src/shade/renderer/light/environment/FAR_FIELD_IRRADIANCE_SH_RESOURCE.d.ts.map +1 -0
- package/src/shade/renderer/light/environment/FAR_FIELD_IRRADIANCE_SH_RESOURCE.js +13 -0
- package/src/shade/renderer/light/environment/shader_project_far_field_irradiance_sh.d.ts +10 -0
- package/src/shade/renderer/light/environment/shader_project_far_field_irradiance_sh.d.ts.map +1 -0
- package/src/shade/renderer/light/environment/shader_project_far_field_irradiance_sh.js +72 -0
- package/src/shade/renderer/light/world_cluster/WORLD_LIGHT_CLUSTER.d.ts +39 -0
- package/src/shade/renderer/light/world_cluster/WORLD_LIGHT_CLUSTER.d.ts.map +1 -0
- package/src/shade/renderer/light/world_cluster/WORLD_LIGHT_CLUSTER.js +90 -0
- package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.d.ts +12 -0
- package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.d.ts.map +1 -0
- package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.js +56 -0
- package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.d.ts +31 -0
- package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.d.ts.map +1 -0
- package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.js +196 -0
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.d.ts +10 -0
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.d.ts.map +1 -0
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.js +163 -0
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_cull_lights.d.ts +4 -0
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_cull_lights.d.ts.map +1 -0
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_cull_lights.js +137 -0
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.js +4 -3
- package/src/shade/renderer/particles/graph_particles_avboit.d.ts +4 -3
- package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph_particles_avboit.js +28 -11
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +9 -13
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +25 -20
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.js +21 -38
- package/src/shade/renderer/postprocess/denoise/fragment_shader_denoise_ao.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/denoise/fragment_shader_denoise_ao.js +154 -147
- package/src/shade/renderer/postprocess/gtao/GTAO.d.ts +2 -1
- package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/GTAO.js +5 -1
- package/src/shade/renderer/postprocess/gtao/GTAO_CONSTANTS.d.ts +8 -0
- package/src/shade/renderer/postprocess/gtao/GTAO_CONSTANTS.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/GTAO_CONSTANTS.js +10 -1
- package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.js +5 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_compute_mip_level.d.ts +22 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_compute_mip_level.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_compute_mip_level.js +33 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_resolve_hzb_depth.d.ts +22 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_resolve_hzb_depth.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_resolve_hzb_depth.js +56 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_sample_depth.d.ts +13 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_sample_depth.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_sample_depth.js +42 -0
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts +2 -1
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.js +382 -357
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.js +414 -410
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -7
- package/src/shade/renderer/shader/chunk/depth/chunk_query_depth_from_screen_space_bb.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/depth/chunk_query_depth_from_screen_space_bb.js +62 -62
- package/src/shade/renderer/shader/chunk/random/lowbias/chunk_hash_lowbias32_vec2u.d.ts +6 -0
- package/src/shade/renderer/shader/chunk/random/lowbias/chunk_hash_lowbias32_vec2u.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/random/lowbias/chunk_hash_lowbias32_vec2u.js +19 -0
- package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_get_mip_resolution.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_get_mip_resolution.js +16 -10
- package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_hierarchical_raymarch.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_hierarchical_raymarch.js +118 -117
- package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.d.ts +41 -7
- package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.d.ts.map +1 -1
- package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.js +162 -58
- package/src/shade/renderer/volumetrics/GPUSceneVolumetrics.js +1 -1
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts +10 -12
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +583 -606
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +1 -1
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +640 -640
- package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts +113 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.js +484 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP.d.ts +50 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP.js +60 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP_STRUCT.d.ts +25 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP_STRUCT.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP_STRUCT.js +47 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IrradianceCascadePlacement.d.ts +74 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IrradianceCascadePlacement.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IrradianceCascadePlacement.js +173 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts +26 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.js +83 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.d.ts +36 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.js +129 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.d.ts +121 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.js +370 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.d.ts +16 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.js +191 -0
- package/src/shade/renderer/wind/GPUSceneWind.d.ts +1 -1
- package/src/shade/renderer/wind/GPUSceneWind.js +1 -1
- package/src/engine/graphics3/decal/DecalAtlas.d.ts +0 -43
- package/src/engine/graphics3/decal/DecalAtlas.d.ts.map +0 -1
- package/src/engine/graphics3/decal/DecalAtlasFill.d.ts.map +0 -1
- package/src/engine/graphics3/decal/DecalAtlasFill.js +0 -32
- package/src/engine/graphics3/decal/decal_atlas_border.d.ts.map +0 -1
- package/src/shade/renderer/postprocess/gtao/graph_build_depth_mipmap.d.ts +0 -26
- package/src/shade/renderer/postprocess/gtao/graph_build_depth_mipmap.d.ts.map +0 -1
- package/src/shade/renderer/postprocess/gtao/graph_build_depth_mipmap.js +0 -268
- package/src/shade/renderer/postprocess/ssr/copy_shader_f32.d.ts +0 -3
- package/src/shade/renderer/postprocess/ssr/copy_shader_f32.d.ts.map +0 -1
- package/src/shade/renderer/postprocess/ssr/copy_shader_f32.js +0 -32
- package/src/shade/renderer/postprocess/ssr/depth_max_shader.d.ts +0 -17
- package/src/shade/renderer/postprocess/ssr/depth_max_shader.d.ts.map +0 -1
- package/src/shade/renderer/postprocess/ssr/depth_max_shader.js +0 -98
- package/src/shade/renderer/postprocess/ssr/graph_build_depth_max_pyramid.d.ts +0 -23
- package/src/shade/renderer/postprocess/ssr/graph_build_depth_max_pyramid.d.ts.map +0 -1
- package/src/shade/renderer/postprocess/ssr/graph_build_depth_max_pyramid.js +0 -118
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.d.ts +0 -15
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.js +0 -49
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts +0 -17
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.js +0 -50
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.d.ts +0 -35
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.js +0 -105
- package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts +0 -35
- package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.js +0 -80
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi.d.ts +0 -10
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi.js +0 -51
- package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.d.ts +0 -28
- package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.js +0 -39
- package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts +0 -48
- package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.js +0 -121
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts +0 -28
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.js +0 -237
|
@@ -1,1104 +1,1871 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* @param {
|
|
39
|
-
* @param {number}
|
|
40
|
-
* @param {number}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
out_pos[
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
out_tan[
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
dst[
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
dst[
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
let
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
out_tan[
|
|
92
|
-
|
|
93
|
-
out_tan[
|
|
94
|
-
|
|
95
|
-
out_tan[
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
*
|
|
103
|
-
* @param {number}
|
|
104
|
-
* @param {number}
|
|
105
|
-
* @param {number}
|
|
106
|
-
* @param {number}
|
|
107
|
-
* @param {number}
|
|
108
|
-
* @param {number}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
let
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
out[
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
* -
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* @param {
|
|
147
|
-
* @param {
|
|
148
|
-
* @
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
//
|
|
686
|
-
if (
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
)
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
)
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
const
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
const
|
|
1065
|
-
const
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1
|
+
import { BVH } from "../../../core/bvh2/bvh3/BVH.js";
|
|
2
|
+
import { bvh_query_user_data_intersects_capsule } from "../../../core/bvh2/bvh3/query/bvh_query_user_data_intersects_capsule.js";
|
|
3
|
+
import { PointHashGrid3 } from "../../../core/geom/3d/hash-grid/PointHashGrid3.js";
|
|
4
|
+
import { line3_compute_segment_segment_closest_points } from "../../../core/geom/3d/line/line3_compute_segment_segment_closest_points.js";
|
|
5
|
+
import { GOLDEN_RATIO } from "../../../core/math/GOLDEN_RATIO.js";
|
|
6
|
+
import { PI2 } from "../../../core/math/PI2.js";
|
|
7
|
+
import { clamp } from "../../../core/math/clamp.js";
|
|
8
|
+
import { lerp } from "../../../core/math/lerp.js";
|
|
9
|
+
import { curl_noise_3d } from "../../../core/math/noise/curl_noise_3d.js";
|
|
10
|
+
import { seededRandom } from "../../../core/math/random/seededRandom.js";
|
|
11
|
+
import { PhyllotaxisMode } from "../config/PhyllotaxisMode.js";
|
|
12
|
+
import { TreeSpeciesConfig } from "../config/TreeSpeciesConfig.js";
|
|
13
|
+
import { TreeSkeleton } from "./TreeSkeleton.js";
|
|
14
|
+
import { crown_envelope_evaluate, crown_envelope_sdf } from "./crown_envelope_evaluate.js";
|
|
15
|
+
import { spine_frame_double_reflection } from "./spine_frame_double_reflection.js";
|
|
16
|
+
import { tree_skeleton_compute_pipe_radii } from "./tree_skeleton_compute_pipe_radii.js";
|
|
17
|
+
|
|
18
|
+
const GOLDEN_ANGLE = PI2 / (GOLDEN_RATIO * GOLDEN_RATIO); // ~2.399963229728653 rad (137.50776 deg)
|
|
19
|
+
|
|
20
|
+
const SCRATCH_CURL = [0, 0, 0];
|
|
21
|
+
const SCRATCH_RMF = new Float64Array(6);
|
|
22
|
+
const SCRATCH_POS = new Float32Array(3);
|
|
23
|
+
const SCRATCH_TAN = new Float32Array(3);
|
|
24
|
+
const SCRATCH_P0 = new Float32Array(3);
|
|
25
|
+
const SCRATCH_T0 = new Float32Array(3);
|
|
26
|
+
const SCRATCH_R0 = new Float32Array(3);
|
|
27
|
+
const SCRATCH_M_PREV = new Float32Array(3);
|
|
28
|
+
const SCRATCH_M_NEXT = new Float32Array(3);
|
|
29
|
+
const SCRATCH_GUIDE_P0 = new Float32Array(3);
|
|
30
|
+
const SCRATCH_GUIDE_P1 = new Float32Array(3);
|
|
31
|
+
const SCRATCH_GUIDE_T = new Float32Array(3);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Evaluates the trunk guide curve position `out_pos` and unit tangent `out_tan` at normalized
|
|
35
|
+
* parameter `t_norm` in [0, 1]. Defaults to the vertical upward plumb line `(0, t_norm * trunk_len, 0)`
|
|
36
|
+
* when `trunk_guide` is null or omitted.
|
|
37
|
+
*
|
|
38
|
+
* @param {Float32Array|number[]} out_pos
|
|
39
|
+
* @param {Float32Array|number[]} out_tan
|
|
40
|
+
* @param {Array<ArrayLike<number>>|Function|null|undefined} trunk_guide
|
|
41
|
+
* @param {number} t_norm
|
|
42
|
+
* @param {number} trunk_len
|
|
43
|
+
*/
|
|
44
|
+
function evaluate_trunk_guide(out_pos, out_tan, trunk_guide, t_norm, trunk_len) {
|
|
45
|
+
const t = clamp(t_norm, 0.0, 1.0);
|
|
46
|
+
if (!trunk_guide) {
|
|
47
|
+
out_pos[0] = 0.0;
|
|
48
|
+
out_pos[1] = t * trunk_len;
|
|
49
|
+
out_pos[2] = 0.0;
|
|
50
|
+
out_tan[0] = 0.0;
|
|
51
|
+
out_tan[1] = 1.0;
|
|
52
|
+
out_tan[2] = 0.0;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const sample_pt = (u, dst) => {
|
|
57
|
+
if (typeof trunk_guide === "function") {
|
|
58
|
+
trunk_guide(u, dst);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(trunk_guide) && trunk_guide.length >= 2) {
|
|
62
|
+
const n = trunk_guide.length - 1;
|
|
63
|
+
const su = clamp(u, 0.0, 1.0) * n;
|
|
64
|
+
const idx = Math.min(n - 1, Math.floor(su));
|
|
65
|
+
const frac = su - idx;
|
|
66
|
+
const a = trunk_guide[idx];
|
|
67
|
+
const b = trunk_guide[idx + 1];
|
|
68
|
+
dst[0] = lerp(a[0] ?? 0.0, b[0] ?? 0.0, frac);
|
|
69
|
+
dst[1] = lerp(a[1] ?? 0.0, b[1] ?? 0.0, frac);
|
|
70
|
+
dst[2] = lerp(a[2] ?? 0.0, b[2] ?? 0.0, frac);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
dst[0] = 0.0;
|
|
74
|
+
dst[1] = u * trunk_len;
|
|
75
|
+
dst[2] = 0.0;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
sample_pt(t, out_pos);
|
|
79
|
+
const eps = 0.01;
|
|
80
|
+
const t_a = Math.max(0.0, t - eps);
|
|
81
|
+
const t_b = Math.min(1.0, t + eps);
|
|
82
|
+
const pa = [0, 0, 0];
|
|
83
|
+
const pb = [0, 0, 0];
|
|
84
|
+
sample_pt(t_a, pa);
|
|
85
|
+
sample_pt(t_b, pb);
|
|
86
|
+
let tx = pb[0] - pa[0];
|
|
87
|
+
let ty = pb[1] - pa[1];
|
|
88
|
+
let tz = pb[2] - pa[2];
|
|
89
|
+
const tl = Math.hypot(tx, ty, tz);
|
|
90
|
+
if (tl > 1e-8) {
|
|
91
|
+
out_tan[0] = tx / tl;
|
|
92
|
+
out_tan[1] = ty / tl;
|
|
93
|
+
out_tan[2] = tz / tl;
|
|
94
|
+
} else {
|
|
95
|
+
out_tan[0] = 0.0;
|
|
96
|
+
out_tan[1] = 1.0;
|
|
97
|
+
out_tan[2] = 0.0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Rotates unit vector `(vx, vy, vz)` around unit axis `(ax, ay, az)` by `angle` radians (Rodrigues' formula).
|
|
103
|
+
* @param {number[]|Float64Array} out
|
|
104
|
+
* @param {number} vx
|
|
105
|
+
* @param {number} vy
|
|
106
|
+
* @param {number} vz
|
|
107
|
+
* @param {number} ax
|
|
108
|
+
* @param {number} ay
|
|
109
|
+
* @param {number} az
|
|
110
|
+
* @param {number} angle
|
|
111
|
+
*/
|
|
112
|
+
function rotate_around_axis(out, vx, vy, vz, ax, ay, az, angle) {
|
|
113
|
+
const c = Math.cos(angle);
|
|
114
|
+
const s = Math.sin(angle);
|
|
115
|
+
const dot = vx * ax + vy * ay + vz * az;
|
|
116
|
+
const omc = 1.0 - c;
|
|
117
|
+
|
|
118
|
+
const cx = ay * vz - az * vy;
|
|
119
|
+
const cy = az * vx - ax * vz;
|
|
120
|
+
const cz = ax * vy - ay * vx;
|
|
121
|
+
|
|
122
|
+
let rx = vx * c + cx * s + ax * dot * omc;
|
|
123
|
+
let ry = vy * c + cy * s + ay * dot * omc;
|
|
124
|
+
let rz = vz * c + cz * s + az * dot * omc;
|
|
125
|
+
|
|
126
|
+
const len = Math.sqrt(rx * rx + ry * ry + rz * rz);
|
|
127
|
+
if (len > 1e-12) {
|
|
128
|
+
const inv = 1.0 / len;
|
|
129
|
+
rx *= inv;
|
|
130
|
+
ry *= inv;
|
|
131
|
+
rz *= inv;
|
|
132
|
+
}
|
|
133
|
+
out[0] = rx;
|
|
134
|
+
out[1] = ry;
|
|
135
|
+
out[2] = rz;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Determines the non-exempt sub-segment parameter intervals `[s_min, 1]` on candidate segment A
|
|
140
|
+
* and `[t_min, 1]` on existing segment B for branch-branch capsule collision testing.
|
|
141
|
+
* Returns `false` if the segment pair is topologically exempt (e.g. direct parent-child base collar).
|
|
142
|
+
*
|
|
143
|
+
* @param {number} bA
|
|
144
|
+
* @param {number} parent_bA
|
|
145
|
+
* @param {number} attach_seg_A
|
|
146
|
+
* @param {boolean} is_split_A
|
|
147
|
+
* @param {number} local_idx_A
|
|
148
|
+
* @param {number} bB
|
|
149
|
+
* @param {number} parent_bB
|
|
150
|
+
* @param {number} attach_seg_B
|
|
151
|
+
* @param {boolean} is_split_B
|
|
152
|
+
* @param {number} local_idx_B
|
|
153
|
+
* @param {Float64Array|number[]} out_bounds `[s_min, t_min]`
|
|
154
|
+
* @returns {boolean}
|
|
155
|
+
*/
|
|
156
|
+
function resolve_subsegment_bounds(
|
|
157
|
+
bA, parent_bA, attach_seg_A, is_split_A, local_idx_A,
|
|
158
|
+
bB, parent_bB, attach_seg_B, is_split_B, local_idx_B,
|
|
159
|
+
out_bounds
|
|
160
|
+
) {
|
|
161
|
+
if (bA === bB) return false;
|
|
162
|
+
|
|
163
|
+
// 1. A is a direct child of B
|
|
164
|
+
if (parent_bA === bB) {
|
|
165
|
+
if (is_split_A) {
|
|
166
|
+
if (local_idx_A === 0 && local_idx_B === attach_seg_A) return false;
|
|
167
|
+
if (local_idx_A === 0 && local_idx_B === attach_seg_A + 1) {
|
|
168
|
+
out_bounds[0] = 0.55;
|
|
169
|
+
out_bounds[1] = 0.55;
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
if (local_idx_A === 1 && (local_idx_B === attach_seg_A || local_idx_B === attach_seg_A + 1)) {
|
|
173
|
+
out_bounds[0] = 0.45;
|
|
174
|
+
out_bounds[1] = 0.45;
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
if (local_idx_A === 0 && Math.abs(local_idx_B - attach_seg_A) <= 1) return false;
|
|
179
|
+
if (local_idx_A === 1 && Math.abs(local_idx_B - attach_seg_A) <= 1) {
|
|
180
|
+
if (bA < 0 || local_idx_B === attach_seg_A) return false;
|
|
181
|
+
out_bounds[0] = 0.35;
|
|
182
|
+
out_bounds[1] = 0.0;
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 2. B is a direct child of A
|
|
189
|
+
if (parent_bB === bA) {
|
|
190
|
+
if (is_split_B) {
|
|
191
|
+
if (local_idx_B === 0 && local_idx_A === attach_seg_B) return false;
|
|
192
|
+
if (local_idx_B === 0 && local_idx_A === attach_seg_B + 1) {
|
|
193
|
+
out_bounds[0] = 0.55;
|
|
194
|
+
out_bounds[1] = 0.55;
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
if (local_idx_B === 1 && (local_idx_A === attach_seg_B || local_idx_A === attach_seg_B + 1)) {
|
|
198
|
+
out_bounds[0] = 0.45;
|
|
199
|
+
out_bounds[1] = 0.45;
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
if (local_idx_B <= 1 && Math.abs(local_idx_A - attach_seg_B) <= 1) return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 3. A and B are siblings sharing the same parent branch
|
|
208
|
+
if (parent_bA >= 0 && parent_bA === parent_bB) {
|
|
209
|
+
if (local_idx_A === 0 && local_idx_B === 0) {
|
|
210
|
+
out_bounds[0] = 0.50;
|
|
211
|
+
out_bounds[1] = 0.50;
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (local_idx_A === 0 && local_idx_B === 1 && Math.abs(attach_seg_A - attach_seg_B) <= 1) {
|
|
215
|
+
out_bounds[0] = 0.25;
|
|
216
|
+
out_bounds[1] = 0.0;
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
if (local_idx_A === 1 && local_idx_B === 0 && Math.abs(attach_seg_A - attach_seg_B) <= 1) {
|
|
220
|
+
out_bounds[0] = 0.0;
|
|
221
|
+
out_bounds[1] = 0.25;
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
out_bounds[0] = 0.0;
|
|
227
|
+
out_bounds[1] = 0.0;
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Generates a directed botanical `TreeSkeleton` from a `TreeSpeciesConfig`, combining:
|
|
233
|
+
* - Multi-level parametric Cubic Hermite spine generation (`BranchLevelConfig`, `AnimationCurve` profiles)
|
|
234
|
+
* - Botanical phyllotaxis (`SpiralGolden`, `AlternateDistichous`, `Tristichous`, `Opposite`, `OppositeDecussate`, `Whorled`)
|
|
235
|
+
* - Gravitropism (toward `-Y`), phototropism (toward `+Y` and outward crown exterior), and 3D `curl_noise_3d` gnarl
|
|
236
|
+
* - Dichotomous / sympodial trunk & bough splitting
|
|
237
|
+
* - Space colonization crown competition via `PointHashGrid3`
|
|
238
|
+
* - Rotation-Minimizing Frames (`spine_frame_double_reflection`)
|
|
239
|
+
* - Pipe Model cross-section radius accumulation (`tree_skeleton_compute_pipe_radii`)
|
|
240
|
+
*
|
|
241
|
+
* @param {TreeSpeciesConfig|Object} [config]
|
|
242
|
+
* @param {TreeSkeleton} [out]
|
|
243
|
+
* @returns {TreeSkeleton}
|
|
244
|
+
*/
|
|
245
|
+
export function tree_skeleton_grow(config = new TreeSpeciesConfig(), out = new TreeSkeleton()) {
|
|
246
|
+
const species = config && config.isTreeSpeciesConfig ? config : TreeSpeciesConfig.from(config);
|
|
247
|
+
out.clear();
|
|
248
|
+
|
|
249
|
+
const rng = seededRandom(species.seed | 0);
|
|
250
|
+
// Seed-derived 3D noise offset so unseeded Simplex/Curl fields are unique and deterministic per seed
|
|
251
|
+
const noise_ox = (rng() - 0.5) * 1000.0;
|
|
252
|
+
const noise_oy = (rng() - 0.5) * 1000.0;
|
|
253
|
+
const noise_oz = (rng() - 0.5) * 1000.0;
|
|
254
|
+
|
|
255
|
+
const crown = species.crown;
|
|
256
|
+
const crown_cy = crown.base_height + crown.height * 0.5;
|
|
257
|
+
out.crown_center[0] = 0.0;
|
|
258
|
+
out.crown_center[1] = crown_cy;
|
|
259
|
+
out.crown_center[2] = 0.0;
|
|
260
|
+
|
|
261
|
+
out.crown_radii[0] = Math.max(0.25, crown.radius);
|
|
262
|
+
out.crown_radii[1] = Math.max(0.25, crown.height * 0.5);
|
|
263
|
+
out.crown_radii[2] = Math.max(0.25, crown.radius);
|
|
264
|
+
|
|
265
|
+
// Build Space Colonization attraction point cloud + PointHashGrid3 once
|
|
266
|
+
const use_space_col = crown.space_colonization_weight > 0.0 && crown.attraction_point_count > 0;
|
|
267
|
+
const attr_count = use_space_col ? (crown.attraction_point_count | 0) : 0;
|
|
268
|
+
const attr_positions = new Float32Array(attr_count * 3);
|
|
269
|
+
const attr_alive = new Uint8Array(attr_count);
|
|
270
|
+
let attr_grid = null;
|
|
271
|
+
|
|
272
|
+
if (use_space_col) {
|
|
273
|
+
let placed = 0;
|
|
274
|
+
let attempts = 0;
|
|
275
|
+
const max_attempts = attr_count * 12;
|
|
276
|
+
while (placed < attr_count && attempts < max_attempts) {
|
|
277
|
+
attempts++;
|
|
278
|
+
const u_h = rng();
|
|
279
|
+
const env_r = crown.radius * crown_envelope_evaluate(crown.envelope_type, u_h);
|
|
280
|
+
const rx = (rng() * 2.0 - 1.0) * env_r;
|
|
281
|
+
const rz = (rng() * 2.0 - 1.0) * env_r;
|
|
282
|
+
if (rx * rx + rz * rz <= env_r * env_r) {
|
|
283
|
+
const ry = crown.base_height + u_h * crown.height;
|
|
284
|
+
attr_positions[placed * 3] = rx;
|
|
285
|
+
attr_positions[placed * 3 + 1] = ry;
|
|
286
|
+
attr_positions[placed * 3 + 2] = rz;
|
|
287
|
+
attr_alive[placed] = 1;
|
|
288
|
+
placed++;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
while (placed < attr_count) {
|
|
292
|
+
const theta = rng() * PI2;
|
|
293
|
+
const u_h = rng();
|
|
294
|
+
const r = Math.sqrt(rng()) * crown.radius * crown_envelope_evaluate(crown.envelope_type, u_h);
|
|
295
|
+
attr_positions[placed * 3] = Math.cos(theta) * r;
|
|
296
|
+
attr_positions[placed * 3 + 1] = crown.base_height + u_h * crown.height;
|
|
297
|
+
attr_positions[placed * 3 + 2] = Math.sin(theta) * r;
|
|
298
|
+
attr_alive[placed] = 1;
|
|
299
|
+
placed++;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
attr_grid = new PointHashGrid3();
|
|
303
|
+
attr_grid.build(attr_positions, attr_count, Math.max(0.25, crown.attraction_radius));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const r_influence_sq = crown.attraction_radius * crown.attraction_radius;
|
|
307
|
+
const r_kill_sq = crown.kill_radius * crown.kill_radius;
|
|
308
|
+
|
|
309
|
+
// Queue of branches to grow in level-order (guarantees parent branches & vertices precede children)
|
|
310
|
+
const queue = [];
|
|
311
|
+
|
|
312
|
+
const lvl0 = species.levels[0];
|
|
313
|
+
if (!lvl0) {
|
|
314
|
+
return out;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const trunk_len = Math.max(0.2, lvl0.length * (1.0 + (rng() * 2.0 - 1.0) * lvl0.length_variance));
|
|
318
|
+
const trunk_base_r = Math.max(0.01, trunk_len * lvl0.radius_ratio);
|
|
319
|
+
|
|
320
|
+
evaluate_trunk_guide(SCRATCH_GUIDE_P0, SCRATCH_GUIDE_T, species.trunk_guide, 0.0, trunk_len);
|
|
321
|
+
|
|
322
|
+
// Apply subtle initial basal trunk lean around the guide tangent
|
|
323
|
+
const max_lean = lvl0.phyllotaxis_mode === PhyllotaxisMode.Whorled ? 0.025 : 0.06;
|
|
324
|
+
const lean_ang = clamp((lvl0.basal_lean ?? 0.02) * (0.75 + 0.5 * rng()), 0.0, max_lean);
|
|
325
|
+
const lean_az = rng() * PI2;
|
|
326
|
+
const init_tx = clamp(SCRATCH_GUIDE_T[0] + Math.sin(lean_ang) * Math.cos(lean_az), -0.95, 0.95);
|
|
327
|
+
const init_ty = Math.max(0.2, SCRATCH_GUIDE_T[1] * Math.cos(lean_ang));
|
|
328
|
+
const init_tz = clamp(SCRATCH_GUIDE_T[2] + Math.sin(lean_ang) * Math.sin(lean_az), -0.95, 0.95);
|
|
329
|
+
const init_t_len = Math.hypot(init_tx, init_ty, init_tz) || 1.0;
|
|
330
|
+
const norm_tx = init_tx / init_t_len;
|
|
331
|
+
const norm_ty = init_ty / init_t_len;
|
|
332
|
+
const norm_tz = init_tz / init_t_len;
|
|
333
|
+
|
|
334
|
+
// Orthonormal initial R perpendicular to norm_T
|
|
335
|
+
let init_rx = 1.0 - norm_tx * norm_tx;
|
|
336
|
+
let init_ry = -norm_ty * norm_tx;
|
|
337
|
+
let init_rz = -norm_tz * norm_tx;
|
|
338
|
+
const init_r_len = Math.hypot(init_rx, init_ry, init_rz) || 1.0;
|
|
339
|
+
init_rx /= init_r_len;
|
|
340
|
+
init_ry /= init_r_len;
|
|
341
|
+
init_rz /= init_r_len;
|
|
342
|
+
|
|
343
|
+
queue.push({
|
|
344
|
+
level: 0,
|
|
345
|
+
parent_branch_id: -1,
|
|
346
|
+
parent_edge_id: -1,
|
|
347
|
+
parent_attach_seg_idx: -1,
|
|
348
|
+
parent_attach_t: 0.0,
|
|
349
|
+
start_vertex: -1,
|
|
350
|
+
px: SCRATCH_GUIDE_P0[0],
|
|
351
|
+
py: SCRATCH_GUIDE_P0[1],
|
|
352
|
+
pz: SCRATCH_GUIDE_P0[2],
|
|
353
|
+
tx: norm_tx,
|
|
354
|
+
ty: norm_ty,
|
|
355
|
+
tz: norm_tz,
|
|
356
|
+
rx: init_rx,
|
|
357
|
+
ry: init_ry,
|
|
358
|
+
rz: init_rz,
|
|
359
|
+
emerge_drx: init_rx,
|
|
360
|
+
emerge_dry: init_ry,
|
|
361
|
+
emerge_drz: init_rz,
|
|
362
|
+
length: trunk_len,
|
|
363
|
+
full_branch_length: trunk_len,
|
|
364
|
+
base_radius: trunk_base_r,
|
|
365
|
+
start_seg: 0,
|
|
366
|
+
total_segs: Math.max(1, lvl0.segments),
|
|
367
|
+
initial_arc_s: 0.0,
|
|
368
|
+
has_split: false,
|
|
369
|
+
is_split_fork: false,
|
|
370
|
+
cum_t0: 0.0,
|
|
371
|
+
cum_t1: 1.0
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const rot_scratch = [0, 0, 0];
|
|
375
|
+
|
|
376
|
+
// Scope-local BVH and per-segment metadata for branch-branch spatial avoidance & collision prevention
|
|
377
|
+
const bvh = new BVH();
|
|
378
|
+
const bvh_hits = new Uint32Array(256);
|
|
379
|
+
const query_capsule = new Float32Array(7);
|
|
380
|
+
const sub_bounds = new Float64Array(2);
|
|
381
|
+
const closest_st = new Float64Array(2);
|
|
382
|
+
|
|
383
|
+
const seg_branch_id = [];
|
|
384
|
+
const seg_local_idx = [];
|
|
385
|
+
const seg_p0x = [], seg_p0y = [], seg_p0z = [], seg_r0 = [];
|
|
386
|
+
const seg_p1x = [], seg_p1y = [], seg_p1z = [], seg_r1 = [];
|
|
387
|
+
const seg_bvh_leaf = [];
|
|
388
|
+
|
|
389
|
+
const branch_parent_seg_idx = [];
|
|
390
|
+
const branch_is_split_fork = [];
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Evaluates the minimum signed surface-to-surface clearance between a candidate segment A
|
|
394
|
+
* and all non-exempt existing segments in `bvh`.
|
|
395
|
+
*/
|
|
396
|
+
const evaluate_candidate_clearance = (
|
|
397
|
+
bA, parent_bA, attach_seg_A, is_split_A, local_idx_A,
|
|
398
|
+
a0x, a0y, a0z, rA0,
|
|
399
|
+
a1x, a1y, a1z, rA1,
|
|
400
|
+
extra_margin
|
|
401
|
+
) => {
|
|
402
|
+
if (bvh.size === 0) return 10.0;
|
|
403
|
+
const max_rA = Math.max(rA0, rA1);
|
|
404
|
+
query_capsule[0] = a0x;
|
|
405
|
+
query_capsule[1] = a0y;
|
|
406
|
+
query_capsule[2] = a0z;
|
|
407
|
+
query_capsule[3] = a1x;
|
|
408
|
+
query_capsule[4] = a1y;
|
|
409
|
+
query_capsule[5] = a1z;
|
|
410
|
+
query_capsule[6] = max_rA * 1.15 + extra_margin + 0.04;
|
|
411
|
+
|
|
412
|
+
const hit_count = bvh_query_user_data_intersects_capsule(bvh_hits, 0, bvh, query_capsule);
|
|
413
|
+
let min_clear = 10.0;
|
|
414
|
+
|
|
415
|
+
for (let h = 0; h < hit_count; h++) {
|
|
416
|
+
const eB = bvh_hits[h];
|
|
417
|
+
const bB = seg_branch_id[eB];
|
|
418
|
+
const local_idx_B = seg_local_idx[eB];
|
|
419
|
+
const parent_bB = out.branch_parent[bB];
|
|
420
|
+
const attach_seg_B = branch_parent_seg_idx[bB];
|
|
421
|
+
const is_split_B = branch_is_split_fork[bB];
|
|
422
|
+
|
|
423
|
+
if (!resolve_subsegment_bounds(
|
|
424
|
+
bA, parent_bA, attach_seg_A, is_split_A, local_idx_A,
|
|
425
|
+
bB, parent_bB, attach_seg_B, is_split_B, local_idx_B,
|
|
426
|
+
sub_bounds
|
|
427
|
+
)) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const s_min = sub_bounds[0];
|
|
432
|
+
const t_min = sub_bounds[1];
|
|
433
|
+
const sa0x = a0x + (a1x - a0x) * s_min;
|
|
434
|
+
const sa0y = a0y + (a1y - a0y) * s_min;
|
|
435
|
+
const sa0z = a0z + (a1z - a0z) * s_min;
|
|
436
|
+
|
|
437
|
+
const b0x = seg_p0x[eB], b0y = seg_p0y[eB], b0z = seg_p0z[eB];
|
|
438
|
+
const b1x = seg_p1x[eB], b1y = seg_p1y[eB], b1z = seg_p1z[eB];
|
|
439
|
+
const sb0x = b0x + (b1x - b0x) * t_min;
|
|
440
|
+
const sb0y = b0y + (b1y - b0y) * t_min;
|
|
441
|
+
const sb0z = b0z + (b1z - b0z) * t_min;
|
|
442
|
+
|
|
443
|
+
line3_compute_segment_segment_closest_points(
|
|
444
|
+
closest_st,
|
|
445
|
+
sa0x, sa0y, sa0z, a1x, a1y, a1z,
|
|
446
|
+
sb0x, sb0y, sb0z, b1x, b1y, b1z
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
const s_f = s_min + (1.0 - s_min) * closest_st[0];
|
|
450
|
+
const t_f = t_min + (1.0 - t_min) * closest_st[1];
|
|
451
|
+
|
|
452
|
+
const qAx = a0x + (a1x - a0x) * s_f;
|
|
453
|
+
const qAy = a0y + (a1y - a0y) * s_f;
|
|
454
|
+
const qAz = a0z + (a1z - a0z) * s_f;
|
|
455
|
+
const qBx = b0x + (b1x - b0x) * t_f;
|
|
456
|
+
const qBy = b0y + (b1y - b0y) * t_f;
|
|
457
|
+
const qBz = b0z + (b1z - b0z) * t_f;
|
|
458
|
+
|
|
459
|
+
const dist = Math.hypot(qAx - qBx, qAy - qBy, qAz - qBz);
|
|
460
|
+
const rA = rA0 + (rA1 - rA0) * s_f;
|
|
461
|
+
const rB = seg_r0[eB] + (seg_r1[eB] - seg_r0[eB]) * t_f;
|
|
462
|
+
const r_req = (rA + rB) * 1.08 + extra_margin;
|
|
463
|
+
const clear = dist - r_req;
|
|
464
|
+
if (clear < min_clear) {
|
|
465
|
+
min_clear = clear;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return min_clear;
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
for (let q_idx = 0; q_idx < queue.length; q_idx++) {
|
|
472
|
+
const job = queue[q_idx];
|
|
473
|
+
const level = job.level;
|
|
474
|
+
const lvl_cfg = species.levels[level];
|
|
475
|
+
if (!lvl_cfg) continue;
|
|
476
|
+
|
|
477
|
+
const phase_hash = rng();
|
|
478
|
+
const N_seg = job.total_segs;
|
|
479
|
+
const remaining_segs = N_seg - job.start_seg;
|
|
480
|
+
if (remaining_segs <= 0) continue;
|
|
481
|
+
|
|
482
|
+
const step_len = job.length / N_seg;
|
|
483
|
+
const prof_p0 = lvl_cfg.radius_profile.evaluate(0.0);
|
|
484
|
+
const prof_p1 = lvl_cfg.radius_profile.evaluate(1.0);
|
|
485
|
+
const prof_span = prof_p0 - prof_p1;
|
|
486
|
+
|
|
487
|
+
let cur_px = job.px;
|
|
488
|
+
let cur_py = job.py;
|
|
489
|
+
let cur_pz = job.pz;
|
|
490
|
+
let cur_tx = job.tx;
|
|
491
|
+
let cur_ty = job.ty;
|
|
492
|
+
let cur_tz = job.tz;
|
|
493
|
+
let cur_rx = job.rx;
|
|
494
|
+
let cur_ry = job.ry;
|
|
495
|
+
let cur_rz = job.rz;
|
|
496
|
+
let cur_arc_s = job.initial_arc_s;
|
|
497
|
+
|
|
498
|
+
// Compute binormal S = T x R
|
|
499
|
+
let cur_sx = cur_ty * cur_rz - cur_tz * cur_ry;
|
|
500
|
+
let cur_sy = cur_tz * cur_rx - cur_tx * cur_rz;
|
|
501
|
+
let cur_sz = cur_tx * cur_ry - cur_ty * cur_rx;
|
|
502
|
+
|
|
503
|
+
let prev_v = job.start_vertex;
|
|
504
|
+
let branch_id = -1;
|
|
505
|
+
|
|
506
|
+
if (prev_v < 0) {
|
|
507
|
+
const t0 = job.start_seg / N_seg;
|
|
508
|
+
const u_prof0 = prof_span > 1e-4
|
|
509
|
+
? clamp((prof_p0 - lvl_cfg.radius_profile.evaluate(t0)) / prof_span, 0.0, 1.0)
|
|
510
|
+
: t0;
|
|
511
|
+
const min_r0_frac = level === 0 ? 0.08 : 0.15;
|
|
512
|
+
const r0 = Math.max(0.002, job.base_radius * Math.max(1.0 - lvl_cfg.taper * u_prof0, min_r0_frac));
|
|
513
|
+
|
|
514
|
+
branch_id = out.add_branch(
|
|
515
|
+
0,
|
|
516
|
+
level,
|
|
517
|
+
job.parent_branch_id,
|
|
518
|
+
job.parent_edge_id,
|
|
519
|
+
job.parent_attach_t,
|
|
520
|
+
job.length,
|
|
521
|
+
r0,
|
|
522
|
+
phase_hash
|
|
523
|
+
);
|
|
524
|
+
prev_v = out.add_node(
|
|
525
|
+
cur_px, cur_py, cur_pz,
|
|
526
|
+
cur_tx, cur_ty, cur_tz,
|
|
527
|
+
cur_rx, cur_ry, cur_rz,
|
|
528
|
+
r0,
|
|
529
|
+
branch_id,
|
|
530
|
+
level,
|
|
531
|
+
job.parent_branch_id,
|
|
532
|
+
t0,
|
|
533
|
+
cur_arc_s,
|
|
534
|
+
phase_hash
|
|
535
|
+
);
|
|
536
|
+
if (job.attach_parent_vertex !== undefined && job.attach_parent_vertex >= 0) {
|
|
537
|
+
out.connect_nodes(job.attach_parent_vertex, prev_v);
|
|
538
|
+
}
|
|
539
|
+
out.branch_root_vertex[branch_id] = prev_v;
|
|
540
|
+
out.branch_tip_vertex[branch_id] = prev_v;
|
|
541
|
+
} else {
|
|
542
|
+
const prev_r = out.node_read_radius(prev_v);
|
|
543
|
+
branch_id = out.add_branch(
|
|
544
|
+
prev_v,
|
|
545
|
+
level,
|
|
546
|
+
job.parent_branch_id,
|
|
547
|
+
job.parent_edge_id,
|
|
548
|
+
job.parent_attach_t,
|
|
549
|
+
job.length,
|
|
550
|
+
prev_r,
|
|
551
|
+
phase_hash
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
branch_parent_seg_idx[branch_id] = job.parent_attach_seg_idx ?? -1;
|
|
556
|
+
branch_is_split_fork[branch_id] = Boolean(job.is_split_fork);
|
|
557
|
+
|
|
558
|
+
const branch_edges = [];
|
|
559
|
+
const branch_edge_t0 = [];
|
|
560
|
+
const branch_edge_t1 = [];
|
|
561
|
+
const branch_vertices = [prev_v];
|
|
562
|
+
|
|
563
|
+
// Pre-schedule phyllotactic child attachment parameters so the parent spine kinks sympodially at lateral emissions
|
|
564
|
+
const next_level_preview = level + 1;
|
|
565
|
+
const len_fraction_est = level >= 1
|
|
566
|
+
? clamp(Math.pow(job.length / Math.max(0.2, lvl_cfg.length), 0.85), 0.0, 1.0)
|
|
567
|
+
: 1.0;
|
|
568
|
+
const has_children = next_level_preview < species.levels.length
|
|
569
|
+
&& lvl_cfg.child_count > 0
|
|
570
|
+
&& job.base_radius >= 0.006
|
|
571
|
+
&& (level === 0 || job.length >= 0.55);
|
|
572
|
+
const span_fraction_est = job.child_span_fraction ?? Math.max(0.25, (N_seg - job.start_seg) / Math.max(1, N_seg));
|
|
573
|
+
const effective_span = span_fraction_est * len_fraction_est;
|
|
574
|
+
const planned_child_count = has_children
|
|
575
|
+
? (effective_span < 0.99 ? Math.max(1, Math.round(lvl_cfg.child_count * effective_span)) : lvl_cfg.child_count)
|
|
576
|
+
: 0;
|
|
577
|
+
const bare_est = clamp(lvl_cfg.bare_length_ratio, 0.0, 0.92);
|
|
578
|
+
const t_min_est = Math.max(bare_est, job.start_seg / N_seg);
|
|
579
|
+
const raw_base_phi = rng() * PI2;
|
|
580
|
+
let base_phi = raw_base_phi;
|
|
581
|
+
if (
|
|
582
|
+
level >= 1
|
|
583
|
+
&& (lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.AlternateDistichous
|
|
584
|
+
|| lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.Opposite)
|
|
585
|
+
) {
|
|
586
|
+
// Align distichous / opposite lateral branching plane with the horizontal plane perpendicular to T
|
|
587
|
+
const horiz_phi = Math.atan2(-cur_ry, cur_sy);
|
|
588
|
+
base_phi = horiz_phi + (raw_base_phi < Math.PI ? 0.0 : Math.PI) + ((raw_base_phi % Math.PI) / Math.PI - 0.5) * 0.28;
|
|
589
|
+
}
|
|
590
|
+
const scheduled_children = [];
|
|
591
|
+
|
|
592
|
+
if (planned_child_count > 0 && 1.0 > t_min_est + 1e-3) {
|
|
593
|
+
for (let c = 0; c < planned_child_count; c++) {
|
|
594
|
+
let u_c = planned_child_count > 1
|
|
595
|
+
? (c + 0.35) / (planned_child_count - 0.55)
|
|
596
|
+
: 0.5;
|
|
597
|
+
if (lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.Opposite
|
|
598
|
+
|| lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.OppositeDecussate) {
|
|
599
|
+
const pair_idx = c >> 1;
|
|
600
|
+
const pair_count = Math.max(1, Math.ceil(planned_child_count / 2));
|
|
601
|
+
u_c = pair_count > 1 ? (pair_idx + 0.35) / (pair_count - 0.55) : 0.5;
|
|
602
|
+
} else if (lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.Whorled) {
|
|
603
|
+
const w_size = Math.max(1, lvl_cfg.whorl_count | 0);
|
|
604
|
+
const whorl_idx = Math.floor(c / w_size);
|
|
605
|
+
const whorl_total = Math.max(1, Math.ceil(planned_child_count / w_size));
|
|
606
|
+
const u_lin = whorl_total > 1 ? (whorl_idx + 0.35) / (whorl_total - 0.55) : 0.5;
|
|
607
|
+
// Self-similar conical internode compression toward the apex (p > 1 => du/dt decreases as u -> 1)
|
|
608
|
+
u_c = level === 0 ? 1.0 - Math.pow(1.0 - clamp(u_lin, 0.0, 1.0), 1.42) : u_lin;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const t_att = clamp(
|
|
612
|
+
lerp(t_min_est, 0.968, clamp(u_c, 0.0, 1.0)) + (rng() - 0.5) * (0.24 / planned_child_count),
|
|
613
|
+
t_min_est,
|
|
614
|
+
0.98
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
let phi_c = base_phi;
|
|
618
|
+
switch (lvl_cfg.phyllotaxis_mode) {
|
|
619
|
+
case PhyllotaxisMode.AlternateDistichous:
|
|
620
|
+
phi_c += c * Math.PI;
|
|
621
|
+
break;
|
|
622
|
+
case PhyllotaxisMode.Tristichous:
|
|
623
|
+
phi_c += c * (PI2 / 3.0);
|
|
624
|
+
break;
|
|
625
|
+
case PhyllotaxisMode.Opposite:
|
|
626
|
+
phi_c += (c & 1) * Math.PI;
|
|
627
|
+
break;
|
|
628
|
+
case PhyllotaxisMode.OppositeDecussate:
|
|
629
|
+
phi_c += (c & 1) * Math.PI + (c >> 1) * (Math.PI * 0.5);
|
|
630
|
+
break;
|
|
631
|
+
case PhyllotaxisMode.Whorled: {
|
|
632
|
+
const w_size = Math.max(1, lvl_cfg.whorl_count | 0);
|
|
633
|
+
const w_idx = Math.floor(c / w_size);
|
|
634
|
+
const sub_idx = c % w_size;
|
|
635
|
+
phi_c += (sub_idx * PI2) / w_size + (w_idx * Math.PI) / w_size;
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
case PhyllotaxisMode.SpiralGolden:
|
|
639
|
+
default:
|
|
640
|
+
phi_c += c * GOLDEN_ANGLE;
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
phi_c += (rng() * 2.0 - 1.0) * lvl_cfg.azimuth_jitter;
|
|
644
|
+
scheduled_children.push({ t_attach: t_att, phi: phi_c });
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const k_symp = lvl_cfg.nodal_kink_strength ?? 0.45;
|
|
649
|
+
const zig_base = lvl_cfg.internode_zigzag ?? 0.06;
|
|
650
|
+
const tip_up = lvl_cfg.tip_upcurl ?? 0.18;
|
|
651
|
+
|
|
652
|
+
// Horizontal emergence direction of this branch for outward radial consistency near the trunk centerline
|
|
653
|
+
const emerge_horiz_len = Math.hypot(job.tx, job.tz);
|
|
654
|
+
const emerge_hx = emerge_horiz_len > 1e-4 ? job.tx / emerge_horiz_len : cur_rx;
|
|
655
|
+
const emerge_hz = emerge_horiz_len > 1e-4 ? job.tz / emerge_horiz_len : cur_rz;
|
|
656
|
+
|
|
657
|
+
for (let s = job.start_seg + 1; s <= N_seg; s++) {
|
|
658
|
+
const t_prev = (s - 1) / N_seg;
|
|
659
|
+
const t_curr = s / N_seg;
|
|
660
|
+
|
|
661
|
+
// 1. Intrinsic S-curve pitch curvature (zero-mean higher harmonic on trunk so trunk weaves rather than leaning)
|
|
662
|
+
let s_wave = level === 0
|
|
663
|
+
? (lvl_cfg.curvature * Math.sin(PI2 * 1.5 * t_curr + phase_hash * PI2))
|
|
664
|
+
: (lvl_cfg.curve_back !== 0.0
|
|
665
|
+
? (lvl_cfg.curvature * Math.cos(Math.PI * t_curr) + lvl_cfg.curve_back * Math.sin(PI2 * t_curr))
|
|
666
|
+
: lvl_cfg.curvature);
|
|
667
|
+
// Attenuate negative (inward-toward-parent) pitch curvature on steep upper-crown insertions
|
|
668
|
+
// so upper lateral branches never curve backward across the parent stem axis.
|
|
669
|
+
if (level >= 1 && s_wave < 0.0 && job.parent_attach_t > 0.60) {
|
|
670
|
+
s_wave *= clamp(1.0 - (job.parent_attach_t - 0.60) * 2.2, 0.15, 1.0);
|
|
671
|
+
}
|
|
672
|
+
const d_pitch = s_wave / Math.max(2, N_seg * 0.65);
|
|
673
|
+
|
|
674
|
+
if (Math.abs(d_pitch) > 1e-6) {
|
|
675
|
+
rotate_around_axis(rot_scratch, cur_tx, cur_ty, cur_tz, cur_sx, cur_sy, cur_sz, d_pitch);
|
|
676
|
+
cur_tx = rot_scratch[0];
|
|
677
|
+
cur_ty = rot_scratch[1];
|
|
678
|
+
cur_tz = rot_scratch[2];
|
|
679
|
+
rotate_around_axis(rot_scratch, cur_rx, cur_ry, cur_rz, cur_sx, cur_sy, cur_sz, d_pitch);
|
|
680
|
+
cur_rx = rot_scratch[0];
|
|
681
|
+
cur_ry = rot_scratch[1];
|
|
682
|
+
cur_rz = rot_scratch[2];
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// 2. Helical spiral twist around T
|
|
686
|
+
if (Math.abs(lvl_cfg.spiral_twist) > 1e-6) {
|
|
687
|
+
const d_twist = lvl_cfg.spiral_twist / N_seg;
|
|
688
|
+
rotate_around_axis(rot_scratch, cur_rx, cur_ry, cur_rz, cur_tx, cur_ty, cur_tz, d_twist);
|
|
689
|
+
cur_rx = rot_scratch[0];
|
|
690
|
+
cur_ry = rot_scratch[1];
|
|
691
|
+
cur_rz = rot_scratch[2];
|
|
692
|
+
cur_sx = cur_ty * cur_rz - cur_tz * cur_ry;
|
|
693
|
+
cur_sy = cur_tz * cur_rx - cur_tx * cur_rz;
|
|
694
|
+
cur_sz = cur_tx * cur_ry - cur_ty * cur_rx;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// 2b. Sympodial Nodal Kinking at scheduled lateral child emissions + Internodal Zig-Zag
|
|
698
|
+
let kink_rx = 0.0;
|
|
699
|
+
let kink_ry = 0.0;
|
|
700
|
+
let kink_rz = 0.0;
|
|
701
|
+
|
|
702
|
+
if (k_symp > 0.0 && scheduled_children.length > 0 && lvl_cfg.phyllotaxis_mode !== PhyllotaxisMode.Whorled) {
|
|
703
|
+
for (let c = 0; c < scheduled_children.length; c++) {
|
|
704
|
+
const sc = scheduled_children[c];
|
|
705
|
+
if (sc.t_attach >= t_prev && sc.t_attach <= t_curr) {
|
|
706
|
+
// Recoil away from child branch emission azimuth phi
|
|
707
|
+
const c_phi = Math.cos(sc.phi);
|
|
708
|
+
const s_phi = Math.sin(sc.phi);
|
|
709
|
+
const em_x = c_phi * cur_rx + s_phi * cur_sx;
|
|
710
|
+
const em_y = c_phi * cur_ry + s_phi * cur_sy;
|
|
711
|
+
const em_z = c_phi * cur_rz + s_phi * cur_sz;
|
|
712
|
+
const apical_boost = sc.t_attach > 0.85 ? 1.35 : 1.0;
|
|
713
|
+
const recoil = k_symp * (level === 0 ? 0.34 : 0.48) * apical_boost * (0.75 + 0.5 * rng());
|
|
714
|
+
kink_rx -= em_x * recoil;
|
|
715
|
+
kink_ry -= em_y * recoil;
|
|
716
|
+
kink_rz -= em_z * recoil;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (zig_base > 0.0) {
|
|
722
|
+
const z_sign = (s & 1) === 0 ? 1.0 : -1.0;
|
|
723
|
+
const z_phi = phase_hash * PI2 + s * 0.85;
|
|
724
|
+
const zx = Math.cos(z_phi) * cur_rx + Math.sin(z_phi) * cur_sx;
|
|
725
|
+
const zy = Math.cos(z_phi) * cur_ry + Math.sin(z_phi) * cur_sy;
|
|
726
|
+
const zz = Math.cos(z_phi) * cur_rz + Math.sin(z_phi) * cur_sz;
|
|
727
|
+
const z_amp = zig_base * z_sign * (0.7 + 0.6 * rng());
|
|
728
|
+
kink_rx += zx * z_amp;
|
|
729
|
+
kink_ry += zy * z_amp;
|
|
730
|
+
kink_rz += zz * z_amp;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const kink_mag = Math.hypot(kink_rx, kink_ry, kink_rz);
|
|
734
|
+
if (kink_mag > 1e-5) {
|
|
735
|
+
const kx = kink_rx / kink_mag;
|
|
736
|
+
const ky = kink_ry / kink_mag;
|
|
737
|
+
const kz = kink_rz / kink_mag;
|
|
738
|
+
// Bend axis = T x K
|
|
739
|
+
let bx = cur_ty * kz - cur_tz * ky;
|
|
740
|
+
let by = cur_tz * kx - cur_tx * kz;
|
|
741
|
+
let bz = cur_tx * ky - cur_ty * kx;
|
|
742
|
+
const b_len = Math.hypot(bx, by, bz);
|
|
743
|
+
if (b_len > 1e-6) {
|
|
744
|
+
bx /= b_len;
|
|
745
|
+
by /= b_len;
|
|
746
|
+
bz /= b_len;
|
|
747
|
+
const clamped_kink = Math.min(0.42, kink_mag);
|
|
748
|
+
rotate_around_axis(rot_scratch, cur_tx, cur_ty, cur_tz, bx, by, bz, clamped_kink);
|
|
749
|
+
cur_tx = rot_scratch[0];
|
|
750
|
+
cur_ty = rot_scratch[1];
|
|
751
|
+
cur_tz = rot_scratch[2];
|
|
752
|
+
rotate_around_axis(rot_scratch, cur_rx, cur_ry, cur_rz, bx, by, bz, clamped_kink);
|
|
753
|
+
cur_rx = rot_scratch[0];
|
|
754
|
+
cur_ry = rot_scratch[1];
|
|
755
|
+
cur_rz = rot_scratch[2];
|
|
756
|
+
cur_sx = cur_ty * cur_rz - cur_tz * cur_ry;
|
|
757
|
+
cur_sy = cur_tz * cur_rx - cur_tx * cur_rz;
|
|
758
|
+
cur_sz = cur_tx * cur_ry - cur_ty * cur_rx;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// 3. Biomechanical Tropism steering (Gravitropic sag + Terminal Phototropic Up-Curl + Outward spread)
|
|
763
|
+
let steer_x = 0.0;
|
|
764
|
+
let steer_y = 0.0;
|
|
765
|
+
let steer_z = 0.0;
|
|
766
|
+
|
|
767
|
+
if (level === 0 && branch_id === 0) {
|
|
768
|
+
// Orthotropic upward striving & plumb-line righting reflex along guide curve (default vertical +Y)
|
|
769
|
+
evaluate_trunk_guide(SCRATCH_GUIDE_P0, SCRATCH_GUIDE_T, species.trunk_guide, t_prev, job.length);
|
|
770
|
+
evaluate_trunk_guide(SCRATCH_GUIDE_P1, SCRATCH_GUIDE_T, species.trunk_guide, t_curr, job.length);
|
|
771
|
+
const gtx = SCRATCH_GUIDE_T[0];
|
|
772
|
+
const gty = SCRATCH_GUIDE_T[1];
|
|
773
|
+
const gtz = SCRATCH_GUIDE_T[2];
|
|
774
|
+
|
|
775
|
+
const is_conifer = lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.Whorled;
|
|
776
|
+
const apical_relax = (!is_conifer && (t_curr > 0.80 || job.has_split)) ? 0.55 : 1.0;
|
|
777
|
+
const righting = (1.65 + Math.max(0.0, -lvl_cfg.gravitropism) * 2.5 + Math.max(0.0, lvl_cfg.phototropism)) * apical_relax;
|
|
778
|
+
steer_x += gtx * righting;
|
|
779
|
+
steer_y += gty * righting;
|
|
780
|
+
steer_z += gtz * righting;
|
|
781
|
+
|
|
782
|
+
// Plumb-line centering force pulling lateral drift back toward the guide curve
|
|
783
|
+
const dx_g = cur_px - SCRATCH_GUIDE_P0[0];
|
|
784
|
+
const dy_g = cur_py - SCRATCH_GUIDE_P0[1];
|
|
785
|
+
const dz_g = cur_pz - SCRATCH_GUIDE_P0[2];
|
|
786
|
+
const dot_g = dx_g * gtx + dy_g * gty + dz_g * gtz;
|
|
787
|
+
const lat_x = dx_g - dot_g * gtx;
|
|
788
|
+
const lat_y = dy_g - dot_g * gty;
|
|
789
|
+
const lat_z = dz_g - dot_g * gtz;
|
|
790
|
+
const plumb_k = ((is_conifer ? 5.2 : 3.4) * apical_relax) / Math.max(1.0, job.length);
|
|
791
|
+
steer_x -= lat_x * plumb_k * N_seg * 0.5;
|
|
792
|
+
steer_y -= lat_y * plumb_k * N_seg * 0.5;
|
|
793
|
+
steer_z -= lat_z * plumb_k * N_seg * 0.5;
|
|
794
|
+
} else if (level === 0 && branch_id > 0) {
|
|
795
|
+
// Secondary bifurcated trunk leader (fork): ascend skyward while preserving fork divergence
|
|
796
|
+
steer_x += emerge_hx * 0.35;
|
|
797
|
+
steer_y += 1.25 + Math.max(0.0, lvl_cfg.phototropism);
|
|
798
|
+
steer_z += emerge_hz * 0.35;
|
|
799
|
+
} else {
|
|
800
|
+
if (lvl_cfg.gravitropism !== 0.0 || tip_up !== 0.0) {
|
|
801
|
+
// Continuous blend from cantilever mid-branch sag arch (sin(pi * t^0.75)) to
|
|
802
|
+
// pendulous weeping cascade (0.65 + 0.95 * t) based on gravitropism vs. tip_upcurl
|
|
803
|
+
const w_pendulous = level >= 2
|
|
804
|
+
? clamp((lvl_cfg.gravitropism - 0.35 - 0.5 * Math.max(0.0, tip_up)) / 0.45, 0.0, 1.0)
|
|
805
|
+
: 0.0;
|
|
806
|
+
const arch_grav_env = Math.sin(Math.PI * Math.pow(t_curr, 0.75));
|
|
807
|
+
const weep_grav_env = 0.65 + 0.95 * t_curr;
|
|
808
|
+
const grav_env = lerp(arch_grav_env, weep_grav_env, w_pendulous);
|
|
809
|
+
const apical_grav_atten = level === 1 && job.parent_attach_t > 0.75 && w_pendulous < 0.1
|
|
810
|
+
? clamp(1.0 - (job.parent_attach_t - 0.75) * 3.2, 0.15, 1.0)
|
|
811
|
+
: 1.0;
|
|
812
|
+
const up_env = t_curr * t_curr * (1.0 + 0.5 * Math.max(0.0, -cur_ty));
|
|
813
|
+
steer_y += -lvl_cfg.gravitropism * grav_env * apical_grav_atten + tip_up * up_env;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (lvl_cfg.phototropism !== 0.0) {
|
|
817
|
+
const sky_mix = crown.sky_phototropism_mix;
|
|
818
|
+
const horiz_r = Math.sqrt(cur_px * cur_px + cur_pz * cur_pz);
|
|
819
|
+
let out_x = 0.0;
|
|
820
|
+
let out_z = 0.0;
|
|
821
|
+
if (horiz_r > 0.52) {
|
|
822
|
+
out_x = cur_px / horiz_r;
|
|
823
|
+
out_z = cur_pz / horiz_r;
|
|
824
|
+
} else if (horiz_r > 1e-4) {
|
|
825
|
+
const w_rad = horiz_r / 0.52;
|
|
826
|
+
const bx = lerp(emerge_hx, cur_px / horiz_r, w_rad);
|
|
827
|
+
const bz = lerp(emerge_hz, cur_pz / horiz_r, w_rad);
|
|
828
|
+
const bl = Math.hypot(bx, bz) || 1.0;
|
|
829
|
+
out_x = bx / bl;
|
|
830
|
+
out_z = bz / bl;
|
|
831
|
+
} else {
|
|
832
|
+
out_x = emerge_hx;
|
|
833
|
+
out_z = emerge_hz;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
steer_x += lvl_cfg.phototropism * (1.0 - sky_mix) * out_x;
|
|
837
|
+
steer_y += lvl_cfg.phototropism * sky_mix;
|
|
838
|
+
steer_z += lvl_cfg.phototropism * (1.0 - sky_mix) * out_z;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// 4. 2-Octave 3D Curl Noise gnarl scaled properly by sqrt(6 / N_seg)
|
|
843
|
+
if (lvl_cfg.gnarl_strength > 0.0) {
|
|
844
|
+
const freq = lvl_cfg.gnarl_frequency;
|
|
845
|
+
curl_noise_3d(
|
|
846
|
+
SCRATCH_CURL,
|
|
847
|
+
cur_px * freq + noise_ox + level * 17.3,
|
|
848
|
+
cur_py * freq + noise_oy + level * 31.7,
|
|
849
|
+
cur_pz * freq + noise_oz + level * 47.1
|
|
850
|
+
);
|
|
851
|
+
const c1x = SCRATCH_CURL[0], c1y = SCRATCH_CURL[1], c1z = SCRATCH_CURL[2];
|
|
852
|
+
curl_noise_3d(
|
|
853
|
+
SCRATCH_CURL,
|
|
854
|
+
cur_px * freq * 2.15 + noise_ox + 53.1 + phase_hash * 29.3,
|
|
855
|
+
cur_py * freq * 2.15 + noise_oy + 79.3,
|
|
856
|
+
cur_pz * freq * 2.15 + noise_oz + 113.7
|
|
857
|
+
);
|
|
858
|
+
const cx_tot = 0.68 * c1x + 0.32 * SCRATCH_CURL[0];
|
|
859
|
+
const cy_tot = 0.68 * c1y + 0.32 * SCRATCH_CURL[1];
|
|
860
|
+
const cz_tot = 0.68 * c1z + 0.32 * SCRATCH_CURL[2];
|
|
861
|
+
|
|
862
|
+
const gnarl_scale = lvl_cfg.gnarl_strength * Math.sqrt(6.0 / Math.max(2, N_seg));
|
|
863
|
+
// Project curl vector perpendicular to T so it steers direction cleanly
|
|
864
|
+
const dot_ct = cx_tot * cur_tx + cy_tot * cur_ty + cz_tot * cur_tz;
|
|
865
|
+
steer_x += (cx_tot - dot_ct * cur_tx) * gnarl_scale * 2.2;
|
|
866
|
+
steer_y += (cy_tot - dot_ct * cur_ty) * gnarl_scale * 1.6;
|
|
867
|
+
steer_z += (cz_tot - dot_ct * cur_tz) * gnarl_scale * 2.2;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// 5. Space colonization attraction steering (for branches in crown, level >= 1)
|
|
871
|
+
if (use_space_col && level >= 1 && attr_grid !== null) {
|
|
872
|
+
let attr_vx = 0.0;
|
|
873
|
+
let attr_vy = 0.0;
|
|
874
|
+
let attr_vz = 0.0;
|
|
875
|
+
let attr_hits = 0;
|
|
876
|
+
|
|
877
|
+
attr_grid.forEachNear(cur_px, cur_py, cur_pz, (idx) => {
|
|
878
|
+
if (attr_alive[idx] === 0) return;
|
|
879
|
+
const ax = attr_positions[idx * 3] - cur_px;
|
|
880
|
+
const ay = attr_positions[idx * 3 + 1] - cur_py;
|
|
881
|
+
const az = attr_positions[idx * 3 + 2] - cur_pz;
|
|
882
|
+
const d2 = ax * ax + ay * ay + az * az;
|
|
883
|
+
if (d2 <= r_kill_sq) {
|
|
884
|
+
attr_alive[idx] = 0;
|
|
885
|
+
} else if (d2 <= r_influence_sq && d2 > 1e-8) {
|
|
886
|
+
const inv_d = 1.0 / Math.sqrt(d2);
|
|
887
|
+
attr_vx += ax * inv_d;
|
|
888
|
+
attr_vy += ay * inv_d;
|
|
889
|
+
attr_vz += az * inv_d;
|
|
890
|
+
attr_hits++;
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
if (attr_hits > 0) {
|
|
895
|
+
const attr_len = Math.sqrt(attr_vx * attr_vx + attr_vy * attr_vy + attr_vz * attr_vz);
|
|
896
|
+
if (attr_len > 1e-6) {
|
|
897
|
+
const w_sc = crown.space_colonization_weight / attr_len;
|
|
898
|
+
steer_x += attr_vx * w_sc;
|
|
899
|
+
steer_y += attr_vy * w_sc;
|
|
900
|
+
steer_z += attr_vz * w_sc;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// 6. Crown envelope soft containment for lateral branches (level >= 1)
|
|
906
|
+
let seg_len = step_len;
|
|
907
|
+
if (level >= 1 && crown.prune_ratio > 0.0) {
|
|
908
|
+
const probe_x = cur_px + cur_tx * step_len;
|
|
909
|
+
const probe_y = cur_py + cur_ty * step_len;
|
|
910
|
+
const probe_z = cur_pz + cur_tz * step_len;
|
|
911
|
+
const sdf = crown_envelope_sdf(probe_x, probe_y, probe_z, crown);
|
|
912
|
+
if (sdf > 0.0) {
|
|
913
|
+
// Steer radially inward toward trunk axis (only pulling vertically if outside vertical crown span)
|
|
914
|
+
const crown_top_y = crown.base_height + crown.height;
|
|
915
|
+
const to_cx = -probe_x;
|
|
916
|
+
const to_cy = probe_y > crown_top_y
|
|
917
|
+
? crown_top_y - probe_y
|
|
918
|
+
: (probe_y < crown.base_height ? crown.base_height - probe_y : 0.0);
|
|
919
|
+
const to_cz = -probe_z;
|
|
920
|
+
const to_c_len = Math.sqrt(to_cx * to_cx + to_cy * to_cy + to_cz * to_cz);
|
|
921
|
+
if (to_c_len > 1e-5) {
|
|
922
|
+
const pull = crown.prune_ratio * 0.45 / to_c_len;
|
|
923
|
+
steer_x += to_cx * pull;
|
|
924
|
+
steer_y += to_cy * pull;
|
|
925
|
+
steer_z += to_cz * pull;
|
|
926
|
+
}
|
|
927
|
+
seg_len *= Math.max(0.35, 1.0 - crown.prune_ratio * 0.5);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Compute target radius r_curr for this segment before BVH collision checks
|
|
932
|
+
const t_spine = level === 0 ? lerp(job.cum_t0 ?? 0.0, job.cum_t1 ?? 1.0, t_curr) : t_curr;
|
|
933
|
+
const u_prof = prof_span > 1e-4
|
|
934
|
+
? clamp((prof_p0 - lvl_cfg.radius_profile.evaluate(t_spine)) / prof_span, 0.0, 1.0)
|
|
935
|
+
: t_spine;
|
|
936
|
+
const min_tip_frac = level === 0 ? 0.12 : (has_children ? 0.14 : 0.24);
|
|
937
|
+
const apical_shoot_taper = level === 0
|
|
938
|
+
? (t_spine > 0.65 ? lerp(1.0, 0.11, Math.pow((t_spine - 0.65) / 0.35, 1.45)) : 1.0)
|
|
939
|
+
: (has_children && t_curr > 0.80 ? lerp(1.0, 0.62, Math.pow((t_curr - 0.80) / 0.20, 1.35)) : 1.0);
|
|
940
|
+
const r_curr = Math.max(
|
|
941
|
+
level === 0 ? 0.0015 : 0.002,
|
|
942
|
+
job.base_radius * Math.max(1.0 - lvl_cfg.taper * u_prof, min_tip_frac) * apical_shoot_taper
|
|
943
|
+
);
|
|
944
|
+
const r_prev = out.node_read_radius(prev_v);
|
|
945
|
+
const local_seg_idx = branch_edges.length;
|
|
946
|
+
const parent_bA = job.parent_branch_id;
|
|
947
|
+
const attach_seg_A = job.parent_attach_seg_idx ?? -1;
|
|
948
|
+
const is_split_A = Boolean(job.is_split_fork);
|
|
949
|
+
|
|
950
|
+
// 6b. Soft BVH Repulsion Field (steers growing shoot smoothly around nearby existing branches)
|
|
951
|
+
if ((level >= 1 || branch_id > 0) && bvh.size > 0) {
|
|
952
|
+
const look_len = seg_len * 1.55;
|
|
953
|
+
const look_x = cur_px + cur_tx * look_len;
|
|
954
|
+
const look_y = cur_py + cur_ty * look_len;
|
|
955
|
+
const look_z = cur_pz + cur_tz * look_len;
|
|
956
|
+
const soft_margin = Math.max(0.08, Math.min(0.24, seg_len * 0.65));
|
|
957
|
+
|
|
958
|
+
query_capsule[0] = cur_px;
|
|
959
|
+
query_capsule[1] = cur_py;
|
|
960
|
+
query_capsule[2] = cur_pz;
|
|
961
|
+
query_capsule[3] = look_x;
|
|
962
|
+
query_capsule[4] = look_y;
|
|
963
|
+
query_capsule[5] = look_z;
|
|
964
|
+
query_capsule[6] = Math.max(r_prev, r_curr) * 1.15 + soft_margin;
|
|
965
|
+
|
|
966
|
+
const hit_count = bvh_query_user_data_intersects_capsule(bvh_hits, 0, bvh, query_capsule);
|
|
967
|
+
for (let h = 0; h < hit_count; h++) {
|
|
968
|
+
const eB = bvh_hits[h];
|
|
969
|
+
const bB = seg_branch_id[eB];
|
|
970
|
+
const local_idx_B = seg_local_idx[eB];
|
|
971
|
+
if (!resolve_subsegment_bounds(
|
|
972
|
+
branch_id, parent_bA, attach_seg_A, is_split_A, local_seg_idx,
|
|
973
|
+
bB, out.branch_parent[bB], branch_parent_seg_idx[bB], branch_is_split_fork[bB], local_idx_B,
|
|
974
|
+
sub_bounds
|
|
975
|
+
)) {
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const s_min = sub_bounds[0];
|
|
980
|
+
const t_min = sub_bounds[1];
|
|
981
|
+
const sa0x = cur_px + (look_x - cur_px) * s_min;
|
|
982
|
+
const sa0y = cur_py + (look_y - cur_py) * s_min;
|
|
983
|
+
const sa0z = cur_pz + (look_z - cur_pz) * s_min;
|
|
984
|
+
|
|
985
|
+
const b0x = seg_p0x[eB], b0y = seg_p0y[eB], b0z = seg_p0z[eB];
|
|
986
|
+
const b1x = seg_p1x[eB], b1y = seg_p1y[eB], b1z = seg_p1z[eB];
|
|
987
|
+
const sb0x = b0x + (b1x - b0x) * t_min;
|
|
988
|
+
const sb0y = b0y + (b1y - b0y) * t_min;
|
|
989
|
+
const sb0z = b0z + (b1z - b0z) * t_min;
|
|
990
|
+
|
|
991
|
+
line3_compute_segment_segment_closest_points(
|
|
992
|
+
closest_st,
|
|
993
|
+
sa0x, sa0y, sa0z, look_x, look_y, look_z,
|
|
994
|
+
sb0x, sb0y, sb0z, b1x, b1y, b1z
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
const s_f = s_min + (1.0 - s_min) * closest_st[0];
|
|
998
|
+
const t_f = t_min + (1.0 - t_min) * closest_st[1];
|
|
999
|
+
|
|
1000
|
+
const qAx = cur_px + (look_x - cur_px) * s_f;
|
|
1001
|
+
const qAy = cur_py + (look_y - cur_py) * s_f;
|
|
1002
|
+
const qAz = cur_pz + (look_z - cur_pz) * s_f;
|
|
1003
|
+
const qBx = b0x + (b1x - b0x) * t_f;
|
|
1004
|
+
const qBy = b0y + (b1y - b0y) * t_f;
|
|
1005
|
+
const qBz = b0z + (b1z - b0z) * t_f;
|
|
1006
|
+
|
|
1007
|
+
const dx = qAx - qBx;
|
|
1008
|
+
const dy = qAy - qBy;
|
|
1009
|
+
const dz = qAz - qBz;
|
|
1010
|
+
const dist = Math.hypot(dx, dy, dz);
|
|
1011
|
+
const rA = r_prev + (r_curr - r_prev) * Math.min(1.0, s_f);
|
|
1012
|
+
const rB = seg_r0[eB] + (seg_r1[eB] - seg_r0[eB]) * t_f;
|
|
1013
|
+
const r_req = (rA + rB) * 1.12 + 0.005;
|
|
1014
|
+
|
|
1015
|
+
if (dist < r_req + soft_margin) {
|
|
1016
|
+
let nx = dist > 1e-5 ? dx / dist : cur_rx;
|
|
1017
|
+
let ny = dist > 1e-5 ? dy / dist : cur_ry;
|
|
1018
|
+
let nz = dist > 1e-5 ? dz / dist : cur_rz;
|
|
1019
|
+
// Project repulsion normal perpendicular to cur_T so it steers smoothly around the obstacle
|
|
1020
|
+
const dot_nt = nx * cur_tx + ny * cur_ty + nz * cur_tz;
|
|
1021
|
+
nx -= dot_nt * cur_tx;
|
|
1022
|
+
ny -= dot_nt * cur_ty;
|
|
1023
|
+
nz -= dot_nt * cur_tz;
|
|
1024
|
+
const n_len = Math.hypot(nx, ny, nz);
|
|
1025
|
+
if (n_len > 1e-5) {
|
|
1026
|
+
nx /= n_len;
|
|
1027
|
+
ny /= n_len;
|
|
1028
|
+
nz /= n_len;
|
|
1029
|
+
const u_prox = clamp(1.0 - Math.max(0.0, dist - r_req) / soft_margin, 0.0, 1.0);
|
|
1030
|
+
const rep_w = u_prox * u_prox * 2.8;
|
|
1031
|
+
steer_x += nx * rep_w;
|
|
1032
|
+
steer_y += ny * rep_w;
|
|
1033
|
+
steer_z += nz * rep_w;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// Apply steering to tangent and normalize
|
|
1040
|
+
const steer_div = Math.max(2.0, Math.sqrt(N_seg) * 1.35);
|
|
1041
|
+
let next_tx = cur_tx + steer_x / steer_div;
|
|
1042
|
+
let next_ty = cur_ty + steer_y / steer_div;
|
|
1043
|
+
let next_tz = cur_tz + steer_z / steer_div;
|
|
1044
|
+
const t_len = Math.sqrt(next_tx * next_tx + next_ty * next_ty + next_tz * next_tz);
|
|
1045
|
+
if (t_len > 1e-12) {
|
|
1046
|
+
next_tx /= t_len;
|
|
1047
|
+
next_ty /= t_len;
|
|
1048
|
+
next_tz /= t_len;
|
|
1049
|
+
} else {
|
|
1050
|
+
next_tx = cur_tx;
|
|
1051
|
+
next_ty = cur_ty;
|
|
1052
|
+
next_tz = cur_tz;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
let next_px = cur_px + 0.5 * (cur_tx + next_tx) * seg_len;
|
|
1056
|
+
let next_py = cur_py + 0.5 * (cur_ty + next_ty) * seg_len;
|
|
1057
|
+
let next_pz = cur_pz + 0.5 * (cur_tz + next_tz) * seg_len;
|
|
1058
|
+
|
|
1059
|
+
// Orthotropic plumb-tube containment on trunk (level === 0):
|
|
1060
|
+
// Preserves local 3D nodal kinks and serpentine sweep while guaranteeing the trunk never drifts sideways
|
|
1061
|
+
if (level === 0 && branch_id === 0) {
|
|
1062
|
+
const gx = SCRATCH_GUIDE_P1[0];
|
|
1063
|
+
const gy = SCRATCH_GUIDE_P1[1];
|
|
1064
|
+
const gz = SCRATCH_GUIDE_P1[2];
|
|
1065
|
+
const gtx = SCRATCH_GUIDE_T[0];
|
|
1066
|
+
const gty = SCRATCH_GUIDE_T[1];
|
|
1067
|
+
const gtz = SCRATCH_GUIDE_T[2];
|
|
1068
|
+
|
|
1069
|
+
const dx = next_px - gx;
|
|
1070
|
+
const dy = next_py - gy;
|
|
1071
|
+
const dz = next_pz - gz;
|
|
1072
|
+
const dot_g = dx * gtx + dy * gty + dz * gtz;
|
|
1073
|
+
const off_x = dx - dot_g * gtx;
|
|
1074
|
+
const off_y = dy - dot_g * gty;
|
|
1075
|
+
const off_z = dz - dot_g * gtz;
|
|
1076
|
+
const off_r = Math.hypot(off_x, off_y, off_z);
|
|
1077
|
+
|
|
1078
|
+
const is_conifer = lvl_cfg.phyllotaxis_mode === PhyllotaxisMode.Whorled;
|
|
1079
|
+
const max_drift_frac = is_conifer ? 0.032 : (job.has_split ? 0.078 : 0.058);
|
|
1080
|
+
// Envelope rises from 0 at root, peaks in mid-trunk (allowing 3D sweep/crookedness), and tapers near tip
|
|
1081
|
+
const env = Math.sin(Math.PI * Math.min(0.92, t_curr * 0.88)) * (1.0 - 0.38 * t_curr * t_curr);
|
|
1082
|
+
const max_r = job.length * max_drift_frac * Math.max(job.has_split ? 0.42 : 0.18, env);
|
|
1083
|
+
|
|
1084
|
+
if (off_r > max_r && off_r > 1e-6) {
|
|
1085
|
+
const scale_r = max_r / off_r;
|
|
1086
|
+
next_px = gx + dot_g * gtx + off_x * scale_r;
|
|
1087
|
+
next_py = gy + dot_g * gty + off_y * scale_r;
|
|
1088
|
+
next_pz = gz + dot_g * gtz + off_z * scale_r;
|
|
1089
|
+
|
|
1090
|
+
let ch_x = next_px - cur_px;
|
|
1091
|
+
let ch_y = next_py - cur_py;
|
|
1092
|
+
let ch_z = next_pz - cur_pz;
|
|
1093
|
+
const ch_l = Math.hypot(ch_x, ch_y, ch_z);
|
|
1094
|
+
if (ch_l > 1e-6) {
|
|
1095
|
+
next_tx = ch_x / ch_l;
|
|
1096
|
+
next_ty = ch_y / ch_l;
|
|
1097
|
+
next_tz = ch_z / ch_l;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// 6c. Monotonic Parent Collar Escape + Hard BVH Capsule Contact Projection & Self-Pruning
|
|
1103
|
+
if ((level >= 1 || branch_id > 0) && bvh.size > 0) {
|
|
1104
|
+
let was_projected = false;
|
|
1105
|
+
|
|
1106
|
+
// Ensure lateral child branch exits its parent attachment segment cleanly on local_seg_idx <= 1
|
|
1107
|
+
if (job.parent_edge_id >= 0 && !is_split_A && local_seg_idx <= 1) {
|
|
1108
|
+
const eP = job.parent_edge_id;
|
|
1109
|
+
const p0x = seg_p0x[eP], p0y = seg_p0y[eP], p0z = seg_p0z[eP];
|
|
1110
|
+
const p1x = seg_p1x[eP], p1y = seg_p1y[eP], p1z = seg_p1z[eP];
|
|
1111
|
+
|
|
1112
|
+
line3_compute_segment_segment_closest_points(
|
|
1113
|
+
closest_st,
|
|
1114
|
+
next_px, next_py, next_pz, next_px, next_py, next_pz,
|
|
1115
|
+
p0x, p0y, p0z, p1x, p1y, p1z
|
|
1116
|
+
);
|
|
1117
|
+
const t_p = closest_st[1];
|
|
1118
|
+
const qPx = p0x + (p1x - p0x) * t_p;
|
|
1119
|
+
const qPy = p0y + (p1y - p0y) * t_p;
|
|
1120
|
+
const qPz = p0z + (p1z - p0z) * t_p;
|
|
1121
|
+
const r_p = seg_r0[eP] + (seg_r1[eP] - seg_r0[eP]) * t_p;
|
|
1122
|
+
|
|
1123
|
+
let rx_p = next_px - qPx;
|
|
1124
|
+
let ry_p = next_py - qPy;
|
|
1125
|
+
let rz_p = next_pz - qPz;
|
|
1126
|
+
const d_p = Math.hypot(rx_p, ry_p, rz_p);
|
|
1127
|
+
if (local_seg_idx === 0 && (r_p + r_curr) * 1.18 > seg_len) {
|
|
1128
|
+
seg_len = (r_p + r_curr) * 1.18;
|
|
1129
|
+
}
|
|
1130
|
+
const min_d_p = local_seg_idx === 0
|
|
1131
|
+
? Math.min(seg_len * 0.88, (r_p + r_curr) * 1.05)
|
|
1132
|
+
: (r_p + r_curr) * 1.10 + 0.004;
|
|
1133
|
+
|
|
1134
|
+
const em_drx = job.emerge_drx ?? cur_rx;
|
|
1135
|
+
const em_dry = job.emerge_dry ?? cur_ry;
|
|
1136
|
+
const em_drz = job.emerge_drz ?? cur_rz;
|
|
1137
|
+
const dot_em = rx_p * em_drx + ry_p * em_dry + rz_p * em_drz;
|
|
1138
|
+
|
|
1139
|
+
if (d_p < min_d_p || dot_em < 0.0) {
|
|
1140
|
+
const ux_p = (d_p > 1e-5 && dot_em > 0.0) ? rx_p / d_p : em_drx;
|
|
1141
|
+
const uy_p = (d_p > 1e-5 && dot_em > 0.0) ? ry_p / d_p : em_dry;
|
|
1142
|
+
const uz_p = (d_p > 1e-5 && dot_em > 0.0) ? rz_p / d_p : em_drz;
|
|
1143
|
+
const push_d = Math.max(0.0, min_d_p - (dot_em > 0.0 ? d_p : -d_p));
|
|
1144
|
+
next_px += ux_p * push_d;
|
|
1145
|
+
next_py += uy_p * push_d;
|
|
1146
|
+
next_pz += uz_p * push_d;
|
|
1147
|
+
|
|
1148
|
+
let ch_x = next_px - cur_px;
|
|
1149
|
+
let ch_y = next_py - cur_py;
|
|
1150
|
+
let ch_z = next_pz - cur_pz;
|
|
1151
|
+
const ch_l = Math.hypot(ch_x, ch_y, ch_z);
|
|
1152
|
+
if (ch_l > 1e-6) {
|
|
1153
|
+
next_tx = ch_x / ch_l;
|
|
1154
|
+
next_ty = ch_y / ch_l;
|
|
1155
|
+
next_tz = ch_z / ch_l;
|
|
1156
|
+
next_px = cur_px + next_tx * seg_len;
|
|
1157
|
+
next_py = cur_py + next_ty * seg_len;
|
|
1158
|
+
next_pz = cur_pz + next_tz * seg_len;
|
|
1159
|
+
was_projected = true;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// Iterative BVH capsule contact projection
|
|
1165
|
+
for (let iter = 0; iter < 4; iter++) {
|
|
1166
|
+
query_capsule[0] = cur_px;
|
|
1167
|
+
query_capsule[1] = cur_py;
|
|
1168
|
+
query_capsule[2] = cur_pz;
|
|
1169
|
+
query_capsule[3] = next_px;
|
|
1170
|
+
query_capsule[4] = next_py;
|
|
1171
|
+
query_capsule[5] = next_pz;
|
|
1172
|
+
query_capsule[6] = Math.max(r_prev, r_curr) * 1.15 + 0.025;
|
|
1173
|
+
|
|
1174
|
+
const hit_count = bvh_query_user_data_intersects_capsule(bvh_hits, 0, bvh, query_capsule);
|
|
1175
|
+
let moved = false;
|
|
1176
|
+
|
|
1177
|
+
for (let h = 0; h < hit_count; h++) {
|
|
1178
|
+
const eB = bvh_hits[h];
|
|
1179
|
+
const bB = seg_branch_id[eB];
|
|
1180
|
+
const local_idx_B = seg_local_idx[eB];
|
|
1181
|
+
if (!resolve_subsegment_bounds(
|
|
1182
|
+
branch_id, parent_bA, attach_seg_A, is_split_A, local_seg_idx,
|
|
1183
|
+
bB, out.branch_parent[bB], branch_parent_seg_idx[bB], branch_is_split_fork[bB], local_idx_B,
|
|
1184
|
+
sub_bounds
|
|
1185
|
+
)) {
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
const s_min = sub_bounds[0];
|
|
1190
|
+
const t_min = sub_bounds[1];
|
|
1191
|
+
const sa0x = cur_px + (next_px - cur_px) * s_min;
|
|
1192
|
+
const sa0y = cur_py + (next_py - cur_py) * s_min;
|
|
1193
|
+
const sa0z = cur_pz + (next_pz - cur_pz) * s_min;
|
|
1194
|
+
|
|
1195
|
+
const b0x = seg_p0x[eB], b0y = seg_p0y[eB], b0z = seg_p0z[eB];
|
|
1196
|
+
const b1x = seg_p1x[eB], b1y = seg_p1y[eB], b1z = seg_p1z[eB];
|
|
1197
|
+
const sb0x = b0x + (b1x - b0x) * t_min;
|
|
1198
|
+
const sb0y = b0y + (b1y - b0y) * t_min;
|
|
1199
|
+
const sb0z = b0z + (b1z - b0z) * t_min;
|
|
1200
|
+
|
|
1201
|
+
line3_compute_segment_segment_closest_points(
|
|
1202
|
+
closest_st,
|
|
1203
|
+
sa0x, sa0y, sa0z, next_px, next_py, next_pz,
|
|
1204
|
+
sb0x, sb0y, sb0z, b1x, b1y, b1z
|
|
1205
|
+
);
|
|
1206
|
+
|
|
1207
|
+
const s_f = s_min + (1.0 - s_min) * closest_st[0];
|
|
1208
|
+
const t_f = t_min + (1.0 - t_min) * closest_st[1];
|
|
1209
|
+
if (local_seg_idx === 0 && s_f <= 0.05) continue;
|
|
1210
|
+
|
|
1211
|
+
const qAx = cur_px + (next_px - cur_px) * s_f;
|
|
1212
|
+
const qAy = cur_py + (next_py - cur_py) * s_f;
|
|
1213
|
+
const qAz = cur_pz + (next_pz - cur_pz) * s_f;
|
|
1214
|
+
const qBx = b0x + (b1x - b0x) * t_f;
|
|
1215
|
+
const qBy = b0y + (b1y - b0y) * t_f;
|
|
1216
|
+
const qBz = b0z + (b1z - b0z) * t_f;
|
|
1217
|
+
|
|
1218
|
+
const dx = qAx - qBx;
|
|
1219
|
+
const dy = qAy - qBy;
|
|
1220
|
+
const dz = qAz - qBz;
|
|
1221
|
+
const dist = Math.hypot(dx, dy, dz);
|
|
1222
|
+
const rA = r_prev + (r_curr - r_prev) * s_f;
|
|
1223
|
+
const rB = seg_r0[eB] + (seg_r1[eB] - seg_r0[eB]) * t_f;
|
|
1224
|
+
const r_req = (rA + rB) * 1.08 + 0.004;
|
|
1225
|
+
|
|
1226
|
+
if (dist < r_req) {
|
|
1227
|
+
let nx = dist > 1e-5 ? dx / dist : cur_rx;
|
|
1228
|
+
let ny = dist > 1e-5 ? dy / dist : cur_ry;
|
|
1229
|
+
let nz = dist > 1e-5 ? dz / dist : cur_rz;
|
|
1230
|
+
const push = (r_req - dist + 0.004) / Math.max(0.35, s_f);
|
|
1231
|
+
next_px += nx * push;
|
|
1232
|
+
next_py += ny * push;
|
|
1233
|
+
next_pz += nz * push;
|
|
1234
|
+
|
|
1235
|
+
let ch_x = next_px - cur_px;
|
|
1236
|
+
let ch_y = next_py - cur_py;
|
|
1237
|
+
let ch_z = next_pz - cur_pz;
|
|
1238
|
+
const ch_l = Math.hypot(ch_x, ch_y, ch_z);
|
|
1239
|
+
if (ch_l > 1e-6) {
|
|
1240
|
+
next_tx = ch_x / ch_l;
|
|
1241
|
+
next_ty = ch_y / ch_l;
|
|
1242
|
+
next_tz = ch_z / ch_l;
|
|
1243
|
+
next_px = cur_px + next_tx * seg_len;
|
|
1244
|
+
next_py = cur_py + next_ty * seg_len;
|
|
1245
|
+
next_pz = cur_pz + next_tz * seg_len;
|
|
1246
|
+
moved = true;
|
|
1247
|
+
was_projected = true;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
if (!moved) break;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// If segment >= 1 still penetrates an existing branch or was forced into an unnatural sharp reversal,
|
|
1255
|
+
// self-prune / arrest the branch at prev_v so it never spears through another bough.
|
|
1256
|
+
if (local_seg_idx >= 1) {
|
|
1257
|
+
const final_clear = evaluate_candidate_clearance(
|
|
1258
|
+
branch_id, parent_bA, attach_seg_A, is_split_A, local_seg_idx,
|
|
1259
|
+
cur_px, cur_py, cur_pz, r_prev,
|
|
1260
|
+
next_px, next_py, next_pz, r_curr,
|
|
1261
|
+
-0.004
|
|
1262
|
+
);
|
|
1263
|
+
const turn_cos = cur_tx * next_tx + cur_ty * next_ty + cur_tz * next_tz;
|
|
1264
|
+
if (final_clear < 0.0 || (was_projected && turn_cos < 0.25)) {
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
1267
|
+
} else if (was_projected) {
|
|
1268
|
+
// Keep root node tangent aligned with projected first segment so base collar has zero S-hook
|
|
1269
|
+
cur_tx = next_tx;
|
|
1270
|
+
cur_ty = next_ty;
|
|
1271
|
+
cur_tz = next_tz;
|
|
1272
|
+
out.node_write_tangent(prev_v, cur_tx, cur_ty, cur_tz);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// Transport RMF via Wang et al. Double Reflection
|
|
1277
|
+
spine_frame_double_reflection(
|
|
1278
|
+
SCRATCH_RMF, 0,
|
|
1279
|
+
cur_px, cur_py, cur_pz,
|
|
1280
|
+
cur_tx, cur_ty, cur_tz,
|
|
1281
|
+
cur_rx, cur_ry, cur_rz,
|
|
1282
|
+
next_px, next_py, next_pz,
|
|
1283
|
+
next_tx, next_ty, next_tz
|
|
1284
|
+
);
|
|
1285
|
+
let next_rx = SCRATCH_RMF[0];
|
|
1286
|
+
let next_ry = SCRATCH_RMF[1];
|
|
1287
|
+
let next_rz = SCRATCH_RMF[2];
|
|
1288
|
+
cur_sx = SCRATCH_RMF[3];
|
|
1289
|
+
cur_sy = SCRATCH_RMF[4];
|
|
1290
|
+
cur_sz = SCRATCH_RMF[5];
|
|
1291
|
+
|
|
1292
|
+
cur_arc_s += seg_len;
|
|
1293
|
+
|
|
1294
|
+
const curr_v = out.add_node(
|
|
1295
|
+
next_px, next_py, next_pz,
|
|
1296
|
+
next_tx, next_ty, next_tz,
|
|
1297
|
+
next_rx, next_ry, next_rz,
|
|
1298
|
+
r_curr,
|
|
1299
|
+
branch_id,
|
|
1300
|
+
level,
|
|
1301
|
+
job.parent_branch_id,
|
|
1302
|
+
t_curr,
|
|
1303
|
+
cur_arc_s,
|
|
1304
|
+
phase_hash
|
|
1305
|
+
);
|
|
1306
|
+
|
|
1307
|
+
const edge_id = out.add_segment(
|
|
1308
|
+
prev_v,
|
|
1309
|
+
curr_v,
|
|
1310
|
+
cur_tx * seg_len,
|
|
1311
|
+
cur_ty * seg_len,
|
|
1312
|
+
cur_tz * seg_len,
|
|
1313
|
+
next_tx * seg_len,
|
|
1314
|
+
next_ty * seg_len,
|
|
1315
|
+
next_tz * seg_len,
|
|
1316
|
+
seg_len,
|
|
1317
|
+
t_prev === 0.0 ? job.parent_attach_t : t_prev
|
|
1318
|
+
);
|
|
1319
|
+
|
|
1320
|
+
// Register segment in scope-local BVH
|
|
1321
|
+
const bvh_pad = Math.max(r_prev, r_curr) * 1.25 + 0.008;
|
|
1322
|
+
const bvh_leaf = bvh.allocate_node();
|
|
1323
|
+
bvh.node_set_user_data(bvh_leaf, edge_id);
|
|
1324
|
+
bvh.node_set_aabb_primitive(
|
|
1325
|
+
bvh_leaf,
|
|
1326
|
+
Math.min(cur_px, next_px) - bvh_pad,
|
|
1327
|
+
Math.min(cur_py, next_py) - bvh_pad,
|
|
1328
|
+
Math.min(cur_pz, next_pz) - bvh_pad,
|
|
1329
|
+
Math.max(cur_px, next_px) + bvh_pad,
|
|
1330
|
+
Math.max(cur_py, next_py) + bvh_pad,
|
|
1331
|
+
Math.max(cur_pz, next_pz) + bvh_pad
|
|
1332
|
+
);
|
|
1333
|
+
bvh.insert_leaf(bvh_leaf);
|
|
1334
|
+
|
|
1335
|
+
seg_branch_id[edge_id] = branch_id;
|
|
1336
|
+
seg_local_idx[edge_id] = local_seg_idx;
|
|
1337
|
+
seg_p0x[edge_id] = cur_px;
|
|
1338
|
+
seg_p0y[edge_id] = cur_py;
|
|
1339
|
+
seg_p0z[edge_id] = cur_pz;
|
|
1340
|
+
seg_r0[edge_id] = r_prev;
|
|
1341
|
+
seg_p1x[edge_id] = next_px;
|
|
1342
|
+
seg_p1y[edge_id] = next_py;
|
|
1343
|
+
seg_p1z[edge_id] = next_pz;
|
|
1344
|
+
seg_r1[edge_id] = r_curr;
|
|
1345
|
+
seg_bvh_leaf[edge_id] = bvh_leaf;
|
|
1346
|
+
|
|
1347
|
+
branch_edges.push(edge_id);
|
|
1348
|
+
branch_edge_t0.push(t_prev);
|
|
1349
|
+
branch_edge_t1.push(t_curr);
|
|
1350
|
+
branch_vertices.push(curr_v);
|
|
1351
|
+
|
|
1352
|
+
out.branch_tip_vertex[branch_id] = curr_v;
|
|
1353
|
+
out.branch_length[branch_id] = cur_arc_s - job.initial_arc_s;
|
|
1354
|
+
|
|
1355
|
+
// Check for dichotomous / sympodial splitting at interior segments (only when stem is structurally thick enough)
|
|
1356
|
+
const min_split_seg = level === 0
|
|
1357
|
+
? Math.max(2, Math.floor(N_seg * 0.5))
|
|
1358
|
+
: Math.max(1, Math.floor(N_seg * 0.25));
|
|
1359
|
+
if (
|
|
1360
|
+
!job.has_split
|
|
1361
|
+
&& lvl_cfg.split_probability > 0.0
|
|
1362
|
+
&& r_curr >= 0.012
|
|
1363
|
+
&& s >= min_split_seg
|
|
1364
|
+
&& s <= Math.floor(N_seg * 0.75)
|
|
1365
|
+
&& rng() < lvl_cfg.split_probability
|
|
1366
|
+
) {
|
|
1367
|
+
job.has_split = true;
|
|
1368
|
+
const fork_count = Math.max(2, lvl_cfg.split_count | 0);
|
|
1369
|
+
const split_ang = lvl_cfg.split_angle;
|
|
1370
|
+
let base_az = rng() * PI2;
|
|
1371
|
+
|
|
1372
|
+
// Preserve pre-split orthonormal triad (T, R, S) before deflecting fork 0
|
|
1373
|
+
const pre_tx = next_tx, pre_ty = next_ty, pre_tz = next_tz;
|
|
1374
|
+
const pre_rx = next_rx, pre_ry = next_ry, pre_rz = next_rz;
|
|
1375
|
+
const pre_sx = cur_sx, pre_sy = cur_sy, pre_sz = cur_sz;
|
|
1376
|
+
|
|
1377
|
+
// On lateral branches (level >= 1), align the split bend axis toward local vertical (+Y)
|
|
1378
|
+
// so the two daughter forks spread horizontally in the canopy rather than pitching into neighbor boughs.
|
|
1379
|
+
if (level >= 1) {
|
|
1380
|
+
const up_az = Math.atan2(pre_sy, pre_ry);
|
|
1381
|
+
const side_sign = base_az < Math.PI ? 1.0 : -1.0;
|
|
1382
|
+
base_az = up_az + (side_sign < 0.0 ? Math.PI : 0.0) + ((base_az % Math.PI) / Math.PI - 0.5) * 0.45;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// Current branch deflects as fork 0: rotate both T and R around bend_axis so R remains orthogonal to T
|
|
1386
|
+
const fork0_deflect = level === 0 ? split_ang * 0.22 : split_ang * 0.5;
|
|
1387
|
+
const bend_axis_x = Math.cos(base_az) * pre_rx + Math.sin(base_az) * pre_sx;
|
|
1388
|
+
const bend_axis_y = Math.cos(base_az) * pre_ry + Math.sin(base_az) * pre_sy;
|
|
1389
|
+
const bend_axis_z = Math.cos(base_az) * pre_rz + Math.sin(base_az) * pre_sz;
|
|
1390
|
+
rotate_around_axis(
|
|
1391
|
+
rot_scratch,
|
|
1392
|
+
pre_tx, pre_ty, pre_tz,
|
|
1393
|
+
bend_axis_x, bend_axis_y, bend_axis_z,
|
|
1394
|
+
fork0_deflect
|
|
1395
|
+
);
|
|
1396
|
+
next_tx = rot_scratch[0];
|
|
1397
|
+
next_ty = rot_scratch[1];
|
|
1398
|
+
next_tz = rot_scratch[2];
|
|
1399
|
+
|
|
1400
|
+
rotate_around_axis(
|
|
1401
|
+
rot_scratch,
|
|
1402
|
+
pre_rx, pre_ry, pre_rz,
|
|
1403
|
+
bend_axis_x, bend_axis_y, bend_axis_z,
|
|
1404
|
+
fork0_deflect
|
|
1405
|
+
);
|
|
1406
|
+
next_rx = rot_scratch[0];
|
|
1407
|
+
next_ry = rot_scratch[1];
|
|
1408
|
+
next_rz = rot_scratch[2];
|
|
1409
|
+
|
|
1410
|
+
cur_sx = next_ty * next_rz - next_tz * next_ry;
|
|
1411
|
+
cur_sy = next_tz * next_rx - next_tx * next_rz;
|
|
1412
|
+
cur_sz = next_tx * next_ry - next_ty * next_rx;
|
|
1413
|
+
|
|
1414
|
+
// Spawn fork 1..fork_count-1 as continuation branches with their own root vertex connected to curr_v
|
|
1415
|
+
const rem_segs = Math.max(1, N_seg - s);
|
|
1416
|
+
const rem_fraction = Math.max(0.15, rem_segs / Math.max(1, N_seg));
|
|
1417
|
+
const fork_cum_t0 = lerp(job.cum_t0 ?? 0.0, job.cum_t1 ?? 1.0, t_curr);
|
|
1418
|
+
const fork_cum_t1 = job.cum_t1 ?? 1.0;
|
|
1419
|
+
const fork_len = Math.max(0.15, job.length * rem_fraction);
|
|
1420
|
+
const fork_r0 = r_curr * 0.78;
|
|
1421
|
+
const fork_step = fork_len / rem_segs;
|
|
1422
|
+
|
|
1423
|
+
for (let f = 1; f < fork_count; f++) {
|
|
1424
|
+
const f_az = base_az + (f * PI2) / fork_count;
|
|
1425
|
+
const fax = Math.cos(f_az) * pre_rx + Math.sin(f_az) * pre_sx;
|
|
1426
|
+
const fay = Math.cos(f_az) * pre_ry + Math.sin(f_az) * pre_sy;
|
|
1427
|
+
const faz = Math.cos(f_az) * pre_rz + Math.sin(f_az) * pre_sz;
|
|
1428
|
+
|
|
1429
|
+
const f_tan = [0, 0, 0];
|
|
1430
|
+
rotate_around_axis(f_tan, pre_tx, pre_ty, pre_tz, fax, fay, faz, split_ang * 0.65);
|
|
1431
|
+
|
|
1432
|
+
// Verify fork emergence clearance against BVH (and prevent inward-toward-trunk forks on lateral boughs)
|
|
1433
|
+
if (level >= 1) {
|
|
1434
|
+
const r_horiz_here = Math.hypot(next_px, next_pz);
|
|
1435
|
+
if (r_horiz_here > 0.35 && (next_px * f_tan[0] + next_pz * f_tan[2]) < -0.10 * r_horiz_here) {
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
const f_clear = evaluate_candidate_clearance(
|
|
1439
|
+
-1, branch_id, local_seg_idx, true, 0,
|
|
1440
|
+
next_px, next_py, next_pz, fork_r0,
|
|
1441
|
+
next_px + f_tan[0] * fork_step,
|
|
1442
|
+
next_py + f_tan[1] * fork_step,
|
|
1443
|
+
next_pz + f_tan[2] * fork_step,
|
|
1444
|
+
fork_r0 * 0.85,
|
|
1445
|
+
0.002
|
|
1446
|
+
);
|
|
1447
|
+
if (f_clear < -0.002) {
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
const f_r = [0, 0, 0];
|
|
1453
|
+
rotate_around_axis(f_r, pre_rx, pre_ry, pre_rz, fax, fay, faz, split_ang * 0.65);
|
|
1454
|
+
|
|
1455
|
+
queue.push({
|
|
1456
|
+
level,
|
|
1457
|
+
parent_branch_id: branch_id,
|
|
1458
|
+
parent_edge_id: edge_id,
|
|
1459
|
+
parent_attach_seg_idx: local_seg_idx,
|
|
1460
|
+
parent_attach_t: t_curr,
|
|
1461
|
+
attach_parent_vertex: curr_v,
|
|
1462
|
+
start_vertex: -1,
|
|
1463
|
+
px: next_px,
|
|
1464
|
+
py: next_py,
|
|
1465
|
+
pz: next_pz,
|
|
1466
|
+
tx: f_tan[0],
|
|
1467
|
+
ty: f_tan[1],
|
|
1468
|
+
tz: f_tan[2],
|
|
1469
|
+
rx: f_r[0],
|
|
1470
|
+
ry: f_r[1],
|
|
1471
|
+
rz: f_r[2],
|
|
1472
|
+
emerge_drx: fax,
|
|
1473
|
+
emerge_dry: fay,
|
|
1474
|
+
emerge_drz: faz,
|
|
1475
|
+
length: fork_len,
|
|
1476
|
+
full_branch_length: job.full_branch_length ?? job.length,
|
|
1477
|
+
base_radius: fork_r0,
|
|
1478
|
+
start_seg: 0,
|
|
1479
|
+
total_segs: rem_segs,
|
|
1480
|
+
initial_arc_s: cur_arc_s,
|
|
1481
|
+
has_split: true,
|
|
1482
|
+
is_split_fork: true,
|
|
1483
|
+
child_span_fraction: rem_fraction,
|
|
1484
|
+
cum_t0: fork_cum_t0,
|
|
1485
|
+
cum_t1: fork_cum_t1
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
cur_px = next_px;
|
|
1491
|
+
cur_py = next_py;
|
|
1492
|
+
cur_pz = next_pz;
|
|
1493
|
+
cur_tx = next_tx;
|
|
1494
|
+
cur_ty = next_ty;
|
|
1495
|
+
cur_tz = next_tz;
|
|
1496
|
+
cur_rx = next_rx;
|
|
1497
|
+
cur_ry = next_ry;
|
|
1498
|
+
cur_rz = next_rz;
|
|
1499
|
+
prev_v = curr_v;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// If the branch was self-pruned early on contact (actual_segs < remaining_segs), renormalize
|
|
1503
|
+
// t_norm and taper radii across the surviving vertices [0..actual_segs] so its tip terminates cleanly at t_norm = 1.0
|
|
1504
|
+
const actual_segs = branch_edges.length;
|
|
1505
|
+
if (actual_segs > 0 && actual_segs < remaining_segs) {
|
|
1506
|
+
const s_param = /** @type {Float32Array} */ (out.attr_node_spine_param.data);
|
|
1507
|
+
for (let i = 0; i < actual_segs; i++) {
|
|
1508
|
+
const tn0 = i / actual_segs;
|
|
1509
|
+
const tn1 = (i + 1) / actual_segs;
|
|
1510
|
+
branch_edge_t0[i] = tn0;
|
|
1511
|
+
branch_edge_t1[i] = tn1;
|
|
1512
|
+
|
|
1513
|
+
const v_curr = branch_vertices[i + 1];
|
|
1514
|
+
s_param[v_curr * 3] = tn1;
|
|
1515
|
+
|
|
1516
|
+
const u_p = prof_span > 1e-4
|
|
1517
|
+
? clamp((prof_p0 - lvl_cfg.radius_profile.evaluate(tn1)) / prof_span, 0.0, 1.0)
|
|
1518
|
+
: tn1;
|
|
1519
|
+
const min_tf = has_children ? 0.14 : 0.24;
|
|
1520
|
+
const ap_taper = has_children && tn1 > 0.80
|
|
1521
|
+
? lerp(1.0, 0.62, Math.pow((tn1 - 0.80) / 0.20, 1.35))
|
|
1522
|
+
: 1.0;
|
|
1523
|
+
const r_new = Math.max(
|
|
1524
|
+
0.002,
|
|
1525
|
+
job.base_radius * Math.max(1.0 - lvl_cfg.taper * u_p, min_tf) * ap_taper
|
|
1526
|
+
);
|
|
1527
|
+
out.node_write_radius(v_curr, r_new);
|
|
1528
|
+
|
|
1529
|
+
const e_curr = branch_edges[i];
|
|
1530
|
+
seg_r1[e_curr] = r_new;
|
|
1531
|
+
if (i + 1 < actual_segs) {
|
|
1532
|
+
seg_r0[branch_edges[i + 1]] = r_new;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// Smooth or preserve crisp interior Hermite tangents across consecutive edges of this branch
|
|
1538
|
+
// (preserving branch_edges[0]'s m0 so continuation forks keep their exact fork departure tangent)
|
|
1539
|
+
if (branch_edges.length >= 2) {
|
|
1540
|
+
const kink_sharp = clamp(lvl_cfg.kink_sharpness ?? 0.5, 0.0, 0.92);
|
|
1541
|
+
for (let k = 1; k < branch_edges.length; k++) {
|
|
1542
|
+
const e_prev = branch_edges[k - 1];
|
|
1543
|
+
const e_next = branch_edges[k];
|
|
1544
|
+
const v_mid = branch_vertices[k];
|
|
1545
|
+
const l_prev = Math.max(1e-5, out.segment_length(e_prev));
|
|
1546
|
+
const l_next = Math.max(1e-5, out.segment_length(e_next));
|
|
1547
|
+
|
|
1548
|
+
out.segment_read_m1(SCRATCH_M_PREV, 0, e_prev);
|
|
1549
|
+
out.segment_read_m0(SCRATCH_M_NEXT, 0, e_next);
|
|
1550
|
+
|
|
1551
|
+
const t_in_x = SCRATCH_M_PREV[0] / l_prev;
|
|
1552
|
+
const t_in_y = SCRATCH_M_PREV[1] / l_prev;
|
|
1553
|
+
const t_in_z = SCRATCH_M_PREV[2] / l_prev;
|
|
1554
|
+
const t_out_x = SCRATCH_M_NEXT[0] / l_next;
|
|
1555
|
+
const t_out_y = SCRATCH_M_NEXT[1] / l_next;
|
|
1556
|
+
const t_out_z = SCRATCH_M_NEXT[2] / l_next;
|
|
1557
|
+
|
|
1558
|
+
let tx = t_in_x + t_out_x;
|
|
1559
|
+
let ty = t_in_y + t_out_y;
|
|
1560
|
+
let tz = t_in_z + t_out_z;
|
|
1561
|
+
const t_len = Math.sqrt(tx * tx + ty * ty + tz * tz);
|
|
1562
|
+
if (t_len > 1e-12) {
|
|
1563
|
+
tx /= t_len;
|
|
1564
|
+
ty /= t_len;
|
|
1565
|
+
tz /= t_len;
|
|
1566
|
+
|
|
1567
|
+
out.node_write_tangent(v_mid, tx, ty, tz);
|
|
1568
|
+
|
|
1569
|
+
// Blend between smooth C1 average tangent and crisp sympodial kink tangents
|
|
1570
|
+
let m1x = lerp(tx, t_in_x, kink_sharp);
|
|
1571
|
+
let m1y = lerp(ty, t_in_y, kink_sharp);
|
|
1572
|
+
let m1z = lerp(tz, t_in_z, kink_sharp);
|
|
1573
|
+
const m1_l = Math.sqrt(m1x * m1x + m1y * m1y + m1z * m1z);
|
|
1574
|
+
if (m1_l > 1e-12) { m1x /= m1_l; m1y /= m1_l; m1z /= m1_l; }
|
|
1575
|
+
|
|
1576
|
+
let m0x = lerp(tx, t_out_x, kink_sharp);
|
|
1577
|
+
let m0y = lerp(ty, t_out_y, kink_sharp);
|
|
1578
|
+
let m0z = lerp(tz, t_out_z, kink_sharp);
|
|
1579
|
+
const m0_l = Math.sqrt(m0x * m0x + m0y * m0y + m0z * m0z);
|
|
1580
|
+
if (m0_l > 1e-12) { m0x /= m0_l; m0y /= m0_l; m0z /= m0_l; }
|
|
1581
|
+
|
|
1582
|
+
out.segment_write_m1(e_prev, m1x * l_prev, m1y * l_prev, m1z * l_prev);
|
|
1583
|
+
out.segment_write_m0(e_next, m0x * l_next, m0y * l_next, m0z * l_next);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
// Spawn lateral child branches at level + 1 using pre-scheduled attachments
|
|
1589
|
+
const next_level = level + 1;
|
|
1590
|
+
if (scheduled_children.length > 0 && branch_edges.length > 0) {
|
|
1591
|
+
const next_cfg = species.levels[next_level];
|
|
1592
|
+
const t_max = branch_edge_t1[branch_edge_t1.length - 1];
|
|
1593
|
+
const PHI_CANDIDATES = [0.0, 0.36, -0.36, 0.72, -0.72, 1.08, -1.08, 1.48, -1.48];
|
|
1594
|
+
|
|
1595
|
+
for (let c = 0; c < scheduled_children.length; c++) {
|
|
1596
|
+
const sc = scheduled_children[c];
|
|
1597
|
+
if (sc.t_attach > t_max * 1.02) continue;
|
|
1598
|
+
const t_attach = clamp(sc.t_attach, branch_edge_t0[0], t_max * 0.985);
|
|
1599
|
+
const base_phi_c = sc.phi;
|
|
1600
|
+
|
|
1601
|
+
// Find parent edge containing t_attach
|
|
1602
|
+
let seg_idx = 0;
|
|
1603
|
+
for (let i = 0; i < branch_edges.length; i++) {
|
|
1604
|
+
if (t_attach >= branch_edge_t0[i] && t_attach <= branch_edge_t1[i]) {
|
|
1605
|
+
seg_idx = i;
|
|
1606
|
+
break;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
const parent_e = branch_edges[seg_idx];
|
|
1611
|
+
const t0_e = branch_edge_t0[seg_idx];
|
|
1612
|
+
const t1_e = branch_edge_t1[seg_idx];
|
|
1613
|
+
const local_u = clamp((t_attach - t0_e) / Math.max(1e-5, t1_e - t0_e), 0.0, 1.0);
|
|
1614
|
+
|
|
1615
|
+
out.evaluate_segment_position(SCRATCH_POS, 0, parent_e, local_u);
|
|
1616
|
+
out.evaluate_segment_tangent(SCRATCH_TAN, 0, parent_e, local_u);
|
|
1617
|
+
|
|
1618
|
+
const p_v0 = branch_vertices[seg_idx];
|
|
1619
|
+
const p_v1 = branch_vertices[seg_idx + 1];
|
|
1620
|
+
const r_p0 = out.node_read_radius(p_v0);
|
|
1621
|
+
const r_p1 = out.node_read_radius(p_v1);
|
|
1622
|
+
const parent_r_at_attach = lerp(r_p0, r_p1, local_u);
|
|
1623
|
+
if (parent_r_at_attach < 0.004) continue;
|
|
1624
|
+
|
|
1625
|
+
// Transport parent RMF R vector from p_v0 along parent_e to SCRATCH_POS at local_u
|
|
1626
|
+
out.node_read_position(SCRATCH_P0, 0, p_v0);
|
|
1627
|
+
out.node_read_tangent(SCRATCH_T0, 0, p_v0);
|
|
1628
|
+
out.node_read_frame_r(SCRATCH_R0, 0, p_v0);
|
|
1629
|
+
const p_rx = SCRATCH_R0[0];
|
|
1630
|
+
const p_ry = SCRATCH_R0[1];
|
|
1631
|
+
const p_rz = SCRATCH_R0[2];
|
|
1632
|
+
|
|
1633
|
+
spine_frame_double_reflection(
|
|
1634
|
+
SCRATCH_RMF, 0,
|
|
1635
|
+
SCRATCH_P0[0], SCRATCH_P0[1], SCRATCH_P0[2],
|
|
1636
|
+
SCRATCH_T0[0], SCRATCH_T0[1], SCRATCH_T0[2],
|
|
1637
|
+
p_rx, p_ry, p_rz,
|
|
1638
|
+
SCRATCH_POS[0], SCRATCH_POS[1], SCRATCH_POS[2],
|
|
1639
|
+
SCRATCH_TAN[0], SCRATCH_TAN[1], SCRATCH_TAN[2]
|
|
1640
|
+
);
|
|
1641
|
+
const prx = SCRATCH_RMF[0], pry = SCRATCH_RMF[1], prz = SCRATCH_RMF[2];
|
|
1642
|
+
const psx = SCRATCH_RMF[3], psy = SCRATCH_RMF[4], psz = SCRATCH_RMF[5];
|
|
1643
|
+
|
|
1644
|
+
// Genealogical position along the full parent axis (accounts for continuation forks)
|
|
1645
|
+
const t_genealogical = lerp(job.cum_t0 ?? 0.0, job.cum_t1 ?? 1.0, t_attach);
|
|
1646
|
+
|
|
1647
|
+
// Insertion polar angle alpha from parent tangent T_p toward D_rad
|
|
1648
|
+
const alpha_scale = lvl_cfg.insertion_angle_profile.evaluate(t_genealogical);
|
|
1649
|
+
const alpha = clamp(
|
|
1650
|
+
lvl_cfg.insertion_angle * alpha_scale + (rng() * 2.0 - 1.0) * lvl_cfg.insertion_angle_variation,
|
|
1651
|
+
0.1,
|
|
1652
|
+
Math.PI * 0.85
|
|
1653
|
+
);
|
|
1654
|
+
const cos_a = Math.cos(alpha);
|
|
1655
|
+
const sin_a = Math.sin(alpha);
|
|
1656
|
+
const var_scale = 1.0 + (rng() * 2.0 - 1.0) * next_cfg.length_variance;
|
|
1657
|
+
const w_plagio = level >= 1
|
|
1658
|
+
? (1.0 - clamp(((next_cfg.gravitropism ?? 0.0) - 0.45) / 0.35, 0.0, 1.0))
|
|
1659
|
+
: 0.0;
|
|
1660
|
+
|
|
1661
|
+
// Helper to compute radial normal D_rad and child tangent T_c for a given azimuth phi_cand
|
|
1662
|
+
const compute_child_basis = (phi_cand, out_dr, out_ct) => {
|
|
1663
|
+
const cp = Math.cos(phi_cand);
|
|
1664
|
+
const sp = Math.sin(phi_cand);
|
|
1665
|
+
let dx_r = cp * prx + sp * psx;
|
|
1666
|
+
let dy_r = cp * pry + sp * psy;
|
|
1667
|
+
let dz_r = cp * prz + sp * psz;
|
|
1668
|
+
|
|
1669
|
+
if (w_plagio > 1e-3) {
|
|
1670
|
+
const flat_dry = dy_r < 0.0 ? dy_r * 0.15 + 0.08 : dy_r * 0.75 + 0.12;
|
|
1671
|
+
dy_r = lerp(dy_r, flat_dry, w_plagio);
|
|
1672
|
+
const dot_tp = dx_r * SCRATCH_TAN[0] + dy_r * SCRATCH_TAN[1] + dz_r * SCRATCH_TAN[2];
|
|
1673
|
+
dx_r -= dot_tp * SCRATCH_TAN[0];
|
|
1674
|
+
dy_r -= dot_tp * SCRATCH_TAN[1];
|
|
1675
|
+
dz_r -= dot_tp * SCRATCH_TAN[2];
|
|
1676
|
+
const d_len = Math.sqrt(dx_r * dx_r + dy_r * dy_r + dz_r * dz_r);
|
|
1677
|
+
if (d_len > 1e-6) {
|
|
1678
|
+
dx_r /= d_len;
|
|
1679
|
+
dy_r /= d_len;
|
|
1680
|
+
dz_r /= d_len;
|
|
1681
|
+
} else {
|
|
1682
|
+
dx_r = cp * prx + sp * psx;
|
|
1683
|
+
dy_r = cp * pry + sp * psy;
|
|
1684
|
+
dz_r = cp * prz + sp * psz;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
out_dr[0] = dx_r;
|
|
1688
|
+
out_dr[1] = dy_r;
|
|
1689
|
+
out_dr[2] = dz_r;
|
|
1690
|
+
|
|
1691
|
+
let tx_c = cos_a * SCRATCH_TAN[0] + sin_a * dx_r;
|
|
1692
|
+
let ty_c = cos_a * SCRATCH_TAN[1] + sin_a * dy_r;
|
|
1693
|
+
let tz_c = cos_a * SCRATCH_TAN[2] + sin_a * dz_r;
|
|
1694
|
+
const ct_l = Math.sqrt(tx_c * tx_c + ty_c * ty_c + tz_c * tz_c) || 1.0;
|
|
1695
|
+
out_ct[0] = tx_c / ct_l;
|
|
1696
|
+
out_ct[1] = ty_c / ct_l;
|
|
1697
|
+
out_ct[2] = tz_c / ct_l;
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1700
|
+
const cand_dr = [0, 0, 0];
|
|
1701
|
+
const cand_ct = [0, 0, 0];
|
|
1702
|
+
compute_child_basis(base_phi_c, cand_dr, cand_ct);
|
|
1703
|
+
|
|
1704
|
+
// 1. Raw configured child branch length modulated by length_profile at t_genealogical and crown envelope
|
|
1705
|
+
const prof_scale = Math.max(0.12, next_cfg.length_profile.evaluate(t_genealogical));
|
|
1706
|
+
const est_mid_y = SCRATCH_POS[1] + Math.max(0.0, cand_ct[1]) * next_cfg.length * prof_scale * 0.35;
|
|
1707
|
+
const h_norm = clamp((est_mid_y - crown.base_height) / Math.max(0.1, crown.height), 0.0, 1.0);
|
|
1708
|
+
const env_scale = level === 0
|
|
1709
|
+
? crown_envelope_evaluate(crown.envelope_type, h_norm)
|
|
1710
|
+
: lerp(1.0, crown_envelope_evaluate(crown.envelope_type, h_norm), 0.5);
|
|
1711
|
+
const raw_child_len = Math.max(0.12, next_cfg.length * prof_scale * env_scale * var_scale);
|
|
1712
|
+
|
|
1713
|
+
// 2. Continuous Gravity-Orientation & Allometric Slenderness Coupling (Cantilever vs. Pendulous Tension)
|
|
1714
|
+
const rho_design = Math.max(0.012, next_cfg.radius_ratio);
|
|
1715
|
+
const s_design = 1.0 / (2.0 * rho_design);
|
|
1716
|
+
const g_trop = next_cfg.gravitropism ?? 0.0;
|
|
1717
|
+
const u_curl = next_cfg.tip_upcurl ?? 0.0;
|
|
1718
|
+
const t_eff_y = clamp(cand_ct[1] - 0.85 * g_trop + 0.35 * u_curl, -1.0, 1.0);
|
|
1719
|
+
|
|
1720
|
+
// Cantilever load (horizontal bending / upward compression) vs. pendulous tensile compliance
|
|
1721
|
+
const k_cantilever = Math.sqrt(Math.max(0.0, 1.0 - t_eff_y * t_eff_y)) + 0.65 * Math.max(0.0, t_eff_y);
|
|
1722
|
+
const tau_tension = clamp(g_trop, 0.0, 1.0) * Math.max(0.0, -t_eff_y);
|
|
1723
|
+
|
|
1724
|
+
// 3. Parent-relative distal conical envelope (prevents distal shoots or split forks from outgrowing their parent)
|
|
1725
|
+
const actual_parent_len = Math.max(0.15, out.branch_length[branch_id]);
|
|
1726
|
+
const parent_full_len = Math.max(actual_parent_len, job.full_branch_length ?? actual_parent_len);
|
|
1727
|
+
const distal_tip_ratio = lerp(0.32, 0.55, tau_tension);
|
|
1728
|
+
const cone_max_len = level === 0
|
|
1729
|
+
? parent_full_len * lerp(0.85, 0.32, Math.pow(t_genealogical, 1.6))
|
|
1730
|
+
: Math.min(
|
|
1731
|
+
actual_parent_len * lerp(0.90, 1.05, tau_tension),
|
|
1732
|
+
parent_full_len * lerp(0.85, distal_tip_ratio, Math.pow(t_genealogical, 1.25))
|
|
1733
|
+
);
|
|
1734
|
+
const desired_len = Math.max(0.12, Math.min(raw_child_len, cone_max_len));
|
|
1735
|
+
|
|
1736
|
+
const nom_len = Math.max(0.15, next_cfg.length);
|
|
1737
|
+
const r_design = (nom_len * rho_design * Math.pow(desired_len / nom_len, 0.75)) / (1.0 + 0.65 * tau_tension);
|
|
1738
|
+
const child_base_r = Math.min(
|
|
1739
|
+
parent_r_at_attach * 0.78,
|
|
1740
|
+
Math.max(0.0035, r_design)
|
|
1741
|
+
);
|
|
1742
|
+
|
|
1743
|
+
const gamma = lerp(0.82, 0.65, tau_tension);
|
|
1744
|
+
const allometric_len = child_base_r < r_design
|
|
1745
|
+
? desired_len * Math.pow(child_base_r / Math.max(1e-5, r_design), gamma)
|
|
1746
|
+
: desired_len;
|
|
1747
|
+
const s_max = s_design * (1.35 + 0.65 * tau_tension) / (0.65 + 0.35 * k_cantilever);
|
|
1748
|
+
const child_len = clamp(Math.min(allometric_len, 2.0 * child_base_r * s_max), 0.12, desired_len);
|
|
1749
|
+
const child_segs = Math.max(1, next_cfg.segments);
|
|
1750
|
+
const child_step = child_len / child_segs;
|
|
1751
|
+
|
|
1752
|
+
// 4. Pre-Spawn BVH Bud Clearance Probing (selects open canopy azimuth or inhibits blocked bud)
|
|
1753
|
+
let best_drx = cand_dr[0], best_dry = cand_dr[1], best_drz = cand_dr[2];
|
|
1754
|
+
let best_ctx = cand_ct[0], best_cty = cand_ct[1], best_ctz = cand_ct[2];
|
|
1755
|
+
let best_clear = -Infinity;
|
|
1756
|
+
const r_horiz_attach = Math.hypot(SCRATCH_POS[0], SCRATCH_POS[2]);
|
|
1757
|
+
|
|
1758
|
+
for (let k = 0; k < PHI_CANDIDATES.length; k++) {
|
|
1759
|
+
if (k > 0) {
|
|
1760
|
+
compute_child_basis(base_phi_c + PHI_CANDIDATES[k], cand_dr, cand_ct);
|
|
1761
|
+
}
|
|
1762
|
+
// Prevent lateral shoots on boughs from pointing backward into the trunk core
|
|
1763
|
+
if (level >= 1 && r_horiz_attach > 0.30) {
|
|
1764
|
+
const radial_dot = (SCRATCH_POS[0] * cand_ct[0] + SCRATCH_POS[2] * cand_ct[2]) / r_horiz_attach;
|
|
1765
|
+
if (radial_dot < -0.15) continue;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
const p1_x = SCRATCH_POS[0] + cand_ct[0] * child_step;
|
|
1769
|
+
const p1_y = SCRATCH_POS[1] + cand_ct[1] * child_step;
|
|
1770
|
+
const p1_z = SCRATCH_POS[2] + cand_ct[2] * child_step;
|
|
1771
|
+
|
|
1772
|
+
const clear0 = evaluate_candidate_clearance(
|
|
1773
|
+
-1, branch_id, seg_idx, false, 0,
|
|
1774
|
+
SCRATCH_POS[0], SCRATCH_POS[1], SCRATCH_POS[2], child_base_r,
|
|
1775
|
+
p1_x, p1_y, p1_z, child_base_r * 0.88,
|
|
1776
|
+
0.002
|
|
1777
|
+
);
|
|
1778
|
+
|
|
1779
|
+
const p2_x = p1_x + cand_ct[0] * child_step;
|
|
1780
|
+
const p2_y = p1_y + cand_ct[1] * child_step;
|
|
1781
|
+
const p2_z = p1_z + cand_ct[2] * child_step;
|
|
1782
|
+
const clear1 = child_segs >= 2
|
|
1783
|
+
? evaluate_candidate_clearance(
|
|
1784
|
+
-1, branch_id, seg_idx, false, 1,
|
|
1785
|
+
p1_x, p1_y, p1_z, child_base_r * 0.88,
|
|
1786
|
+
p2_x, p2_y, p2_z, child_base_r * 0.75,
|
|
1787
|
+
0.002
|
|
1788
|
+
)
|
|
1789
|
+
: clear0;
|
|
1790
|
+
|
|
1791
|
+
const min_c = Math.min(clear0, clear1);
|
|
1792
|
+
if (min_c > best_clear) {
|
|
1793
|
+
best_clear = min_c;
|
|
1794
|
+
best_drx = cand_dr[0];
|
|
1795
|
+
best_dry = cand_dr[1];
|
|
1796
|
+
best_drz = cand_dr[2];
|
|
1797
|
+
best_ctx = cand_ct[0];
|
|
1798
|
+
best_cty = cand_ct[1];
|
|
1799
|
+
best_ctz = cand_ct[2];
|
|
1800
|
+
}
|
|
1801
|
+
if (min_c >= 0.004) {
|
|
1802
|
+
break;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// Botanical bud inhibition: if all candidate directions are blocked by existing branches, suppress bud
|
|
1807
|
+
if (best_clear < -0.004) {
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
const drx = best_drx, dry = best_dry, drz = best_drz;
|
|
1812
|
+
const ctx = best_ctx, cty = best_cty, ctz = best_ctz;
|
|
1813
|
+
|
|
1814
|
+
// Initial RMF radial vector for child branch:
|
|
1815
|
+
// R_c = cos(alpha) * D_rad - sin(alpha) * T_p is exact unit length and orthogonal
|
|
1816
|
+
// to T_c = cos(alpha) * T_p + sin(alpha) * D_rad for all alpha in [0, pi],
|
|
1817
|
+
// avoiding any singularity at alpha = pi/2.
|
|
1818
|
+
let crx = cos_a * drx - sin_a * SCRATCH_TAN[0];
|
|
1819
|
+
let cry = cos_a * dry - sin_a * SCRATCH_TAN[1];
|
|
1820
|
+
let crz = cos_a * drz - sin_a * SCRATCH_TAN[2];
|
|
1821
|
+
const cr_dot = crx * ctx + cry * cty + crz * ctz;
|
|
1822
|
+
crx -= cr_dot * ctx;
|
|
1823
|
+
cry -= cr_dot * cty;
|
|
1824
|
+
crz -= cr_dot * ctz;
|
|
1825
|
+
const cr_len = Math.sqrt(crx * crx + cry * cry + crz * crz);
|
|
1826
|
+
if (cr_len > 1e-12) {
|
|
1827
|
+
crx /= cr_len;
|
|
1828
|
+
cry /= cr_len;
|
|
1829
|
+
crz /= cr_len;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
queue.push({
|
|
1833
|
+
level: next_level,
|
|
1834
|
+
parent_branch_id: branch_id,
|
|
1835
|
+
parent_edge_id: parent_e,
|
|
1836
|
+
parent_attach_seg_idx: seg_idx,
|
|
1837
|
+
parent_attach_t: t_attach,
|
|
1838
|
+
attach_parent_vertex: p_v0,
|
|
1839
|
+
start_vertex: -1,
|
|
1840
|
+
px: SCRATCH_POS[0],
|
|
1841
|
+
py: SCRATCH_POS[1],
|
|
1842
|
+
pz: SCRATCH_POS[2],
|
|
1843
|
+
tx: ctx,
|
|
1844
|
+
ty: cty,
|
|
1845
|
+
tz: ctz,
|
|
1846
|
+
rx: crx,
|
|
1847
|
+
ry: cry,
|
|
1848
|
+
rz: crz,
|
|
1849
|
+
emerge_drx: drx,
|
|
1850
|
+
emerge_dry: dry,
|
|
1851
|
+
emerge_drz: drz,
|
|
1852
|
+
length: child_len,
|
|
1853
|
+
full_branch_length: child_len,
|
|
1854
|
+
base_radius: child_base_r,
|
|
1855
|
+
start_seg: 0,
|
|
1856
|
+
total_segs: child_segs,
|
|
1857
|
+
initial_arc_s: 0.0,
|
|
1858
|
+
has_split: false,
|
|
1859
|
+
is_split_fork: false,
|
|
1860
|
+
cum_t0: 0.0,
|
|
1861
|
+
cum_t1: 1.0
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// Bottom-up Pipe Model radius calculation & blending
|
|
1868
|
+
tree_skeleton_compute_pipe_radii(out, species);
|
|
1869
|
+
|
|
1870
|
+
return out;
|
|
1871
|
+
}
|