@woosh/meep-engine 3.58.0 → 3.59.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/editor/particles/effect/ParticleEffectDocument.d.ts +162 -43
- package/editor/particles/effect/ParticleEffectDocument.d.ts.map +1 -1
- package/editor/particles/effect/ParticleEffectDocument.js +450 -43
- package/editor/particles/effect/ParticleReferenceSimulation.d.ts +12 -3
- package/editor/particles/effect/ParticleReferenceSimulation.d.ts.map +1 -1
- package/editor/particles/effect/ParticleReferenceSimulation.js +78 -23
- package/editor/particles/effect/make_starter_particle_effect.js +17 -17
- package/editor/view/particles/effect/ParticleEmitterInspectorView.d.ts +3 -3
- package/editor/view/particles/effect/ParticleEmitterInspectorView.d.ts.map +1 -1
- package/editor/view/particles/effect/ParticleEmitterInspectorView.js +224 -67
- package/editor/view/particles/effect/ParticleGraphEditorView.d.ts.map +1 -1
- package/editor/view/particles/effect/ParticleGraphEditorView.js +44 -11
- package/editor/view/particles/effect/ParticleNodeParametersView.d.ts +2 -1
- package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
- package/editor/view/particles/effect/ParticleNodeParametersView.js +46 -16
- package/editor/view/particles/effect/ParticlePreviewView.js +2 -2
- package/editor/view/particles/effect/particle-editor.css +77 -68
- package/package.json +1 -1
- package/src/engine/Engine.d.ts.map +1 -1
- package/src/engine/Engine.js +2 -2
- package/src/engine/ecs/EntityManager.d.ts +29 -5
- package/src/engine/ecs/EntityManager.d.ts.map +1 -1
- package/src/engine/ecs/EntityManager.js +37 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +4 -0
- package/src/engine/graphics/ecs/particles/ParticleEffect.d.ts +74 -17
- package/src/engine/graphics/ecs/particles/ParticleEffect.d.ts.map +1 -1
- package/src/engine/graphics/ecs/particles/ParticleEffect.js +156 -25
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.d.ts +13 -3
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.d.ts.map +1 -1
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.js +59 -13
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationUpgrader_1_2.d.ts +3 -3
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationUpgrader_1_2.js +9 -9
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationUpgrader_2_3.d.ts +15 -0
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationUpgrader_2_3.d.ts.map +1 -0
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationUpgrader_2_3.js +78 -0
- package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts +2 -0
- package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail3d/Trail3D.js +2 -0
- package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.js +1 -1
- package/src/engine/graphics/ecs/trail3d/seed_trail_tube.js +1 -1
- package/src/engine/graphics/trail/tube/TubeX.d.ts +5 -3
- package/src/engine/graphics/trail/tube/TubeX.d.ts.map +1 -1
- package/src/engine/graphics/trail/tube/TubeX.js +5 -3
- package/src/engine/graphics/trail/tube/tube_attributes_spec.d.ts +3 -2
- package/src/engine/graphics/trail/tube/tube_attributes_spec.d.ts.map +1 -1
- package/src/engine/graphics/trail/tube/tube_attributes_spec.js +9 -8
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +65 -4
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
- package/src/engine/graphics3/GPUParticleEmitterSystem.js +1351 -904
- package/src/engine/graphics3/trail/trail_tube_write_geometry.d.ts +1 -1
- package/src/engine/graphics3/trail/trail_tube_write_geometry.js +13 -13
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/generation/foliage/FOLIAGE_DESIGN.md +26 -5
- package/src/generation/foliage/LOD_REWORK_BRIEF_2026_10_27.md +35 -0
- package/src/generation/foliage/SELF_ORGANIZING_FRAMEWORK_BRIEF_2026_10_26.md +2 -1
- package/src/generation/foliage/TWO_FRONT_ENDS_BRIEF_2026_10_28.md +552 -0
- package/src/generation/foliage/analysis/tree_crown_opacity.d.ts.map +1 -1
- package/src/generation/foliage/analysis/tree_crown_opacity.js +2 -1
- package/src/generation/foliage/analysis/tree_render_cost.d.ts +5 -3
- package/src/generation/foliage/analysis/tree_render_cost.d.ts.map +1 -1
- package/src/generation/foliage/analysis/tree_render_cost.js +8 -7
- package/src/generation/foliage/asset/TreeAsset.d.ts +17 -0
- package/src/generation/foliage/asset/TreeAsset.d.ts.map +1 -1
- package/src/generation/foliage/asset/TreeAsset.js +20 -0
- package/src/generation/foliage/asset/TreeSpeciesCache.d.ts +69 -0
- package/src/generation/foliage/asset/TreeSpeciesCache.d.ts.map +1 -0
- package/src/generation/foliage/asset/TreeSpeciesCache.js +147 -0
- package/src/generation/foliage/asset/generate_tree.d.ts +10 -0
- package/src/generation/foliage/asset/generate_tree.d.ts.map +1 -1
- package/src/generation/foliage/asset/generate_tree.js +57 -29
- package/src/generation/foliage/asset/place_tree.d.ts +2 -1
- package/src/generation/foliage/asset/place_tree.d.ts.map +1 -1
- package/src/generation/foliage/asset/place_tree.js +2 -1
- package/src/generation/foliage/asset/tree_lod_plan.d.ts +4 -2
- package/src/generation/foliage/asset/tree_lod_plan.d.ts.map +1 -1
- package/src/generation/foliage/asset/tree_lod_plan.js +12 -14
- package/src/generation/foliage/asset/tree_shoot_instances.d.ts +119 -0
- package/src/generation/foliage/asset/tree_shoot_instances.d.ts.map +1 -0
- package/src/generation/foliage/asset/tree_shoot_instances.js +410 -0
- package/src/generation/foliage/asset/tree_shoot_prototypes.d.ts +155 -0
- package/src/generation/foliage/asset/tree_shoot_prototypes.d.ts.map +1 -0
- package/src/generation/foliage/asset/tree_shoot_prototypes.js +251 -0
- package/src/generation/foliage/config/TreeGrowth.d.ts +10 -0
- package/src/generation/foliage/config/TreeGrowth.d.ts.map +1 -0
- package/src/generation/foliage/config/TreeGrowth.js +18 -0
- package/src/generation/foliage/config/TreeLODConfig.d.ts +6 -0
- package/src/generation/foliage/config/TreeLODConfig.d.ts.map +1 -1
- package/src/generation/foliage/config/TreeLODConfig.js +7 -0
- package/src/generation/foliage/index.d.ts +3 -0
- package/src/generation/foliage/index.js +3 -0
- package/src/generation/foliage/material/foliage_tile.d.ts +6 -6
- package/src/generation/foliage/material/foliage_tile.js +3 -3
- package/src/generation/foliage/material/tree_cluster_atlas.d.ts +44 -0
- package/src/generation/foliage/material/tree_cluster_atlas.d.ts.map +1 -0
- package/src/generation/foliage/material/tree_cluster_atlas.js +37 -0
- package/src/generation/foliage/mesh/foliage_collect_attachments.js +2 -2
- package/src/generation/foliage/mesh/foliage_scale_spread.d.ts +14 -0
- package/src/generation/foliage/mesh/foliage_scale_spread.d.ts.map +1 -0
- package/src/generation/foliage/mesh/foliage_scale_spread.js +15 -0
- package/src/generation/foliage/mesh/tree_cluster_representatives.d.ts +8 -7
- package/src/generation/foliage/mesh/tree_cluster_representatives.d.ts.map +1 -1
- package/src/generation/foliage/mesh/tree_cluster_representatives.js +85 -40
- package/src/generation/foliage/mesh/tree_clusters.d.ts +26 -5
- package/src/generation/foliage/mesh/tree_clusters.d.ts.map +1 -1
- package/src/generation/foliage/mesh/tree_clusters.js +52 -39
- package/src/generation/foliage/mesh/tree_mesh_build_bark.d.ts.map +1 -1
- package/src/generation/foliage/mesh/tree_mesh_build_bark.js +32 -54
- package/src/generation/foliage/presets/make_tree_species_preset.d.ts +6 -1
- package/src/generation/foliage/presets/make_tree_species_preset.d.ts.map +1 -1
- package/src/generation/foliage/presets/make_tree_species_preset.js +157 -3
- package/src/generation/foliage/skeleton/TreeSkeleton.d.ts +19 -0
- package/src/generation/foliage/skeleton/TreeSkeleton.d.ts.map +1 -1
- package/src/generation/foliage/skeleton/TreeSkeleton.js +41 -0
- package/src/generation/foliage/skeleton/tree_skeleton_compute_joint_mechanics.d.ts +5 -0
- package/src/generation/foliage/skeleton/tree_skeleton_compute_joint_mechanics.d.ts.map +1 -1
- package/src/generation/foliage/skeleton/tree_skeleton_compute_joint_mechanics.js +8 -1
- package/src/generation/foliage/skeleton/tree_skeleton_copy_branches.d.ts +70 -0
- package/src/generation/foliage/skeleton/tree_skeleton_copy_branches.d.ts.map +1 -0
- package/src/generation/foliage/skeleton/tree_skeleton_copy_branches.js +171 -0
- package/src/generation/foliage/skeleton/tree_skeleton_extend_branch.d.ts +17 -0
- package/src/generation/foliage/skeleton/tree_skeleton_extend_branch.d.ts.map +1 -0
- package/src/generation/foliage/skeleton/tree_skeleton_extend_branch.js +79 -0
- package/src/generation/foliage/skeleton/tree_skeleton_fit_crown_frame.d.ts +12 -0
- package/src/generation/foliage/skeleton/tree_skeleton_fit_crown_frame.d.ts.map +1 -0
- package/src/generation/foliage/skeleton/tree_skeleton_fit_crown_frame.js +56 -0
- package/src/generation/foliage/skeleton/tree_skeleton_floor_girth.d.ts +11 -0
- package/src/generation/foliage/skeleton/tree_skeleton_floor_girth.d.ts.map +1 -0
- package/src/generation/foliage/skeleton/tree_skeleton_floor_girth.js +38 -0
- package/src/generation/foliage/skeleton/tree_skeleton_grow.d.ts +4 -11
- package/src/generation/foliage/skeleton/tree_skeleton_grow.d.ts.map +1 -1
- package/src/generation/foliage/skeleton/tree_skeleton_grow.js +8 -2034
- package/src/generation/foliage/skeleton/tree_skeleton_grow_framework.d.ts +22 -0
- package/src/generation/foliage/skeleton/tree_skeleton_grow_framework.d.ts.map +1 -0
- package/src/generation/foliage/skeleton/tree_skeleton_grow_framework.js +2050 -0
- package/src/generation/foliage/skeleton/tree_skeleton_grow_shoots.d.ts +10 -0
- package/src/generation/foliage/skeleton/tree_skeleton_grow_shoots.d.ts.map +1 -1
- package/src/generation/foliage/skeleton/tree_skeleton_grow_shoots.js +27 -56
- package/src/shade/descriptor/pipeline/render/DepthStencilState.d.ts +4 -0
- package/src/shade/descriptor/pipeline/render/DepthStencilState.d.ts.map +1 -1
- package/src/shade/descriptor/pipeline/render/DepthStencilState.js +170 -144
- package/src/shade/descriptor/pipeline/render/MultisampleState.d.ts +4 -0
- package/src/shade/descriptor/pipeline/render/MultisampleState.d.ts.map +1 -1
- package/src/shade/descriptor/pipeline/render/MultisampleState.js +78 -65
- package/src/shade/descriptor/pipeline/render/RenderPipelineDescriptor.d.ts.map +1 -1
- package/src/shade/descriptor/pipeline/render/RenderPipelineDescriptor.js +176 -172
- package/src/shade/descriptor/pipeline/render/StencilFaceState.d.ts +4 -0
- package/src/shade/descriptor/pipeline/render/StencilFaceState.d.ts.map +1 -1
- package/src/shade/descriptor/pipeline/render/StencilFaceState.js +107 -88
- package/src/shade/playground/particle_ecs/README.md +9 -8
- package/src/shade/playground/particle_ecs/bonfire_editor.js +3 -3
- package/src/shade/playground/particle_ecs/bonfire_effects.d.ts +6 -6
- package/src/shade/playground/particle_ecs/bonfire_effects.js +6 -6
- package/src/shade/playground/particle_editor/README.md +14 -11
- package/src/shade/playground/particle_system/particle_prototype.js +18 -18
- package/src/shade/playground/particle_system/particle_scene.js +13 -13
- package/src/shade/playground/probe_clipmap/README.md +42 -0
- package/src/shade/playground/probe_clipmap/index.html +178 -0
- package/src/shade/playground/probe_clipmap/main.d.ts +8 -0
- package/src/shade/playground/probe_clipmap/main.d.ts.map +1 -0
- package/src/shade/playground/probe_clipmap/main.js +360 -0
- package/src/shade/renderer/Renderer.d.ts +3 -1
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +94 -38
- package/src/shade/renderer/extension/ViewTextures.d.ts +2 -1
- package/src/shade/renderer/extension/ViewTextures.d.ts.map +1 -1
- package/src/shade/renderer/extension/ViewTextures.js +2 -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/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +34 -12
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +56 -15
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts +2 -2
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +13 -26
- package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.d.ts +25 -2
- package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.js +54 -32
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_VERDICT.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_VERDICT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_VERDICT.js +37 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_arena.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_arena.js +16 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_claim.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_claim.js +2 -6
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_descent.d.ts +131 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_descent.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_descent.js +467 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_instance.d.ts +24 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_instance.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_instance.js +111 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_rule.d.ts +42 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_rule.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_rule.js +404 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_stat.d.ts +16 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_stat.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_stat.js +23 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_want.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_want.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_want.js +65 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.js +14 -70
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +13 -773
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_cut_descent.d.ts +55 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_cut_descent.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_cut_descent.js +248 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +15 -174
- package/src/shade/renderer/global_illumination/brick4/gpu/draw/chunk_brick4_probe_meta_pick2.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/brick4/gpu/draw/chunk_brick4_probe_meta_pick2.js +8 -24
- package/src/shade/renderer/global_illumination/brick4/gpu/draw/chunk_brick4_sample_forward_indirect.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/brick4/gpu/draw/chunk_brick4_sample_forward_indirect.js +1 -25
- package/src/shade/renderer/global_illumination/lpv/deprecated/chunk_lpv_get_visibility_mask.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/lpv/deprecated/chunk_lpv_get_visibility_mask.js +5 -23
- package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts +25 -4
- package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +126 -8
- package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +832 -4
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/BRICK_CLIPMAP.d.ts +7 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/BRICK_CLIPMAP.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/BRICK_CLIPMAP.js +8 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/GPUBrickClipmap.d.ts +16 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/GPUBrickClipmap.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/GPUBrickClipmap.js +9 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/{mesh/chunk_mesh_brick_object.d.ts → chunk_brick_object.d.ts} +8 -8
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/chunk_brick_object.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/chunk_brick_object.js +42 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/chunk_brick_provider.d.ts +45 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/chunk_brick_provider.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/chunk_brick_provider.js +93 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/MESH_BRICK_INSTANCE_STRUCT.d.ts +0 -7
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/MESH_BRICK_INSTANCE_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/MESH_BRICK_INSTANCE_STRUCT.js +0 -8
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_instance.d.ts +2 -4
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_instance.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_instance.js +5 -8
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_provider.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/shader_mesh_brick_provider.js +13 -22
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/VGEO_BRICK.d.ts +99 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/VGEO_BRICK.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/VGEO_BRICK.js +139 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/VGeoBrickProvider.d.ts +65 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/VGeoBrickProvider.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/VGeoBrickProvider.js +393 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_box.d.ts +17 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_box.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_box.js +36 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_pair.d.ts +29 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_pair.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_pair.js +108 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_texel.d.ts +31 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_texel.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_texel.js +113 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_triangle.d.ts +36 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_triangle.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/chunk_vgeo_brick_triangle.js +85 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_distance.d.ts +23 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_distance.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_distance.js +273 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_gather.d.ts +13 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_gather.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_gather.js +151 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_traverse.d.ts +13 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_traverse.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_traverse.js +133 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_wait.d.ts +22 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_wait.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/vgeo/shader_vgeo_brick_wait.js +135 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +20 -6
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +152 -10
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.d.ts +7 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.js +8 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.d.ts +14 -2
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.js +50 -5
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_cage.d.ts +19 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_cage.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_cage.js +40 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_pick.d.ts +72 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_pick.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_pick.js +343 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.d.ts +29 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.js +188 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_want.d.ts +26 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_want.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_want.js +132 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.d.ts +4 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.js +26 -3
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_admit.d.ts +3 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_admit.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_admit.js +8 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_diffuse.d.ts +12 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_diffuse.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_diffuse.js +101 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_due.d.ts +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_due.js +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_indirect_lighting.d.ts +15 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_indirect_lighting.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_indirect_lighting.js +175 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_light.d.ts +26 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_light.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_light.js +210 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_retire.d.ts +3 -3
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_retire.js +3 -3
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts +2 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js +5 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_trace.js +3 -10
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_want.d.ts +5 -9
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_want.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_want.js +9 -110
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_want_hits.d.ts +27 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_want_hits.d.ts.map +1 -0
- package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_want_hits.js +110 -0
- package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.d.ts +13 -0
- package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.d.ts.map +1 -1
- package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.js +40 -0
- package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.d.ts +11 -6
- package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.d.ts.map +1 -1
- package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.js +15 -8
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.d.ts +9 -2
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.d.ts.map +1 -1
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.js +25 -11
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/particles/DESIGN.md +151 -42
- package/src/shade/renderer/particles/EMITTER_GLOBAL_STATE_PLAN_2026_10_29.md +283 -0
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts +10 -2
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.js +46 -8
- package/src/shade/renderer/particles/ParticleConstants.d.ts +23 -6
- package/src/shade/renderer/particles/ParticleConstants.d.ts.map +1 -1
- package/src/shade/renderer/particles/ParticleConstants.js +25 -6
- package/src/shade/renderer/particles/bounds/chunk_particle_bounds.js +5 -5
- package/src/shade/renderer/particles/bounds/shader_particle_bounds.js +187 -187
- package/src/shade/renderer/particles/data/PARTICLE_DATABASE_SPEC.d.ts +20 -9
- package/src/shade/renderer/particles/data/PARTICLE_DATABASE_SPEC.d.ts.map +1 -1
- package/src/shade/renderer/particles/data/PARTICLE_DATABASE_SPEC.js +36 -10
- package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts +8 -7
- package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.js +25 -16
- package/src/shade/renderer/particles/data/PARTICLE_FIELD_BINDING.d.ts +51 -0
- package/src/shade/renderer/particles/data/PARTICLE_FIELD_BINDING.d.ts.map +1 -0
- package/src/shade/renderer/particles/data/PARTICLE_FIELD_BINDING.js +56 -0
- package/src/shade/renderer/particles/data/particle_emitter_record.d.ts.map +1 -1
- package/src/shade/renderer/particles/data/particle_emitter_record.js +2 -0
- package/src/shade/renderer/particles/graph/ParticleGroupBuilder.d.ts +1 -1
- package/src/shade/renderer/particles/graph/ParticleGroupBuilder.js +1 -1
- package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +22 -11
- package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +14 -11
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +4 -4
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +59 -24
- package/src/shade/renderer/particles/graph/ParticleValueType.d.ts +3 -3
- package/src/shade/renderer/particles/graph/ParticleValueType.js +287 -287
- package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts +13 -6
- package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/compile_particle_graph.js +46 -27
- package/src/shade/renderer/particles/graph/infer_particle_graph_types.d.ts +8 -2
- package/src/shade/renderer/particles/graph/infer_particle_graph_types.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/infer_particle_graph_types.js +240 -234
- package/src/shade/renderer/particles/graph/make_particle_group.d.ts +1 -1
- package/src/shade/renderer/particles/graph/make_particle_group.js +1 -1
- package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts +3 -2
- package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/particle_graph_authoring.js +3 -2
- package/src/shade/renderer/particles/graph/particle_graph_distributions.js +170 -170
- package/src/shade/renderer/particles/graph/particle_graph_json.d.ts +25 -16
- package/src/shade/renderer/particles/graph/particle_graph_json.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/particle_graph_json.js +16 -2
- package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts +29 -15
- package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/particle_node_parameters.js +54 -22
- package/src/shade/renderer/particles/graph/particle_node_presentation.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/particle_node_presentation.js +18 -8
- package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts +9 -2
- package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/validate_particle_graph.js +70 -32
- package/src/shade/renderer/particles/graph_particles.d.ts +9 -4
- package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph_particles.js +27 -4
- package/src/shade/renderer/particles/isa/InstructionStream.d.ts +7 -6
- package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -1
- package/src/shade/renderer/particles/isa/InstructionStream.js +17 -16
- package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts +17 -0
- package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts.map +1 -1
- package/src/shade/renderer/particles/isa/ParticleAssembler.js +35 -0
- package/src/shade/renderer/particles/isa/ParticleProgram.d.ts +26 -6
- package/src/shade/renderer/particles/isa/ParticleProgram.d.ts.map +1 -1
- package/src/shade/renderer/particles/isa/ParticleProgram.js +61 -9
- package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts +55 -4
- package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts.map +1 -1
- package/src/shade/renderer/particles/isa/ParticleVMISA.js +79 -10
- package/src/shade/renderer/particles/isa/particle_assembly.d.ts +20 -6
- package/src/shade/renderer/particles/isa/particle_assembly.d.ts.map +1 -1
- package/src/shade/renderer/particles/isa/particle_assembly.js +136 -25
- package/src/shade/renderer/particles/isa/particle_link_global_fields.d.ts +20 -0
- package/src/shade/renderer/particles/isa/particle_link_global_fields.d.ts.map +1 -0
- package/src/shade/renderer/particles/isa/particle_link_global_fields.js +46 -0
- package/src/shade/renderer/particles/isa/validate_particle_program.js +8 -8
- package/src/shade/renderer/particles/layout/ParticleLayout.d.ts +62 -28
- package/src/shade/renderer/particles/layout/ParticleLayout.d.ts.map +1 -1
- package/src/shade/renderer/particles/layout/ParticleLayout.js +102 -62
- package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.js +2 -0
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.js +1 -1
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.js +4 -3
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.js +2 -2
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts +3 -3
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.js +11 -11
- package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts +4 -4
- package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/optimize_particle_program.js +5 -4
- package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts +1 -1
- package/src/shade/renderer/particles/optimizer/particle_integer_bounds.js +1 -1
- package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts +4 -4
- package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_program_equivalence.js +13 -11
- package/src/shade/renderer/particles/optimizer/particle_program_fuzz.js +7 -7
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts +3 -2
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +5 -4
- package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_eliminate_dead_code.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_eliminate_dead_code.js +51 -51
- package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +5 -5
- package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_narrow_widths.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_pack_registers.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_peephole.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_propagate_copies.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_schedule.js +2 -2
- package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.js +5 -4
- package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts +93 -22
- package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts.map +1 -1
- package/src/shade/renderer/particles/runtime/EmitterRegistry.js +336 -29
- package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts +43 -3
- package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts.map +1 -1
- package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.js +43 -3
- package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +56 -26
- package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
- package/src/shade/renderer/particles/runtime/ParticleEmitter.js +89 -33
- package/src/shade/renderer/particles/runtime/ParticleGlobalFields.d.ts +85 -0
- package/src/shade/renderer/particles/runtime/ParticleGlobalFields.d.ts.map +1 -0
- package/src/shade/renderer/particles/runtime/ParticleGlobalFields.js +221 -0
- package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts +143 -18
- package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts.map +1 -1
- package/src/shade/renderer/particles/runtime/ProgramHeap.js +378 -28
- package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +19 -7
- package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
- package/src/shade/renderer/particles/runtime/create_particle_effect.js +22 -9
- package/src/shade/renderer/particles/shaders/chunk_particle_billboard_vertex.js +8 -8
- package/src/shade/renderer/particles/shaders/chunk_particle_render_math.js +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_setup_context.d.ts +3 -0
- package/src/shade/renderer/particles/shaders/chunk_particle_setup_context.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_setup_context.js +4 -0
- package/src/shade/renderer/particles/shaders/shader_particle_field_bind.d.ts +30 -0
- package/src/shade/renderer/particles/shaders/shader_particle_field_bind.d.ts.map +1 -0
- package/src/shade/renderer/particles/shaders/shader_particle_field_bind.js +121 -0
- package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.js +3 -1
- package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +12 -4
- package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
- package/src/shade/renderer/particles/vm/ParticleVMReference.js +48 -11
- package/src/shade/renderer/particles/vm/chunk_particle_vm.d.ts.map +1 -1
- package/src/shade/renderer/particles/vm/chunk_particle_vm.js +36 -14
- package/src/shade/renderer/postprocess/PostProcess.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/PostProcess.js +2 -1
- package/src/shade/renderer/postprocess/gtao/GTAO.d.ts +31 -19
- package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/GTAO.js +44 -30
- package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.js +16 -2
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/postprocess/nss/NSS.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/nss/NSS.js +4 -3
- package/src/shade/renderer/postprocess/ssr/SSR.d.ts +7 -1
- package/src/shade/renderer/postprocess/ssr/SSR.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/SSR.js +16 -0
- package/src/shade/renderer/postprocess/ssr/resolve/chunk_ssr_add_hit_irradiance.d.ts +20 -2
- package/src/shade/renderer/postprocess/ssr/resolve/chunk_ssr_add_hit_irradiance.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/resolve/chunk_ssr_add_hit_irradiance.js +53 -3
- package/src/shade/renderer/postprocess/ssr/resolve/chunk_ssr_sample_radiance.d.ts +10 -2
- package/src/shade/renderer/postprocess/ssr/resolve/chunk_ssr_sample_radiance.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/resolve/chunk_ssr_sample_radiance.js +31 -3
- package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader.js +17 -11
- package/src/shade/renderer/postprocess/taa/TAA.d.ts +4 -2
- package/src/shade/renderer/postprocess/taa/TAA.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/taa/TAA.js +6 -1
- package/src/shade/renderer/postprocess/taa/chunk_fsr2_compute_depth_clip.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/taa/chunk_fsr2_compute_depth_clip.js +10 -5
- package/src/shade/renderer/postprocess/taa/shader_taa.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/taa/shader_taa.js +15 -12
- package/src/shade/renderer/postprocess/velocity/graph_add_per_object_velocity.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/velocity/graph_add_per_object_velocity.js +26 -17
- package/src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect.d.ts +4 -3
- package/src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect.js +6 -4
- package/src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect_probe_clipmap.d.ts +12 -0
- package/src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect_probe_clipmap.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/forward/chunk_forward_sample_indirect_probe_clipmap.js +57 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts +4 -2
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.js +4 -2
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts +8 -2
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +28 -15
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw.d.ts +2 -1
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw.js +19 -3
- package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.js +4 -2
- package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.d.ts +3 -1
- package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.js +27 -14
- package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments.d.ts +2 -1
- package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments.js +19 -3
- package/src/shade/renderer/shader/chunk/geometry/meshlet/chunk_read_meshlet_triangle_bounds.d.ts +2 -8
- package/src/shade/renderer/shader/chunk/geometry/meshlet/chunk_read_meshlet_triangle_bounds.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/meshlet/chunk_read_meshlet_triangle_bounds.js +25 -52
- package/src/shade/renderer/shader/chunk/geometry/meshlet/chunk_read_meshlet_triangle_positions.d.ts +15 -0
- package/src/shade/renderer/shader/chunk/geometry/meshlet/chunk_read_meshlet_triangle_positions.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/meshlet/chunk_read_meshlet_triangle_positions.js +52 -0
- package/src/shade/renderer/shader/chunk/geometry/triangle/TRIANGLE_POSITIONS_STRUCT.d.ts +7 -0
- package/src/shade/renderer/shader/chunk/geometry/triangle/TRIANGLE_POSITIONS_STRUCT.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/triangle/TRIANGLE_POSITIONS_STRUCT.js +12 -0
- package/src/shade/renderer/shader/chunk/grid/chunk_index_to_grid2d_reciprocal.d.ts +22 -0
- package/src/shade/renderer/shader/chunk/grid/chunk_index_to_grid2d_reciprocal.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/grid/chunk_index_to_grid2d_reciprocal.js +37 -0
- package/src/shade/renderer/shader/chunk/light/lpv/depth/chunk_lpv_mask_weights_by_visibility.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/light/lpv/depth/chunk_lpv_mask_weights_by_visibility.js +26 -60
- package/src/shade/renderer/shader/chunk/light/probe/chunk_probe_visibility_weight.d.ts +23 -0
- package/src/shade/renderer/shader/chunk/light/probe/chunk_probe_visibility_weight.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/light/probe/chunk_probe_visibility_weight.js +54 -0
- package/src/shade/renderer/shader/chunk/math/chunk_f16_round_stochastic.d.ts +25 -0
- package/src/shade/renderer/shader/chunk/math/chunk_f16_round_stochastic.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/math/chunk_f16_round_stochastic.js +43 -0
- package/src/shade/renderer/shader/chunk/random/chunk_jitter_from_vectors.d.ts +14 -0
- package/src/shade/renderer/shader/chunk/random/chunk_jitter_from_vectors.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/random/chunk_jitter_from_vectors.js +37 -0
- package/src/shade/renderer/shader/chunk/random/reservoir/chunk_pick2_wrs_mat2x4.d.ts +27 -0
- package/src/shade/renderer/shader/chunk/random/reservoir/chunk_pick2_wrs_mat2x4.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/random/reservoir/chunk_pick2_wrs_mat2x4.js +61 -0
- package/src/shade/renderer/shader/chunk/spherical_harmonics/l1i/chunk_resolve_irradiance_sh_to_specular.d.ts +21 -0
- package/src/shade/renderer/shader/chunk/spherical_harmonics/l1i/chunk_resolve_irradiance_sh_to_specular.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/spherical_harmonics/l1i/chunk_resolve_irradiance_sh_to_specular.js +41 -0
- package/src/shade/renderer/texture/util/texture_fill_rectangle.d.ts.map +1 -1
- package/src/shade/renderer/texture/util/texture_fill_rectangle.js +282 -267
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts +8 -4
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +36 -36
- package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts +6 -1
- package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.js +498 -484
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts +15 -2
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.js +57 -3
- package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.js +197 -191
- package/src/shade/renderer/volumetrics/taa/shader_volumetrics_taa.js +4 -4
- package/src/shade/renderer/water/GPUWaterRenderer.d.ts +2 -1
- package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
- package/src/shade/renderer/water/GPUWaterRenderer.js +5 -1
- package/src/shade/renderer/water/chunk_water_gi.d.ts +13 -3
- package/src/shade/renderer/water/chunk_water_gi.d.ts.map +1 -1
- package/src/shade/renderer/water/chunk_water_gi.js +47 -5
- package/src/shade/renderer/water/query/GPUWaterQueries.d.ts +1 -1
- package/src/shade/renderer/water/shader_water.d.ts +3 -2
- package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
- package/src/shade/renderer/water/shader_water.js +9 -3
- package/src/shade/renderer/geometry/sdf/SDF_BUILD_SETTINGS_STRUCT.d.ts +0 -3
- package/src/shade/renderer/geometry/sdf/SDF_BUILD_SETTINGS_STRUCT.d.ts.map +0 -1
- package/src/shade/renderer/geometry/sdf/SFD_BUILD_WORKGROUP_SIZE.d.ts +0 -2
- package/src/shade/renderer/geometry/sdf/SFD_BUILD_WORKGROUP_SIZE.d.ts.map +0 -1
- package/src/shade/renderer/geometry/sdf/chunk_blas_query_sdf.d.ts +0 -8
- package/src/shade/renderer/geometry/sdf/chunk_blas_query_sdf.d.ts.map +0 -1
- package/src/shade/renderer/geometry/sdf/chunk_blas_query_volume_sign.d.ts +0 -3
- package/src/shade/renderer/geometry/sdf/chunk_blas_query_volume_sign.d.ts.map +0 -1
- package/src/shade/renderer/geometry/sdf/shader_build_sdf.d.ts +0 -3
- package/src/shade/renderer/geometry/sdf/shader_build_sdf.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_object.d.ts.map +0 -1
- package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_object.js +0 -42
- package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts +0 -31
- package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts.map +0 -1
- package/src/shade/renderer/shadow/vsm/VSMMoverTracker.d.ts +0 -42
- package/src/shade/renderer/shadow/vsm/VSMMoverTracker.d.ts.map +0 -1
- package/src/shade/renderer/shadow/vsm/VSM_MOVER_LAYOUT.d.ts +0 -54
- package/src/shade/renderer/shadow/vsm/VSM_MOVER_LAYOUT.d.ts.map +0 -1
- package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_append_mover.d.ts +0 -14
- package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_append_mover.d.ts.map +0 -1
- package/src/shade/renderer/shadow/vsm/shader/shader_vsm_movers.d.ts +0 -30
- package/src/shade/renderer/shadow/vsm/shader/shader_vsm_movers.d.ts.map +0 -1
- package/src/shade/renderer/shadow/vsm/shader/shader_vsm_skinned_movers.d.ts +0 -23
- package/src/shade/renderer/shadow/vsm/shader/shader_vsm_skinned_movers.d.ts.map +0 -1
|
@@ -1,301 +1,14 @@
|
|
|
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, phyllotaxis_sample_angle } from "../config/PhyllotaxisMode.js";
|
|
12
1
|
import { TreeSpeciesConfig } from "../config/TreeSpeciesConfig.js";
|
|
13
2
|
import { TreeSkeleton } from "./TreeSkeleton.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { crown_envelope_evaluate, crown_envelope_sdf } from "./crown_envelope_evaluate.js";
|
|
17
|
-
import { spine_frame_double_reflection } from "./spine_frame_double_reflection.js";
|
|
18
|
-
import { tree_skeleton_compute_pipe_radii } from "./tree_skeleton_compute_pipe_radii.js";
|
|
3
|
+
import { tree_skeleton_fit_crown_frame } from "./tree_skeleton_fit_crown_frame.js";
|
|
4
|
+
import { tree_skeleton_grow_framework } from "./tree_skeleton_grow_framework.js";
|
|
19
5
|
import { tree_skeleton_grow_shoots } from "./tree_skeleton_grow_shoots.js";
|
|
20
6
|
|
|
21
|
-
const GOLDEN_ANGLE = PI2 / (GOLDEN_RATIO * GOLDEN_RATIO); // ~2.399963229728653 rad (137.50776 deg)
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Canonical order of a generator's outgoing rules: by target, then by content, so the order rules are listed in
|
|
25
|
-
* never changes the tree.
|
|
26
|
-
* @param {import("../config/BranchSpawnRule.js").BranchSpawnRule} a
|
|
27
|
-
* @param {import("../config/BranchSpawnRule.js").BranchSpawnRule} b
|
|
28
|
-
* @returns {number}
|
|
29
|
-
*/
|
|
30
|
-
function compare_rule_target(a, b) {
|
|
31
|
-
return (a.target - b.target) || (a.hash() - b.hash());
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Node position `u` of child `c` of a rule laying out `planned` children over its attachment interval, `u` in
|
|
36
|
-
* [0, 1) for `c` in [0, planned). The same layout continued to `c < 0` gives `u < 0`: where the rule's earlier
|
|
37
|
-
* children grew, below its interval.
|
|
38
|
-
*
|
|
39
|
-
* @param {import("../config/BranchSpawnRule.js").BranchSpawnRule} rule
|
|
40
|
-
* @param {number} c
|
|
41
|
-
* @param {number} planned
|
|
42
|
-
* @param {boolean} on_trunk whorls compress toward the apex of a trunk (self-similar conical internodes)
|
|
43
|
-
* @returns {number}
|
|
44
|
-
*/
|
|
45
|
-
function rule_node_u(rule, c, planned, on_trunk) {
|
|
46
|
-
switch (rule.phyllotaxis_mode) {
|
|
47
|
-
case PhyllotaxisMode.Opposite:
|
|
48
|
-
case PhyllotaxisMode.OppositeDecussate: {
|
|
49
|
-
const pair_count = Math.max(1, Math.ceil(planned / 2));
|
|
50
|
-
return pair_count > 1 ? ((c >> 1) + 0.35) / (pair_count - 0.55) : 0.5;
|
|
51
|
-
}
|
|
52
|
-
case PhyllotaxisMode.Whorled: {
|
|
53
|
-
const w_size = Math.max(1, rule.whorl_count | 0);
|
|
54
|
-
const whorl_total = Math.max(1, Math.ceil(planned / w_size));
|
|
55
|
-
const u_lin = whorl_total > 1 ? (Math.floor(c / w_size) + 0.35) / (whorl_total - 0.55) : 0.5;
|
|
56
|
-
// p > 1 => du/dt decreases as u -> 1; u_lin < 1 for every c < planned
|
|
57
|
-
return on_trunk ? 1.0 - Math.pow(1.0 - u_lin, 1.42) : u_lin;
|
|
58
|
-
}
|
|
59
|
-
default:
|
|
60
|
-
return planned > 1 ? (c + 0.35) / (planned - 0.55) : 0.5;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Azimuth of child `c` of a rule relative to its family's base azimuth, for any integer `c` (also `c < 0`).
|
|
66
|
-
* `FlareLobes` is placed by the caller.
|
|
67
|
-
*
|
|
68
|
-
* @param {import("../config/BranchSpawnRule.js").BranchSpawnRule} rule
|
|
69
|
-
* @param {number} c
|
|
70
|
-
* @returns {number}
|
|
71
|
-
*/
|
|
72
|
-
function rule_node_phi(rule, c) {
|
|
73
|
-
return phyllotaxis_sample_angle(rule.phyllotaxis_mode, c, rule.whorl_count);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const SCRATCH_CURL = [0, 0, 0];
|
|
77
|
-
const SCRATCH_RMF = new Float64Array(6);
|
|
78
|
-
const SCRATCH_POS = new Float32Array(3);
|
|
79
|
-
const SCRATCH_TAN = new Float32Array(3);
|
|
80
|
-
const SCRATCH_P0 = new Float32Array(3);
|
|
81
|
-
const SCRATCH_T0 = new Float32Array(3);
|
|
82
|
-
const SCRATCH_R0 = new Float32Array(3);
|
|
83
|
-
const SCRATCH_M_PREV = new Float32Array(3);
|
|
84
|
-
const SCRATCH_M_NEXT = new Float32Array(3);
|
|
85
|
-
const SCRATCH_GUIDE_P0 = new Float32Array(3);
|
|
86
|
-
const SCRATCH_GUIDE_P1 = new Float32Array(3);
|
|
87
|
-
const SCRATCH_GUIDE_T = new Float32Array(3);
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Evaluates the trunk guide curve position `out_pos` and unit tangent `out_tan` at normalized
|
|
91
|
-
* parameter `t_norm` in [0, 1]. Defaults to the vertical upward plumb line `(0, t_norm * trunk_len, 0)`
|
|
92
|
-
* when `trunk_guide` is null or omitted.
|
|
93
|
-
*
|
|
94
|
-
* @param {Float32Array|number[]} out_pos
|
|
95
|
-
* @param {Float32Array|number[]} out_tan
|
|
96
|
-
* @param {Array<ArrayLike<number>>|Function|null|undefined} trunk_guide
|
|
97
|
-
* @param {number} t_norm
|
|
98
|
-
* @param {number} trunk_len
|
|
99
|
-
*/
|
|
100
|
-
function evaluate_trunk_guide(out_pos, out_tan, trunk_guide, t_norm, trunk_len) {
|
|
101
|
-
const t = clamp(t_norm, 0.0, 1.0);
|
|
102
|
-
if (!trunk_guide) {
|
|
103
|
-
out_pos[0] = 0.0;
|
|
104
|
-
out_pos[1] = t * trunk_len;
|
|
105
|
-
out_pos[2] = 0.0;
|
|
106
|
-
out_tan[0] = 0.0;
|
|
107
|
-
out_tan[1] = 1.0;
|
|
108
|
-
out_tan[2] = 0.0;
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const sample_pt = (u, dst) => {
|
|
113
|
-
if (typeof trunk_guide === "function") {
|
|
114
|
-
trunk_guide(u, dst);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
if (Array.isArray(trunk_guide) && trunk_guide.length >= 2) {
|
|
118
|
-
const n = trunk_guide.length - 1;
|
|
119
|
-
const su = clamp(u, 0.0, 1.0) * n;
|
|
120
|
-
const idx = Math.min(n - 1, Math.floor(su));
|
|
121
|
-
const frac = su - idx;
|
|
122
|
-
const a = trunk_guide[idx];
|
|
123
|
-
const b = trunk_guide[idx + 1];
|
|
124
|
-
dst[0] = lerp(a[0] ?? 0.0, b[0] ?? 0.0, frac);
|
|
125
|
-
dst[1] = lerp(a[1] ?? 0.0, b[1] ?? 0.0, frac);
|
|
126
|
-
dst[2] = lerp(a[2] ?? 0.0, b[2] ?? 0.0, frac);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
dst[0] = 0.0;
|
|
130
|
-
dst[1] = u * trunk_len;
|
|
131
|
-
dst[2] = 0.0;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
sample_pt(t, out_pos);
|
|
135
|
-
const eps = 0.01;
|
|
136
|
-
const t_a = Math.max(0.0, t - eps);
|
|
137
|
-
const t_b = Math.min(1.0, t + eps);
|
|
138
|
-
const pa = [0, 0, 0];
|
|
139
|
-
const pb = [0, 0, 0];
|
|
140
|
-
sample_pt(t_a, pa);
|
|
141
|
-
sample_pt(t_b, pb);
|
|
142
|
-
let tx = pb[0] - pa[0];
|
|
143
|
-
let ty = pb[1] - pa[1];
|
|
144
|
-
let tz = pb[2] - pa[2];
|
|
145
|
-
const tl = Math.hypot(tx, ty, tz);
|
|
146
|
-
if (tl > 1e-8) {
|
|
147
|
-
out_tan[0] = tx / tl;
|
|
148
|
-
out_tan[1] = ty / tl;
|
|
149
|
-
out_tan[2] = tz / tl;
|
|
150
|
-
} else {
|
|
151
|
-
out_tan[0] = 0.0;
|
|
152
|
-
out_tan[1] = 1.0;
|
|
153
|
-
out_tan[2] = 0.0;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Rotates unit vector `(vx, vy, vz)` around unit axis `(ax, ay, az)` by `angle` radians (Rodrigues' formula).
|
|
159
|
-
* @param {number[]|Float64Array} out
|
|
160
|
-
* @param {number} vx
|
|
161
|
-
* @param {number} vy
|
|
162
|
-
* @param {number} vz
|
|
163
|
-
* @param {number} ax
|
|
164
|
-
* @param {number} ay
|
|
165
|
-
* @param {number} az
|
|
166
|
-
* @param {number} angle
|
|
167
|
-
*/
|
|
168
|
-
function rotate_around_axis(out, vx, vy, vz, ax, ay, az, angle) {
|
|
169
|
-
const c = Math.cos(angle);
|
|
170
|
-
const s = Math.sin(angle);
|
|
171
|
-
const dot = vx * ax + vy * ay + vz * az;
|
|
172
|
-
const omc = 1.0 - c;
|
|
173
|
-
|
|
174
|
-
const cx = ay * vz - az * vy;
|
|
175
|
-
const cy = az * vx - ax * vz;
|
|
176
|
-
const cz = ax * vy - ay * vx;
|
|
177
|
-
|
|
178
|
-
let rx = vx * c + cx * s + ax * dot * omc;
|
|
179
|
-
let ry = vy * c + cy * s + ay * dot * omc;
|
|
180
|
-
let rz = vz * c + cz * s + az * dot * omc;
|
|
181
|
-
|
|
182
|
-
const len = Math.sqrt(rx * rx + ry * ry + rz * rz);
|
|
183
|
-
if (len > 1e-12) {
|
|
184
|
-
const inv = 1.0 / len;
|
|
185
|
-
rx *= inv;
|
|
186
|
-
ry *= inv;
|
|
187
|
-
rz *= inv;
|
|
188
|
-
}
|
|
189
|
-
out[0] = rx;
|
|
190
|
-
out[1] = ry;
|
|
191
|
-
out[2] = rz;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Determines the non-exempt sub-segment parameter intervals `[s_min, 1]` on candidate segment A
|
|
196
|
-
* and `[t_min, 1]` on existing segment B for branch-branch capsule collision testing.
|
|
197
|
-
* Returns `false` if the segment pair is topologically exempt (e.g. direct parent-child base collar).
|
|
198
|
-
*
|
|
199
|
-
* @param {number} bA
|
|
200
|
-
* @param {number} parent_bA
|
|
201
|
-
* @param {number} attach_seg_A
|
|
202
|
-
* @param {boolean} is_split_A
|
|
203
|
-
* @param {number} local_idx_A
|
|
204
|
-
* @param {number} bB
|
|
205
|
-
* @param {number} parent_bB
|
|
206
|
-
* @param {number} attach_seg_B
|
|
207
|
-
* @param {boolean} is_split_B
|
|
208
|
-
* @param {number} local_idx_B
|
|
209
|
-
* @param {Float64Array|number[]} out_bounds `[s_min, t_min]`
|
|
210
|
-
* @returns {boolean}
|
|
211
|
-
*/
|
|
212
|
-
function resolve_subsegment_bounds(
|
|
213
|
-
bA, parent_bA, attach_seg_A, is_split_A, local_idx_A,
|
|
214
|
-
bB, parent_bB, attach_seg_B, is_split_B, local_idx_B,
|
|
215
|
-
out_bounds
|
|
216
|
-
) {
|
|
217
|
-
if (bA === bB) return false;
|
|
218
|
-
|
|
219
|
-
// 1. A is a direct child of B
|
|
220
|
-
if (parent_bA === bB) {
|
|
221
|
-
if (is_split_A) {
|
|
222
|
-
if (local_idx_A === 0 && local_idx_B === attach_seg_A) return false;
|
|
223
|
-
if (local_idx_A === 0 && local_idx_B === attach_seg_A + 1) {
|
|
224
|
-
out_bounds[0] = 0.55;
|
|
225
|
-
out_bounds[1] = 0.55;
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
if (local_idx_A === 1 && (local_idx_B === attach_seg_A || local_idx_B === attach_seg_A + 1)) {
|
|
229
|
-
out_bounds[0] = 0.45;
|
|
230
|
-
out_bounds[1] = 0.45;
|
|
231
|
-
return true;
|
|
232
|
-
}
|
|
233
|
-
} else {
|
|
234
|
-
if (local_idx_A === 0 && Math.abs(local_idx_B - attach_seg_A) <= 1) return false;
|
|
235
|
-
if (local_idx_A === 1 && Math.abs(local_idx_B - attach_seg_A) <= 1) {
|
|
236
|
-
if (bA < 0 || local_idx_B === attach_seg_A) return false;
|
|
237
|
-
out_bounds[0] = 0.35;
|
|
238
|
-
out_bounds[1] = 0.0;
|
|
239
|
-
return true;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// 2. B is a direct child of A
|
|
245
|
-
if (parent_bB === bA) {
|
|
246
|
-
if (is_split_B) {
|
|
247
|
-
if (local_idx_B === 0 && local_idx_A === attach_seg_B) return false;
|
|
248
|
-
if (local_idx_B === 0 && local_idx_A === attach_seg_B + 1) {
|
|
249
|
-
out_bounds[0] = 0.55;
|
|
250
|
-
out_bounds[1] = 0.55;
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
if (local_idx_B === 1 && (local_idx_A === attach_seg_B || local_idx_A === attach_seg_B + 1)) {
|
|
254
|
-
out_bounds[0] = 0.45;
|
|
255
|
-
out_bounds[1] = 0.45;
|
|
256
|
-
return true;
|
|
257
|
-
}
|
|
258
|
-
} else {
|
|
259
|
-
if (local_idx_B <= 1 && Math.abs(local_idx_A - attach_seg_B) <= 1) return false;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// 3. A and B are siblings sharing the same parent branch
|
|
264
|
-
if (parent_bA >= 0 && parent_bA === parent_bB) {
|
|
265
|
-
if (local_idx_A === 0 && local_idx_B === 0) {
|
|
266
|
-
out_bounds[0] = 0.50;
|
|
267
|
-
out_bounds[1] = 0.50;
|
|
268
|
-
return true;
|
|
269
|
-
}
|
|
270
|
-
if (local_idx_A === 0 && local_idx_B === 1 && Math.abs(attach_seg_A - attach_seg_B) <= 1) {
|
|
271
|
-
out_bounds[0] = 0.25;
|
|
272
|
-
out_bounds[1] = 0.0;
|
|
273
|
-
return true;
|
|
274
|
-
}
|
|
275
|
-
if (local_idx_A === 1 && local_idx_B === 0 && Math.abs(attach_seg_A - attach_seg_B) <= 1) {
|
|
276
|
-
out_bounds[0] = 0.0;
|
|
277
|
-
out_bounds[1] = 0.25;
|
|
278
|
-
return true;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
out_bounds[0] = 0.0;
|
|
283
|
-
out_bounds[1] = 0.0;
|
|
284
|
-
return true;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
7
|
/**
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
* - Botanical phyllotaxis (`SpiralGolden`, `AlternateDistichous`, `Tristichous`, `Opposite`, `OppositeDecussate`, `Whorled`, `FlareLobes`)
|
|
293
|
-
* - Structural surface roots (`BranchGeneratorConfig.root`) that creep along the ground plane `y = 0`
|
|
294
|
-
* - Gravitropism (toward `-Y`), phototropism (toward `+Y` and outward crown exterior), and 3D `curl_noise_3d` gnarl
|
|
295
|
-
* - Dichotomous / sympodial trunk & bough splitting
|
|
296
|
-
* - Space colonization crown competition via `PointHashGrid3`
|
|
297
|
-
* - Rotation-Minimizing Frames (`spine_frame_double_reflection`)
|
|
298
|
-
* - Pipe Model cross-section radius accumulation (`tree_skeleton_compute_pipe_radii`)
|
|
8
|
+
* Grows a whole tree botanically (`TreeGrowth.Botanical`): its framework from the generator graph
|
|
9
|
+
* (`tree_skeleton_grow_framework`), then the shoot systems of its `shoot_rules`, year by year on that framework, its
|
|
10
|
+
* girth final (`tree_skeleton_grow_shoots`), and fits the crown frame to them as the framework did to its carriers
|
|
11
|
+
* (`tree_skeleton_fit_crown_frame`).
|
|
299
12
|
*
|
|
300
13
|
* @param {TreeSpeciesConfig|Object} [config]
|
|
301
14
|
* @param {TreeSkeleton} [out]
|
|
@@ -303,1747 +16,8 @@ function resolve_subsegment_bounds(
|
|
|
303
16
|
*/
|
|
304
17
|
export function tree_skeleton_grow(config = new TreeSpeciesConfig(), out = new TreeSkeleton()) {
|
|
305
18
|
const species = config && config.isTreeSpeciesConfig ? config : TreeSpeciesConfig.from(config);
|
|
306
|
-
out
|
|
307
|
-
|
|
308
|
-
const rng = seededRandom(species.seed | 0);
|
|
309
|
-
// Seed-derived 3D noise offset so unseeded Simplex/Curl fields are unique and deterministic per seed
|
|
310
|
-
const noise_ox = (rng() - 0.5) * 1000.0;
|
|
311
|
-
const noise_oy = (rng() - 0.5) * 1000.0;
|
|
312
|
-
const noise_oz = (rng() - 0.5) * 1000.0;
|
|
313
|
-
|
|
314
|
-
const crown = species.crown;
|
|
315
|
-
const crown_cy = crown.base_height + crown.height * 0.5;
|
|
316
|
-
out.crown_center[0] = 0.0;
|
|
317
|
-
out.crown_center[1] = crown_cy;
|
|
318
|
-
out.crown_center[2] = 0.0;
|
|
319
|
-
|
|
320
|
-
out.crown_radii[0] = Math.max(0.25, crown.radius);
|
|
321
|
-
out.crown_radii[1] = Math.max(0.25, crown.height * 0.5);
|
|
322
|
-
out.crown_radii[2] = Math.max(0.25, crown.radius);
|
|
323
|
-
|
|
324
|
-
// Build Space Colonization attraction point cloud + PointHashGrid3 once
|
|
325
|
-
const use_space_col = crown.space_colonization_weight > 0.0 && crown.attraction_point_count > 0;
|
|
326
|
-
const attr_count = use_space_col ? (crown.attraction_point_count | 0) : 0;
|
|
327
|
-
const attr_positions = new Float32Array(attr_count * 3);
|
|
328
|
-
const attr_alive = new Uint8Array(attr_count);
|
|
329
|
-
let attr_grid = null;
|
|
330
|
-
|
|
331
|
-
if (use_space_col) {
|
|
332
|
-
let placed = 0;
|
|
333
|
-
let attempts = 0;
|
|
334
|
-
const max_attempts = attr_count * 12;
|
|
335
|
-
while (placed < attr_count && attempts < max_attempts) {
|
|
336
|
-
attempts++;
|
|
337
|
-
const u_h = rng();
|
|
338
|
-
const env_r = crown.radius * crown_envelope_evaluate(crown.envelope_type, u_h);
|
|
339
|
-
const rx = (rng() * 2.0 - 1.0) * env_r;
|
|
340
|
-
const rz = (rng() * 2.0 - 1.0) * env_r;
|
|
341
|
-
if (rx * rx + rz * rz <= env_r * env_r) {
|
|
342
|
-
const ry = crown.base_height + u_h * crown.height;
|
|
343
|
-
attr_positions[placed * 3] = rx;
|
|
344
|
-
attr_positions[placed * 3 + 1] = ry;
|
|
345
|
-
attr_positions[placed * 3 + 2] = rz;
|
|
346
|
-
attr_alive[placed] = 1;
|
|
347
|
-
placed++;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
while (placed < attr_count) {
|
|
351
|
-
const theta = rng() * PI2;
|
|
352
|
-
const u_h = rng();
|
|
353
|
-
const r = Math.sqrt(rng()) * crown.radius * crown_envelope_evaluate(crown.envelope_type, u_h);
|
|
354
|
-
attr_positions[placed * 3] = Math.cos(theta) * r;
|
|
355
|
-
attr_positions[placed * 3 + 1] = crown.base_height + u_h * crown.height;
|
|
356
|
-
attr_positions[placed * 3 + 2] = Math.sin(theta) * r;
|
|
357
|
-
attr_alive[placed] = 1;
|
|
358
|
-
placed++;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
attr_grid = new PointHashGrid3();
|
|
362
|
-
attr_grid.build(attr_positions, attr_count, Math.max(0.25, crown.attraction_radius));
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const r_influence_sq = crown.attraction_radius * crown.attraction_radius;
|
|
366
|
-
const r_kill_sq = crown.kill_radius * crown.kill_radius;
|
|
367
|
-
|
|
368
|
-
// Queue of branches to grow in level-order (guarantees parent branches & vertices precede children)
|
|
369
|
-
const queue = [];
|
|
370
|
-
|
|
371
|
-
const lvl0 = species.branches[0];
|
|
372
|
-
if (!lvl0) {
|
|
373
|
-
return out;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Outgoing rules of every generator in canonical order, so the order rules are listed in does not change the tree
|
|
377
|
-
const outgoing_rules = species.branches.map(() => []);
|
|
378
|
-
for (let r = 0; r < species.branch_rules.length; r++) {
|
|
379
|
-
const rule = species.branch_rules[r];
|
|
380
|
-
assert.ok(rule.source >= 0 && rule.source < rule.target && rule.target < species.branches.length,
|
|
381
|
-
'branch rule must satisfy 0 <= source < target < branches.length');
|
|
382
|
-
outgoing_rules[rule.source].push(rule);
|
|
383
|
-
}
|
|
384
|
-
for (let g = 0; g < outgoing_rules.length; g++) {
|
|
385
|
-
outgoing_rules[g].sort(compare_rule_target);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// a seed leader is a shoot a few centimetres long
|
|
389
|
-
const trunk_len = Math.max(1e-3, lvl0.length * (1.0 + (rng() * 2.0 - 1.0) * lvl0.length_variance));
|
|
390
|
-
const trunk_base_r = Math.max(0.01, trunk_len * lvl0.radius_ratio);
|
|
391
|
-
|
|
392
|
-
evaluate_trunk_guide(SCRATCH_GUIDE_P0, SCRATCH_GUIDE_T, species.trunk_guide, 0.0, trunk_len);
|
|
393
|
-
|
|
394
|
-
// Apply subtle initial basal trunk lean around the guide tangent
|
|
395
|
-
const max_lean = lvl0.excurrent ? 0.025 : 0.06;
|
|
396
|
-
const lean_ang = clamp((lvl0.basal_lean ?? 0.02) * (0.75 + 0.5 * rng()), 0.0, max_lean);
|
|
397
|
-
const lean_az = rng() * PI2;
|
|
398
|
-
const init_tx = clamp(SCRATCH_GUIDE_T[0] + Math.sin(lean_ang) * Math.cos(lean_az), -0.95, 0.95);
|
|
399
|
-
const init_ty = Math.max(0.2, SCRATCH_GUIDE_T[1] * Math.cos(lean_ang));
|
|
400
|
-
const init_tz = clamp(SCRATCH_GUIDE_T[2] + Math.sin(lean_ang) * Math.sin(lean_az), -0.95, 0.95);
|
|
401
|
-
const init_t_len = Math.hypot(init_tx, init_ty, init_tz) || 1.0;
|
|
402
|
-
const norm_tx = init_tx / init_t_len;
|
|
403
|
-
const norm_ty = init_ty / init_t_len;
|
|
404
|
-
const norm_tz = init_tz / init_t_len;
|
|
405
|
-
|
|
406
|
-
// Orthonormal initial R perpendicular to norm_T
|
|
407
|
-
let init_rx = 1.0 - norm_tx * norm_tx;
|
|
408
|
-
let init_ry = -norm_ty * norm_tx;
|
|
409
|
-
let init_rz = -norm_tz * norm_tx;
|
|
410
|
-
const init_r_len = Math.hypot(init_rx, init_ry, init_rz) || 1.0;
|
|
411
|
-
init_rx /= init_r_len;
|
|
412
|
-
init_ry /= init_r_len;
|
|
413
|
-
init_rz /= init_r_len;
|
|
414
|
-
|
|
415
|
-
queue.push({
|
|
416
|
-
level: 0,
|
|
417
|
-
generator: 0,
|
|
418
|
-
in_crown: true,
|
|
419
|
-
parent_branch_id: -1,
|
|
420
|
-
parent_edge_id: -1,
|
|
421
|
-
parent_attach_seg_idx: -1,
|
|
422
|
-
parent_attach_t: 0.0,
|
|
423
|
-
start_vertex: -1,
|
|
424
|
-
px: SCRATCH_GUIDE_P0[0],
|
|
425
|
-
py: SCRATCH_GUIDE_P0[1],
|
|
426
|
-
pz: SCRATCH_GUIDE_P0[2],
|
|
427
|
-
tx: norm_tx,
|
|
428
|
-
ty: norm_ty,
|
|
429
|
-
tz: norm_tz,
|
|
430
|
-
rx: init_rx,
|
|
431
|
-
ry: init_ry,
|
|
432
|
-
rz: init_rz,
|
|
433
|
-
emerge_drx: init_rx,
|
|
434
|
-
emerge_dry: init_ry,
|
|
435
|
-
emerge_drz: init_rz,
|
|
436
|
-
length: trunk_len,
|
|
437
|
-
full_branch_length: trunk_len,
|
|
438
|
-
base_radius: trunk_base_r,
|
|
439
|
-
start_seg: 0,
|
|
440
|
-
total_segs: Math.max(1, lvl0.segments),
|
|
441
|
-
shape_segs: Math.max(1, lvl0.segments),
|
|
442
|
-
initial_arc_s: 0.0,
|
|
443
|
-
has_split: false,
|
|
444
|
-
is_split_fork: false,
|
|
445
|
-
cum_t0: 0.0,
|
|
446
|
-
cum_t1: 1.0
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
const rot_scratch = [0, 0, 0];
|
|
450
|
-
|
|
451
|
-
// Scope-local BVH and per-segment metadata for branch-branch spatial avoidance & collision prevention
|
|
452
|
-
const bvh = new BVH();
|
|
453
|
-
const bvh_hits = new Uint32Array(256);
|
|
454
|
-
const query_capsule = new Float32Array(7);
|
|
455
|
-
const sub_bounds = new Float64Array(2);
|
|
456
|
-
const closest_st = new Float64Array(2);
|
|
457
|
-
|
|
458
|
-
const seg_branch_id = [];
|
|
459
|
-
const seg_local_idx = [];
|
|
460
|
-
const seg_p0x = [], seg_p0y = [], seg_p0z = [], seg_r0 = [];
|
|
461
|
-
const seg_p1x = [], seg_p1y = [], seg_p1z = [], seg_r1 = [];
|
|
462
|
-
const seg_bvh_leaf = [];
|
|
463
|
-
|
|
464
|
-
const branch_parent_seg_idx = [];
|
|
465
|
-
const branch_is_split_fork = [];
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Evaluates the minimum signed surface-to-surface clearance between a candidate segment A
|
|
469
|
-
* and all non-exempt existing segments in `bvh`.
|
|
470
|
-
*/
|
|
471
|
-
const evaluate_candidate_clearance = (
|
|
472
|
-
bA, parent_bA, attach_seg_A, is_split_A, local_idx_A,
|
|
473
|
-
a0x, a0y, a0z, rA0,
|
|
474
|
-
a1x, a1y, a1z, rA1,
|
|
475
|
-
extra_margin
|
|
476
|
-
) => {
|
|
477
|
-
if (bvh.size === 0) return 10.0;
|
|
478
|
-
const max_rA = Math.max(rA0, rA1);
|
|
479
|
-
query_capsule[0] = a0x;
|
|
480
|
-
query_capsule[1] = a0y;
|
|
481
|
-
query_capsule[2] = a0z;
|
|
482
|
-
query_capsule[3] = a1x;
|
|
483
|
-
query_capsule[4] = a1y;
|
|
484
|
-
query_capsule[5] = a1z;
|
|
485
|
-
query_capsule[6] = max_rA * 1.15 + extra_margin + 0.04;
|
|
486
|
-
|
|
487
|
-
const hit_count = bvh_query_user_data_intersects_capsule(bvh_hits, 0, bvh, query_capsule);
|
|
488
|
-
let min_clear = 10.0;
|
|
489
|
-
|
|
490
|
-
for (let h = 0; h < hit_count; h++) {
|
|
491
|
-
const eB = bvh_hits[h];
|
|
492
|
-
const bB = seg_branch_id[eB];
|
|
493
|
-
const local_idx_B = seg_local_idx[eB];
|
|
494
|
-
const parent_bB = out.branch_parent[bB];
|
|
495
|
-
const attach_seg_B = branch_parent_seg_idx[bB];
|
|
496
|
-
const is_split_B = branch_is_split_fork[bB];
|
|
497
|
-
|
|
498
|
-
if (!resolve_subsegment_bounds(
|
|
499
|
-
bA, parent_bA, attach_seg_A, is_split_A, local_idx_A,
|
|
500
|
-
bB, parent_bB, attach_seg_B, is_split_B, local_idx_B,
|
|
501
|
-
sub_bounds
|
|
502
|
-
)) {
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
const s_min = sub_bounds[0];
|
|
507
|
-
const t_min = sub_bounds[1];
|
|
508
|
-
const sa0x = a0x + (a1x - a0x) * s_min;
|
|
509
|
-
const sa0y = a0y + (a1y - a0y) * s_min;
|
|
510
|
-
const sa0z = a0z + (a1z - a0z) * s_min;
|
|
511
|
-
|
|
512
|
-
const b0x = seg_p0x[eB], b0y = seg_p0y[eB], b0z = seg_p0z[eB];
|
|
513
|
-
const b1x = seg_p1x[eB], b1y = seg_p1y[eB], b1z = seg_p1z[eB];
|
|
514
|
-
const sb0x = b0x + (b1x - b0x) * t_min;
|
|
515
|
-
const sb0y = b0y + (b1y - b0y) * t_min;
|
|
516
|
-
const sb0z = b0z + (b1z - b0z) * t_min;
|
|
517
|
-
|
|
518
|
-
line3_compute_segment_segment_closest_points(
|
|
519
|
-
closest_st,
|
|
520
|
-
sa0x, sa0y, sa0z, a1x, a1y, a1z,
|
|
521
|
-
sb0x, sb0y, sb0z, b1x, b1y, b1z
|
|
522
|
-
);
|
|
523
|
-
|
|
524
|
-
const s_f = s_min + (1.0 - s_min) * closest_st[0];
|
|
525
|
-
const t_f = t_min + (1.0 - t_min) * closest_st[1];
|
|
526
|
-
|
|
527
|
-
const qAx = a0x + (a1x - a0x) * s_f;
|
|
528
|
-
const qAy = a0y + (a1y - a0y) * s_f;
|
|
529
|
-
const qAz = a0z + (a1z - a0z) * s_f;
|
|
530
|
-
const qBx = b0x + (b1x - b0x) * t_f;
|
|
531
|
-
const qBy = b0y + (b1y - b0y) * t_f;
|
|
532
|
-
const qBz = b0z + (b1z - b0z) * t_f;
|
|
533
|
-
|
|
534
|
-
const dist = Math.hypot(qAx - qBx, qAy - qBy, qAz - qBz);
|
|
535
|
-
const rA = rA0 + (rA1 - rA0) * s_f;
|
|
536
|
-
const rB = seg_r0[eB] + (seg_r1[eB] - seg_r0[eB]) * t_f;
|
|
537
|
-
const r_req = (rA + rB) * 1.08 + extra_margin;
|
|
538
|
-
const clear = dist - r_req;
|
|
539
|
-
if (clear < min_clear) {
|
|
540
|
-
min_clear = clear;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
return min_clear;
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
for (let q_idx = 0; q_idx < queue.length; q_idx++) {
|
|
547
|
-
const job = queue[q_idx];
|
|
548
|
-
const level = job.level;
|
|
549
|
-
const generator = job.generator;
|
|
550
|
-
const lvl_cfg = species.branches[generator];
|
|
551
|
-
|
|
552
|
-
const phase_hash = rng();
|
|
553
|
-
const N_seg = job.total_segs;
|
|
554
|
-
// Shape is a function of position along the whole branch: envelopes take the genealogical t (cum_t0..cum_t1)
|
|
555
|
-
// and per-step amounts the whole branch's segmentation, so a dead stub bends as the base of its branch did
|
|
556
|
-
const N_shape = job.shape_segs;
|
|
557
|
-
const remaining_segs = N_seg - job.start_seg;
|
|
558
|
-
if (remaining_segs <= 0) continue;
|
|
559
|
-
|
|
560
|
-
const step_len = job.length / N_seg;
|
|
561
|
-
const prof_p0 = lvl_cfg.radius_profile.evaluate(0.0);
|
|
562
|
-
const prof_p1 = lvl_cfg.radius_profile.evaluate(1.0);
|
|
563
|
-
const prof_span = prof_p0 - prof_p1;
|
|
564
|
-
|
|
565
|
-
let cur_px = job.px;
|
|
566
|
-
let cur_py = job.py;
|
|
567
|
-
let cur_pz = job.pz;
|
|
568
|
-
let cur_tx = job.tx;
|
|
569
|
-
let cur_ty = job.ty;
|
|
570
|
-
let cur_tz = job.tz;
|
|
571
|
-
let cur_rx = job.rx;
|
|
572
|
-
let cur_ry = job.ry;
|
|
573
|
-
let cur_rz = job.rz;
|
|
574
|
-
let cur_arc_s = job.initial_arc_s;
|
|
575
|
-
|
|
576
|
-
// Compute binormal S = T x R
|
|
577
|
-
let cur_sx = cur_ty * cur_rz - cur_tz * cur_ry;
|
|
578
|
-
let cur_sy = cur_tz * cur_rx - cur_tx * cur_rz;
|
|
579
|
-
let cur_sz = cur_tx * cur_ry - cur_ty * cur_rx;
|
|
580
|
-
|
|
581
|
-
let prev_v = job.start_vertex;
|
|
582
|
-
let branch_id = -1;
|
|
583
|
-
|
|
584
|
-
if (prev_v < 0) {
|
|
585
|
-
const t0 = job.start_seg / N_seg;
|
|
586
|
-
const u_prof0 = prof_span > 1e-4
|
|
587
|
-
? clamp((prof_p0 - lvl_cfg.radius_profile.evaluate(t0)) / prof_span, 0.0, 1.0)
|
|
588
|
-
: t0;
|
|
589
|
-
const min_r0_frac = level === 0 ? 0.08 : 0.15;
|
|
590
|
-
const r0 = Math.max(0.002, job.base_radius * Math.max(1.0 - lvl_cfg.taper * u_prof0, min_r0_frac));
|
|
591
|
-
|
|
592
|
-
branch_id = out.add_branch(
|
|
593
|
-
0,
|
|
594
|
-
level,
|
|
595
|
-
generator,
|
|
596
|
-
job.parent_branch_id,
|
|
597
|
-
job.parent_edge_id,
|
|
598
|
-
job.parent_attach_t,
|
|
599
|
-
job.length,
|
|
600
|
-
r0,
|
|
601
|
-
phase_hash
|
|
602
|
-
);
|
|
603
|
-
prev_v = out.add_node(
|
|
604
|
-
cur_px, cur_py, cur_pz,
|
|
605
|
-
cur_tx, cur_ty, cur_tz,
|
|
606
|
-
cur_rx, cur_ry, cur_rz,
|
|
607
|
-
r0,
|
|
608
|
-
branch_id,
|
|
609
|
-
level,
|
|
610
|
-
job.parent_branch_id,
|
|
611
|
-
t0,
|
|
612
|
-
cur_arc_s,
|
|
613
|
-
phase_hash
|
|
614
|
-
);
|
|
615
|
-
if (job.attach_parent_vertex !== undefined && job.attach_parent_vertex >= 0) {
|
|
616
|
-
out.connect_nodes(job.attach_parent_vertex, prev_v);
|
|
617
|
-
}
|
|
618
|
-
out.branch_root_vertex[branch_id] = prev_v;
|
|
619
|
-
out.branch_tip_vertex[branch_id] = prev_v;
|
|
620
|
-
} else {
|
|
621
|
-
const prev_r = out.node_read_radius(prev_v);
|
|
622
|
-
branch_id = out.add_branch(
|
|
623
|
-
prev_v,
|
|
624
|
-
level,
|
|
625
|
-
generator,
|
|
626
|
-
job.parent_branch_id,
|
|
627
|
-
job.parent_edge_id,
|
|
628
|
-
job.parent_attach_t,
|
|
629
|
-
job.length,
|
|
630
|
-
prev_r,
|
|
631
|
-
phase_hash
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
branch_parent_seg_idx[branch_id] = job.parent_attach_seg_idx ?? -1;
|
|
636
|
-
branch_is_split_fork[branch_id] = Boolean(job.is_split_fork);
|
|
637
|
-
out.branch_dead[branch_id] = job.dead ? 1 : 0;
|
|
638
|
-
|
|
639
|
-
const branch_edges = [];
|
|
640
|
-
const branch_edge_t0 = [];
|
|
641
|
-
const branch_edge_t1 = [];
|
|
642
|
-
const branch_vertices = [prev_v];
|
|
643
|
-
|
|
644
|
-
// Pre-schedule the phyllotactic child attachments of every outgoing rule so the parent spine kinks
|
|
645
|
-
// sympodially at lateral emissions
|
|
646
|
-
const len_fraction_est = level >= 1
|
|
647
|
-
? clamp(Math.pow(job.length / Math.max(0.2, lvl_cfg.length), 0.85), 0.0, 1.0)
|
|
648
|
-
: 1.0;
|
|
649
|
-
const can_bear_children = !job.dead && job.base_radius >= 0.006 && (level === 0 || job.length >= 0.55);
|
|
650
|
-
const span_fraction_est = job.child_span_fraction ?? Math.max(0.25, (N_seg - job.start_seg) / Math.max(1, N_seg));
|
|
651
|
-
const effective_span = span_fraction_est * len_fraction_est;
|
|
652
|
-
const t_start_est = job.start_seg / N_seg;
|
|
653
|
-
const raw_base_phi = rng() * PI2;
|
|
654
|
-
const scheduled_children = [];
|
|
655
|
-
let has_children = false;
|
|
656
|
-
|
|
657
|
-
const rules = outgoing_rules[generator];
|
|
658
|
-
for (let r = 0; r < rules.length; r++) {
|
|
659
|
-
const rule = rules[r];
|
|
660
|
-
if (rule.count <= 0 || !can_bear_children) continue;
|
|
661
|
-
has_children = true;
|
|
662
|
-
|
|
663
|
-
const planned_child_count = effective_span < 0.99
|
|
664
|
-
? Math.max(1, Math.round(rule.count * effective_span))
|
|
665
|
-
: rule.count;
|
|
666
|
-
const t_lo = Math.max(clamp(rule.attach_min, 0.0, 0.92), t_start_est);
|
|
667
|
-
const t_hi = Math.min(0.968, rule.attach_max);
|
|
668
|
-
const t_cap = Math.min(0.98, rule.attach_max);
|
|
669
|
-
if (t_hi <= t_lo + 1e-3) continue;
|
|
670
|
-
|
|
671
|
-
// Each rule turns its family by the golden angle, so families sharing a stretch of the parent interleave
|
|
672
|
-
let base_phi = raw_base_phi + r * GOLDEN_ANGLE;
|
|
673
|
-
if (
|
|
674
|
-
level >= 1
|
|
675
|
-
&& (rule.phyllotaxis_mode === PhyllotaxisMode.AlternateDistichous
|
|
676
|
-
|| rule.phyllotaxis_mode === PhyllotaxisMode.Opposite)
|
|
677
|
-
) {
|
|
678
|
-
// Align distichous / opposite lateral branching plane with the horizontal plane perpendicular to T
|
|
679
|
-
const horiz_phi = Math.atan2(-cur_ry, cur_sy);
|
|
680
|
-
base_phi = horiz_phi + (raw_base_phi < Math.PI ? 0.0 : Math.PI) + ((raw_base_phi % Math.PI) / Math.PI - 0.5) * 0.28;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
for (let c = 0; c < planned_child_count; c++) {
|
|
684
|
-
const u_c = rule_node_u(rule, c, planned_child_count, level === 0);
|
|
685
|
-
const t_att = clamp(
|
|
686
|
-
lerp(t_lo, t_hi, clamp(u_c, 0.0, 1.0)) + (rng() - 0.5) * ((t_hi - t_lo) * 0.25 / planned_child_count),
|
|
687
|
-
t_lo,
|
|
688
|
-
t_cap
|
|
689
|
-
);
|
|
690
|
-
|
|
691
|
-
let phi_c = base_phi + rule_node_phi(rule, c);
|
|
692
|
-
if (rule.phyllotaxis_mode === PhyllotaxisMode.FlareLobes) {
|
|
693
|
-
// On a crest of this branch's basal flare lobes, in the same frame the bark mesher shapes them in
|
|
694
|
-
const lobes = Math.max(1, lvl_cfg.flare_lobes | 0);
|
|
695
|
-
const arc_est = job.initial_arc_s + t_att * job.length;
|
|
696
|
-
phi_c = ((c % lobes) * PI2) / lobes - flare_lobe_phase_offset(arc_est, phase_hash);
|
|
697
|
-
}
|
|
698
|
-
phi_c += (rng() * 2.0 - 1.0) * rule.azimuth_jitter;
|
|
699
|
-
scheduled_children.push({ t_attach: t_att, phi: phi_c, rule, age: 0.0, retained: 1.0 });
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
// Self-pruning: below t_lo this crown rule once bore children too, at the same nodes, which died as the
|
|
703
|
-
// crown rose above them and were shed. Age runs from 0 just below the living children to 1 at the stem's
|
|
704
|
-
// base; a branch that died earlier died smaller, (1 - age) of its size today. It keeps
|
|
705
|
-
// (1 - self_pruning) - age of its length as a dead stub, and once that no longer clears the bark it has
|
|
706
|
-
// been grown over, leaving a scar.
|
|
707
|
-
if (rule.crown_envelope && !job.is_split_fork && planned_child_count > 1) {
|
|
708
|
-
const persistence = 1.0 - species.branches[rule.target].self_pruning;
|
|
709
|
-
let t_node = t_lo;
|
|
710
|
-
for (let c = -1; ; c--) {
|
|
711
|
-
const t_dead = lerp(t_lo, t_hi, rule_node_u(rule, c, planned_child_count, level === 0));
|
|
712
|
-
// past the stem's base, or a layout of a single node (one pair, one whorl) that has no 'below'
|
|
713
|
-
if (t_dead <= 0.0 || t_dead > t_node) break;
|
|
714
|
-
t_node = t_dead;
|
|
715
|
-
const age = (t_lo - t_dead) / t_lo;
|
|
716
|
-
const phi_c = base_phi + rule_node_phi(rule, c) + (rng() * 2.0 - 1.0) * rule.azimuth_jitter;
|
|
717
|
-
scheduled_children.push({ t_attach: t_dead, phi: phi_c, rule, age, retained: persistence - age });
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
const k_symp = lvl_cfg.nodal_kink_strength ?? 0.45;
|
|
723
|
-
const zig_base = lvl_cfg.internode_zigzag ?? 0.06;
|
|
724
|
-
const tip_up = lvl_cfg.tip_upcurl ?? 0.18;
|
|
725
|
-
|
|
726
|
-
// Horizontal emergence direction of this branch for outward radial consistency near the trunk centerline
|
|
727
|
-
const emerge_horiz_len = Math.hypot(job.tx, job.tz);
|
|
728
|
-
const emerge_hx = emerge_horiz_len > 1e-4 ? job.tx / emerge_horiz_len : cur_rx;
|
|
729
|
-
const emerge_hz = emerge_horiz_len > 1e-4 ? job.tz / emerge_horiz_len : cur_rz;
|
|
730
|
-
|
|
731
|
-
// heading that straightness pulls back towards: the bud's, re-aimed by a split for the fork that continues
|
|
732
|
-
let aim_x = job.tx;
|
|
733
|
-
let aim_y = job.ty;
|
|
734
|
-
let aim_z = job.tz;
|
|
735
|
-
|
|
736
|
-
for (let s = job.start_seg + 1; s <= N_seg; s++) {
|
|
737
|
-
const t_prev = (s - 1) / N_seg;
|
|
738
|
-
const t_curr = s / N_seg;
|
|
739
|
-
const t_shape = lerp(job.cum_t0, job.cum_t1, t_curr);
|
|
740
|
-
|
|
741
|
-
// 1. Intrinsic S-curve pitch curvature (zero-mean higher harmonic on trunk so trunk weaves rather than leaning)
|
|
742
|
-
let s_wave = level === 0
|
|
743
|
-
? (lvl_cfg.curvature * Math.sin(PI2 * 1.5 * t_shape + phase_hash * PI2))
|
|
744
|
-
: (lvl_cfg.curve_back !== 0.0
|
|
745
|
-
? (lvl_cfg.curvature * Math.cos(Math.PI * t_shape) + lvl_cfg.curve_back * Math.sin(PI2 * t_shape))
|
|
746
|
-
: lvl_cfg.curvature);
|
|
747
|
-
// Attenuate negative (inward-toward-parent) pitch curvature on steep upper-crown insertions
|
|
748
|
-
// so upper lateral branches never curve backward across the parent stem axis.
|
|
749
|
-
if (level >= 1 && s_wave < 0.0 && job.parent_attach_t > 0.60) {
|
|
750
|
-
s_wave *= clamp(1.0 - (job.parent_attach_t - 0.60) * 2.2, 0.15, 1.0);
|
|
751
|
-
}
|
|
752
|
-
const d_pitch = s_wave / Math.max(2, N_shape * 0.65);
|
|
753
|
-
|
|
754
|
-
if (Math.abs(d_pitch) > 1e-6) {
|
|
755
|
-
rotate_around_axis(rot_scratch, cur_tx, cur_ty, cur_tz, cur_sx, cur_sy, cur_sz, d_pitch);
|
|
756
|
-
cur_tx = rot_scratch[0];
|
|
757
|
-
cur_ty = rot_scratch[1];
|
|
758
|
-
cur_tz = rot_scratch[2];
|
|
759
|
-
rotate_around_axis(rot_scratch, cur_rx, cur_ry, cur_rz, cur_sx, cur_sy, cur_sz, d_pitch);
|
|
760
|
-
cur_rx = rot_scratch[0];
|
|
761
|
-
cur_ry = rot_scratch[1];
|
|
762
|
-
cur_rz = rot_scratch[2];
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// 2. Helical spiral twist around T
|
|
766
|
-
if (Math.abs(lvl_cfg.spiral_twist) > 1e-6) {
|
|
767
|
-
const d_twist = lvl_cfg.spiral_twist / N_shape;
|
|
768
|
-
rotate_around_axis(rot_scratch, cur_rx, cur_ry, cur_rz, cur_tx, cur_ty, cur_tz, d_twist);
|
|
769
|
-
cur_rx = rot_scratch[0];
|
|
770
|
-
cur_ry = rot_scratch[1];
|
|
771
|
-
cur_rz = rot_scratch[2];
|
|
772
|
-
cur_sx = cur_ty * cur_rz - cur_tz * cur_ry;
|
|
773
|
-
cur_sy = cur_tz * cur_rx - cur_tx * cur_rz;
|
|
774
|
-
cur_sz = cur_tx * cur_ry - cur_ty * cur_rx;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// 2b. Sympodial Nodal Kinking at scheduled lateral child emissions + Internodal Zig-Zag
|
|
778
|
-
let kink_rx = 0.0;
|
|
779
|
-
let kink_ry = 0.0;
|
|
780
|
-
let kink_rz = 0.0;
|
|
781
|
-
|
|
782
|
-
if (k_symp > 0.0) {
|
|
783
|
-
for (let c = 0; c < scheduled_children.length; c++) {
|
|
784
|
-
const sc = scheduled_children[c];
|
|
785
|
-
if (sc.rule.parent_kink > 0.0 && sc.t_attach >= t_prev && sc.t_attach <= t_curr) {
|
|
786
|
-
// Recoil away from child branch emission azimuth phi
|
|
787
|
-
const c_phi = Math.cos(sc.phi);
|
|
788
|
-
const s_phi = Math.sin(sc.phi);
|
|
789
|
-
const em_x = c_phi * cur_rx + s_phi * cur_sx;
|
|
790
|
-
const em_y = c_phi * cur_ry + s_phi * cur_sy;
|
|
791
|
-
const em_z = c_phi * cur_rz + s_phi * cur_sz;
|
|
792
|
-
const apical_boost = sc.t_attach > 0.85 ? 1.35 : 1.0;
|
|
793
|
-
const recoil = k_symp * sc.rule.parent_kink * (level === 0 ? 0.34 : 0.48) * apical_boost * (0.75 + 0.5 * rng());
|
|
794
|
-
kink_rx -= em_x * recoil;
|
|
795
|
-
kink_ry -= em_y * recoil;
|
|
796
|
-
kink_rz -= em_z * recoil;
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
if (zig_base > 0.0) {
|
|
802
|
-
const z_sign = (s & 1) === 0 ? 1.0 : -1.0;
|
|
803
|
-
const z_phi = phase_hash * PI2 + s * 0.85;
|
|
804
|
-
const zx = Math.cos(z_phi) * cur_rx + Math.sin(z_phi) * cur_sx;
|
|
805
|
-
const zy = Math.cos(z_phi) * cur_ry + Math.sin(z_phi) * cur_sy;
|
|
806
|
-
const zz = Math.cos(z_phi) * cur_rz + Math.sin(z_phi) * cur_sz;
|
|
807
|
-
const z_amp = zig_base * z_sign * (0.7 + 0.6 * rng());
|
|
808
|
-
kink_rx += zx * z_amp;
|
|
809
|
-
kink_ry += zy * z_amp;
|
|
810
|
-
kink_rz += zz * z_amp;
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
const kink_mag = Math.hypot(kink_rx, kink_ry, kink_rz);
|
|
814
|
-
if (kink_mag > 1e-5) {
|
|
815
|
-
const kx = kink_rx / kink_mag;
|
|
816
|
-
const ky = kink_ry / kink_mag;
|
|
817
|
-
const kz = kink_rz / kink_mag;
|
|
818
|
-
// Bend axis = T x K
|
|
819
|
-
let bx = cur_ty * kz - cur_tz * ky;
|
|
820
|
-
let by = cur_tz * kx - cur_tx * kz;
|
|
821
|
-
let bz = cur_tx * ky - cur_ty * kx;
|
|
822
|
-
const b_len = Math.hypot(bx, by, bz);
|
|
823
|
-
if (b_len > 1e-6) {
|
|
824
|
-
bx /= b_len;
|
|
825
|
-
by /= b_len;
|
|
826
|
-
bz /= b_len;
|
|
827
|
-
const clamped_kink = Math.min(0.42, kink_mag);
|
|
828
|
-
rotate_around_axis(rot_scratch, cur_tx, cur_ty, cur_tz, bx, by, bz, clamped_kink);
|
|
829
|
-
cur_tx = rot_scratch[0];
|
|
830
|
-
cur_ty = rot_scratch[1];
|
|
831
|
-
cur_tz = rot_scratch[2];
|
|
832
|
-
rotate_around_axis(rot_scratch, cur_rx, cur_ry, cur_rz, bx, by, bz, clamped_kink);
|
|
833
|
-
cur_rx = rot_scratch[0];
|
|
834
|
-
cur_ry = rot_scratch[1];
|
|
835
|
-
cur_rz = rot_scratch[2];
|
|
836
|
-
cur_sx = cur_ty * cur_rz - cur_tz * cur_ry;
|
|
837
|
-
cur_sy = cur_tz * cur_rx - cur_tx * cur_rz;
|
|
838
|
-
cur_sz = cur_tx * cur_ry - cur_ty * cur_rx;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// 3. Biomechanical Tropism steering (Gravitropic sag + Terminal Phototropic Up-Curl + Outward spread)
|
|
843
|
-
let steer_x = 0.0;
|
|
844
|
-
let steer_y = 0.0;
|
|
845
|
-
let steer_z = 0.0;
|
|
846
|
-
|
|
847
|
-
if (level === 0 && branch_id === 0) {
|
|
848
|
-
// Orthotropic upward striving & plumb-line righting reflex along guide curve (default vertical +Y)
|
|
849
|
-
evaluate_trunk_guide(SCRATCH_GUIDE_P0, SCRATCH_GUIDE_T, species.trunk_guide, t_prev, job.length);
|
|
850
|
-
evaluate_trunk_guide(SCRATCH_GUIDE_P1, SCRATCH_GUIDE_T, species.trunk_guide, t_curr, job.length);
|
|
851
|
-
const gtx = SCRATCH_GUIDE_T[0];
|
|
852
|
-
const gty = SCRATCH_GUIDE_T[1];
|
|
853
|
-
const gtz = SCRATCH_GUIDE_T[2];
|
|
854
|
-
|
|
855
|
-
const is_conifer = lvl_cfg.excurrent;
|
|
856
|
-
const apical_relax = (!is_conifer && (t_shape > 0.80 || job.has_split)) ? 0.55 : 1.0;
|
|
857
|
-
const righting = (1.65 + Math.max(0.0, -lvl_cfg.gravitropism) * 2.5 + Math.max(0.0, lvl_cfg.phototropism)) * apical_relax;
|
|
858
|
-
steer_x += gtx * righting;
|
|
859
|
-
steer_y += gty * righting;
|
|
860
|
-
steer_z += gtz * righting;
|
|
861
|
-
|
|
862
|
-
// Plumb-line centering force pulling lateral drift back toward the guide curve
|
|
863
|
-
const dx_g = cur_px - SCRATCH_GUIDE_P0[0];
|
|
864
|
-
const dy_g = cur_py - SCRATCH_GUIDE_P0[1];
|
|
865
|
-
const dz_g = cur_pz - SCRATCH_GUIDE_P0[2];
|
|
866
|
-
const dot_g = dx_g * gtx + dy_g * gty + dz_g * gtz;
|
|
867
|
-
const lat_x = dx_g - dot_g * gtx;
|
|
868
|
-
const lat_y = dy_g - dot_g * gty;
|
|
869
|
-
const lat_z = dz_g - dot_g * gtz;
|
|
870
|
-
const plumb_k = ((is_conifer ? 5.2 : 3.4) * apical_relax) / Math.max(1.0, job.length);
|
|
871
|
-
steer_x -= lat_x * plumb_k * N_seg * 0.5;
|
|
872
|
-
steer_y -= lat_y * plumb_k * N_seg * 0.5;
|
|
873
|
-
steer_z -= lat_z * plumb_k * N_seg * 0.5;
|
|
874
|
-
} else if (level === 0 && branch_id > 0) {
|
|
875
|
-
// Secondary bifurcated trunk leader (fork): ascend skyward while preserving fork divergence
|
|
876
|
-
steer_x += emerge_hx * 0.35;
|
|
877
|
-
steer_y += 1.25 + Math.max(0.0, lvl_cfg.phototropism);
|
|
878
|
-
steer_z += emerge_hz * 0.35;
|
|
879
|
-
} else {
|
|
880
|
-
if (lvl_cfg.gravitropism !== 0.0 || tip_up !== 0.0) {
|
|
881
|
-
// Continuous blend from cantilever mid-branch sag arch (sin(pi * t^0.75)) to
|
|
882
|
-
// pendulous weeping cascade (0.65 + 0.95 * t) based on gravitropism vs. tip_upcurl
|
|
883
|
-
const w_pendulous = level >= 2
|
|
884
|
-
? clamp((lvl_cfg.gravitropism - 0.35 - 0.5 * Math.max(0.0, tip_up)) / 0.45, 0.0, 1.0)
|
|
885
|
-
: 0.0;
|
|
886
|
-
const arch_grav_env = Math.sin(Math.PI * Math.pow(t_shape, 0.75));
|
|
887
|
-
const weep_grav_env = 0.65 + 0.95 * t_shape;
|
|
888
|
-
const grav_env = lerp(arch_grav_env, weep_grav_env, w_pendulous);
|
|
889
|
-
const apical_grav_atten = level === 1 && job.parent_attach_t > 0.75 && w_pendulous < 0.1
|
|
890
|
-
? clamp(1.0 - (job.parent_attach_t - 0.75) * 3.2, 0.15, 1.0)
|
|
891
|
-
: 1.0;
|
|
892
|
-
const up_env = t_shape * t_shape * (1.0 + 0.5 * Math.max(0.0, -cur_ty));
|
|
893
|
-
steer_y += -lvl_cfg.gravitropism * grav_env * apical_grav_atten + tip_up * up_env;
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
if (lvl_cfg.phototropism !== 0.0) {
|
|
897
|
-
const sky_mix = crown.sky_phototropism_mix;
|
|
898
|
-
const horiz_r = Math.sqrt(cur_px * cur_px + cur_pz * cur_pz);
|
|
899
|
-
let out_x = 0.0;
|
|
900
|
-
let out_z = 0.0;
|
|
901
|
-
if (horiz_r > 0.52) {
|
|
902
|
-
out_x = cur_px / horiz_r;
|
|
903
|
-
out_z = cur_pz / horiz_r;
|
|
904
|
-
} else if (horiz_r > 1e-4) {
|
|
905
|
-
const w_rad = horiz_r / 0.52;
|
|
906
|
-
const bx = lerp(emerge_hx, cur_px / horiz_r, w_rad);
|
|
907
|
-
const bz = lerp(emerge_hz, cur_pz / horiz_r, w_rad);
|
|
908
|
-
const bl = Math.hypot(bx, bz) || 1.0;
|
|
909
|
-
out_x = bx / bl;
|
|
910
|
-
out_z = bz / bl;
|
|
911
|
-
} else {
|
|
912
|
-
out_x = emerge_hx;
|
|
913
|
-
out_z = emerge_hz;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
steer_x += lvl_cfg.phototropism * (1.0 - sky_mix) * out_x;
|
|
917
|
-
steer_y += lvl_cfg.phototropism * sky_mix;
|
|
918
|
-
steer_z += lvl_cfg.phototropism * (1.0 - sky_mix) * out_z;
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
// 4. 2-Octave 3D Curl Noise gnarl scaled properly by sqrt(6 / N_seg)
|
|
923
|
-
if (lvl_cfg.gnarl_strength > 0.0) {
|
|
924
|
-
const freq = lvl_cfg.gnarl_frequency;
|
|
925
|
-
curl_noise_3d(
|
|
926
|
-
SCRATCH_CURL,
|
|
927
|
-
cur_px * freq + noise_ox + level * 17.3,
|
|
928
|
-
cur_py * freq + noise_oy + level * 31.7,
|
|
929
|
-
cur_pz * freq + noise_oz + level * 47.1
|
|
930
|
-
);
|
|
931
|
-
const c1x = SCRATCH_CURL[0], c1y = SCRATCH_CURL[1], c1z = SCRATCH_CURL[2];
|
|
932
|
-
curl_noise_3d(
|
|
933
|
-
SCRATCH_CURL,
|
|
934
|
-
cur_px * freq * 2.15 + noise_ox + 53.1 + phase_hash * 29.3,
|
|
935
|
-
cur_py * freq * 2.15 + noise_oy + 79.3,
|
|
936
|
-
cur_pz * freq * 2.15 + noise_oz + 113.7
|
|
937
|
-
);
|
|
938
|
-
const cx_tot = 0.68 * c1x + 0.32 * SCRATCH_CURL[0];
|
|
939
|
-
const cy_tot = 0.68 * c1y + 0.32 * SCRATCH_CURL[1];
|
|
940
|
-
const cz_tot = 0.68 * c1z + 0.32 * SCRATCH_CURL[2];
|
|
941
|
-
|
|
942
|
-
const gnarl_scale = lvl_cfg.gnarl_strength * Math.sqrt(6.0 / Math.max(2, N_shape));
|
|
943
|
-
// Project curl vector perpendicular to T so it steers direction cleanly
|
|
944
|
-
const dot_ct = cx_tot * cur_tx + cy_tot * cur_ty + cz_tot * cur_tz;
|
|
945
|
-
steer_x += (cx_tot - dot_ct * cur_tx) * gnarl_scale * 2.2;
|
|
946
|
-
steer_y += (cy_tot - dot_ct * cur_ty) * gnarl_scale * 1.6;
|
|
947
|
-
steer_z += (cz_tot - dot_ct * cur_tz) * gnarl_scale * 2.2;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
// 4b. A plagiotropic shoot is pulled towards the horizontal; straightness pulls back towards the bud's heading
|
|
951
|
-
if (level >= 1 && lvl_cfg.plagiotropism > 0.0) {
|
|
952
|
-
steer_y -= lvl_cfg.plagiotropism * cur_ty;
|
|
953
|
-
}
|
|
954
|
-
if (lvl_cfg.straightness > 0.0 && !(level === 0 && branch_id === 0)) {
|
|
955
|
-
steer_x += lvl_cfg.straightness * (aim_x - cur_tx);
|
|
956
|
-
steer_y += lvl_cfg.straightness * (aim_y - cur_ty);
|
|
957
|
-
steer_z += lvl_cfg.straightness * (aim_z - cur_tz);
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
// 5. Space colonization attraction steering (for branches in crown, level >= 1)
|
|
961
|
-
if (use_space_col && level >= 1 && job.in_crown && attr_grid !== null) {
|
|
962
|
-
let attr_vx = 0.0;
|
|
963
|
-
let attr_vy = 0.0;
|
|
964
|
-
let attr_vz = 0.0;
|
|
965
|
-
let attr_hits = 0;
|
|
966
|
-
|
|
967
|
-
attr_grid.forEachNear(cur_px, cur_py, cur_pz, (idx) => {
|
|
968
|
-
if (attr_alive[idx] === 0) return;
|
|
969
|
-
const ax = attr_positions[idx * 3] - cur_px;
|
|
970
|
-
const ay = attr_positions[idx * 3 + 1] - cur_py;
|
|
971
|
-
const az = attr_positions[idx * 3 + 2] - cur_pz;
|
|
972
|
-
const d2 = ax * ax + ay * ay + az * az;
|
|
973
|
-
if (d2 <= r_kill_sq) {
|
|
974
|
-
attr_alive[idx] = 0;
|
|
975
|
-
} else if (d2 <= r_influence_sq && d2 > 1e-8) {
|
|
976
|
-
const inv_d = 1.0 / Math.sqrt(d2);
|
|
977
|
-
attr_vx += ax * inv_d;
|
|
978
|
-
attr_vy += ay * inv_d;
|
|
979
|
-
attr_vz += az * inv_d;
|
|
980
|
-
attr_hits++;
|
|
981
|
-
}
|
|
982
|
-
});
|
|
983
|
-
|
|
984
|
-
if (attr_hits > 0) {
|
|
985
|
-
const attr_len = Math.sqrt(attr_vx * attr_vx + attr_vy * attr_vy + attr_vz * attr_vz);
|
|
986
|
-
if (attr_len > 1e-6) {
|
|
987
|
-
const w_sc = crown.space_colonization_weight / attr_len;
|
|
988
|
-
steer_x += attr_vx * w_sc;
|
|
989
|
-
steer_y += attr_vy * w_sc;
|
|
990
|
-
steer_z += attr_vz * w_sc;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// 6. Crown envelope soft containment for lateral branches (level >= 1)
|
|
996
|
-
let seg_len = step_len;
|
|
997
|
-
if (level >= 1 && job.in_crown && crown.prune_ratio > 0.0) {
|
|
998
|
-
const probe_x = cur_px + cur_tx * step_len;
|
|
999
|
-
const probe_y = cur_py + cur_ty * step_len;
|
|
1000
|
-
const probe_z = cur_pz + cur_tz * step_len;
|
|
1001
|
-
const sdf = crown_envelope_sdf(probe_x, probe_y, probe_z, crown);
|
|
1002
|
-
if (sdf > 0.0) {
|
|
1003
|
-
// Steer radially inward toward trunk axis (only pulling vertically if outside vertical crown span)
|
|
1004
|
-
const crown_top_y = crown.base_height + crown.height;
|
|
1005
|
-
const to_cx = -probe_x;
|
|
1006
|
-
const to_cy = probe_y > crown_top_y
|
|
1007
|
-
? crown_top_y - probe_y
|
|
1008
|
-
: (probe_y < crown.base_height ? crown.base_height - probe_y : 0.0);
|
|
1009
|
-
const to_cz = -probe_z;
|
|
1010
|
-
const to_c_len = Math.sqrt(to_cx * to_cx + to_cy * to_cy + to_cz * to_cz);
|
|
1011
|
-
if (to_c_len > 1e-5) {
|
|
1012
|
-
const pull = crown.prune_ratio * 0.45 / to_c_len;
|
|
1013
|
-
steer_x += to_cx * pull;
|
|
1014
|
-
steer_y += to_cy * pull;
|
|
1015
|
-
steer_z += to_cz * pull;
|
|
1016
|
-
}
|
|
1017
|
-
seg_len *= Math.max(0.35, 1.0 - crown.prune_ratio * 0.5);
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// Compute target radius r_curr for this segment before BVH collision checks
|
|
1022
|
-
const t_spine = t_shape;
|
|
1023
|
-
const u_prof = prof_span > 1e-4
|
|
1024
|
-
? clamp((prof_p0 - lvl_cfg.radius_profile.evaluate(t_spine)) / prof_span, 0.0, 1.0)
|
|
1025
|
-
: t_spine;
|
|
1026
|
-
const min_tip_frac = level === 0 ? 0.12 : (has_children ? 0.14 : 0.24);
|
|
1027
|
-
const apical_shoot_taper = level === 0
|
|
1028
|
-
? (t_spine > 0.65 ? lerp(1.0, 0.11, Math.pow((t_spine - 0.65) / 0.35, 1.45)) : 1.0)
|
|
1029
|
-
: (has_children && t_curr > 0.80 ? lerp(1.0, 0.62, Math.pow((t_curr - 0.80) / 0.20, 1.35)) : 1.0);
|
|
1030
|
-
const r_curr = Math.max(
|
|
1031
|
-
level === 0 ? 0.0015 : 0.002,
|
|
1032
|
-
job.base_radius * Math.max(1.0 - lvl_cfg.taper * u_prof, min_tip_frac) * apical_shoot_taper
|
|
1033
|
-
);
|
|
1034
|
-
const r_prev = out.node_read_radius(prev_v);
|
|
1035
|
-
const local_seg_idx = branch_edges.length;
|
|
1036
|
-
const parent_bA = job.parent_branch_id;
|
|
1037
|
-
const attach_seg_A = job.parent_attach_seg_idx ?? -1;
|
|
1038
|
-
const is_split_A = Boolean(job.is_split_fork);
|
|
1039
|
-
|
|
1040
|
-
// 6b. Soft BVH Repulsion Field (steers growing shoot smoothly around nearby existing branches)
|
|
1041
|
-
if ((level >= 1 || branch_id > 0) && bvh.size > 0) {
|
|
1042
|
-
const look_len = seg_len * 1.55;
|
|
1043
|
-
const look_x = cur_px + cur_tx * look_len;
|
|
1044
|
-
const look_y = cur_py + cur_ty * look_len;
|
|
1045
|
-
const look_z = cur_pz + cur_tz * look_len;
|
|
1046
|
-
const soft_margin = Math.max(0.08, Math.min(0.24, seg_len * 0.65));
|
|
1047
|
-
|
|
1048
|
-
query_capsule[0] = cur_px;
|
|
1049
|
-
query_capsule[1] = cur_py;
|
|
1050
|
-
query_capsule[2] = cur_pz;
|
|
1051
|
-
query_capsule[3] = look_x;
|
|
1052
|
-
query_capsule[4] = look_y;
|
|
1053
|
-
query_capsule[5] = look_z;
|
|
1054
|
-
query_capsule[6] = Math.max(r_prev, r_curr) * 1.15 + soft_margin;
|
|
1055
|
-
|
|
1056
|
-
const hit_count = bvh_query_user_data_intersects_capsule(bvh_hits, 0, bvh, query_capsule);
|
|
1057
|
-
for (let h = 0; h < hit_count; h++) {
|
|
1058
|
-
const eB = bvh_hits[h];
|
|
1059
|
-
const bB = seg_branch_id[eB];
|
|
1060
|
-
const local_idx_B = seg_local_idx[eB];
|
|
1061
|
-
if (!resolve_subsegment_bounds(
|
|
1062
|
-
branch_id, parent_bA, attach_seg_A, is_split_A, local_seg_idx,
|
|
1063
|
-
bB, out.branch_parent[bB], branch_parent_seg_idx[bB], branch_is_split_fork[bB], local_idx_B,
|
|
1064
|
-
sub_bounds
|
|
1065
|
-
)) {
|
|
1066
|
-
continue;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
const s_min = sub_bounds[0];
|
|
1070
|
-
const t_min = sub_bounds[1];
|
|
1071
|
-
const sa0x = cur_px + (look_x - cur_px) * s_min;
|
|
1072
|
-
const sa0y = cur_py + (look_y - cur_py) * s_min;
|
|
1073
|
-
const sa0z = cur_pz + (look_z - cur_pz) * s_min;
|
|
1074
|
-
|
|
1075
|
-
const b0x = seg_p0x[eB], b0y = seg_p0y[eB], b0z = seg_p0z[eB];
|
|
1076
|
-
const b1x = seg_p1x[eB], b1y = seg_p1y[eB], b1z = seg_p1z[eB];
|
|
1077
|
-
const sb0x = b0x + (b1x - b0x) * t_min;
|
|
1078
|
-
const sb0y = b0y + (b1y - b0y) * t_min;
|
|
1079
|
-
const sb0z = b0z + (b1z - b0z) * t_min;
|
|
1080
|
-
|
|
1081
|
-
line3_compute_segment_segment_closest_points(
|
|
1082
|
-
closest_st,
|
|
1083
|
-
sa0x, sa0y, sa0z, look_x, look_y, look_z,
|
|
1084
|
-
sb0x, sb0y, sb0z, b1x, b1y, b1z
|
|
1085
|
-
);
|
|
1086
|
-
|
|
1087
|
-
const s_f = s_min + (1.0 - s_min) * closest_st[0];
|
|
1088
|
-
const t_f = t_min + (1.0 - t_min) * closest_st[1];
|
|
1089
|
-
|
|
1090
|
-
const qAx = cur_px + (look_x - cur_px) * s_f;
|
|
1091
|
-
const qAy = cur_py + (look_y - cur_py) * s_f;
|
|
1092
|
-
const qAz = cur_pz + (look_z - cur_pz) * s_f;
|
|
1093
|
-
const qBx = b0x + (b1x - b0x) * t_f;
|
|
1094
|
-
const qBy = b0y + (b1y - b0y) * t_f;
|
|
1095
|
-
const qBz = b0z + (b1z - b0z) * t_f;
|
|
1096
|
-
|
|
1097
|
-
const dx = qAx - qBx;
|
|
1098
|
-
const dy = qAy - qBy;
|
|
1099
|
-
const dz = qAz - qBz;
|
|
1100
|
-
const dist = Math.hypot(dx, dy, dz);
|
|
1101
|
-
const rA = r_prev + (r_curr - r_prev) * Math.min(1.0, s_f);
|
|
1102
|
-
const rB = seg_r0[eB] + (seg_r1[eB] - seg_r0[eB]) * t_f;
|
|
1103
|
-
const r_req = (rA + rB) * 1.12 + 0.005;
|
|
1104
|
-
|
|
1105
|
-
if (dist < r_req + soft_margin) {
|
|
1106
|
-
let nx = dist > 1e-5 ? dx / dist : cur_rx;
|
|
1107
|
-
let ny = dist > 1e-5 ? dy / dist : cur_ry;
|
|
1108
|
-
let nz = dist > 1e-5 ? dz / dist : cur_rz;
|
|
1109
|
-
// Project repulsion normal perpendicular to cur_T so it steers smoothly around the obstacle
|
|
1110
|
-
const dot_nt = nx * cur_tx + ny * cur_ty + nz * cur_tz;
|
|
1111
|
-
nx -= dot_nt * cur_tx;
|
|
1112
|
-
ny -= dot_nt * cur_ty;
|
|
1113
|
-
nz -= dot_nt * cur_tz;
|
|
1114
|
-
const n_len = Math.hypot(nx, ny, nz);
|
|
1115
|
-
if (n_len > 1e-5) {
|
|
1116
|
-
nx /= n_len;
|
|
1117
|
-
ny /= n_len;
|
|
1118
|
-
nz /= n_len;
|
|
1119
|
-
const u_prox = clamp(1.0 - Math.max(0.0, dist - r_req) / soft_margin, 0.0, 1.0);
|
|
1120
|
-
const rep_w = u_prox * u_prox * 2.8;
|
|
1121
|
-
steer_x += nx * rep_w;
|
|
1122
|
-
steer_y += ny * rep_w;
|
|
1123
|
-
steer_z += nz * rep_w;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
// Apply steering to tangent and normalize
|
|
1130
|
-
const steer_div = Math.max(2.0, Math.sqrt(N_shape) * 1.35);
|
|
1131
|
-
let next_tx = cur_tx + steer_x / steer_div;
|
|
1132
|
-
let next_ty = cur_ty + steer_y / steer_div;
|
|
1133
|
-
let next_tz = cur_tz + steer_z / steer_div;
|
|
1134
|
-
const t_len = Math.sqrt(next_tx * next_tx + next_ty * next_ty + next_tz * next_tz);
|
|
1135
|
-
if (t_len > 1e-12) {
|
|
1136
|
-
next_tx /= t_len;
|
|
1137
|
-
next_ty /= t_len;
|
|
1138
|
-
next_tz /= t_len;
|
|
1139
|
-
} else {
|
|
1140
|
-
next_tx = cur_tx;
|
|
1141
|
-
next_ty = cur_ty;
|
|
1142
|
-
next_tz = cur_tz;
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
let next_px = cur_px + 0.5 * (cur_tx + next_tx) * seg_len;
|
|
1146
|
-
let next_py = cur_py + 0.5 * (cur_ty + next_ty) * seg_len;
|
|
1147
|
-
let next_pz = cur_pz + 0.5 * (cur_tz + next_tz) * seg_len;
|
|
1148
|
-
|
|
1149
|
-
// Orthotropic plumb-tube containment on trunk (level === 0):
|
|
1150
|
-
// Preserves local 3D nodal kinks and serpentine sweep while guaranteeing the trunk never drifts sideways
|
|
1151
|
-
if (level === 0 && branch_id === 0) {
|
|
1152
|
-
const gx = SCRATCH_GUIDE_P1[0];
|
|
1153
|
-
const gy = SCRATCH_GUIDE_P1[1];
|
|
1154
|
-
const gz = SCRATCH_GUIDE_P1[2];
|
|
1155
|
-
const gtx = SCRATCH_GUIDE_T[0];
|
|
1156
|
-
const gty = SCRATCH_GUIDE_T[1];
|
|
1157
|
-
const gtz = SCRATCH_GUIDE_T[2];
|
|
1158
|
-
|
|
1159
|
-
const dx = next_px - gx;
|
|
1160
|
-
const dy = next_py - gy;
|
|
1161
|
-
const dz = next_pz - gz;
|
|
1162
|
-
const dot_g = dx * gtx + dy * gty + dz * gtz;
|
|
1163
|
-
const off_x = dx - dot_g * gtx;
|
|
1164
|
-
const off_y = dy - dot_g * gty;
|
|
1165
|
-
const off_z = dz - dot_g * gtz;
|
|
1166
|
-
const off_r = Math.hypot(off_x, off_y, off_z);
|
|
1167
|
-
|
|
1168
|
-
const is_conifer = lvl_cfg.excurrent;
|
|
1169
|
-
const max_drift_frac = is_conifer ? 0.032 : (job.has_split ? 0.078 : 0.058);
|
|
1170
|
-
// Envelope rises from 0 at root, peaks in mid-trunk (allowing 3D sweep/crookedness), and tapers near tip
|
|
1171
|
-
const env = Math.sin(Math.PI * Math.min(0.92, t_curr * 0.88)) * (1.0 - 0.38 * t_curr * t_curr);
|
|
1172
|
-
const max_r = job.length * max_drift_frac * Math.max(job.has_split ? 0.42 : 0.18, env);
|
|
1173
|
-
|
|
1174
|
-
if (off_r > max_r && off_r > 1e-6) {
|
|
1175
|
-
const scale_r = max_r / off_r;
|
|
1176
|
-
next_px = gx + dot_g * gtx + off_x * scale_r;
|
|
1177
|
-
next_py = gy + dot_g * gty + off_y * scale_r;
|
|
1178
|
-
next_pz = gz + dot_g * gtz + off_z * scale_r;
|
|
1179
|
-
|
|
1180
|
-
let ch_x = next_px - cur_px;
|
|
1181
|
-
let ch_y = next_py - cur_py;
|
|
1182
|
-
let ch_z = next_pz - cur_pz;
|
|
1183
|
-
const ch_l = Math.hypot(ch_x, ch_y, ch_z);
|
|
1184
|
-
if (ch_l > 1e-6) {
|
|
1185
|
-
next_tx = ch_x / ch_l;
|
|
1186
|
-
next_ty = ch_y / ch_l;
|
|
1187
|
-
next_tz = ch_z / ch_l;
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
// 6c. Monotonic Parent Collar Escape + Hard BVH Capsule Contact Projection & Self-Pruning
|
|
1193
|
-
if ((level >= 1 || branch_id > 0) && bvh.size > 0) {
|
|
1194
|
-
let was_projected = false;
|
|
1195
|
-
|
|
1196
|
-
// Ensure lateral child branch exits its parent attachment segment cleanly on local_seg_idx <= 1
|
|
1197
|
-
if (job.parent_edge_id >= 0 && !is_split_A && local_seg_idx <= 1) {
|
|
1198
|
-
const eP = job.parent_edge_id;
|
|
1199
|
-
const p0x = seg_p0x[eP], p0y = seg_p0y[eP], p0z = seg_p0z[eP];
|
|
1200
|
-
const p1x = seg_p1x[eP], p1y = seg_p1y[eP], p1z = seg_p1z[eP];
|
|
1201
|
-
|
|
1202
|
-
line3_compute_segment_segment_closest_points(
|
|
1203
|
-
closest_st,
|
|
1204
|
-
next_px, next_py, next_pz, next_px, next_py, next_pz,
|
|
1205
|
-
p0x, p0y, p0z, p1x, p1y, p1z
|
|
1206
|
-
);
|
|
1207
|
-
const t_p = closest_st[1];
|
|
1208
|
-
const qPx = p0x + (p1x - p0x) * t_p;
|
|
1209
|
-
const qPy = p0y + (p1y - p0y) * t_p;
|
|
1210
|
-
const qPz = p0z + (p1z - p0z) * t_p;
|
|
1211
|
-
const r_p = seg_r0[eP] + (seg_r1[eP] - seg_r0[eP]) * t_p;
|
|
1212
|
-
|
|
1213
|
-
let rx_p = next_px - qPx;
|
|
1214
|
-
let ry_p = next_py - qPy;
|
|
1215
|
-
let rz_p = next_pz - qPz;
|
|
1216
|
-
const d_p = Math.hypot(rx_p, ry_p, rz_p);
|
|
1217
|
-
if (local_seg_idx === 0 && (r_p + r_curr) * 1.18 > seg_len) {
|
|
1218
|
-
seg_len = (r_p + r_curr) * 1.18;
|
|
1219
|
-
}
|
|
1220
|
-
const min_d_p = local_seg_idx === 0
|
|
1221
|
-
? Math.min(seg_len * 0.88, (r_p + r_curr) * 1.05)
|
|
1222
|
-
: (r_p + r_curr) * 1.10 + 0.004;
|
|
1223
|
-
|
|
1224
|
-
const em_drx = job.emerge_drx ?? cur_rx;
|
|
1225
|
-
const em_dry = job.emerge_dry ?? cur_ry;
|
|
1226
|
-
const em_drz = job.emerge_drz ?? cur_rz;
|
|
1227
|
-
const dot_em = rx_p * em_drx + ry_p * em_dry + rz_p * em_drz;
|
|
1228
|
-
|
|
1229
|
-
if (d_p < min_d_p || dot_em < 0.0) {
|
|
1230
|
-
const ux_p = (d_p > 1e-5 && dot_em > 0.0) ? rx_p / d_p : em_drx;
|
|
1231
|
-
const uy_p = (d_p > 1e-5 && dot_em > 0.0) ? ry_p / d_p : em_dry;
|
|
1232
|
-
const uz_p = (d_p > 1e-5 && dot_em > 0.0) ? rz_p / d_p : em_drz;
|
|
1233
|
-
const push_d = Math.max(0.0, min_d_p - (dot_em > 0.0 ? d_p : -d_p));
|
|
1234
|
-
next_px += ux_p * push_d;
|
|
1235
|
-
next_py += uy_p * push_d;
|
|
1236
|
-
next_pz += uz_p * push_d;
|
|
1237
|
-
|
|
1238
|
-
let ch_x = next_px - cur_px;
|
|
1239
|
-
let ch_y = next_py - cur_py;
|
|
1240
|
-
let ch_z = next_pz - cur_pz;
|
|
1241
|
-
const ch_l = Math.hypot(ch_x, ch_y, ch_z);
|
|
1242
|
-
if (ch_l > 1e-6) {
|
|
1243
|
-
next_tx = ch_x / ch_l;
|
|
1244
|
-
next_ty = ch_y / ch_l;
|
|
1245
|
-
next_tz = ch_z / ch_l;
|
|
1246
|
-
next_px = cur_px + next_tx * seg_len;
|
|
1247
|
-
next_py = cur_py + next_ty * seg_len;
|
|
1248
|
-
next_pz = cur_pz + next_tz * seg_len;
|
|
1249
|
-
was_projected = true;
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
// Iterative BVH capsule contact projection
|
|
1255
|
-
for (let iter = 0; iter < 4; iter++) {
|
|
1256
|
-
query_capsule[0] = cur_px;
|
|
1257
|
-
query_capsule[1] = cur_py;
|
|
1258
|
-
query_capsule[2] = cur_pz;
|
|
1259
|
-
query_capsule[3] = next_px;
|
|
1260
|
-
query_capsule[4] = next_py;
|
|
1261
|
-
query_capsule[5] = next_pz;
|
|
1262
|
-
query_capsule[6] = Math.max(r_prev, r_curr) * 1.15 + 0.025;
|
|
1263
|
-
|
|
1264
|
-
const hit_count = bvh_query_user_data_intersects_capsule(bvh_hits, 0, bvh, query_capsule);
|
|
1265
|
-
let moved = false;
|
|
1266
|
-
|
|
1267
|
-
for (let h = 0; h < hit_count; h++) {
|
|
1268
|
-
const eB = bvh_hits[h];
|
|
1269
|
-
const bB = seg_branch_id[eB];
|
|
1270
|
-
const local_idx_B = seg_local_idx[eB];
|
|
1271
|
-
if (!resolve_subsegment_bounds(
|
|
1272
|
-
branch_id, parent_bA, attach_seg_A, is_split_A, local_seg_idx,
|
|
1273
|
-
bB, out.branch_parent[bB], branch_parent_seg_idx[bB], branch_is_split_fork[bB], local_idx_B,
|
|
1274
|
-
sub_bounds
|
|
1275
|
-
)) {
|
|
1276
|
-
continue;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
const s_min = sub_bounds[0];
|
|
1280
|
-
const t_min = sub_bounds[1];
|
|
1281
|
-
const sa0x = cur_px + (next_px - cur_px) * s_min;
|
|
1282
|
-
const sa0y = cur_py + (next_py - cur_py) * s_min;
|
|
1283
|
-
const sa0z = cur_pz + (next_pz - cur_pz) * s_min;
|
|
1284
|
-
|
|
1285
|
-
const b0x = seg_p0x[eB], b0y = seg_p0y[eB], b0z = seg_p0z[eB];
|
|
1286
|
-
const b1x = seg_p1x[eB], b1y = seg_p1y[eB], b1z = seg_p1z[eB];
|
|
1287
|
-
const sb0x = b0x + (b1x - b0x) * t_min;
|
|
1288
|
-
const sb0y = b0y + (b1y - b0y) * t_min;
|
|
1289
|
-
const sb0z = b0z + (b1z - b0z) * t_min;
|
|
1290
|
-
|
|
1291
|
-
line3_compute_segment_segment_closest_points(
|
|
1292
|
-
closest_st,
|
|
1293
|
-
sa0x, sa0y, sa0z, next_px, next_py, next_pz,
|
|
1294
|
-
sb0x, sb0y, sb0z, b1x, b1y, b1z
|
|
1295
|
-
);
|
|
1296
|
-
|
|
1297
|
-
const s_f = s_min + (1.0 - s_min) * closest_st[0];
|
|
1298
|
-
const t_f = t_min + (1.0 - t_min) * closest_st[1];
|
|
1299
|
-
if (local_seg_idx === 0 && s_f <= 0.05) continue;
|
|
1300
|
-
|
|
1301
|
-
const qAx = cur_px + (next_px - cur_px) * s_f;
|
|
1302
|
-
const qAy = cur_py + (next_py - cur_py) * s_f;
|
|
1303
|
-
const qAz = cur_pz + (next_pz - cur_pz) * s_f;
|
|
1304
|
-
const qBx = b0x + (b1x - b0x) * t_f;
|
|
1305
|
-
const qBy = b0y + (b1y - b0y) * t_f;
|
|
1306
|
-
const qBz = b0z + (b1z - b0z) * t_f;
|
|
1307
|
-
|
|
1308
|
-
const dx = qAx - qBx;
|
|
1309
|
-
const dy = qAy - qBy;
|
|
1310
|
-
const dz = qAz - qBz;
|
|
1311
|
-
const dist = Math.hypot(dx, dy, dz);
|
|
1312
|
-
const rA = r_prev + (r_curr - r_prev) * s_f;
|
|
1313
|
-
const rB = seg_r0[eB] + (seg_r1[eB] - seg_r0[eB]) * t_f;
|
|
1314
|
-
const r_req = (rA + rB) * 1.08 + 0.004;
|
|
1315
|
-
|
|
1316
|
-
if (dist < r_req) {
|
|
1317
|
-
let nx = dist > 1e-5 ? dx / dist : cur_rx;
|
|
1318
|
-
let ny = dist > 1e-5 ? dy / dist : cur_ry;
|
|
1319
|
-
let nz = dist > 1e-5 ? dz / dist : cur_rz;
|
|
1320
|
-
const push = (r_req - dist + 0.004) / Math.max(0.35, s_f);
|
|
1321
|
-
next_px += nx * push;
|
|
1322
|
-
next_py += ny * push;
|
|
1323
|
-
next_pz += nz * push;
|
|
1324
|
-
|
|
1325
|
-
let ch_x = next_px - cur_px;
|
|
1326
|
-
let ch_y = next_py - cur_py;
|
|
1327
|
-
let ch_z = next_pz - cur_pz;
|
|
1328
|
-
const ch_l = Math.hypot(ch_x, ch_y, ch_z);
|
|
1329
|
-
if (ch_l > 1e-6) {
|
|
1330
|
-
next_tx = ch_x / ch_l;
|
|
1331
|
-
next_ty = ch_y / ch_l;
|
|
1332
|
-
next_tz = ch_z / ch_l;
|
|
1333
|
-
next_px = cur_px + next_tx * seg_len;
|
|
1334
|
-
next_py = cur_py + next_ty * seg_len;
|
|
1335
|
-
next_pz = cur_pz + next_tz * seg_len;
|
|
1336
|
-
moved = true;
|
|
1337
|
-
was_projected = true;
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
if (!moved) break;
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
// If segment >= 1 still penetrates an existing branch or was forced into an unnatural sharp reversal,
|
|
1345
|
-
// self-prune / arrest the branch at prev_v so it never spears through another bough.
|
|
1346
|
-
if (local_seg_idx >= 1) {
|
|
1347
|
-
const final_clear = evaluate_candidate_clearance(
|
|
1348
|
-
branch_id, parent_bA, attach_seg_A, is_split_A, local_seg_idx,
|
|
1349
|
-
cur_px, cur_py, cur_pz, r_prev,
|
|
1350
|
-
next_px, next_py, next_pz, r_curr,
|
|
1351
|
-
-0.004
|
|
1352
|
-
);
|
|
1353
|
-
const turn_cos = cur_tx * next_tx + cur_ty * next_ty + cur_tz * next_tz;
|
|
1354
|
-
if (final_clear < 0.0 || (was_projected && turn_cos < 0.25)) {
|
|
1355
|
-
break;
|
|
1356
|
-
}
|
|
1357
|
-
} else if (was_projected) {
|
|
1358
|
-
// Keep root node tangent aligned with projected first segment so base collar has zero S-hook
|
|
1359
|
-
cur_tx = next_tx;
|
|
1360
|
-
cur_ty = next_ty;
|
|
1361
|
-
cur_tz = next_tz;
|
|
1362
|
-
out.node_write_tangent(prev_v, cur_tx, cur_ty, cur_tz);
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
// Structural surface root: creep along the ground plane y = 0 while sinking into it, its centreline at
|
|
1367
|
-
// r(t) - 2 r_base t: it leaves the stem fully exposed and its top drops by twice its base radius over
|
|
1368
|
-
// its length, so it dissolves into the soil about halfway out and its tip is never seen. Applied after all
|
|
1369
|
-
// steering and contact handling so nothing lifts it off or pushes it under; the first segment keeps
|
|
1370
|
-
// the bud's heading so the root leaves the stem where it was planted (e.g. on a flare lobe crest).
|
|
1371
|
-
if (lvl_cfg.root) {
|
|
1372
|
-
let hx = local_seg_idx === 0 ? emerge_hx : next_px - cur_px;
|
|
1373
|
-
let hz = local_seg_idx === 0 ? emerge_hz : next_pz - cur_pz;
|
|
1374
|
-
let h_len = Math.hypot(hx, hz);
|
|
1375
|
-
if (h_len < 1e-6) {
|
|
1376
|
-
hx = emerge_hx;
|
|
1377
|
-
hz = emerge_hz;
|
|
1378
|
-
h_len = Math.hypot(hx, hz) || 1.0;
|
|
1379
|
-
}
|
|
1380
|
-
hx /= h_len;
|
|
1381
|
-
hz /= h_len;
|
|
1382
|
-
const dy = clamp(r_curr - 2.0 * job.base_radius * t_curr - cur_py, -0.7 * seg_len, 0.7 * seg_len);
|
|
1383
|
-
const h_step = Math.sqrt(seg_len * seg_len - dy * dy);
|
|
1384
|
-
next_px = cur_px + hx * h_step;
|
|
1385
|
-
next_py = cur_py + dy;
|
|
1386
|
-
next_pz = cur_pz + hz * h_step;
|
|
1387
|
-
// arrive following the sinking profile, whose slope along the root is -(2 + taper) r_base / length
|
|
1388
|
-
const sink_slope = -(2.0 + lvl_cfg.taper) * job.base_radius / Math.max(1e-3, job.length);
|
|
1389
|
-
const t_norm_len = Math.hypot(1.0, sink_slope);
|
|
1390
|
-
next_tx = hx / t_norm_len;
|
|
1391
|
-
next_ty = sink_slope / t_norm_len;
|
|
1392
|
-
next_tz = hz / t_norm_len;
|
|
1393
|
-
if (local_seg_idx === 0) {
|
|
1394
|
-
// the root node leaves along its first chord, as a projected collar escape would
|
|
1395
|
-
cur_tx = (next_px - cur_px) / seg_len;
|
|
1396
|
-
cur_ty = (next_py - cur_py) / seg_len;
|
|
1397
|
-
cur_tz = (next_pz - cur_pz) / seg_len;
|
|
1398
|
-
out.node_write_tangent(prev_v, cur_tx, cur_ty, cur_tz);
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
// Transport RMF via Wang et al. Double Reflection
|
|
1403
|
-
spine_frame_double_reflection(
|
|
1404
|
-
SCRATCH_RMF, 0,
|
|
1405
|
-
cur_px, cur_py, cur_pz,
|
|
1406
|
-
cur_tx, cur_ty, cur_tz,
|
|
1407
|
-
cur_rx, cur_ry, cur_rz,
|
|
1408
|
-
next_px, next_py, next_pz,
|
|
1409
|
-
next_tx, next_ty, next_tz
|
|
1410
|
-
);
|
|
1411
|
-
let next_rx = SCRATCH_RMF[0];
|
|
1412
|
-
let next_ry = SCRATCH_RMF[1];
|
|
1413
|
-
let next_rz = SCRATCH_RMF[2];
|
|
1414
|
-
cur_sx = SCRATCH_RMF[3];
|
|
1415
|
-
cur_sy = SCRATCH_RMF[4];
|
|
1416
|
-
cur_sz = SCRATCH_RMF[5];
|
|
1417
|
-
|
|
1418
|
-
cur_arc_s += seg_len;
|
|
1419
|
-
|
|
1420
|
-
const curr_v = out.add_node(
|
|
1421
|
-
next_px, next_py, next_pz,
|
|
1422
|
-
next_tx, next_ty, next_tz,
|
|
1423
|
-
next_rx, next_ry, next_rz,
|
|
1424
|
-
r_curr,
|
|
1425
|
-
branch_id,
|
|
1426
|
-
level,
|
|
1427
|
-
job.parent_branch_id,
|
|
1428
|
-
t_curr,
|
|
1429
|
-
cur_arc_s,
|
|
1430
|
-
phase_hash
|
|
1431
|
-
);
|
|
1432
|
-
|
|
1433
|
-
const edge_id = out.add_segment(
|
|
1434
|
-
prev_v,
|
|
1435
|
-
curr_v,
|
|
1436
|
-
cur_tx * seg_len,
|
|
1437
|
-
cur_ty * seg_len,
|
|
1438
|
-
cur_tz * seg_len,
|
|
1439
|
-
next_tx * seg_len,
|
|
1440
|
-
next_ty * seg_len,
|
|
1441
|
-
next_tz * seg_len,
|
|
1442
|
-
seg_len,
|
|
1443
|
-
t_prev === 0.0 ? job.parent_attach_t : t_prev
|
|
1444
|
-
);
|
|
1445
|
-
|
|
1446
|
-
// Register segment in scope-local BVH
|
|
1447
|
-
const bvh_pad = Math.max(r_prev, r_curr) * 1.25 + 0.008;
|
|
1448
|
-
const bvh_leaf = bvh.allocate_node();
|
|
1449
|
-
bvh.node_set_user_data(bvh_leaf, edge_id);
|
|
1450
|
-
bvh.node_set_aabb_primitive(
|
|
1451
|
-
bvh_leaf,
|
|
1452
|
-
Math.min(cur_px, next_px) - bvh_pad,
|
|
1453
|
-
Math.min(cur_py, next_py) - bvh_pad,
|
|
1454
|
-
Math.min(cur_pz, next_pz) - bvh_pad,
|
|
1455
|
-
Math.max(cur_px, next_px) + bvh_pad,
|
|
1456
|
-
Math.max(cur_py, next_py) + bvh_pad,
|
|
1457
|
-
Math.max(cur_pz, next_pz) + bvh_pad
|
|
1458
|
-
);
|
|
1459
|
-
bvh.insert_leaf(bvh_leaf);
|
|
1460
|
-
|
|
1461
|
-
seg_branch_id[edge_id] = branch_id;
|
|
1462
|
-
seg_local_idx[edge_id] = local_seg_idx;
|
|
1463
|
-
seg_p0x[edge_id] = cur_px;
|
|
1464
|
-
seg_p0y[edge_id] = cur_py;
|
|
1465
|
-
seg_p0z[edge_id] = cur_pz;
|
|
1466
|
-
seg_r0[edge_id] = r_prev;
|
|
1467
|
-
seg_p1x[edge_id] = next_px;
|
|
1468
|
-
seg_p1y[edge_id] = next_py;
|
|
1469
|
-
seg_p1z[edge_id] = next_pz;
|
|
1470
|
-
seg_r1[edge_id] = r_curr;
|
|
1471
|
-
seg_bvh_leaf[edge_id] = bvh_leaf;
|
|
1472
|
-
|
|
1473
|
-
branch_edges.push(edge_id);
|
|
1474
|
-
branch_edge_t0.push(t_prev);
|
|
1475
|
-
branch_edge_t1.push(t_curr);
|
|
1476
|
-
branch_vertices.push(curr_v);
|
|
1477
|
-
|
|
1478
|
-
out.branch_tip_vertex[branch_id] = curr_v;
|
|
1479
|
-
out.branch_length[branch_id] = cur_arc_s - job.initial_arc_s;
|
|
1480
|
-
|
|
1481
|
-
// Check for dichotomous / sympodial splitting at interior segments (only when stem is structurally thick enough)
|
|
1482
|
-
const min_split_seg = level === 0
|
|
1483
|
-
? Math.max(2, Math.floor(N_seg * 0.5))
|
|
1484
|
-
: Math.max(1, Math.floor(N_seg * 0.25));
|
|
1485
|
-
if (
|
|
1486
|
-
!job.has_split
|
|
1487
|
-
&& !job.dead
|
|
1488
|
-
&& lvl_cfg.split_probability > 0.0
|
|
1489
|
-
&& r_curr >= 0.012
|
|
1490
|
-
&& s >= min_split_seg
|
|
1491
|
-
&& s <= Math.floor(N_seg * 0.75)
|
|
1492
|
-
&& rng() < lvl_cfg.split_probability
|
|
1493
|
-
) {
|
|
1494
|
-
job.has_split = true;
|
|
1495
|
-
const fork_count = Math.max(2, lvl_cfg.split_count | 0);
|
|
1496
|
-
const split_ang = lvl_cfg.split_angle;
|
|
1497
|
-
let base_az = rng() * PI2;
|
|
1498
|
-
|
|
1499
|
-
// Preserve pre-split orthonormal triad (T, R, S) before deflecting fork 0
|
|
1500
|
-
const pre_tx = next_tx, pre_ty = next_ty, pre_tz = next_tz;
|
|
1501
|
-
const pre_rx = next_rx, pre_ry = next_ry, pre_rz = next_rz;
|
|
1502
|
-
const pre_sx = cur_sx, pre_sy = cur_sy, pre_sz = cur_sz;
|
|
1503
|
-
|
|
1504
|
-
// On lateral branches (level >= 1), align the split bend axis toward local vertical (+Y)
|
|
1505
|
-
// so the two daughter forks spread horizontally in the canopy rather than pitching into neighbor boughs.
|
|
1506
|
-
if (level >= 1) {
|
|
1507
|
-
const up_az = Math.atan2(pre_sy, pre_ry);
|
|
1508
|
-
const side_sign = base_az < Math.PI ? 1.0 : -1.0;
|
|
1509
|
-
base_az = up_az + (side_sign < 0.0 ? Math.PI : 0.0) + ((base_az % Math.PI) / Math.PI - 0.5) * 0.45;
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
// Current branch deflects as fork 0: rotate both T and R around bend_axis so R remains orthogonal to T
|
|
1513
|
-
const fork0_deflect = level === 0 ? split_ang * 0.22 : split_ang * 0.5;
|
|
1514
|
-
const bend_axis_x = Math.cos(base_az) * pre_rx + Math.sin(base_az) * pre_sx;
|
|
1515
|
-
const bend_axis_y = Math.cos(base_az) * pre_ry + Math.sin(base_az) * pre_sy;
|
|
1516
|
-
const bend_axis_z = Math.cos(base_az) * pre_rz + Math.sin(base_az) * pre_sz;
|
|
1517
|
-
rotate_around_axis(
|
|
1518
|
-
rot_scratch,
|
|
1519
|
-
pre_tx, pre_ty, pre_tz,
|
|
1520
|
-
bend_axis_x, bend_axis_y, bend_axis_z,
|
|
1521
|
-
fork0_deflect
|
|
1522
|
-
);
|
|
1523
|
-
next_tx = rot_scratch[0];
|
|
1524
|
-
next_ty = rot_scratch[1];
|
|
1525
|
-
next_tz = rot_scratch[2];
|
|
1526
|
-
// fork 0 is aimed where it was deflected to, as the other forks are aimed along their own headings
|
|
1527
|
-
aim_x = next_tx;
|
|
1528
|
-
aim_y = next_ty;
|
|
1529
|
-
aim_z = next_tz;
|
|
1530
|
-
|
|
1531
|
-
rotate_around_axis(
|
|
1532
|
-
rot_scratch,
|
|
1533
|
-
pre_rx, pre_ry, pre_rz,
|
|
1534
|
-
bend_axis_x, bend_axis_y, bend_axis_z,
|
|
1535
|
-
fork0_deflect
|
|
1536
|
-
);
|
|
1537
|
-
next_rx = rot_scratch[0];
|
|
1538
|
-
next_ry = rot_scratch[1];
|
|
1539
|
-
next_rz = rot_scratch[2];
|
|
1540
|
-
|
|
1541
|
-
cur_sx = next_ty * next_rz - next_tz * next_ry;
|
|
1542
|
-
cur_sy = next_tz * next_rx - next_tx * next_rz;
|
|
1543
|
-
cur_sz = next_tx * next_ry - next_ty * next_rx;
|
|
1544
|
-
|
|
1545
|
-
// Spawn fork 1..fork_count-1 as continuation branches with their own root vertex connected to curr_v
|
|
1546
|
-
const rem_segs = Math.max(1, N_seg - s);
|
|
1547
|
-
const rem_fraction = Math.max(0.15, rem_segs / Math.max(1, N_seg));
|
|
1548
|
-
const fork_cum_t0 = lerp(job.cum_t0 ?? 0.0, job.cum_t1 ?? 1.0, t_curr);
|
|
1549
|
-
const fork_cum_t1 = job.cum_t1 ?? 1.0;
|
|
1550
|
-
const fork_len = Math.max(0.15, job.length * rem_fraction);
|
|
1551
|
-
const fork_r0 = r_curr * 0.78;
|
|
1552
|
-
const fork_step = fork_len / rem_segs;
|
|
1553
|
-
|
|
1554
|
-
for (let f = 1; f < fork_count; f++) {
|
|
1555
|
-
const f_az = base_az + (f * PI2) / fork_count;
|
|
1556
|
-
const fax = Math.cos(f_az) * pre_rx + Math.sin(f_az) * pre_sx;
|
|
1557
|
-
const fay = Math.cos(f_az) * pre_ry + Math.sin(f_az) * pre_sy;
|
|
1558
|
-
const faz = Math.cos(f_az) * pre_rz + Math.sin(f_az) * pre_sz;
|
|
1559
|
-
|
|
1560
|
-
const f_tan = [0, 0, 0];
|
|
1561
|
-
rotate_around_axis(f_tan, pre_tx, pre_ty, pre_tz, fax, fay, faz, split_ang * 0.65);
|
|
1562
|
-
|
|
1563
|
-
// Verify fork emergence clearance against BVH (and prevent inward-toward-trunk forks on lateral boughs)
|
|
1564
|
-
if (level >= 1) {
|
|
1565
|
-
const r_horiz_here = Math.hypot(next_px, next_pz);
|
|
1566
|
-
if (r_horiz_here > 0.35 && (next_px * f_tan[0] + next_pz * f_tan[2]) < -0.10 * r_horiz_here) {
|
|
1567
|
-
continue;
|
|
1568
|
-
}
|
|
1569
|
-
const f_clear = evaluate_candidate_clearance(
|
|
1570
|
-
-1, branch_id, local_seg_idx, true, 0,
|
|
1571
|
-
next_px, next_py, next_pz, fork_r0,
|
|
1572
|
-
next_px + f_tan[0] * fork_step,
|
|
1573
|
-
next_py + f_tan[1] * fork_step,
|
|
1574
|
-
next_pz + f_tan[2] * fork_step,
|
|
1575
|
-
fork_r0 * 0.85,
|
|
1576
|
-
0.002
|
|
1577
|
-
);
|
|
1578
|
-
if (f_clear < -0.002) {
|
|
1579
|
-
continue;
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
const f_r = [0, 0, 0];
|
|
1584
|
-
rotate_around_axis(f_r, pre_rx, pre_ry, pre_rz, fax, fay, faz, split_ang * 0.65);
|
|
1585
|
-
|
|
1586
|
-
queue.push({
|
|
1587
|
-
level,
|
|
1588
|
-
generator,
|
|
1589
|
-
in_crown: job.in_crown,
|
|
1590
|
-
parent_branch_id: branch_id,
|
|
1591
|
-
parent_edge_id: edge_id,
|
|
1592
|
-
parent_attach_seg_idx: local_seg_idx,
|
|
1593
|
-
parent_attach_t: t_curr,
|
|
1594
|
-
attach_parent_vertex: curr_v,
|
|
1595
|
-
start_vertex: -1,
|
|
1596
|
-
px: next_px,
|
|
1597
|
-
py: next_py,
|
|
1598
|
-
pz: next_pz,
|
|
1599
|
-
tx: f_tan[0],
|
|
1600
|
-
ty: f_tan[1],
|
|
1601
|
-
tz: f_tan[2],
|
|
1602
|
-
rx: f_r[0],
|
|
1603
|
-
ry: f_r[1],
|
|
1604
|
-
rz: f_r[2],
|
|
1605
|
-
emerge_drx: fax,
|
|
1606
|
-
emerge_dry: fay,
|
|
1607
|
-
emerge_drz: faz,
|
|
1608
|
-
length: fork_len,
|
|
1609
|
-
full_branch_length: job.full_branch_length ?? job.length,
|
|
1610
|
-
base_radius: fork_r0,
|
|
1611
|
-
start_seg: 0,
|
|
1612
|
-
total_segs: rem_segs,
|
|
1613
|
-
shape_segs: rem_segs,
|
|
1614
|
-
initial_arc_s: cur_arc_s,
|
|
1615
|
-
has_split: true,
|
|
1616
|
-
is_split_fork: true,
|
|
1617
|
-
child_span_fraction: rem_fraction,
|
|
1618
|
-
cum_t0: fork_cum_t0,
|
|
1619
|
-
cum_t1: fork_cum_t1
|
|
1620
|
-
});
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
cur_px = next_px;
|
|
1625
|
-
cur_py = next_py;
|
|
1626
|
-
cur_pz = next_pz;
|
|
1627
|
-
cur_tx = next_tx;
|
|
1628
|
-
cur_ty = next_ty;
|
|
1629
|
-
cur_tz = next_tz;
|
|
1630
|
-
cur_rx = next_rx;
|
|
1631
|
-
cur_ry = next_ry;
|
|
1632
|
-
cur_rz = next_rz;
|
|
1633
|
-
prev_v = curr_v;
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
// If the branch was self-pruned early on contact (actual_segs < remaining_segs), renormalize
|
|
1637
|
-
// t_norm and taper radii across the surviving vertices [0..actual_segs] so its tip terminates cleanly at t_norm = 1.0
|
|
1638
|
-
const actual_segs = branch_edges.length;
|
|
1639
|
-
if (actual_segs > 0 && actual_segs < remaining_segs) {
|
|
1640
|
-
const s_param = /** @type {Float32Array} */ (out.attr_node_spine_param.data);
|
|
1641
|
-
for (let i = 0; i < actual_segs; i++) {
|
|
1642
|
-
const tn0 = i / actual_segs;
|
|
1643
|
-
const tn1 = (i + 1) / actual_segs;
|
|
1644
|
-
branch_edge_t0[i] = tn0;
|
|
1645
|
-
branch_edge_t1[i] = tn1;
|
|
1646
|
-
|
|
1647
|
-
const v_curr = branch_vertices[i + 1];
|
|
1648
|
-
s_param[v_curr * 3] = tn1;
|
|
1649
|
-
|
|
1650
|
-
const u_p = prof_span > 1e-4
|
|
1651
|
-
? clamp((prof_p0 - lvl_cfg.radius_profile.evaluate(lerp(job.cum_t0, job.cum_t1, tn1))) / prof_span, 0.0, 1.0)
|
|
1652
|
-
: tn1;
|
|
1653
|
-
const min_tf = has_children ? 0.14 : 0.24;
|
|
1654
|
-
const ap_taper = has_children && tn1 > 0.80
|
|
1655
|
-
? lerp(1.0, 0.62, Math.pow((tn1 - 0.80) / 0.20, 1.35))
|
|
1656
|
-
: 1.0;
|
|
1657
|
-
const r_new = Math.max(
|
|
1658
|
-
0.002,
|
|
1659
|
-
job.base_radius * Math.max(1.0 - lvl_cfg.taper * u_p, min_tf) * ap_taper
|
|
1660
|
-
);
|
|
1661
|
-
out.node_write_radius(v_curr, r_new);
|
|
1662
|
-
|
|
1663
|
-
const e_curr = branch_edges[i];
|
|
1664
|
-
seg_r1[e_curr] = r_new;
|
|
1665
|
-
if (i + 1 < actual_segs) {
|
|
1666
|
-
seg_r0[branch_edges[i + 1]] = r_new;
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
// Smooth or preserve crisp interior Hermite tangents across consecutive edges of this branch
|
|
1672
|
-
// (preserving branch_edges[0]'s m0 so continuation forks keep their exact fork departure tangent)
|
|
1673
|
-
if (branch_edges.length >= 2) {
|
|
1674
|
-
const kink_sharp = clamp(lvl_cfg.kink_sharpness ?? 0.5, 0.0, 0.92);
|
|
1675
|
-
for (let k = 1; k < branch_edges.length; k++) {
|
|
1676
|
-
const e_prev = branch_edges[k - 1];
|
|
1677
|
-
const e_next = branch_edges[k];
|
|
1678
|
-
const v_mid = branch_vertices[k];
|
|
1679
|
-
const l_prev = Math.max(1e-5, out.segment_length(e_prev));
|
|
1680
|
-
const l_next = Math.max(1e-5, out.segment_length(e_next));
|
|
1681
|
-
|
|
1682
|
-
out.segment_read_m1(SCRATCH_M_PREV, 0, e_prev);
|
|
1683
|
-
out.segment_read_m0(SCRATCH_M_NEXT, 0, e_next);
|
|
1684
|
-
|
|
1685
|
-
const t_in_x = SCRATCH_M_PREV[0] / l_prev;
|
|
1686
|
-
const t_in_y = SCRATCH_M_PREV[1] / l_prev;
|
|
1687
|
-
const t_in_z = SCRATCH_M_PREV[2] / l_prev;
|
|
1688
|
-
const t_out_x = SCRATCH_M_NEXT[0] / l_next;
|
|
1689
|
-
const t_out_y = SCRATCH_M_NEXT[1] / l_next;
|
|
1690
|
-
const t_out_z = SCRATCH_M_NEXT[2] / l_next;
|
|
1691
|
-
|
|
1692
|
-
let tx = t_in_x + t_out_x;
|
|
1693
|
-
let ty = t_in_y + t_out_y;
|
|
1694
|
-
let tz = t_in_z + t_out_z;
|
|
1695
|
-
const t_len = Math.sqrt(tx * tx + ty * ty + tz * tz);
|
|
1696
|
-
if (t_len > 1e-12) {
|
|
1697
|
-
tx /= t_len;
|
|
1698
|
-
ty /= t_len;
|
|
1699
|
-
tz /= t_len;
|
|
1700
|
-
|
|
1701
|
-
out.node_write_tangent(v_mid, tx, ty, tz);
|
|
1702
|
-
|
|
1703
|
-
// Blend between smooth C1 average tangent and crisp sympodial kink tangents
|
|
1704
|
-
let m1x = lerp(tx, t_in_x, kink_sharp);
|
|
1705
|
-
let m1y = lerp(ty, t_in_y, kink_sharp);
|
|
1706
|
-
let m1z = lerp(tz, t_in_z, kink_sharp);
|
|
1707
|
-
const m1_l = Math.sqrt(m1x * m1x + m1y * m1y + m1z * m1z);
|
|
1708
|
-
if (m1_l > 1e-12) { m1x /= m1_l; m1y /= m1_l; m1z /= m1_l; }
|
|
1709
|
-
|
|
1710
|
-
let m0x = lerp(tx, t_out_x, kink_sharp);
|
|
1711
|
-
let m0y = lerp(ty, t_out_y, kink_sharp);
|
|
1712
|
-
let m0z = lerp(tz, t_out_z, kink_sharp);
|
|
1713
|
-
const m0_l = Math.sqrt(m0x * m0x + m0y * m0y + m0z * m0z);
|
|
1714
|
-
if (m0_l > 1e-12) { m0x /= m0_l; m0y /= m0_l; m0z /= m0_l; }
|
|
1715
|
-
|
|
1716
|
-
out.segment_write_m1(e_prev, m1x * l_prev, m1y * l_prev, m1z * l_prev);
|
|
1717
|
-
out.segment_write_m0(e_next, m0x * l_next, m0y * l_next, m0z * l_next);
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
// Spawn lateral child branches at level + 1 using pre-scheduled attachments
|
|
1723
|
-
const next_level = level + 1;
|
|
1724
|
-
if (scheduled_children.length > 0 && branch_edges.length > 0) {
|
|
1725
|
-
const t_max = branch_edge_t1[branch_edge_t1.length - 1];
|
|
1726
|
-
const PHI_CANDIDATES = [0.0, 0.36, -0.36, 0.72, -0.72, 1.08, -1.08, 1.48, -1.48];
|
|
1727
|
-
|
|
1728
|
-
for (let c = 0; c < scheduled_children.length; c++) {
|
|
1729
|
-
const sc = scheduled_children[c];
|
|
1730
|
-
const rule = sc.rule;
|
|
1731
|
-
const next_cfg = species.branches[rule.target];
|
|
1732
|
-
if (sc.t_attach > t_max * 1.02) continue;
|
|
1733
|
-
const t_attach = clamp(sc.t_attach, branch_edge_t0[0], t_max * 0.985);
|
|
1734
|
-
const base_phi_c = sc.phi;
|
|
1735
|
-
|
|
1736
|
-
// Find parent edge containing t_attach
|
|
1737
|
-
let seg_idx = 0;
|
|
1738
|
-
for (let i = 0; i < branch_edges.length; i++) {
|
|
1739
|
-
if (t_attach >= branch_edge_t0[i] && t_attach <= branch_edge_t1[i]) {
|
|
1740
|
-
seg_idx = i;
|
|
1741
|
-
break;
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
const parent_e = branch_edges[seg_idx];
|
|
1746
|
-
const t0_e = branch_edge_t0[seg_idx];
|
|
1747
|
-
const t1_e = branch_edge_t1[seg_idx];
|
|
1748
|
-
const local_u = clamp((t_attach - t0_e) / Math.max(1e-5, t1_e - t0_e), 0.0, 1.0);
|
|
1749
|
-
|
|
1750
|
-
out.evaluate_segment_position(SCRATCH_POS, 0, parent_e, local_u);
|
|
1751
|
-
out.evaluate_segment_tangent(SCRATCH_TAN, 0, parent_e, local_u);
|
|
1752
|
-
|
|
1753
|
-
const p_v0 = branch_vertices[seg_idx];
|
|
1754
|
-
const p_v1 = branch_vertices[seg_idx + 1];
|
|
1755
|
-
const r_p0 = out.node_read_radius(p_v0);
|
|
1756
|
-
const r_p1 = out.node_read_radius(p_v1);
|
|
1757
|
-
const parent_r_at_attach = lerp(r_p0, r_p1, local_u);
|
|
1758
|
-
if (parent_r_at_attach < 0.004) continue;
|
|
1759
|
-
|
|
1760
|
-
// Transport parent RMF R vector from p_v0 along parent_e to SCRATCH_POS at local_u
|
|
1761
|
-
out.node_read_position(SCRATCH_P0, 0, p_v0);
|
|
1762
|
-
out.node_read_tangent(SCRATCH_T0, 0, p_v0);
|
|
1763
|
-
out.node_read_frame_r(SCRATCH_R0, 0, p_v0);
|
|
1764
|
-
const p_rx = SCRATCH_R0[0];
|
|
1765
|
-
const p_ry = SCRATCH_R0[1];
|
|
1766
|
-
const p_rz = SCRATCH_R0[2];
|
|
1767
|
-
|
|
1768
|
-
spine_frame_double_reflection(
|
|
1769
|
-
SCRATCH_RMF, 0,
|
|
1770
|
-
SCRATCH_P0[0], SCRATCH_P0[1], SCRATCH_P0[2],
|
|
1771
|
-
SCRATCH_T0[0], SCRATCH_T0[1], SCRATCH_T0[2],
|
|
1772
|
-
p_rx, p_ry, p_rz,
|
|
1773
|
-
SCRATCH_POS[0], SCRATCH_POS[1], SCRATCH_POS[2],
|
|
1774
|
-
SCRATCH_TAN[0], SCRATCH_TAN[1], SCRATCH_TAN[2]
|
|
1775
|
-
);
|
|
1776
|
-
const prx = SCRATCH_RMF[0], pry = SCRATCH_RMF[1], prz = SCRATCH_RMF[2];
|
|
1777
|
-
const psx = SCRATCH_RMF[3], psy = SCRATCH_RMF[4], psz = SCRATCH_RMF[5];
|
|
1778
|
-
|
|
1779
|
-
// Genealogical position along the full parent axis (accounts for continuation forks)
|
|
1780
|
-
const t_genealogical = lerp(job.cum_t0 ?? 0.0, job.cum_t1 ?? 1.0, t_attach);
|
|
1781
|
-
|
|
1782
|
-
// Insertion polar angle alpha from parent tangent T_p toward D_rad
|
|
1783
|
-
const alpha_scale = rule.insertion_angle_profile.evaluate(t_genealogical);
|
|
1784
|
-
const alpha = clamp(
|
|
1785
|
-
rule.insertion_angle * alpha_scale + (rng() * 2.0 - 1.0) * rule.insertion_angle_variation,
|
|
1786
|
-
0.1,
|
|
1787
|
-
Math.PI * 0.85
|
|
1788
|
-
);
|
|
1789
|
-
const cos_a = Math.cos(alpha);
|
|
1790
|
-
const sin_a = Math.sin(alpha);
|
|
1791
|
-
const var_scale = 1.0 + (rng() * 2.0 - 1.0) * next_cfg.length_variance;
|
|
1792
|
-
|
|
1793
|
-
// A plagiotropic parent is dorsiventral: its buds lie in the plane of its tangent T_p and the horizontal
|
|
1794
|
-
// H = T_p x Y across it, whose normal N = H x T_p points up. A vertical parent has no such plane.
|
|
1795
|
-
const tpx = SCRATCH_TAN[0], tpy = SCRATCH_TAN[1], tpz = SCRATCH_TAN[2];
|
|
1796
|
-
const horizontality = Math.hypot(tpx, tpz);
|
|
1797
|
-
const plagio = level >= 1 ? lvl_cfg.plagiotropism : 0.0;
|
|
1798
|
-
const w_plagio = horizontality > 1e-3 ? plagio * horizontality : 0.0;
|
|
1799
|
-
const dv_nx = horizontality > 1e-3 ? (-tpx * tpy) / horizontality : 0.0;
|
|
1800
|
-
const dv_ny = horizontality;
|
|
1801
|
-
const dv_nz = horizontality > 1e-3 ? (-tpy * tpz) / horizontality : 0.0;
|
|
1802
|
-
|
|
1803
|
-
// Helper to compute radial normal D_rad and child tangent T_c for a given azimuth phi_cand
|
|
1804
|
-
const compute_child_basis = (phi_cand, out_dr, out_ct) => {
|
|
1805
|
-
const cp = Math.cos(phi_cand);
|
|
1806
|
-
const sp = Math.sin(phi_cand);
|
|
1807
|
-
let dx_r = cp * prx + sp * psx;
|
|
1808
|
-
let dy_r = cp * pry + sp * psy;
|
|
1809
|
-
let dz_r = cp * prz + sp * psz;
|
|
1810
|
-
|
|
1811
|
-
if (w_plagio > 1e-3) {
|
|
1812
|
-
// fan into the dorsiventral plane: take off w of the radial's component along N
|
|
1813
|
-
const along_n = (dx_r * dv_nx + dy_r * dv_ny + dz_r * dv_nz) * w_plagio;
|
|
1814
|
-
dx_r -= along_n * dv_nx;
|
|
1815
|
-
dy_r -= along_n * dv_ny;
|
|
1816
|
-
dz_r -= along_n * dv_nz;
|
|
1817
|
-
const d_len = Math.sqrt(dx_r * dx_r + dy_r * dy_r + dz_r * dz_r);
|
|
1818
|
-
if (d_len > 1e-6) {
|
|
1819
|
-
dx_r /= d_len;
|
|
1820
|
-
dy_r /= d_len;
|
|
1821
|
-
dz_r /= d_len;
|
|
1822
|
-
} else {
|
|
1823
|
-
// a bud straight above (below) a fully dorsiventral shoot: to the side, along H
|
|
1824
|
-
dx_r = -tpz / horizontality;
|
|
1825
|
-
dy_r = 0.0;
|
|
1826
|
-
dz_r = tpx / horizontality;
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
out_dr[0] = dx_r;
|
|
1830
|
-
out_dr[1] = dy_r;
|
|
1831
|
-
out_dr[2] = dz_r;
|
|
1832
|
-
|
|
1833
|
-
let tx_c = cos_a * SCRATCH_TAN[0] + sin_a * dx_r;
|
|
1834
|
-
let ty_c = cos_a * SCRATCH_TAN[1] + sin_a * dy_r;
|
|
1835
|
-
let tz_c = cos_a * SCRATCH_TAN[2] + sin_a * dz_r;
|
|
1836
|
-
const ct_l = Math.sqrt(tx_c * tx_c + ty_c * ty_c + tz_c * tz_c) || 1.0;
|
|
1837
|
-
out_ct[0] = tx_c / ct_l;
|
|
1838
|
-
out_ct[1] = ty_c / ct_l;
|
|
1839
|
-
out_ct[2] = tz_c / ct_l;
|
|
1840
|
-
};
|
|
1841
|
-
|
|
1842
|
-
// Ventral knockout: on a dorsiventral parent a bud whose own heading points into the downward cone of
|
|
1843
|
-
// half-angle plagiotropism * pi / 2 does not break
|
|
1844
|
-
const ventral_cos = Math.cos(plagio * Math.PI * 0.5);
|
|
1845
|
-
if (plagio > 0.0) {
|
|
1846
|
-
const bud_y = cos_a * tpy + sin_a * (Math.cos(base_phi_c) * pry + Math.sin(base_phi_c) * psy);
|
|
1847
|
-
if (-bud_y > ventral_cos) {
|
|
1848
|
-
continue;
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
|
-
const cand_dr = [0, 0, 0];
|
|
1853
|
-
const cand_ct = [0, 0, 0];
|
|
1854
|
-
compute_child_basis(base_phi_c, cand_dr, cand_ct);
|
|
1855
|
-
|
|
1856
|
-
// 1. Raw configured child branch length modulated by length_profile at t_genealogical and crown envelope
|
|
1857
|
-
const prof_scale = Math.max(0.12, rule.length_profile.evaluate(t_genealogical));
|
|
1858
|
-
const est_mid_y = SCRATCH_POS[1] + Math.max(0.0, cand_ct[1]) * next_cfg.length * prof_scale * 0.35;
|
|
1859
|
-
const h_norm = clamp((est_mid_y - crown.base_height) / Math.max(0.1, crown.height), 0.0, 1.0);
|
|
1860
|
-
// a dead branch grew before today's crown took its shape
|
|
1861
|
-
const dead = sc.age > 0.0;
|
|
1862
|
-
const env_scale = !rule.crown_envelope || dead
|
|
1863
|
-
? 1.0
|
|
1864
|
-
: (level === 0
|
|
1865
|
-
? crown_envelope_evaluate(crown.envelope_type, h_norm)
|
|
1866
|
-
: lerp(1.0, crown_envelope_evaluate(crown.envelope_type, h_norm), 0.5));
|
|
1867
|
-
const raw_child_len = Math.max(0.12, next_cfg.length * prof_scale * env_scale * var_scale);
|
|
1868
|
-
|
|
1869
|
-
// 2. Continuous Gravity-Orientation & Allometric Slenderness Coupling (Cantilever vs. Pendulous Tension)
|
|
1870
|
-
const rho_design = Math.max(0.012, next_cfg.radius_ratio);
|
|
1871
|
-
const s_design = 1.0 / (2.0 * rho_design);
|
|
1872
|
-
const g_trop = next_cfg.gravitropism ?? 0.0;
|
|
1873
|
-
const u_curl = next_cfg.tip_upcurl ?? 0.0;
|
|
1874
|
-
const t_eff_y = clamp(cand_ct[1] - 0.85 * g_trop + 0.35 * u_curl, -1.0, 1.0);
|
|
1875
|
-
|
|
1876
|
-
// Cantilever load (horizontal bending / upward compression) vs. pendulous tensile compliance
|
|
1877
|
-
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);
|
|
1878
|
-
const tau_tension = clamp(g_trop, 0.0, 1.0) * Math.max(0.0, -t_eff_y);
|
|
1879
|
-
|
|
1880
|
-
// 3. Parent-relative distal conical envelope (prevents distal shoots or split forks from outgrowing their parent)
|
|
1881
|
-
const actual_parent_len = Math.max(0.15, out.branch_length[branch_id]);
|
|
1882
|
-
const parent_full_len = Math.max(actual_parent_len, job.full_branch_length ?? actual_parent_len);
|
|
1883
|
-
const distal_tip_ratio = lerp(0.32, 0.55, tau_tension);
|
|
1884
|
-
const cone_max_len = level === 0
|
|
1885
|
-
? parent_full_len * lerp(0.85, 0.32, Math.pow(t_genealogical, 1.6))
|
|
1886
|
-
: Math.min(
|
|
1887
|
-
actual_parent_len * lerp(0.90, 1.05, tau_tension),
|
|
1888
|
-
parent_full_len * lerp(0.85, distal_tip_ratio, Math.pow(t_genealogical, 1.25))
|
|
1889
|
-
);
|
|
1890
|
-
const desired_len = Math.max(0.12, Math.min(raw_child_len, cone_max_len));
|
|
1891
|
-
|
|
1892
|
-
const nom_len = Math.max(0.15, next_cfg.length);
|
|
1893
|
-
const r_design = (nom_len * rho_design * Math.pow(desired_len / nom_len, 0.75)) / (1.0 + 0.65 * tau_tension);
|
|
1894
|
-
// children on the flare crests sit side by side around the stem: N of them fit at most R sin(pi / N) thick
|
|
1895
|
-
const side_by_side_r = rule.phyllotaxis_mode === PhyllotaxisMode.FlareLobes
|
|
1896
|
-
? parent_r_at_attach * Math.sin(Math.PI / Math.max(2, lvl_cfg.flare_lobes | 0))
|
|
1897
|
-
: Infinity;
|
|
1898
|
-
const child_base_r = Math.min(
|
|
1899
|
-
parent_r_at_attach * 0.78,
|
|
1900
|
-
side_by_side_r,
|
|
1901
|
-
Math.max(0.0035, r_design)
|
|
1902
|
-
);
|
|
1903
|
-
|
|
1904
|
-
const gamma = lerp(0.82, 0.65, tau_tension);
|
|
1905
|
-
const allometric_len = child_base_r < r_design
|
|
1906
|
-
? desired_len * Math.pow(child_base_r / Math.max(1e-5, r_design), gamma)
|
|
1907
|
-
: desired_len;
|
|
1908
|
-
const s_max = s_design * (1.35 + 0.65 * tau_tension) / (0.65 + 0.35 * k_cantilever);
|
|
1909
|
-
const full_len = clamp(Math.min(allometric_len, 2.0 * child_base_r * s_max), 0.12, desired_len);
|
|
1910
|
-
// a dead branch is what is left of it: its base part, if that still clears the bark; otherwise it has
|
|
1911
|
-
// been grown over and the stem carries its scar
|
|
1912
|
-
const child_len = full_len * sc.retained;
|
|
1913
|
-
if (dead && child_len <= parent_r_at_attach) {
|
|
1914
|
-
out.add_scar(branch_id, t_attach, cand_ct[0], cand_ct[1], cand_ct[2], child_base_r * (1.0 - sc.age));
|
|
1915
|
-
continue;
|
|
1916
|
-
}
|
|
1917
|
-
const child_segs = Math.max(1, Math.ceil(next_cfg.segments * sc.retained));
|
|
1918
|
-
const child_step = child_len / child_segs;
|
|
1919
|
-
|
|
1920
|
-
// 4. Pre-Spawn BVH Bud Clearance Probing (selects open canopy azimuth or inhibits blocked bud)
|
|
1921
|
-
let best_drx = cand_dr[0], best_dry = cand_dr[1], best_drz = cand_dr[2];
|
|
1922
|
-
let best_ctx = cand_ct[0], best_cty = cand_ct[1], best_ctz = cand_ct[2];
|
|
1923
|
-
let best_clear = -Infinity;
|
|
1924
|
-
const r_horiz_attach = Math.hypot(SCRATCH_POS[0], SCRATCH_POS[2]);
|
|
1925
|
-
|
|
1926
|
-
for (let k = 0; k < PHI_CANDIDATES.length; k++) {
|
|
1927
|
-
if (k > 0) {
|
|
1928
|
-
compute_child_basis(base_phi_c + PHI_CANDIDATES[k], cand_dr, cand_ct);
|
|
1929
|
-
}
|
|
1930
|
-
// nor may fanning or a turn for clearance carry it into the cone: a bud with no heading left
|
|
1931
|
-
// outside it does not break
|
|
1932
|
-
if (plagio > 0.0 && -cand_ct[1] > ventral_cos) continue;
|
|
1933
|
-
// Prevent lateral shoots on boughs from pointing backward into the trunk core
|
|
1934
|
-
if (level >= 1 && r_horiz_attach > 0.30) {
|
|
1935
|
-
const radial_dot = (SCRATCH_POS[0] * cand_ct[0] + SCRATCH_POS[2] * cand_ct[2]) / r_horiz_attach;
|
|
1936
|
-
if (radial_dot < -0.15) continue;
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
const p1_x = SCRATCH_POS[0] + cand_ct[0] * child_step;
|
|
1940
|
-
const p1_y = SCRATCH_POS[1] + cand_ct[1] * child_step;
|
|
1941
|
-
const p1_z = SCRATCH_POS[2] + cand_ct[2] * child_step;
|
|
1942
|
-
|
|
1943
|
-
const clear0 = evaluate_candidate_clearance(
|
|
1944
|
-
-1, branch_id, seg_idx, false, 0,
|
|
1945
|
-
SCRATCH_POS[0], SCRATCH_POS[1], SCRATCH_POS[2], child_base_r,
|
|
1946
|
-
p1_x, p1_y, p1_z, child_base_r * 0.88,
|
|
1947
|
-
0.002
|
|
1948
|
-
);
|
|
1949
|
-
|
|
1950
|
-
const p2_x = p1_x + cand_ct[0] * child_step;
|
|
1951
|
-
const p2_y = p1_y + cand_ct[1] * child_step;
|
|
1952
|
-
const p2_z = p1_z + cand_ct[2] * child_step;
|
|
1953
|
-
const clear1 = child_segs >= 2
|
|
1954
|
-
? evaluate_candidate_clearance(
|
|
1955
|
-
-1, branch_id, seg_idx, false, 1,
|
|
1956
|
-
p1_x, p1_y, p1_z, child_base_r * 0.88,
|
|
1957
|
-
p2_x, p2_y, p2_z, child_base_r * 0.75,
|
|
1958
|
-
0.002
|
|
1959
|
-
)
|
|
1960
|
-
: clear0;
|
|
1961
|
-
|
|
1962
|
-
const min_c = Math.min(clear0, clear1);
|
|
1963
|
-
if (min_c > best_clear) {
|
|
1964
|
-
best_clear = min_c;
|
|
1965
|
-
best_drx = cand_dr[0];
|
|
1966
|
-
best_dry = cand_dr[1];
|
|
1967
|
-
best_drz = cand_dr[2];
|
|
1968
|
-
best_ctx = cand_ct[0];
|
|
1969
|
-
best_cty = cand_ct[1];
|
|
1970
|
-
best_ctz = cand_ct[2];
|
|
1971
|
-
}
|
|
1972
|
-
if (min_c >= 0.004) {
|
|
1973
|
-
break;
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
// Botanical bud inhibition: if all candidate directions are blocked by existing branches, suppress bud
|
|
1978
|
-
if (best_clear < -0.004) {
|
|
1979
|
-
continue;
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
const drx = best_drx, dry = best_dry, drz = best_drz;
|
|
1983
|
-
const ctx = best_ctx, cty = best_cty, ctz = best_ctz;
|
|
1984
|
-
|
|
1985
|
-
// Initial RMF radial vector for child branch:
|
|
1986
|
-
// R_c = cos(alpha) * D_rad - sin(alpha) * T_p is exact unit length and orthogonal
|
|
1987
|
-
// to T_c = cos(alpha) * T_p + sin(alpha) * D_rad for all alpha in [0, pi],
|
|
1988
|
-
// avoiding any singularity at alpha = pi/2.
|
|
1989
|
-
let crx = cos_a * drx - sin_a * SCRATCH_TAN[0];
|
|
1990
|
-
let cry = cos_a * dry - sin_a * SCRATCH_TAN[1];
|
|
1991
|
-
let crz = cos_a * drz - sin_a * SCRATCH_TAN[2];
|
|
1992
|
-
const cr_dot = crx * ctx + cry * cty + crz * ctz;
|
|
1993
|
-
crx -= cr_dot * ctx;
|
|
1994
|
-
cry -= cr_dot * cty;
|
|
1995
|
-
crz -= cr_dot * ctz;
|
|
1996
|
-
const cr_len = Math.sqrt(crx * crx + cry * cry + crz * crz);
|
|
1997
|
-
if (cr_len > 1e-12) {
|
|
1998
|
-
crx /= cr_len;
|
|
1999
|
-
cry /= cr_len;
|
|
2000
|
-
crz /= cr_len;
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
queue.push({
|
|
2004
|
-
level: next_level,
|
|
2005
|
-
generator: rule.target,
|
|
2006
|
-
in_crown: rule.crown_envelope && !dead,
|
|
2007
|
-
dead,
|
|
2008
|
-
parent_branch_id: branch_id,
|
|
2009
|
-
parent_edge_id: parent_e,
|
|
2010
|
-
parent_attach_seg_idx: seg_idx,
|
|
2011
|
-
parent_attach_t: t_attach,
|
|
2012
|
-
attach_parent_vertex: p_v0,
|
|
2013
|
-
start_vertex: -1,
|
|
2014
|
-
px: SCRATCH_POS[0],
|
|
2015
|
-
py: SCRATCH_POS[1],
|
|
2016
|
-
pz: SCRATCH_POS[2],
|
|
2017
|
-
tx: ctx,
|
|
2018
|
-
ty: cty,
|
|
2019
|
-
tz: ctz,
|
|
2020
|
-
rx: crx,
|
|
2021
|
-
ry: cry,
|
|
2022
|
-
rz: crz,
|
|
2023
|
-
emerge_drx: drx,
|
|
2024
|
-
emerge_dry: dry,
|
|
2025
|
-
emerge_drz: drz,
|
|
2026
|
-
length: child_len,
|
|
2027
|
-
full_branch_length: child_len,
|
|
2028
|
-
base_radius: child_base_r * (1.0 - sc.age),
|
|
2029
|
-
start_seg: 0,
|
|
2030
|
-
total_segs: child_segs,
|
|
2031
|
-
shape_segs: Math.max(1, next_cfg.segments),
|
|
2032
|
-
initial_arc_s: 0.0,
|
|
2033
|
-
has_split: false,
|
|
2034
|
-
is_split_fork: false,
|
|
2035
|
-
cum_t0: 0.0,
|
|
2036
|
-
cum_t1: sc.retained
|
|
2037
|
-
});
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
|
-
// Bottom-up Pipe Model radius calculation & blending
|
|
2043
|
-
tree_skeleton_compute_pipe_radii(out, species);
|
|
2044
|
-
|
|
2045
|
-
// The peripheral twig systems, grown year by year on the finished framework, its girth final
|
|
19
|
+
tree_skeleton_grow_framework(species, out);
|
|
2046
20
|
tree_skeleton_grow_shoots(out, species);
|
|
2047
|
-
|
|
21
|
+
tree_skeleton_fit_crown_frame(out, species);
|
|
2048
22
|
return out;
|
|
2049
23
|
}
|