@woosh/meep-engine 3.53.0 → 3.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/math/noise/worley_noise_2d.d.ts +14 -0
- package/src/core/math/noise/worley_noise_2d.d.ts.map +1 -0
- package/src/core/math/noise/worley_noise_2d.js +74 -0
- package/src/engine/graphics/texture/atlas/CachingTextureAtlas.d.ts +6 -1
- package/src/engine/graphics/texture/atlas/CachingTextureAtlas.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/CachingTextureAtlas.js +363 -325
- package/src/engine/graphics/texture/atlas/ManagedTextureAtlas.d.ts +93 -14
- package/src/engine/graphics/texture/atlas/ManagedTextureAtlas.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/ManagedTextureAtlas.js +322 -183
- package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.d.ts +9 -0
- package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.js +110 -83
- package/src/engine/graphics/texture/atlas/SpriteAtlas.d.ts +42 -0
- package/src/engine/graphics/texture/atlas/SpriteAtlas.d.ts.map +1 -0
- package/src/engine/{graphics3/decal/DecalAtlas.js → graphics/texture/atlas/SpriteAtlas.js} +202 -194
- package/src/engine/{graphics3/decal/DecalAtlasFill.d.ts → graphics/texture/atlas/SpriteAtlasFill.d.ts} +4 -4
- package/src/engine/graphics/texture/atlas/SpriteAtlasFill.d.ts.map +1 -0
- package/src/engine/graphics/texture/atlas/SpriteAtlasFill.js +30 -0
- package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts +7 -0
- package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/TextureAtlas.js +11 -1
- package/src/engine/{graphics3/decal/decal_atlas_border.d.ts → graphics/texture/atlas/sprite_atlas_border.d.ts} +12 -12
- package/src/engine/graphics/texture/atlas/sprite_atlas_border.d.ts.map +1 -0
- package/src/engine/{graphics3/decal/decal_atlas_border.js → graphics/texture/atlas/sprite_atlas_border.js} +69 -69
- package/src/engine/graphics3/DecalSystem.d.ts.map +1 -1
- package/src/engine/graphics3/DecalSystem.js +447 -450
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +9 -22
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
- package/src/engine/graphics3/GPUParticleEmitterSystem.js +892 -951
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/generation/foliage/FOLIAGE_DESIGN.md +2 -1
- package/src/generation/foliage/config/BarkMaterialConfig.d.ts +4 -3
- package/src/generation/foliage/config/BarkMaterialConfig.d.ts.map +1 -1
- package/src/generation/foliage/config/BarkMaterialConfig.js +6 -6
- package/src/generation/foliage/config/FoliageBarkType.d.ts +11 -0
- package/src/generation/foliage/config/FoliageBarkType.d.ts.map +1 -0
- package/src/generation/foliage/config/FoliageBarkType.js +16 -0
- package/src/generation/foliage/config/FoliageMaterialConfig.d.ts +58 -5
- package/src/generation/foliage/config/FoliageMaterialConfig.d.ts.map +1 -1
- package/src/generation/foliage/config/FoliageMaterialConfig.js +279 -181
- package/src/generation/foliage/index.d.ts +2 -1
- package/src/generation/foliage/index.js +44 -43
- package/src/generation/foliage/material/make_bark_material.d.ts.map +1 -1
- package/src/generation/foliage/material/make_bark_material.js +23 -95
- package/src/generation/foliage/material/make_foliage_material.d.ts +29 -3
- package/src/generation/foliage/material/make_foliage_material.d.ts.map +1 -1
- package/src/generation/foliage/material/make_foliage_material.js +605 -359
- package/src/generation/foliage/mesh/branch_collar_project.d.ts.map +1 -1
- package/src/generation/foliage/mesh/branch_collar_project.js +205 -203
- package/src/generation/foliage/mesh/foliage_collect_attachments.d.ts +15 -0
- package/src/generation/foliage/mesh/foliage_collect_attachments.d.ts.map +1 -1
- package/src/generation/foliage/mesh/foliage_collect_attachments.js +569 -502
- package/src/generation/foliage/mesh/tree_mesh_build_bark.d.ts.map +1 -1
- package/src/generation/foliage/mesh/tree_mesh_build_bark.js +1049 -1047
- package/src/generation/foliage/mesh/tree_mesh_build_foliage.d.ts +2 -1
- package/src/generation/foliage/mesh/tree_mesh_build_foliage.d.ts.map +1 -1
- package/src/generation/foliage/mesh/tree_mesh_build_foliage.js +454 -415
- package/src/generation/foliage/presets/make_tree_species_preset.d.ts.map +1 -1
- package/src/generation/foliage/presets/make_tree_species_preset.js +870 -837
- package/src/generation/foliage/skeleton/crown_envelope_evaluate.js +121 -121
- package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.d.ts +10 -7
- package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.d.ts.map +1 -1
- package/src/generation/foliage/skeleton/tree_skeleton_compute_pipe_radii.js +206 -179
- package/src/generation/foliage/skeleton/tree_skeleton_grow.d.ts.map +1 -1
- package/src/generation/foliage/skeleton/tree_skeleton_grow.js +1871 -1104
- package/src/shade/RENDERER_CONTRACT.md +5 -2
- package/src/shade/device/SHADE_DEVICE_FLOOR.d.ts.map +1 -1
- package/src/shade/device/SHADE_DEVICE_FLOOR.js +2 -0
- package/src/shade/playground/foliage_wind/foliage_world.d.ts +6 -0
- package/src/shade/playground/foliage_wind/foliage_world.d.ts.map +1 -1
- package/src/shade/playground/foliage_wind/foliage_world.js +602 -593
- package/src/shade/playground/foliage_wind/index.html +260 -254
- package/src/shade/playground/foliage_wind/main.js +275 -263
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +1 -1
- package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
- package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
- package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
- package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
- package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
- package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +1 -1
- package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +3 -3
- package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
- package/src/shade/renderer/hiz/HierarchicalZBuffer.js +350 -294
- package/src/shade/renderer/hiz/generate_downsampler_code.d.ts +0 -2
- package/src/shade/renderer/hiz/generate_downsampler_code.d.ts.map +1 -1
- package/src/shade/renderer/hiz/generate_downsampler_code.js +99 -100
- package/src/shade/renderer/hiz/hzb_pick_alignment.d.ts +15 -0
- package/src/shade/renderer/hiz/hzb_pick_alignment.d.ts.map +1 -0
- package/src/shade/renderer/hiz/hzb_pick_alignment.js +148 -0
- package/src/shade/renderer/hiz/hzb_plan_reduction_steps.d.ts +28 -0
- package/src/shade/renderer/hiz/hzb_plan_reduction_steps.d.ts.map +1 -0
- package/src/shade/renderer/hiz/hzb_plan_reduction_steps.js +58 -0
- package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +5 -5
- package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
- package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +57 -67
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +3 -3
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +1 -1
- package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.js +111 -112
- package/src/shade/renderer/hiz/shader_hzb_spd.d.ts +5 -0
- package/src/shade/renderer/hiz/shader_hzb_spd.d.ts.map +1 -0
- package/src/shade/renderer/hiz/shader_hzb_spd.js +160 -0
- package/src/shade/renderer/light/GPULightCollection.d.ts +8 -0
- package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
- package/src/shade/renderer/light/GPULightCollection.js +67 -0
- package/src/shade/renderer/light/environment/FAR_FIELD_IRRADIANCE_SH_RESOURCE.d.ts +7 -0
- package/src/shade/renderer/light/environment/FAR_FIELD_IRRADIANCE_SH_RESOURCE.d.ts.map +1 -0
- package/src/shade/renderer/light/environment/FAR_FIELD_IRRADIANCE_SH_RESOURCE.js +13 -0
- package/src/shade/renderer/light/environment/shader_project_far_field_irradiance_sh.d.ts +10 -0
- package/src/shade/renderer/light/environment/shader_project_far_field_irradiance_sh.d.ts.map +1 -0
- package/src/shade/renderer/light/environment/shader_project_far_field_irradiance_sh.js +72 -0
- package/src/shade/renderer/light/world_cluster/WORLD_LIGHT_CLUSTER.d.ts +39 -0
- package/src/shade/renderer/light/world_cluster/WORLD_LIGHT_CLUSTER.d.ts.map +1 -0
- package/src/shade/renderer/light/world_cluster/WORLD_LIGHT_CLUSTER.js +90 -0
- package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.d.ts +12 -0
- package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.d.ts.map +1 -0
- package/src/shade/renderer/light/world_cluster/chunk_world_light_cluster_lookup.js +56 -0
- package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.d.ts +31 -0
- package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.d.ts.map +1 -0
- package/src/shade/renderer/light/world_cluster/graph_build_world_light_clusters.js +196 -0
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.d.ts +10 -0
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.d.ts.map +1 -0
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_assign_lights.js +163 -0
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_cull_lights.d.ts +4 -0
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_cull_lights.d.ts.map +1 -0
- package/src/shade/renderer/light/world_cluster/shader_world_cluster_cull_lights.js +137 -0
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.js +4 -3
- package/src/shade/renderer/particles/graph_particles_avboit.d.ts +4 -3
- package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph_particles_avboit.js +28 -11
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +9 -13
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +25 -20
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.js +21 -38
- package/src/shade/renderer/postprocess/denoise/fragment_shader_denoise_ao.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/denoise/fragment_shader_denoise_ao.js +154 -147
- package/src/shade/renderer/postprocess/gtao/GTAO.d.ts +2 -1
- package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/GTAO.js +5 -1
- package/src/shade/renderer/postprocess/gtao/GTAO_CONSTANTS.d.ts +8 -0
- package/src/shade/renderer/postprocess/gtao/GTAO_CONSTANTS.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/GTAO_CONSTANTS.js +10 -1
- package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.js +5 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_compute_mip_level.d.ts +22 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_compute_mip_level.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_compute_mip_level.js +33 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_resolve_hzb_depth.d.ts +22 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_resolve_hzb_depth.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_resolve_hzb_depth.js +56 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_sample_depth.d.ts +13 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_sample_depth.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/gtao/chunk_gtao_sample_depth.js +42 -0
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts +2 -1
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.js +382 -357
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.js +414 -410
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -7
- package/src/shade/renderer/shader/chunk/depth/chunk_query_depth_from_screen_space_bb.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/depth/chunk_query_depth_from_screen_space_bb.js +62 -62
- package/src/shade/renderer/shader/chunk/random/lowbias/chunk_hash_lowbias32_vec2u.d.ts +6 -0
- package/src/shade/renderer/shader/chunk/random/lowbias/chunk_hash_lowbias32_vec2u.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/random/lowbias/chunk_hash_lowbias32_vec2u.js +19 -0
- package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_get_mip_resolution.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_get_mip_resolution.js +16 -10
- package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_hierarchical_raymarch.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/screen_space/chunk_ffx_sssr_hierarchical_raymarch.js +118 -117
- package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.d.ts +41 -7
- package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.d.ts.map +1 -1
- package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.js +162 -58
- package/src/shade/renderer/volumetrics/GPUSceneVolumetrics.js +1 -1
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts +10 -12
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +583 -606
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +1 -1
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +640 -640
- package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts +113 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/GPUIrradianceClipmap.js +484 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP.d.ts +50 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP.js +60 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP_STRUCT.d.ts +25 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP_STRUCT.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IRRADIANCE_CLIPMAP_STRUCT.js +47 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IrradianceCascadePlacement.d.ts +74 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IrradianceCascadePlacement.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/IrradianceCascadePlacement.js +173 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts +26 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_gi_sample.js +83 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.d.ts +36 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.js +129 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.d.ts +121 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.js +370 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.d.ts +16 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/irradiance_clipmap/shader_irradiance_clipmap_build.js +191 -0
- package/src/shade/renderer/wind/GPUSceneWind.d.ts +1 -1
- package/src/shade/renderer/wind/GPUSceneWind.js +1 -1
- package/src/engine/graphics3/decal/DecalAtlas.d.ts +0 -43
- package/src/engine/graphics3/decal/DecalAtlas.d.ts.map +0 -1
- package/src/engine/graphics3/decal/DecalAtlasFill.d.ts.map +0 -1
- package/src/engine/graphics3/decal/DecalAtlasFill.js +0 -32
- package/src/engine/graphics3/decal/decal_atlas_border.d.ts.map +0 -1
- package/src/shade/renderer/postprocess/gtao/graph_build_depth_mipmap.d.ts +0 -26
- package/src/shade/renderer/postprocess/gtao/graph_build_depth_mipmap.d.ts.map +0 -1
- package/src/shade/renderer/postprocess/gtao/graph_build_depth_mipmap.js +0 -268
- package/src/shade/renderer/postprocess/ssr/copy_shader_f32.d.ts +0 -3
- package/src/shade/renderer/postprocess/ssr/copy_shader_f32.d.ts.map +0 -1
- package/src/shade/renderer/postprocess/ssr/copy_shader_f32.js +0 -32
- package/src/shade/renderer/postprocess/ssr/depth_max_shader.d.ts +0 -17
- package/src/shade/renderer/postprocess/ssr/depth_max_shader.d.ts.map +0 -1
- package/src/shade/renderer/postprocess/ssr/depth_max_shader.js +0 -98
- package/src/shade/renderer/postprocess/ssr/graph_build_depth_max_pyramid.d.ts +0 -23
- package/src/shade/renderer/postprocess/ssr/graph_build_depth_max_pyramid.d.ts.map +0 -1
- package/src/shade/renderer/postprocess/ssr/graph_build_depth_max_pyramid.js +0 -118
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.d.ts +0 -15
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.js +0 -49
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts +0 -17
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.js +0 -50
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.d.ts +0 -35
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.js +0 -105
- package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts +0 -35
- package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.js +0 -80
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi.d.ts +0 -10
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi.js +0 -51
- package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.d.ts +0 -28
- package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.js +0 -39
- package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts +0 -48
- package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.js +0 -121
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts +0 -28
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.js +0 -237
|
@@ -1,1047 +1,1049 @@
|
|
|
1
|
-
import { PI2 } from "../../../core/math/PI2.js";
|
|
2
|
-
import { clamp } from "../../../core/math/clamp.js";
|
|
3
|
-
import { lerp } from "../../../core/math/lerp.js";
|
|
4
|
-
import { Attribute } from "../../../shade/renderer/geometry/Attribute.js";
|
|
5
|
-
import { Geometry } from "../../../shade/renderer/geometry/Geometry.js";
|
|
6
|
-
import {
|
|
7
|
-
geometry_obtain_attribute,
|
|
8
|
-
geometry_obtain_index
|
|
9
|
-
} from "../../../shade/renderer/geometry/geometry_obtain_attribute.js";
|
|
10
|
-
import { StandardAttributes } from "../../../shade/renderer/geometry/StandardAttributes.js";
|
|
11
|
-
import { BranchLevelConfig } from "../config/BranchLevelConfig.js";
|
|
12
|
-
import { TreeLODConfig } from "../config/TreeLODConfig.js";
|
|
13
|
-
import { TreeSpeciesConfig } from "../config/TreeSpeciesConfig.js";
|
|
14
|
-
import { evaluate_bark_relief } from "../material/make_bark_material.js";
|
|
15
|
-
import { spine_frame_double_reflection } from "../skeleton/spine_frame_double_reflection.js";
|
|
16
|
-
import { branch_collar_project } from "./branch_collar_project.js";
|
|
17
|
-
import { branch_apex_triangulate, branch_ring_triangulate } from "./branch_ring_triangulate.js";
|
|
18
|
-
import { tree_mesh_apply_canopy_ao } from "./tree_mesh_apply_canopy_ao.js";
|
|
19
|
-
|
|
20
|
-
const DEFAULT_LEVEL_CONFIG = new BranchLevelConfig();
|
|
21
|
-
|
|
22
|
-
const SCRATCH_P0 = new Float32Array(3);
|
|
23
|
-
const SCRATCH_P1 = new Float32Array(3);
|
|
24
|
-
const SCRATCH_T0 = new Float32Array(3);
|
|
25
|
-
const SCRATCH_V_TAN = new Float32Array(3);
|
|
26
|
-
const SCRATCH_R0 = new Float32Array(3);
|
|
27
|
-
const SCRATCH_SPINE0 = new Float32Array(3);
|
|
28
|
-
const SCRATCH_SPINE1 = new Float32Array(3);
|
|
29
|
-
const SCRATCH_RMF = new Float64Array(6);
|
|
30
|
-
const SCRATCH_PARENT_P = new Float32Array(3);
|
|
31
|
-
const SCRATCH_PARENT_T = new Float32Array(3);
|
|
32
|
-
const SCRATCH_COLLAR_POS = new Float32Array(3);
|
|
33
|
-
const SCRATCH_COLLAR_NORM = new Float32Array(3);
|
|
34
|
-
const SCRATCH_RELIEF = new Float32Array(4);
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Deterministic [0, 1) hash for procedural knot/burl placement on branch `b`.
|
|
38
|
-
* @param {number} seed
|
|
39
|
-
* @param {number} b
|
|
40
|
-
* @param {number} k
|
|
41
|
-
* @param {number} salt
|
|
42
|
-
* @returns {number}
|
|
43
|
-
*/
|
|
44
|
-
function knot_rand01(seed, b, k, salt) {
|
|
45
|
-
let h = (seed ^ Math.imul(b + 1, 0x9E3779B1) ^ Math.imul(k + 1, 0x85EBCA6B) ^ Math.imul(salt, 0xC2B2AE35)) | 0;
|
|
46
|
-
h = Math.imul(h ^ (h >>> 16), 0x7FEB352D);
|
|
47
|
-
h = Math.imul(h ^ (h >>> 15), 0x846CA68B);
|
|
48
|
-
h = (h ^ (h >>> 16)) >>> 0;
|
|
49
|
-
return h / 4294967296.0;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Helper to obtain a `Uint16Array` attribute on `Geometry` for `StandardAttributes.SkinningJoints`.
|
|
54
|
-
* @param {Geometry} geometry
|
|
55
|
-
* @param {number} count vertex count
|
|
56
|
-
* @returns {Uint16Array}
|
|
57
|
-
*/
|
|
58
|
-
export function geometry_obtain_joints_attribute(geometry, count) {
|
|
59
|
-
const existing = geometry.getAttribute(StandardAttributes.SkinningJoints);
|
|
60
|
-
if (existing === undefined) {
|
|
61
|
-
const attr = Attribute.from(new Uint16Array(count * 4), 4, StandardAttributes.SkinningJoints);
|
|
62
|
-
geometry.addAttribute(attr);
|
|
63
|
-
return /** @type {Uint16Array} */ (attr.data);
|
|
64
|
-
}
|
|
65
|
-
if (existing.count !== count || !(existing.data instanceof Uint16Array)) {
|
|
66
|
-
existing.data = new Uint16Array(count * 4);
|
|
67
|
-
existing.count = count;
|
|
68
|
-
}
|
|
69
|
-
existing.needsUpdate = true;
|
|
70
|
-
return /** @type {Uint16Array} */ (existing.data);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Sweeps the Cubic Hermite branch spines of a `TreeSkeleton` into a single manifold, seamless-UV
|
|
75
|
-
* bark `Geometry` with root buttress lobes, longitudinal bark flutes, welded lateral branch collars,
|
|
76
|
-
* canopy ambient occlusion, and 4-joint skeletal wind skinning attributes.
|
|
77
|
-
*
|
|
78
|
-
* @param {import("../skeleton/TreeSkeleton.js").TreeSkeleton} skeleton
|
|
79
|
-
* @param {TreeSpeciesConfig} [config]
|
|
80
|
-
* @param {TreeLODConfig} [lod_config]
|
|
81
|
-
* @param {import("../../../shade/renderer/geometry/Geometry.js").Geometry} [out]
|
|
82
|
-
* @returns {Geometry}
|
|
83
|
-
*/
|
|
84
|
-
export function tree_mesh_build_bark(
|
|
85
|
-
skeleton,
|
|
86
|
-
config = new TreeSpeciesConfig(),
|
|
87
|
-
lod_config = new TreeLODConfig(),
|
|
88
|
-
out = new Geometry(),
|
|
89
|
-
only_rings = false
|
|
90
|
-
) {
|
|
91
|
-
const species = config && config.isTreeSpeciesConfig ? config : TreeSpeciesConfig.from(config);
|
|
92
|
-
const lod_cfg = lod_config && lod_config.isTreeLODConfig ? lod_config : TreeLODConfig.from(lod_config);
|
|
93
|
-
|
|
94
|
-
const branch_count = skeleton.branch_count;
|
|
95
|
-
|
|
96
|
-
const radial_scale = clamp(lod_cfg.radial_scale ?? 1.0, 0.2, 2.0);
|
|
97
|
-
const long_scale = clamp(lod_cfg.longitudinal_scale ?? 1.0, 0.2, 4.0);
|
|
98
|
-
const max_level = lod_cfg.max_branch_level ?? 3;
|
|
99
|
-
const min_radius = lod_cfg.min_branch_radius ?? 0.0;
|
|
100
|
-
const texel_density = Math.max(0.1, species.bark_material.texel_density ?? 1.2);
|
|
101
|
-
const twist_uv = species.bark_material.twist_uv ?? 0.0;
|
|
102
|
-
|
|
103
|
-
// Collect retained branches and their resampled longitudinal cross-sectional rings
|
|
104
|
-
const branch_plans = [];
|
|
105
|
-
let total_vertices = 0;
|
|
106
|
-
let total_indices = 0;
|
|
107
|
-
|
|
108
|
-
for (let b = 0; b < branch_count; b++) {
|
|
109
|
-
const level = skeleton.branch_level[b];
|
|
110
|
-
const base_r = skeleton.branch_base_radius[b];
|
|
111
|
-
if (level > max_level) continue;
|
|
112
|
-
if (level > 0 && base_r < min_radius) continue;
|
|
113
|
-
|
|
114
|
-
const lvl_cfg = (species.levels && species.levels.length > 0)
|
|
115
|
-
? (species.levels[Math.min(level, species.levels.length - 1)] || DEFAULT_LEVEL_CONFIG)
|
|
116
|
-
: DEFAULT_LEVEL_CONFIG;
|
|
117
|
-
const m_max = Math.max(3, Math.round(lvl_cfg.radial_segments_max * radial_scale));
|
|
118
|
-
const m_min = Math.max(3, Math.min(m_max, Math.round(lvl_cfg.radial_segments_min * radial_scale)));
|
|
119
|
-
|
|
120
|
-
// Walk the node/segment spine of branch b via `skeleton.graph`
|
|
121
|
-
const nodes = [];
|
|
122
|
-
const edges = [];
|
|
123
|
-
skeleton.collect_branch_chain(b, nodes, edges);
|
|
124
|
-
|
|
125
|
-
const E = edges.length;
|
|
126
|
-
if (E < 1) continue;
|
|
127
|
-
|
|
128
|
-
const sample_steps = Math.max(1, Math.round(E * long_scale));
|
|
129
|
-
const ring_count = sample_steps + 1;
|
|
130
|
-
const ring_m = new Int32Array(ring_count);
|
|
131
|
-
let branch_verts = 0;
|
|
132
|
-
let branch_tris = 0;
|
|
133
|
-
|
|
134
|
-
for (let i = 0; i <= sample_steps; i++) {
|
|
135
|
-
const u_branch = (i / sample_steps) * E;
|
|
136
|
-
const edge_idx = Math.min(E - 1, Math.floor(u_branch));
|
|
137
|
-
const local_u = i === sample_steps ? 1.0 : (u_branch - edge_idx);
|
|
138
|
-
|
|
139
|
-
const r0 = skeleton.node_read_radius(nodes[edge_idx]);
|
|
140
|
-
const r1 = skeleton.node_read_radius(nodes[edge_idx + 1]);
|
|
141
|
-
const r_i = lerp(r0, r1, local_u);
|
|
142
|
-
|
|
143
|
-
const ratio = base_r > 1e-5 ? clamp(r_i / base_r, 0.0, 1.0) : 1.0;
|
|
144
|
-
const m_i = Math.max(3, Math.round(lerp(m_min, m_max, Math.pow(ratio, 0.7))));
|
|
145
|
-
ring_m[i] = m_i;
|
|
146
|
-
branch_verts += (m_i + 1);
|
|
147
|
-
if (i > 0) {
|
|
148
|
-
branch_tris += (ring_m[i - 1] + m_i);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Terminal apex cone cap (m_tip vertices, m_tip triangles)
|
|
153
|
-
const m_tip = ring_m[sample_steps];
|
|
154
|
-
branch_verts += m_tip;
|
|
155
|
-
branch_tris += m_tip;
|
|
156
|
-
|
|
157
|
-
branch_plans.push({
|
|
158
|
-
branch_id: b,
|
|
159
|
-
level,
|
|
160
|
-
base_r,
|
|
161
|
-
lvl_cfg,
|
|
162
|
-
nodes,
|
|
163
|
-
edges,
|
|
164
|
-
sample_steps,
|
|
165
|
-
ring_m
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
total_vertices += branch_verts;
|
|
169
|
-
total_indices += branch_tris * 3;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const is_empty_fallback = branch_plans.length === 0 || total_vertices === 0 || total_indices === 0;
|
|
173
|
-
if (is_empty_fallback) {
|
|
174
|
-
if (only_rings) {
|
|
175
|
-
return /** @type {any} */ (new Map());
|
|
176
|
-
}
|
|
177
|
-
total_vertices = 4;
|
|
178
|
-
total_indices = 6;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const positions = geometry_obtain_attribute(out, StandardAttributes.Position, 3, total_vertices);
|
|
182
|
-
const normals = geometry_obtain_attribute(out, StandardAttributes.Normal, 3, total_vertices);
|
|
183
|
-
const uv0 = geometry_obtain_attribute(out, StandardAttributes.TextureCoordinates0, 2, total_vertices);
|
|
184
|
-
const uv1 = geometry_obtain_attribute(out, StandardAttributes.TextureCoordinates1, 2, total_vertices);
|
|
185
|
-
const colors = geometry_obtain_attribute(out, StandardAttributes.Color, 3, total_vertices);
|
|
186
|
-
const joints = geometry_obtain_joints_attribute(out, total_vertices);
|
|
187
|
-
const weights = geometry_obtain_attribute(out, StandardAttributes.SkinningWeights, 4, total_vertices);
|
|
188
|
-
const indices = geometry_obtain_index(out, total_indices);
|
|
189
|
-
|
|
190
|
-
if (is_empty_fallback) {
|
|
191
|
-
// Populate valid non-degenerate fallback quad so ensureTangents() and MeshoptClusterizer always succeed
|
|
192
|
-
const quad_pos = [
|
|
193
|
-
-0.02, 0.0, -0.02,
|
|
194
|
-
0.02, 0.0, -0.02,
|
|
195
|
-
0.02, 0.04, 0.02,
|
|
196
|
-
-0.02, 0.04, 0.02
|
|
197
|
-
];
|
|
198
|
-
const quad_uv = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
|
|
199
|
-
for (let k = 0; k < 4; k++) {
|
|
200
|
-
positions[k * 3] = quad_pos[k * 3];
|
|
201
|
-
positions[k * 3 + 1] = quad_pos[k * 3 + 1];
|
|
202
|
-
positions[k * 3 + 2] = quad_pos[k * 3 + 2];
|
|
203
|
-
normals[k * 3] = 0.0;
|
|
204
|
-
normals[k * 3 + 1] = 1.0;
|
|
205
|
-
normals[k * 3 + 2] = 0.0;
|
|
206
|
-
uv0[k * 2] = quad_uv[k * 2];
|
|
207
|
-
uv0[k * 2 + 1] = quad_uv[k * 2 + 1];
|
|
208
|
-
uv1[k * 2] = 0.0;
|
|
209
|
-
uv1[k * 2 + 1] = 0.0;
|
|
210
|
-
colors[k * 3] = 1.0;
|
|
211
|
-
colors[k * 3 + 1] = 1.0;
|
|
212
|
-
colors[k * 3 + 2] = 1.0;
|
|
213
|
-
joints[k * 4] = 0;
|
|
214
|
-
joints[k * 4 + 1] = 0;
|
|
215
|
-
joints[k * 4 + 2] = 0;
|
|
216
|
-
joints[k * 4 + 3] = 0;
|
|
217
|
-
weights[k * 4] = 1.0;
|
|
218
|
-
weights[k * 4 + 1] = 0.0;
|
|
219
|
-
weights[k * 4 + 2] = 0.0;
|
|
220
|
-
weights[k * 4 + 3] = 0.0;
|
|
221
|
-
}
|
|
222
|
-
indices[0] = 0;
|
|
223
|
-
indices[1] = 1;
|
|
224
|
-
indices[2] = 2;
|
|
225
|
-
indices[3] = 0;
|
|
226
|
-
indices[4] = 2;
|
|
227
|
-
indices[5] = 3;
|
|
228
|
-
|
|
229
|
-
out.name = `${species.name || "tree"}_bark_lod${lod_cfg.level}`;
|
|
230
|
-
out.ensureBounds();
|
|
231
|
-
out.ensureTangents();
|
|
232
|
-
return out;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
let v_cursor = 0;
|
|
236
|
-
let i_cursor = 0;
|
|
237
|
-
const meshed_branch_rings = new Map();
|
|
238
|
-
|
|
239
|
-
for (let p_idx = 0; p_idx < branch_plans.length; p_idx++) {
|
|
240
|
-
const plan = branch_plans[p_idx];
|
|
241
|
-
const b = plan.branch_id;
|
|
242
|
-
const level = plan.level;
|
|
243
|
-
const base_r = plan.base_r;
|
|
244
|
-
const lvl_cfg = plan.lvl_cfg;
|
|
245
|
-
const nodes = plan.nodes;
|
|
246
|
-
const edges = plan.edges;
|
|
247
|
-
const E = edges.length;
|
|
248
|
-
const sample_steps = plan.sample_steps;
|
|
249
|
-
const ring_m = plan.ring_m;
|
|
250
|
-
|
|
251
|
-
// Quantized integer circumference wrap count (1 or 2) for seamless bark tiling proportional to stem radius
|
|
252
|
-
const k_wrap = Math.max(1, Math.min(2, Math.round(PI2 * base_r * texel_density)));
|
|
253
|
-
|
|
254
|
-
// Pre-collect child branch attachment collars on this branch `b` (for parent-stem collar bosses & UV grain flow)
|
|
255
|
-
const child_collars = [];
|
|
256
|
-
const b_len = Math.max(0.2, skeleton.branch_length[b]);
|
|
257
|
-
for (let cb = b + 1; cb < branch_count; cb++) {
|
|
258
|
-
if (skeleton.branch_parent[cb] === b) {
|
|
259
|
-
const c_root = skeleton.branch_root_vertex[cb];
|
|
260
|
-
skeleton.node_read_tangent(SCRATCH_V_TAN, 0, c_root);
|
|
261
|
-
const c_r = skeleton.branch_base_radius[cb];
|
|
262
|
-
const r_ratio = clamp(c_r / Math.max(1e-4, base_r), 0.15, 0.82);
|
|
263
|
-
child_collars.push({
|
|
264
|
-
t_c: skeleton.branch_parent_attach_t[cb],
|
|
265
|
-
tx_c: SCRATCH_V_TAN[0],
|
|
266
|
-
ty_c: SCRATCH_V_TAN[1],
|
|
267
|
-
tz_c: SCRATCH_V_TAN[2],
|
|
268
|
-
r_ratio,
|
|
269
|
-
sigma_t: clamp(1.55 * c_r / b_len, 0.035, 0.13),
|
|
270
|
-
sigma_theta: clamp(0.60 * r_ratio + 0.22, 0.28, 0.72)
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Pre-collect deterministic 3D knots / callus eyes / burls along this branch `b`
|
|
276
|
-
const branch_knots = [];
|
|
277
|
-
const knot_count = lvl_cfg.knot_count | 0;
|
|
278
|
-
const knot_str = lvl_cfg.knot_strength ?? 0.0;
|
|
279
|
-
if (knot_count > 0 && knot_str > 0.0) {
|
|
280
|
-
const seed_i = species.seed | 0;
|
|
281
|
-
for (let k = 0; k < knot_count; k++) {
|
|
282
|
-
const t_k = clamp(0.09 + (k + 0.2 + 0.6 * knot_rand01(seed_i, b, k, 1)) * (0.78 / knot_count), 0.08, 0.88);
|
|
283
|
-
const theta_k = (k * 2.399963229728653 + knot_rand01(seed_i, b, k, 2) * 1.4) % PI2;
|
|
284
|
-
const sigma_t = clamp(0.045 + 0.035 * knot_rand01(seed_i, b, k, 3), 0.04, 0.085);
|
|
285
|
-
const sigma_theta = clamp(0.32 + 0.24 * knot_rand01(seed_i, b, k, 4), 0.30, 0.56);
|
|
286
|
-
const amp_k = knot_str * (0.80 + 0.55 * knot_rand01(seed_i, b, k, 5));
|
|
287
|
-
const is_burl = knot_rand01(seed_i, b, k, 6) > 0.45;
|
|
288
|
-
branch_knots.push({ t_k, theta_k, sigma_t, sigma_theta, amp_k, is_burl });
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Parent branch attachment context from actual meshed parent frustum
|
|
293
|
-
const parent_b = skeleton.branch_parent[b];
|
|
294
|
-
const parent_attach_t = skeleton.branch_parent_attach_t[b];
|
|
295
|
-
const p_rings = parent_b >= 0 ? meshed_branch_rings.get(parent_b) : undefined;
|
|
296
|
-
const has_meshed_parent = b > 0 && p_rings !== undefined && p_rings.length >= 2;
|
|
297
|
-
|
|
298
|
-
let parent_px = 0.0, parent_py = 0.0, parent_pz = 0.0;
|
|
299
|
-
let parent_tx = 0.0, parent_ty = 1.0, parent_tz = 0.0;
|
|
300
|
-
let parent_r = base_r * 1.4;
|
|
301
|
-
let parent_v_attach = 0.02;
|
|
302
|
-
let parent_taper_slope = 0.0;
|
|
303
|
-
let parent_m = 8;
|
|
304
|
-
let parent_flute_amp = 0.0;
|
|
305
|
-
let parent_z_min = -Infinity;
|
|
306
|
-
let parent_z_max = Infinity;
|
|
307
|
-
let shift_x = 0.0, shift_y = 0.0, shift_z = 0.0;
|
|
308
|
-
let base_j0 = 0, base_j1 = 0, base_j2 = 0, base_j3 = 0;
|
|
309
|
-
let base_w0 = 1.0, base_w1 = 0.0, base_w2 = 0.0, base_w3 = 0.0;
|
|
310
|
-
let emerge_tx = 0.0, emerge_ty = 1.0, emerge_tz = 0.0;
|
|
311
|
-
|
|
312
|
-
if (has_meshed_parent) {
|
|
313
|
-
let k_attach = p_rings.length - 2;
|
|
314
|
-
for (let k = 0; k < p_rings.length - 1; k++) {
|
|
315
|
-
if (parent_attach_t >= p_rings[k].t_norm - 1e-5 && parent_attach_t <= p_rings[k + 1].t_norm + 1e-5) {
|
|
316
|
-
k_attach = k;
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
const pr_a = p_rings[k_attach];
|
|
321
|
-
const pr_b = p_rings[k_attach + 1];
|
|
322
|
-
const xi = clamp((parent_attach_t - pr_a.t_norm) / Math.max(1e-5, pr_b.t_norm - pr_a.t_norm), 0.0, 1.0);
|
|
323
|
-
|
|
324
|
-
parent_px = lerp(pr_a.cx, pr_b.cx, xi);
|
|
325
|
-
parent_py = lerp(pr_a.cy, pr_b.cy, xi);
|
|
326
|
-
parent_pz = lerp(pr_a.cz, pr_b.cz, xi);
|
|
327
|
-
|
|
328
|
-
const ax = pr_b.cx - pr_a.cx;
|
|
329
|
-
const ay = pr_b.cy - pr_a.cy;
|
|
330
|
-
const az = pr_b.cz - pr_a.cz;
|
|
331
|
-
const parent_seg_len = Math.sqrt(ax * ax + ay * ay + az * az) || 1e-5;
|
|
332
|
-
parent_tx = ax / parent_seg_len;
|
|
333
|
-
parent_ty = ay / parent_seg_len;
|
|
334
|
-
parent_tz = az / parent_seg_len;
|
|
335
|
-
|
|
336
|
-
parent_r = lerp(pr_a.r, pr_b.r, xi);
|
|
337
|
-
parent_v_attach = lerp(pr_a.v_uv ?? 0.25, pr_b.v_uv ?? 0.25, xi);
|
|
338
|
-
parent_taper_slope = (pr_b.r - pr_a.r) / parent_seg_len;
|
|
339
|
-
parent_m = Math.min(pr_a.m, pr_b.m);
|
|
340
|
-
parent_flute_amp = pr_a.flute_amp;
|
|
341
|
-
parent_z_min = -(xi + 0.25) * parent_seg_len;
|
|
342
|
-
parent_z_max = (1.0 - xi + 0.25) * parent_seg_len;
|
|
343
|
-
|
|
344
|
-
// Translate child spine so its root lies on the exact parent mesh frustum chord
|
|
345
|
-
skeleton.node_read_position(SCRATCH_P0, 0, nodes[0]);
|
|
346
|
-
shift_x = parent_px - SCRATCH_P0[0];
|
|
347
|
-
shift_y = parent_py - SCRATCH_P0[1];
|
|
348
|
-
shift_z = parent_pz - SCRATCH_P0[2];
|
|
349
|
-
|
|
350
|
-
// Compute emergence chord direction from parent_P to child Ring i = 1 spine center
|
|
351
|
-
const u_step1 = (1 / sample_steps) * E;
|
|
352
|
-
const edge_idx1 = Math.min(E - 1, Math.floor(u_step1));
|
|
353
|
-
const local_u1 = 1 === sample_steps ? 1.0 : (u_step1 - edge_idx1);
|
|
354
|
-
skeleton.evaluate_segment_position(SCRATCH_P1, 0, edges[edge_idx1], local_u1);
|
|
355
|
-
const p1x = SCRATCH_P1[0] + shift_x;
|
|
356
|
-
const p1y = SCRATCH_P1[1] + shift_y;
|
|
357
|
-
const p1z = SCRATCH_P1[2] + shift_z;
|
|
358
|
-
const emx = p1x - parent_px;
|
|
359
|
-
const emy = p1y - parent_py;
|
|
360
|
-
const emz = p1z - parent_pz;
|
|
361
|
-
const em_len = Math.sqrt(emx * emx + emy * emy + emz * emz);
|
|
362
|
-
if (em_len > 1e-6) {
|
|
363
|
-
emerge_tx = emx / em_len;
|
|
364
|
-
emerge_ty = emy / em_len;
|
|
365
|
-
emerge_tz = emz / em_len;
|
|
366
|
-
} else {
|
|
367
|
-
skeleton.node_read_tangent(SCRATCH_V_TAN, 0, nodes[0]);
|
|
368
|
-
emerge_tx = SCRATCH_V_TAN[0];
|
|
369
|
-
emerge_ty = SCRATCH_V_TAN[1];
|
|
370
|
-
emerge_tz = SCRATCH_V_TAN[2];
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Merge parent frustum skinning weights at xi so child Ring i = 0 deforms identically to parent
|
|
374
|
-
const merged_j = [];
|
|
375
|
-
const merged_w = [];
|
|
376
|
-
const accumulate_joint = (j_id, w_val) => {
|
|
377
|
-
if (w_val <= 1e-7) return;
|
|
378
|
-
for (let s = 0; s < merged_j.length; s++) {
|
|
379
|
-
if (merged_j[s] === j_id) {
|
|
380
|
-
merged_w[s] += w_val;
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
merged_j.push(j_id);
|
|
385
|
-
merged_w.push(w_val);
|
|
386
|
-
};
|
|
387
|
-
const w_a = 1.0 - xi;
|
|
388
|
-
accumulate_joint(pr_a.j0, pr_a.w0 * w_a);
|
|
389
|
-
accumulate_joint(pr_a.j1, pr_a.w1 * w_a);
|
|
390
|
-
accumulate_joint(pr_a.j2, pr_a.w2 * w_a);
|
|
391
|
-
accumulate_joint(pr_a.j3, pr_a.w3 * w_a);
|
|
392
|
-
accumulate_joint(pr_b.j0, pr_b.w0 * xi);
|
|
393
|
-
accumulate_joint(pr_b.j1, pr_b.w1 * xi);
|
|
394
|
-
accumulate_joint(pr_b.j2, pr_b.w2 * xi);
|
|
395
|
-
accumulate_joint(pr_b.j3, pr_b.w3 * xi);
|
|
396
|
-
|
|
397
|
-
// Sort descending by weight and normalize top 4
|
|
398
|
-
for (let a = 0; a < merged_j.length; a++) {
|
|
399
|
-
for (let c = a + 1; c < merged_j.length; c++) {
|
|
400
|
-
if (merged_w[c] > merged_w[a]) {
|
|
401
|
-
const tj = merged_j[a]; merged_j[a] = merged_j[c]; merged_j[c] = tj;
|
|
402
|
-
const tw = merged_w[a]; merged_w[a] = merged_w[c]; merged_w[c] = tw;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
base_j0 = merged_j[0] ?? 0;
|
|
407
|
-
base_j1 = merged_j[1] ?? 0;
|
|
408
|
-
base_j2 = merged_j[2] ?? 0;
|
|
409
|
-
base_j3 = merged_j[3] ?? 0;
|
|
410
|
-
base_w0 = merged_w[0] ?? 1.0;
|
|
411
|
-
base_w1 = merged_w[1] ?? 0.0;
|
|
412
|
-
base_w2 = merged_w[2] ?? 0.0;
|
|
413
|
-
base_w3 = merged_w[3] ?? 0.0;
|
|
414
|
-
const w_sum = base_w0 + base_w1 + base_w2 + base_w3;
|
|
415
|
-
if (w_sum > 1e-6) {
|
|
416
|
-
base_w0 /= w_sum;
|
|
417
|
-
base_w1 /= w_sum;
|
|
418
|
-
base_w2 /= w_sum;
|
|
419
|
-
base_w3 /= w_sum;
|
|
420
|
-
} else {
|
|
421
|
-
base_w0 = 1.0;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
let prev_ring_start = -1;
|
|
426
|
-
let prev_m = 0;
|
|
427
|
-
const current_branch_rings = [];
|
|
428
|
-
|
|
429
|
-
// Track continuous RMF along the resampled spine rings
|
|
430
|
-
let prev_px = 0.0, prev_py = 0.0, prev_pz = 0.0;
|
|
431
|
-
let prev_tx = 0.0, prev_ty = 1.0, prev_tz = 0.0;
|
|
432
|
-
let cur_rx = 1.0, cur_ry = 0.0, cur_rz = 0.0;
|
|
433
|
-
let cur_sx = 0.0, cur_sy = 0.0, cur_sz = 1.0;
|
|
434
|
-
|
|
435
|
-
// Keep terminal ring state for apex cap
|
|
436
|
-
let tip_px = 0.0, tip_py = 0.0, tip_pz = 0.0;
|
|
437
|
-
let tip_tx = 0.0, tip_ty = 1.0, tip_tz = 0.0;
|
|
438
|
-
let tip_rx = 1.0, tip_ry = 0.0, tip_rz = 0.0;
|
|
439
|
-
let tip_sx = 0.0, tip_sy = 0.0, tip_sz = 1.0;
|
|
440
|
-
let tip_r = 0.002;
|
|
441
|
-
let tip_arc_s = 0.0;
|
|
442
|
-
let tip_v_uv = 0.90;
|
|
443
|
-
let tip_wind_sway = 0.0;
|
|
444
|
-
let tip_wind_phase = 0.0;
|
|
445
|
-
let tip_j0 = 0, tip_j1 = 0, tip_j2 = 0, tip_j3 = 0;
|
|
446
|
-
let tip_w0 = 1.0, tip_w1 = 0.0, tip_w2 = 0.0, tip_w3 = 0.0;
|
|
447
|
-
|
|
448
|
-
// Continuous ontogenetic V range [v_start, v_end] in [0, 1] from trunk base (old bark) to twig tips (young shoot bark)
|
|
449
|
-
const num_levels = (species.levels && species.levels.length > 0) ? species.levels.length : 1;
|
|
450
|
-
let v_start = 0.02;
|
|
451
|
-
let v_end = num_levels > 2 ? 0.62 : (num_levels === 2 ? 0.80 : 0.90);
|
|
452
|
-
if (has_meshed_parent) {
|
|
453
|
-
v_start = parent_v_attach;
|
|
454
|
-
if (level === 1 && num_levels > 2) {
|
|
455
|
-
v_end = clamp(lerp(v_start, 0.80, 0.65), v_start + 0.10, 0.84);
|
|
456
|
-
} else {
|
|
457
|
-
v_end = clamp(lerp(v_start, 0.95, 0.82), Math.max(0.82, v_start + 0.08), 0.97);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const max_weld_rings = Math.min(lvl_cfg.collar_weld_rings, Math.max(0, sample_steps - 1));
|
|
462
|
-
|
|
463
|
-
for (let i = 0; i <= sample_steps; i++) {
|
|
464
|
-
const m_i = ring_m[i];
|
|
465
|
-
const ring_start = v_cursor;
|
|
466
|
-
|
|
467
|
-
const u_branch = (i / sample_steps) * E;
|
|
468
|
-
const edge_idx = Math.min(E - 1, Math.floor(u_branch));
|
|
469
|
-
const local_u = i === sample_steps ? 1.0 : (u_branch - edge_idx);
|
|
470
|
-
const edge_id = edges[edge_idx];
|
|
471
|
-
const v0 = nodes[edge_idx];
|
|
472
|
-
const v1 = nodes[edge_idx + 1];
|
|
473
|
-
|
|
474
|
-
skeleton.evaluate_segment_position(SCRATCH_P0, 0, edge_id, local_u);
|
|
475
|
-
skeleton.evaluate_segment_tangent(SCRATCH_T0, 0, edge_id, local_u);
|
|
476
|
-
|
|
477
|
-
const px = SCRATCH_P0[0] + shift_x;
|
|
478
|
-
const py = SCRATCH_P0[1] + shift_y;
|
|
479
|
-
const pz = SCRATCH_P0[2] + shift_z;
|
|
480
|
-
const tx = SCRATCH_T0[0], ty = SCRATCH_T0[1], tz = SCRATCH_T0[2];
|
|
481
|
-
|
|
482
|
-
if (i === 0) {
|
|
483
|
-
skeleton.node_read_tangent(SCRATCH_V_TAN, 0, v0);
|
|
484
|
-
skeleton.node_read_frame_r(SCRATCH_R0, 0, v0);
|
|
485
|
-
spine_frame_double_reflection(
|
|
486
|
-
SCRATCH_RMF, 0,
|
|
487
|
-
px, py, pz,
|
|
488
|
-
SCRATCH_V_TAN[0], SCRATCH_V_TAN[1], SCRATCH_V_TAN[2],
|
|
489
|
-
SCRATCH_R0[0], SCRATCH_R0[1], SCRATCH_R0[2],
|
|
490
|
-
px, py, pz,
|
|
491
|
-
tx, ty, tz
|
|
492
|
-
);
|
|
493
|
-
} else {
|
|
494
|
-
spine_frame_double_reflection(
|
|
495
|
-
SCRATCH_RMF, 0,
|
|
496
|
-
prev_px, prev_py, prev_pz,
|
|
497
|
-
prev_tx, prev_ty, prev_tz,
|
|
498
|
-
cur_rx, cur_ry, cur_rz,
|
|
499
|
-
px, py, pz,
|
|
500
|
-
tx, ty, tz
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
cur_rx = SCRATCH_RMF[0];
|
|
504
|
-
cur_ry = SCRATCH_RMF[1];
|
|
505
|
-
cur_rz = SCRATCH_RMF[2];
|
|
506
|
-
cur_sx = SCRATCH_RMF[3];
|
|
507
|
-
cur_sy = SCRATCH_RMF[4];
|
|
508
|
-
cur_sz = SCRATCH_RMF[5];
|
|
509
|
-
|
|
510
|
-
prev_px = px;
|
|
511
|
-
prev_py = py;
|
|
512
|
-
prev_pz = pz;
|
|
513
|
-
prev_tx = tx;
|
|
514
|
-
prev_ty = ty;
|
|
515
|
-
prev_tz = tz;
|
|
516
|
-
|
|
517
|
-
const rx = cur_rx, ry = cur_ry, rz = cur_rz;
|
|
518
|
-
const sx = cur_sx, sy = cur_sy, sz = cur_sz;
|
|
519
|
-
|
|
520
|
-
const r0 = skeleton.node_read_radius(v0);
|
|
521
|
-
const r1 = skeleton.node_read_radius(v1);
|
|
522
|
-
const r_node = lerp(r0, r1, local_u);
|
|
523
|
-
|
|
524
|
-
skeleton.node_read_spine_param(SCRATCH_SPINE0, 0, v0);
|
|
525
|
-
skeleton.node_read_spine_param(SCRATCH_SPINE1, 0, v1);
|
|
526
|
-
const t_norm = lerp(SCRATCH_SPINE0[0], SCRATCH_SPINE1[0], local_u);
|
|
527
|
-
const arc_s = lerp(SCRATCH_SPINE0[1], SCRATCH_SPINE1[1], local_u);
|
|
528
|
-
const phase_hash = skeleton.branch_phase_hash[b];
|
|
529
|
-
|
|
530
|
-
// Skinning joint indices & weights at this ring (causal upstream-only + <= 4 joints per frustum slice)
|
|
531
|
-
let ring_j0 = 0, ring_j1 = 0, ring_j2 = 0, ring_j3 = 0;
|
|
532
|
-
let ring_w0 = 1.0, ring_w1 = 0.0, ring_w2 = 0.0, ring_w3 = 0.0;
|
|
533
|
-
if (has_meshed_parent) {
|
|
534
|
-
const parent_attach_v = skeleton.node_parent_vertex(nodes[0]);
|
|
535
|
-
const ancestor_j = parent_attach_v >= 0 ? skeleton.node_read_joint(parent_attach_v, 0) : 0;
|
|
536
|
-
const j_branch_root = skeleton.node_read_joint(nodes[0], 0);
|
|
537
|
-
const j_v0 = skeleton.node_read_joint(v0, 0);
|
|
538
|
-
if (i === 0 || j_v0 === ancestor_j) {
|
|
539
|
-
ring_j0 = base_j0;
|
|
540
|
-
ring_j1 = base_j1;
|
|
541
|
-
ring_j2 = base_j2;
|
|
542
|
-
ring_j3 = base_j3;
|
|
543
|
-
ring_w0 = base_w0;
|
|
544
|
-
ring_w1 = base_w1;
|
|
545
|
-
ring_w2 = base_w2;
|
|
546
|
-
ring_w3 = base_w3;
|
|
547
|
-
} else if (j_v0 === j_branch_root) {
|
|
548
|
-
const w_own = clamp(0.85 * t_norm, 0.0, 0.90);
|
|
549
|
-
const w_base = 1.0 - w_own;
|
|
550
|
-
ring_j0 = j_v0;
|
|
551
|
-
ring_j1 = base_j0;
|
|
552
|
-
ring_j2 = base_j1;
|
|
553
|
-
ring_j3 = 0;
|
|
554
|
-
ring_w0 = w_own;
|
|
555
|
-
ring_w1 = w_base * base_w0;
|
|
556
|
-
ring_w2 = w_base * base_w1;
|
|
557
|
-
ring_w3 = 0.0;
|
|
558
|
-
const rw_sum = ring_w0 + ring_w1 + ring_w2;
|
|
559
|
-
if (rw_sum > 1e-6) {
|
|
560
|
-
ring_w0 /= rw_sum;
|
|
561
|
-
ring_w1 /= rw_sum;
|
|
562
|
-
ring_w2 /= rw_sum;
|
|
563
|
-
}
|
|
564
|
-
} else {
|
|
565
|
-
const w_own = clamp(0.50 + 0.45 * t_norm, 0.0, 0.95);
|
|
566
|
-
ring_j0 = j_v0;
|
|
567
|
-
ring_j1 = j_branch_root;
|
|
568
|
-
ring_j2 = 0;
|
|
569
|
-
ring_j3 = 0;
|
|
570
|
-
ring_w0 = w_own;
|
|
571
|
-
ring_w1 = 1.0 - w_own;
|
|
572
|
-
ring_w2 = 0.0;
|
|
573
|
-
ring_w3 = 0.0;
|
|
574
|
-
}
|
|
575
|
-
} else {
|
|
576
|
-
const j_primary = skeleton.node_read_joint(v0, 0);
|
|
577
|
-
const j_parent = skeleton.node_read_joint(v0, 1);
|
|
578
|
-
const w_primary = (v0 === 0 || j_primary === j_parent) ? 1.0 : clamp(0.25 + 0.75 * t_norm, 0.0, 1.0);
|
|
579
|
-
ring_j0 = j_primary;
|
|
580
|
-
ring_j1 = j_parent;
|
|
581
|
-
ring_w0 = w_primary;
|
|
582
|
-
ring_w1 = 1.0 - w_primary;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// Collar weld factor: i === 0 of every child branch is always seated into its meshed parent frustum
|
|
586
|
-
let weld_factor = 0.0;
|
|
587
|
-
if (has_meshed_parent) {
|
|
588
|
-
if (i === 0) {
|
|
589
|
-
weld_factor = 1.0;
|
|
590
|
-
} else if (i <= max_weld_rings && i < sample_steps && t_norm < 0.65) {
|
|
591
|
-
const u_collar = 1.0 - i / (max_weld_rings + 1);
|
|
592
|
-
weld_factor = u_collar * u_collar;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// Root flare factor on trunk/basal branches
|
|
597
|
-
const has_flare = lvl_cfg.flare_amplitude > 0.0 && t_norm < lvl_cfg.flare_height_ratio;
|
|
598
|
-
let flare_env = 0.0;
|
|
599
|
-
if (has_flare) {
|
|
600
|
-
const u_f = 1.0 - t_norm / Math.max(1e-4, lvl_cfg.flare_height_ratio);
|
|
601
|
-
flare_env = lvl_cfg.flare_amplitude * u_f * u_f;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
const unwelded = 1.0 - weld_factor;
|
|
605
|
-
const v_base = clamp(lerp(v_start, v_end, Math.pow(t_norm, 0.88)), 0.01, 0.98);
|
|
606
|
-
|
|
607
|
-
const
|
|
608
|
-
const
|
|
609
|
-
const
|
|
610
|
-
const
|
|
611
|
-
const
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
let
|
|
639
|
-
let
|
|
640
|
-
let
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
const
|
|
683
|
-
const
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
let
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
let
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
positions[v3
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
normals[v3
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
colors[v3
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
joints[v4
|
|
834
|
-
joints[v4 +
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
weights[v4
|
|
839
|
-
weights[v4 +
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
positions[vi3
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
const
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
let
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
const
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
positions[v3
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
normals[v3
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
colors[v3
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
joints[v4
|
|
995
|
-
joints[v4 +
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
weights[v4
|
|
1000
|
-
weights[v4 +
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
out.
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
*
|
|
1030
|
-
*
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1033
|
-
* @param {
|
|
1034
|
-
* @
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1039
|
-
*
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
)
|
|
1046
|
-
|
|
1047
|
-
|
|
1
|
+
import { PI2 } from "../../../core/math/PI2.js";
|
|
2
|
+
import { clamp } from "../../../core/math/clamp.js";
|
|
3
|
+
import { lerp } from "../../../core/math/lerp.js";
|
|
4
|
+
import { Attribute } from "../../../shade/renderer/geometry/Attribute.js";
|
|
5
|
+
import { Geometry } from "../../../shade/renderer/geometry/Geometry.js";
|
|
6
|
+
import {
|
|
7
|
+
geometry_obtain_attribute,
|
|
8
|
+
geometry_obtain_index
|
|
9
|
+
} from "../../../shade/renderer/geometry/geometry_obtain_attribute.js";
|
|
10
|
+
import { StandardAttributes } from "../../../shade/renderer/geometry/StandardAttributes.js";
|
|
11
|
+
import { BranchLevelConfig } from "../config/BranchLevelConfig.js";
|
|
12
|
+
import { TreeLODConfig } from "../config/TreeLODConfig.js";
|
|
13
|
+
import { TreeSpeciesConfig } from "../config/TreeSpeciesConfig.js";
|
|
14
|
+
import { evaluate_bark_relief } from "../material/make_bark_material.js";
|
|
15
|
+
import { spine_frame_double_reflection } from "../skeleton/spine_frame_double_reflection.js";
|
|
16
|
+
import { branch_collar_project } from "./branch_collar_project.js";
|
|
17
|
+
import { branch_apex_triangulate, branch_ring_triangulate } from "./branch_ring_triangulate.js";
|
|
18
|
+
import { tree_mesh_apply_canopy_ao } from "./tree_mesh_apply_canopy_ao.js";
|
|
19
|
+
|
|
20
|
+
const DEFAULT_LEVEL_CONFIG = new BranchLevelConfig();
|
|
21
|
+
|
|
22
|
+
const SCRATCH_P0 = new Float32Array(3);
|
|
23
|
+
const SCRATCH_P1 = new Float32Array(3);
|
|
24
|
+
const SCRATCH_T0 = new Float32Array(3);
|
|
25
|
+
const SCRATCH_V_TAN = new Float32Array(3);
|
|
26
|
+
const SCRATCH_R0 = new Float32Array(3);
|
|
27
|
+
const SCRATCH_SPINE0 = new Float32Array(3);
|
|
28
|
+
const SCRATCH_SPINE1 = new Float32Array(3);
|
|
29
|
+
const SCRATCH_RMF = new Float64Array(6);
|
|
30
|
+
const SCRATCH_PARENT_P = new Float32Array(3);
|
|
31
|
+
const SCRATCH_PARENT_T = new Float32Array(3);
|
|
32
|
+
const SCRATCH_COLLAR_POS = new Float32Array(3);
|
|
33
|
+
const SCRATCH_COLLAR_NORM = new Float32Array(3);
|
|
34
|
+
const SCRATCH_RELIEF = new Float32Array(4);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Deterministic [0, 1) hash for procedural knot/burl placement on branch `b`.
|
|
38
|
+
* @param {number} seed
|
|
39
|
+
* @param {number} b
|
|
40
|
+
* @param {number} k
|
|
41
|
+
* @param {number} salt
|
|
42
|
+
* @returns {number}
|
|
43
|
+
*/
|
|
44
|
+
function knot_rand01(seed, b, k, salt) {
|
|
45
|
+
let h = (seed ^ Math.imul(b + 1, 0x9E3779B1) ^ Math.imul(k + 1, 0x85EBCA6B) ^ Math.imul(salt, 0xC2B2AE35)) | 0;
|
|
46
|
+
h = Math.imul(h ^ (h >>> 16), 0x7FEB352D);
|
|
47
|
+
h = Math.imul(h ^ (h >>> 15), 0x846CA68B);
|
|
48
|
+
h = (h ^ (h >>> 16)) >>> 0;
|
|
49
|
+
return h / 4294967296.0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Helper to obtain a `Uint16Array` attribute on `Geometry` for `StandardAttributes.SkinningJoints`.
|
|
54
|
+
* @param {Geometry} geometry
|
|
55
|
+
* @param {number} count vertex count
|
|
56
|
+
* @returns {Uint16Array}
|
|
57
|
+
*/
|
|
58
|
+
export function geometry_obtain_joints_attribute(geometry, count) {
|
|
59
|
+
const existing = geometry.getAttribute(StandardAttributes.SkinningJoints);
|
|
60
|
+
if (existing === undefined) {
|
|
61
|
+
const attr = Attribute.from(new Uint16Array(count * 4), 4, StandardAttributes.SkinningJoints);
|
|
62
|
+
geometry.addAttribute(attr);
|
|
63
|
+
return /** @type {Uint16Array} */ (attr.data);
|
|
64
|
+
}
|
|
65
|
+
if (existing.count !== count || !(existing.data instanceof Uint16Array)) {
|
|
66
|
+
existing.data = new Uint16Array(count * 4);
|
|
67
|
+
existing.count = count;
|
|
68
|
+
}
|
|
69
|
+
existing.needsUpdate = true;
|
|
70
|
+
return /** @type {Uint16Array} */ (existing.data);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Sweeps the Cubic Hermite branch spines of a `TreeSkeleton` into a single manifold, seamless-UV
|
|
75
|
+
* bark `Geometry` with root buttress lobes, longitudinal bark flutes, welded lateral branch collars,
|
|
76
|
+
* canopy ambient occlusion, and 4-joint skeletal wind skinning attributes.
|
|
77
|
+
*
|
|
78
|
+
* @param {import("../skeleton/TreeSkeleton.js").TreeSkeleton} skeleton
|
|
79
|
+
* @param {TreeSpeciesConfig} [config]
|
|
80
|
+
* @param {TreeLODConfig} [lod_config]
|
|
81
|
+
* @param {import("../../../shade/renderer/geometry/Geometry.js").Geometry} [out]
|
|
82
|
+
* @returns {Geometry}
|
|
83
|
+
*/
|
|
84
|
+
export function tree_mesh_build_bark(
|
|
85
|
+
skeleton,
|
|
86
|
+
config = new TreeSpeciesConfig(),
|
|
87
|
+
lod_config = new TreeLODConfig(),
|
|
88
|
+
out = new Geometry(),
|
|
89
|
+
only_rings = false
|
|
90
|
+
) {
|
|
91
|
+
const species = config && config.isTreeSpeciesConfig ? config : TreeSpeciesConfig.from(config);
|
|
92
|
+
const lod_cfg = lod_config && lod_config.isTreeLODConfig ? lod_config : TreeLODConfig.from(lod_config);
|
|
93
|
+
|
|
94
|
+
const branch_count = skeleton.branch_count;
|
|
95
|
+
|
|
96
|
+
const radial_scale = clamp(lod_cfg.radial_scale ?? 1.0, 0.2, 2.0);
|
|
97
|
+
const long_scale = clamp(lod_cfg.longitudinal_scale ?? 1.0, 0.2, 4.0);
|
|
98
|
+
const max_level = lod_cfg.max_branch_level ?? 3;
|
|
99
|
+
const min_radius = lod_cfg.min_branch_radius ?? 0.0;
|
|
100
|
+
const texel_density = Math.max(0.1, species.bark_material.texel_density ?? 1.2);
|
|
101
|
+
const twist_uv = species.bark_material.twist_uv ?? 0.0;
|
|
102
|
+
|
|
103
|
+
// Collect retained branches and their resampled longitudinal cross-sectional rings
|
|
104
|
+
const branch_plans = [];
|
|
105
|
+
let total_vertices = 0;
|
|
106
|
+
let total_indices = 0;
|
|
107
|
+
|
|
108
|
+
for (let b = 0; b < branch_count; b++) {
|
|
109
|
+
const level = skeleton.branch_level[b];
|
|
110
|
+
const base_r = skeleton.branch_base_radius[b];
|
|
111
|
+
if (level > max_level) continue;
|
|
112
|
+
if (level > 0 && base_r < min_radius) continue;
|
|
113
|
+
|
|
114
|
+
const lvl_cfg = (species.levels && species.levels.length > 0)
|
|
115
|
+
? (species.levels[Math.min(level, species.levels.length - 1)] || DEFAULT_LEVEL_CONFIG)
|
|
116
|
+
: DEFAULT_LEVEL_CONFIG;
|
|
117
|
+
const m_max = Math.max(3, Math.round(lvl_cfg.radial_segments_max * radial_scale));
|
|
118
|
+
const m_min = Math.max(3, Math.min(m_max, Math.round(lvl_cfg.radial_segments_min * radial_scale)));
|
|
119
|
+
|
|
120
|
+
// Walk the node/segment spine of branch b via `skeleton.graph`
|
|
121
|
+
const nodes = [];
|
|
122
|
+
const edges = [];
|
|
123
|
+
skeleton.collect_branch_chain(b, nodes, edges);
|
|
124
|
+
|
|
125
|
+
const E = edges.length;
|
|
126
|
+
if (E < 1) continue;
|
|
127
|
+
|
|
128
|
+
const sample_steps = Math.max(1, Math.round(E * long_scale));
|
|
129
|
+
const ring_count = sample_steps + 1;
|
|
130
|
+
const ring_m = new Int32Array(ring_count);
|
|
131
|
+
let branch_verts = 0;
|
|
132
|
+
let branch_tris = 0;
|
|
133
|
+
|
|
134
|
+
for (let i = 0; i <= sample_steps; i++) {
|
|
135
|
+
const u_branch = (i / sample_steps) * E;
|
|
136
|
+
const edge_idx = Math.min(E - 1, Math.floor(u_branch));
|
|
137
|
+
const local_u = i === sample_steps ? 1.0 : (u_branch - edge_idx);
|
|
138
|
+
|
|
139
|
+
const r0 = skeleton.node_read_radius(nodes[edge_idx]);
|
|
140
|
+
const r1 = skeleton.node_read_radius(nodes[edge_idx + 1]);
|
|
141
|
+
const r_i = lerp(r0, r1, local_u);
|
|
142
|
+
|
|
143
|
+
const ratio = base_r > 1e-5 ? clamp(r_i / base_r, 0.0, 1.0) : 1.0;
|
|
144
|
+
const m_i = Math.max(3, Math.round(lerp(m_min, m_max, Math.pow(ratio, 0.7))));
|
|
145
|
+
ring_m[i] = m_i;
|
|
146
|
+
branch_verts += (m_i + 1);
|
|
147
|
+
if (i > 0) {
|
|
148
|
+
branch_tris += (ring_m[i - 1] + m_i);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Terminal apex cone cap (m_tip vertices, m_tip triangles)
|
|
153
|
+
const m_tip = ring_m[sample_steps];
|
|
154
|
+
branch_verts += m_tip;
|
|
155
|
+
branch_tris += m_tip;
|
|
156
|
+
|
|
157
|
+
branch_plans.push({
|
|
158
|
+
branch_id: b,
|
|
159
|
+
level,
|
|
160
|
+
base_r,
|
|
161
|
+
lvl_cfg,
|
|
162
|
+
nodes,
|
|
163
|
+
edges,
|
|
164
|
+
sample_steps,
|
|
165
|
+
ring_m
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
total_vertices += branch_verts;
|
|
169
|
+
total_indices += branch_tris * 3;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const is_empty_fallback = branch_plans.length === 0 || total_vertices === 0 || total_indices === 0;
|
|
173
|
+
if (is_empty_fallback) {
|
|
174
|
+
if (only_rings) {
|
|
175
|
+
return /** @type {any} */ (new Map());
|
|
176
|
+
}
|
|
177
|
+
total_vertices = 4;
|
|
178
|
+
total_indices = 6;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const positions = geometry_obtain_attribute(out, StandardAttributes.Position, 3, total_vertices);
|
|
182
|
+
const normals = geometry_obtain_attribute(out, StandardAttributes.Normal, 3, total_vertices);
|
|
183
|
+
const uv0 = geometry_obtain_attribute(out, StandardAttributes.TextureCoordinates0, 2, total_vertices);
|
|
184
|
+
const uv1 = geometry_obtain_attribute(out, StandardAttributes.TextureCoordinates1, 2, total_vertices);
|
|
185
|
+
const colors = geometry_obtain_attribute(out, StandardAttributes.Color, 3, total_vertices);
|
|
186
|
+
const joints = geometry_obtain_joints_attribute(out, total_vertices);
|
|
187
|
+
const weights = geometry_obtain_attribute(out, StandardAttributes.SkinningWeights, 4, total_vertices);
|
|
188
|
+
const indices = geometry_obtain_index(out, total_indices);
|
|
189
|
+
|
|
190
|
+
if (is_empty_fallback) {
|
|
191
|
+
// Populate valid non-degenerate fallback quad so ensureTangents() and MeshoptClusterizer always succeed
|
|
192
|
+
const quad_pos = [
|
|
193
|
+
-0.02, 0.0, -0.02,
|
|
194
|
+
0.02, 0.0, -0.02,
|
|
195
|
+
0.02, 0.04, 0.02,
|
|
196
|
+
-0.02, 0.04, 0.02
|
|
197
|
+
];
|
|
198
|
+
const quad_uv = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
|
|
199
|
+
for (let k = 0; k < 4; k++) {
|
|
200
|
+
positions[k * 3] = quad_pos[k * 3];
|
|
201
|
+
positions[k * 3 + 1] = quad_pos[k * 3 + 1];
|
|
202
|
+
positions[k * 3 + 2] = quad_pos[k * 3 + 2];
|
|
203
|
+
normals[k * 3] = 0.0;
|
|
204
|
+
normals[k * 3 + 1] = 1.0;
|
|
205
|
+
normals[k * 3 + 2] = 0.0;
|
|
206
|
+
uv0[k * 2] = quad_uv[k * 2];
|
|
207
|
+
uv0[k * 2 + 1] = quad_uv[k * 2 + 1];
|
|
208
|
+
uv1[k * 2] = 0.0;
|
|
209
|
+
uv1[k * 2 + 1] = 0.0;
|
|
210
|
+
colors[k * 3] = 1.0;
|
|
211
|
+
colors[k * 3 + 1] = 1.0;
|
|
212
|
+
colors[k * 3 + 2] = 1.0;
|
|
213
|
+
joints[k * 4] = 0;
|
|
214
|
+
joints[k * 4 + 1] = 0;
|
|
215
|
+
joints[k * 4 + 2] = 0;
|
|
216
|
+
joints[k * 4 + 3] = 0;
|
|
217
|
+
weights[k * 4] = 1.0;
|
|
218
|
+
weights[k * 4 + 1] = 0.0;
|
|
219
|
+
weights[k * 4 + 2] = 0.0;
|
|
220
|
+
weights[k * 4 + 3] = 0.0;
|
|
221
|
+
}
|
|
222
|
+
indices[0] = 0;
|
|
223
|
+
indices[1] = 1;
|
|
224
|
+
indices[2] = 2;
|
|
225
|
+
indices[3] = 0;
|
|
226
|
+
indices[4] = 2;
|
|
227
|
+
indices[5] = 3;
|
|
228
|
+
|
|
229
|
+
out.name = `${species.name || "tree"}_bark_lod${lod_cfg.level}`;
|
|
230
|
+
out.ensureBounds();
|
|
231
|
+
out.ensureTangents();
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let v_cursor = 0;
|
|
236
|
+
let i_cursor = 0;
|
|
237
|
+
const meshed_branch_rings = new Map();
|
|
238
|
+
|
|
239
|
+
for (let p_idx = 0; p_idx < branch_plans.length; p_idx++) {
|
|
240
|
+
const plan = branch_plans[p_idx];
|
|
241
|
+
const b = plan.branch_id;
|
|
242
|
+
const level = plan.level;
|
|
243
|
+
const base_r = plan.base_r;
|
|
244
|
+
const lvl_cfg = plan.lvl_cfg;
|
|
245
|
+
const nodes = plan.nodes;
|
|
246
|
+
const edges = plan.edges;
|
|
247
|
+
const E = edges.length;
|
|
248
|
+
const sample_steps = plan.sample_steps;
|
|
249
|
+
const ring_m = plan.ring_m;
|
|
250
|
+
|
|
251
|
+
// Quantized integer circumference wrap count (1 or 2) for seamless bark tiling proportional to stem radius
|
|
252
|
+
const k_wrap = Math.max(1, Math.min(2, Math.round(PI2 * base_r * texel_density)));
|
|
253
|
+
|
|
254
|
+
// Pre-collect child branch attachment collars on this branch `b` (for parent-stem collar bosses & UV grain flow)
|
|
255
|
+
const child_collars = [];
|
|
256
|
+
const b_len = Math.max(0.2, skeleton.branch_length[b]);
|
|
257
|
+
for (let cb = b + 1; cb < branch_count; cb++) {
|
|
258
|
+
if (skeleton.branch_parent[cb] === b) {
|
|
259
|
+
const c_root = skeleton.branch_root_vertex[cb];
|
|
260
|
+
skeleton.node_read_tangent(SCRATCH_V_TAN, 0, c_root);
|
|
261
|
+
const c_r = skeleton.branch_base_radius[cb];
|
|
262
|
+
const r_ratio = clamp(c_r / Math.max(1e-4, base_r), 0.15, 0.82);
|
|
263
|
+
child_collars.push({
|
|
264
|
+
t_c: skeleton.branch_parent_attach_t[cb],
|
|
265
|
+
tx_c: SCRATCH_V_TAN[0],
|
|
266
|
+
ty_c: SCRATCH_V_TAN[1],
|
|
267
|
+
tz_c: SCRATCH_V_TAN[2],
|
|
268
|
+
r_ratio,
|
|
269
|
+
sigma_t: clamp(1.55 * c_r / b_len, 0.035, 0.13),
|
|
270
|
+
sigma_theta: clamp(0.60 * r_ratio + 0.22, 0.28, 0.72)
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Pre-collect deterministic 3D knots / callus eyes / burls along this branch `b`
|
|
276
|
+
const branch_knots = [];
|
|
277
|
+
const knot_count = lvl_cfg.knot_count | 0;
|
|
278
|
+
const knot_str = lvl_cfg.knot_strength ?? 0.0;
|
|
279
|
+
if (knot_count > 0 && knot_str > 0.0) {
|
|
280
|
+
const seed_i = species.seed | 0;
|
|
281
|
+
for (let k = 0; k < knot_count; k++) {
|
|
282
|
+
const t_k = clamp(0.09 + (k + 0.2 + 0.6 * knot_rand01(seed_i, b, k, 1)) * (0.78 / knot_count), 0.08, 0.88);
|
|
283
|
+
const theta_k = (k * 2.399963229728653 + knot_rand01(seed_i, b, k, 2) * 1.4) % PI2;
|
|
284
|
+
const sigma_t = clamp(0.045 + 0.035 * knot_rand01(seed_i, b, k, 3), 0.04, 0.085);
|
|
285
|
+
const sigma_theta = clamp(0.32 + 0.24 * knot_rand01(seed_i, b, k, 4), 0.30, 0.56);
|
|
286
|
+
const amp_k = knot_str * (0.80 + 0.55 * knot_rand01(seed_i, b, k, 5));
|
|
287
|
+
const is_burl = knot_rand01(seed_i, b, k, 6) > 0.45;
|
|
288
|
+
branch_knots.push({ t_k, theta_k, sigma_t, sigma_theta, amp_k, is_burl });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Parent branch attachment context from actual meshed parent frustum
|
|
293
|
+
const parent_b = skeleton.branch_parent[b];
|
|
294
|
+
const parent_attach_t = skeleton.branch_parent_attach_t[b];
|
|
295
|
+
const p_rings = parent_b >= 0 ? meshed_branch_rings.get(parent_b) : undefined;
|
|
296
|
+
const has_meshed_parent = b > 0 && p_rings !== undefined && p_rings.length >= 2;
|
|
297
|
+
|
|
298
|
+
let parent_px = 0.0, parent_py = 0.0, parent_pz = 0.0;
|
|
299
|
+
let parent_tx = 0.0, parent_ty = 1.0, parent_tz = 0.0;
|
|
300
|
+
let parent_r = base_r * 1.4;
|
|
301
|
+
let parent_v_attach = 0.02;
|
|
302
|
+
let parent_taper_slope = 0.0;
|
|
303
|
+
let parent_m = 8;
|
|
304
|
+
let parent_flute_amp = 0.0;
|
|
305
|
+
let parent_z_min = -Infinity;
|
|
306
|
+
let parent_z_max = Infinity;
|
|
307
|
+
let shift_x = 0.0, shift_y = 0.0, shift_z = 0.0;
|
|
308
|
+
let base_j0 = 0, base_j1 = 0, base_j2 = 0, base_j3 = 0;
|
|
309
|
+
let base_w0 = 1.0, base_w1 = 0.0, base_w2 = 0.0, base_w3 = 0.0;
|
|
310
|
+
let emerge_tx = 0.0, emerge_ty = 1.0, emerge_tz = 0.0;
|
|
311
|
+
|
|
312
|
+
if (has_meshed_parent) {
|
|
313
|
+
let k_attach = p_rings.length - 2;
|
|
314
|
+
for (let k = 0; k < p_rings.length - 1; k++) {
|
|
315
|
+
if (parent_attach_t >= p_rings[k].t_norm - 1e-5 && parent_attach_t <= p_rings[k + 1].t_norm + 1e-5) {
|
|
316
|
+
k_attach = k;
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const pr_a = p_rings[k_attach];
|
|
321
|
+
const pr_b = p_rings[k_attach + 1];
|
|
322
|
+
const xi = clamp((parent_attach_t - pr_a.t_norm) / Math.max(1e-5, pr_b.t_norm - pr_a.t_norm), 0.0, 1.0);
|
|
323
|
+
|
|
324
|
+
parent_px = lerp(pr_a.cx, pr_b.cx, xi);
|
|
325
|
+
parent_py = lerp(pr_a.cy, pr_b.cy, xi);
|
|
326
|
+
parent_pz = lerp(pr_a.cz, pr_b.cz, xi);
|
|
327
|
+
|
|
328
|
+
const ax = pr_b.cx - pr_a.cx;
|
|
329
|
+
const ay = pr_b.cy - pr_a.cy;
|
|
330
|
+
const az = pr_b.cz - pr_a.cz;
|
|
331
|
+
const parent_seg_len = Math.sqrt(ax * ax + ay * ay + az * az) || 1e-5;
|
|
332
|
+
parent_tx = ax / parent_seg_len;
|
|
333
|
+
parent_ty = ay / parent_seg_len;
|
|
334
|
+
parent_tz = az / parent_seg_len;
|
|
335
|
+
|
|
336
|
+
parent_r = lerp(pr_a.r, pr_b.r, xi);
|
|
337
|
+
parent_v_attach = lerp(pr_a.v_uv ?? 0.25, pr_b.v_uv ?? 0.25, xi);
|
|
338
|
+
parent_taper_slope = (pr_b.r - pr_a.r) / parent_seg_len;
|
|
339
|
+
parent_m = Math.min(pr_a.m, pr_b.m);
|
|
340
|
+
parent_flute_amp = pr_a.flute_amp;
|
|
341
|
+
parent_z_min = -(xi + 0.25) * parent_seg_len;
|
|
342
|
+
parent_z_max = (1.0 - xi + 0.25) * parent_seg_len;
|
|
343
|
+
|
|
344
|
+
// Translate child spine so its root lies on the exact parent mesh frustum chord
|
|
345
|
+
skeleton.node_read_position(SCRATCH_P0, 0, nodes[0]);
|
|
346
|
+
shift_x = parent_px - SCRATCH_P0[0];
|
|
347
|
+
shift_y = parent_py - SCRATCH_P0[1];
|
|
348
|
+
shift_z = parent_pz - SCRATCH_P0[2];
|
|
349
|
+
|
|
350
|
+
// Compute emergence chord direction from parent_P to child Ring i = 1 spine center
|
|
351
|
+
const u_step1 = (1 / sample_steps) * E;
|
|
352
|
+
const edge_idx1 = Math.min(E - 1, Math.floor(u_step1));
|
|
353
|
+
const local_u1 = 1 === sample_steps ? 1.0 : (u_step1 - edge_idx1);
|
|
354
|
+
skeleton.evaluate_segment_position(SCRATCH_P1, 0, edges[edge_idx1], local_u1);
|
|
355
|
+
const p1x = SCRATCH_P1[0] + shift_x;
|
|
356
|
+
const p1y = SCRATCH_P1[1] + shift_y;
|
|
357
|
+
const p1z = SCRATCH_P1[2] + shift_z;
|
|
358
|
+
const emx = p1x - parent_px;
|
|
359
|
+
const emy = p1y - parent_py;
|
|
360
|
+
const emz = p1z - parent_pz;
|
|
361
|
+
const em_len = Math.sqrt(emx * emx + emy * emy + emz * emz);
|
|
362
|
+
if (em_len > 1e-6) {
|
|
363
|
+
emerge_tx = emx / em_len;
|
|
364
|
+
emerge_ty = emy / em_len;
|
|
365
|
+
emerge_tz = emz / em_len;
|
|
366
|
+
} else {
|
|
367
|
+
skeleton.node_read_tangent(SCRATCH_V_TAN, 0, nodes[0]);
|
|
368
|
+
emerge_tx = SCRATCH_V_TAN[0];
|
|
369
|
+
emerge_ty = SCRATCH_V_TAN[1];
|
|
370
|
+
emerge_tz = SCRATCH_V_TAN[2];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Merge parent frustum skinning weights at xi so child Ring i = 0 deforms identically to parent
|
|
374
|
+
const merged_j = [];
|
|
375
|
+
const merged_w = [];
|
|
376
|
+
const accumulate_joint = (j_id, w_val) => {
|
|
377
|
+
if (w_val <= 1e-7) return;
|
|
378
|
+
for (let s = 0; s < merged_j.length; s++) {
|
|
379
|
+
if (merged_j[s] === j_id) {
|
|
380
|
+
merged_w[s] += w_val;
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
merged_j.push(j_id);
|
|
385
|
+
merged_w.push(w_val);
|
|
386
|
+
};
|
|
387
|
+
const w_a = 1.0 - xi;
|
|
388
|
+
accumulate_joint(pr_a.j0, pr_a.w0 * w_a);
|
|
389
|
+
accumulate_joint(pr_a.j1, pr_a.w1 * w_a);
|
|
390
|
+
accumulate_joint(pr_a.j2, pr_a.w2 * w_a);
|
|
391
|
+
accumulate_joint(pr_a.j3, pr_a.w3 * w_a);
|
|
392
|
+
accumulate_joint(pr_b.j0, pr_b.w0 * xi);
|
|
393
|
+
accumulate_joint(pr_b.j1, pr_b.w1 * xi);
|
|
394
|
+
accumulate_joint(pr_b.j2, pr_b.w2 * xi);
|
|
395
|
+
accumulate_joint(pr_b.j3, pr_b.w3 * xi);
|
|
396
|
+
|
|
397
|
+
// Sort descending by weight and normalize top 4
|
|
398
|
+
for (let a = 0; a < merged_j.length; a++) {
|
|
399
|
+
for (let c = a + 1; c < merged_j.length; c++) {
|
|
400
|
+
if (merged_w[c] > merged_w[a]) {
|
|
401
|
+
const tj = merged_j[a]; merged_j[a] = merged_j[c]; merged_j[c] = tj;
|
|
402
|
+
const tw = merged_w[a]; merged_w[a] = merged_w[c]; merged_w[c] = tw;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
base_j0 = merged_j[0] ?? 0;
|
|
407
|
+
base_j1 = merged_j[1] ?? 0;
|
|
408
|
+
base_j2 = merged_j[2] ?? 0;
|
|
409
|
+
base_j3 = merged_j[3] ?? 0;
|
|
410
|
+
base_w0 = merged_w[0] ?? 1.0;
|
|
411
|
+
base_w1 = merged_w[1] ?? 0.0;
|
|
412
|
+
base_w2 = merged_w[2] ?? 0.0;
|
|
413
|
+
base_w3 = merged_w[3] ?? 0.0;
|
|
414
|
+
const w_sum = base_w0 + base_w1 + base_w2 + base_w3;
|
|
415
|
+
if (w_sum > 1e-6) {
|
|
416
|
+
base_w0 /= w_sum;
|
|
417
|
+
base_w1 /= w_sum;
|
|
418
|
+
base_w2 /= w_sum;
|
|
419
|
+
base_w3 /= w_sum;
|
|
420
|
+
} else {
|
|
421
|
+
base_w0 = 1.0;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
let prev_ring_start = -1;
|
|
426
|
+
let prev_m = 0;
|
|
427
|
+
const current_branch_rings = [];
|
|
428
|
+
|
|
429
|
+
// Track continuous RMF along the resampled spine rings
|
|
430
|
+
let prev_px = 0.0, prev_py = 0.0, prev_pz = 0.0;
|
|
431
|
+
let prev_tx = 0.0, prev_ty = 1.0, prev_tz = 0.0;
|
|
432
|
+
let cur_rx = 1.0, cur_ry = 0.0, cur_rz = 0.0;
|
|
433
|
+
let cur_sx = 0.0, cur_sy = 0.0, cur_sz = 1.0;
|
|
434
|
+
|
|
435
|
+
// Keep terminal ring state for apex cap
|
|
436
|
+
let tip_px = 0.0, tip_py = 0.0, tip_pz = 0.0;
|
|
437
|
+
let tip_tx = 0.0, tip_ty = 1.0, tip_tz = 0.0;
|
|
438
|
+
let tip_rx = 1.0, tip_ry = 0.0, tip_rz = 0.0;
|
|
439
|
+
let tip_sx = 0.0, tip_sy = 0.0, tip_sz = 1.0;
|
|
440
|
+
let tip_r = 0.002;
|
|
441
|
+
let tip_arc_s = 0.0;
|
|
442
|
+
let tip_v_uv = 0.90;
|
|
443
|
+
let tip_wind_sway = 0.0;
|
|
444
|
+
let tip_wind_phase = 0.0;
|
|
445
|
+
let tip_j0 = 0, tip_j1 = 0, tip_j2 = 0, tip_j3 = 0;
|
|
446
|
+
let tip_w0 = 1.0, tip_w1 = 0.0, tip_w2 = 0.0, tip_w3 = 0.0;
|
|
447
|
+
|
|
448
|
+
// Continuous ontogenetic V range [v_start, v_end] in [0, 1] from trunk base (old bark) to twig tips (young shoot bark)
|
|
449
|
+
const num_levels = (species.levels && species.levels.length > 0) ? species.levels.length : 1;
|
|
450
|
+
let v_start = 0.02;
|
|
451
|
+
let v_end = num_levels > 2 ? 0.62 : (num_levels === 2 ? 0.80 : 0.90);
|
|
452
|
+
if (has_meshed_parent) {
|
|
453
|
+
v_start = parent_v_attach;
|
|
454
|
+
if (level === 1 && num_levels > 2) {
|
|
455
|
+
v_end = clamp(lerp(v_start, 0.80, 0.65), v_start + 0.10, 0.84);
|
|
456
|
+
} else {
|
|
457
|
+
v_end = clamp(lerp(v_start, 0.95, 0.82), Math.max(0.82, v_start + 0.08), 0.97);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const max_weld_rings = Math.min(lvl_cfg.collar_weld_rings, Math.max(0, sample_steps - 1));
|
|
462
|
+
|
|
463
|
+
for (let i = 0; i <= sample_steps; i++) {
|
|
464
|
+
const m_i = ring_m[i];
|
|
465
|
+
const ring_start = v_cursor;
|
|
466
|
+
|
|
467
|
+
const u_branch = (i / sample_steps) * E;
|
|
468
|
+
const edge_idx = Math.min(E - 1, Math.floor(u_branch));
|
|
469
|
+
const local_u = i === sample_steps ? 1.0 : (u_branch - edge_idx);
|
|
470
|
+
const edge_id = edges[edge_idx];
|
|
471
|
+
const v0 = nodes[edge_idx];
|
|
472
|
+
const v1 = nodes[edge_idx + 1];
|
|
473
|
+
|
|
474
|
+
skeleton.evaluate_segment_position(SCRATCH_P0, 0, edge_id, local_u);
|
|
475
|
+
skeleton.evaluate_segment_tangent(SCRATCH_T0, 0, edge_id, local_u);
|
|
476
|
+
|
|
477
|
+
const px = SCRATCH_P0[0] + shift_x;
|
|
478
|
+
const py = SCRATCH_P0[1] + shift_y;
|
|
479
|
+
const pz = SCRATCH_P0[2] + shift_z;
|
|
480
|
+
const tx = SCRATCH_T0[0], ty = SCRATCH_T0[1], tz = SCRATCH_T0[2];
|
|
481
|
+
|
|
482
|
+
if (i === 0) {
|
|
483
|
+
skeleton.node_read_tangent(SCRATCH_V_TAN, 0, v0);
|
|
484
|
+
skeleton.node_read_frame_r(SCRATCH_R0, 0, v0);
|
|
485
|
+
spine_frame_double_reflection(
|
|
486
|
+
SCRATCH_RMF, 0,
|
|
487
|
+
px, py, pz,
|
|
488
|
+
SCRATCH_V_TAN[0], SCRATCH_V_TAN[1], SCRATCH_V_TAN[2],
|
|
489
|
+
SCRATCH_R0[0], SCRATCH_R0[1], SCRATCH_R0[2],
|
|
490
|
+
px, py, pz,
|
|
491
|
+
tx, ty, tz
|
|
492
|
+
);
|
|
493
|
+
} else {
|
|
494
|
+
spine_frame_double_reflection(
|
|
495
|
+
SCRATCH_RMF, 0,
|
|
496
|
+
prev_px, prev_py, prev_pz,
|
|
497
|
+
prev_tx, prev_ty, prev_tz,
|
|
498
|
+
cur_rx, cur_ry, cur_rz,
|
|
499
|
+
px, py, pz,
|
|
500
|
+
tx, ty, tz
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
cur_rx = SCRATCH_RMF[0];
|
|
504
|
+
cur_ry = SCRATCH_RMF[1];
|
|
505
|
+
cur_rz = SCRATCH_RMF[2];
|
|
506
|
+
cur_sx = SCRATCH_RMF[3];
|
|
507
|
+
cur_sy = SCRATCH_RMF[4];
|
|
508
|
+
cur_sz = SCRATCH_RMF[5];
|
|
509
|
+
|
|
510
|
+
prev_px = px;
|
|
511
|
+
prev_py = py;
|
|
512
|
+
prev_pz = pz;
|
|
513
|
+
prev_tx = tx;
|
|
514
|
+
prev_ty = ty;
|
|
515
|
+
prev_tz = tz;
|
|
516
|
+
|
|
517
|
+
const rx = cur_rx, ry = cur_ry, rz = cur_rz;
|
|
518
|
+
const sx = cur_sx, sy = cur_sy, sz = cur_sz;
|
|
519
|
+
|
|
520
|
+
const r0 = skeleton.node_read_radius(v0);
|
|
521
|
+
const r1 = skeleton.node_read_radius(v1);
|
|
522
|
+
const r_node = lerp(r0, r1, local_u);
|
|
523
|
+
|
|
524
|
+
skeleton.node_read_spine_param(SCRATCH_SPINE0, 0, v0);
|
|
525
|
+
skeleton.node_read_spine_param(SCRATCH_SPINE1, 0, v1);
|
|
526
|
+
const t_norm = lerp(SCRATCH_SPINE0[0], SCRATCH_SPINE1[0], local_u);
|
|
527
|
+
const arc_s = lerp(SCRATCH_SPINE0[1], SCRATCH_SPINE1[1], local_u);
|
|
528
|
+
const phase_hash = skeleton.branch_phase_hash[b];
|
|
529
|
+
|
|
530
|
+
// Skinning joint indices & weights at this ring (causal upstream-only + <= 4 joints per frustum slice)
|
|
531
|
+
let ring_j0 = 0, ring_j1 = 0, ring_j2 = 0, ring_j3 = 0;
|
|
532
|
+
let ring_w0 = 1.0, ring_w1 = 0.0, ring_w2 = 0.0, ring_w3 = 0.0;
|
|
533
|
+
if (has_meshed_parent) {
|
|
534
|
+
const parent_attach_v = skeleton.node_parent_vertex(nodes[0]);
|
|
535
|
+
const ancestor_j = parent_attach_v >= 0 ? skeleton.node_read_joint(parent_attach_v, 0) : 0;
|
|
536
|
+
const j_branch_root = skeleton.node_read_joint(nodes[0], 0);
|
|
537
|
+
const j_v0 = skeleton.node_read_joint(v0, 0);
|
|
538
|
+
if (i === 0 || j_v0 === ancestor_j) {
|
|
539
|
+
ring_j0 = base_j0;
|
|
540
|
+
ring_j1 = base_j1;
|
|
541
|
+
ring_j2 = base_j2;
|
|
542
|
+
ring_j3 = base_j3;
|
|
543
|
+
ring_w0 = base_w0;
|
|
544
|
+
ring_w1 = base_w1;
|
|
545
|
+
ring_w2 = base_w2;
|
|
546
|
+
ring_w3 = base_w3;
|
|
547
|
+
} else if (j_v0 === j_branch_root) {
|
|
548
|
+
const w_own = clamp(0.85 * t_norm, 0.0, 0.90);
|
|
549
|
+
const w_base = 1.0 - w_own;
|
|
550
|
+
ring_j0 = j_v0;
|
|
551
|
+
ring_j1 = base_j0;
|
|
552
|
+
ring_j2 = base_j1;
|
|
553
|
+
ring_j3 = 0;
|
|
554
|
+
ring_w0 = w_own;
|
|
555
|
+
ring_w1 = w_base * base_w0;
|
|
556
|
+
ring_w2 = w_base * base_w1;
|
|
557
|
+
ring_w3 = 0.0;
|
|
558
|
+
const rw_sum = ring_w0 + ring_w1 + ring_w2;
|
|
559
|
+
if (rw_sum > 1e-6) {
|
|
560
|
+
ring_w0 /= rw_sum;
|
|
561
|
+
ring_w1 /= rw_sum;
|
|
562
|
+
ring_w2 /= rw_sum;
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
const w_own = clamp(0.50 + 0.45 * t_norm, 0.0, 0.95);
|
|
566
|
+
ring_j0 = j_v0;
|
|
567
|
+
ring_j1 = j_branch_root;
|
|
568
|
+
ring_j2 = 0;
|
|
569
|
+
ring_j3 = 0;
|
|
570
|
+
ring_w0 = w_own;
|
|
571
|
+
ring_w1 = 1.0 - w_own;
|
|
572
|
+
ring_w2 = 0.0;
|
|
573
|
+
ring_w3 = 0.0;
|
|
574
|
+
}
|
|
575
|
+
} else {
|
|
576
|
+
const j_primary = skeleton.node_read_joint(v0, 0);
|
|
577
|
+
const j_parent = skeleton.node_read_joint(v0, 1);
|
|
578
|
+
const w_primary = (v0 === 0 || j_primary === j_parent) ? 1.0 : clamp(0.25 + 0.75 * t_norm, 0.0, 1.0);
|
|
579
|
+
ring_j0 = j_primary;
|
|
580
|
+
ring_j1 = j_parent;
|
|
581
|
+
ring_w0 = w_primary;
|
|
582
|
+
ring_w1 = 1.0 - w_primary;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Collar weld factor: i === 0 of every child branch is always seated into its meshed parent frustum
|
|
586
|
+
let weld_factor = 0.0;
|
|
587
|
+
if (has_meshed_parent) {
|
|
588
|
+
if (i === 0) {
|
|
589
|
+
weld_factor = 1.0;
|
|
590
|
+
} else if (i <= max_weld_rings && i < sample_steps && t_norm < 0.65) {
|
|
591
|
+
const u_collar = 1.0 - i / (max_weld_rings + 1);
|
|
592
|
+
weld_factor = u_collar * u_collar;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Root flare factor on trunk/basal branches
|
|
597
|
+
const has_flare = lvl_cfg.flare_amplitude > 0.0 && t_norm < lvl_cfg.flare_height_ratio;
|
|
598
|
+
let flare_env = 0.0;
|
|
599
|
+
if (has_flare) {
|
|
600
|
+
const u_f = 1.0 - t_norm / Math.max(1e-4, lvl_cfg.flare_height_ratio);
|
|
601
|
+
flare_env = lvl_cfg.flare_amplitude * u_f * u_f;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const unwelded = 1.0 - weld_factor;
|
|
605
|
+
const v_base = clamp(lerp(v_start, v_end, Math.pow(t_norm, 0.88)), 0.01, 0.98);
|
|
606
|
+
// Smooth terminal shoot rounding over t_norm in (0.88, 1.0] so young apical tips terminate as circular shoots without pleated flutes
|
|
607
|
+
const tip_roundness = i === sample_steps ? 0.0 : clamp((1.0 - t_norm) / 0.12, 0.0, 1.0);
|
|
608
|
+
const ontogenetic_maturity = (level >= 2 ? clamp(1.0 - 0.72 * ((v_base - 0.55) / 0.45), 0.22, 1.0) : 1.0) * tip_roundness;
|
|
609
|
+
const flute_env = lvl_cfg.flute_amplitude * unwelded * ontogenetic_maturity;
|
|
610
|
+
const has_flute = flute_env > 1e-5;
|
|
611
|
+
const horiz_sin = Math.sqrt(Math.max(0.0, 1.0 - ty * ty));
|
|
612
|
+
const oval_ecc = (lvl_cfg.oval_eccentricity ?? 0.0) * unwelded * horiz_sin * tip_roundness;
|
|
613
|
+
const bark_disp = (lvl_cfg.bark_displacement ?? 0.0) * unwelded * ontogenetic_maturity;
|
|
614
|
+
const parent_collar_amp = (lvl_cfg.parent_collar_bulge ?? 0.0) * unwelded * tip_roundness;
|
|
615
|
+
|
|
616
|
+
const wind_sway = clamp((level + t_norm) * 0.25, 0.0, 1.0);
|
|
617
|
+
const wind_phase = clamp(phase_hash, 0.0, 1.0);
|
|
618
|
+
const collar_ao = 1.0 - 0.16 * weld_factor;
|
|
619
|
+
|
|
620
|
+
for (let j = 0; j <= m_i; j++) {
|
|
621
|
+
// Ensure exact periodic closure between j=0 and j=m_i
|
|
622
|
+
const j_wrapped = j === m_i ? 0 : j;
|
|
623
|
+
const theta = (j_wrapped / m_i) * PI2;
|
|
624
|
+
const cos_t = Math.cos(theta);
|
|
625
|
+
const sin_t = Math.sin(theta);
|
|
626
|
+
|
|
627
|
+
// Outward radial unit vector in cross-section plane
|
|
628
|
+
const n_rad_x = cos_t * rx + sin_t * sx;
|
|
629
|
+
const n_rad_y = cos_t * ry + cos_t * 0.0 + sin_t * sy;
|
|
630
|
+
const n_rad_z = cos_t * rz + sin_t * sz;
|
|
631
|
+
|
|
632
|
+
// Circumferential tangent unit vector (-sin_t R + cos_t S)
|
|
633
|
+
const tan_theta_x = -sin_t * rx + cos_t * sx;
|
|
634
|
+
const tan_theta_y = -sin_t * ry + cos_t * sy;
|
|
635
|
+
const tan_theta_z = -sin_t * rz + cos_t * sz;
|
|
636
|
+
|
|
637
|
+
// Evaluate lobe/flute/oval/collar/knot/bark radius multiplier & UV grain streamline warp
|
|
638
|
+
let r_mult = 1.0;
|
|
639
|
+
let d_rmult_dtheta = 0.0;
|
|
640
|
+
let u_warp = 0.0;
|
|
641
|
+
let vert_ao = collar_ao;
|
|
642
|
+
let d_tan = 0.0;
|
|
643
|
+
|
|
644
|
+
// 1. Phase-locked root buttress lobes flowing continuously into twisting trunk flutes
|
|
645
|
+
const theta_flute = theta + 0.32 * arc_s + phase_hash * PI2;
|
|
646
|
+
if (has_flare) {
|
|
647
|
+
const lobe_phase = lvl_cfg.flare_lobes * theta_flute;
|
|
648
|
+
const cos_lobe = Math.cos(lobe_phase);
|
|
649
|
+
if (cos_lobe > 0.0) {
|
|
650
|
+
const p = lvl_cfg.flare_sharpness;
|
|
651
|
+
r_mult += flare_env * Math.pow(cos_lobe, p);
|
|
652
|
+
d_rmult_dtheta -= flare_env * p * lvl_cfg.flare_lobes * Math.pow(cos_lobe, Math.max(0.0, p - 1.0)) * Math.sin(lobe_phase);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (has_flute) {
|
|
657
|
+
const f_freq = Math.min(4, Math.max(2, lvl_cfg.flute_frequency | 0));
|
|
658
|
+
const flute_phase = f_freq * theta_flute;
|
|
659
|
+
const f_val = Math.sin(flute_phase);
|
|
660
|
+
const f_der = f_freq * Math.cos(flute_phase);
|
|
661
|
+
r_mult += flute_env * f_val;
|
|
662
|
+
d_rmult_dtheta += flute_env * f_der * 0.45;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// 2. Biomechanical reaction-wood ovalization on inclined/horizontal boughs
|
|
666
|
+
if (oval_ecc > 1e-4) {
|
|
667
|
+
const oval_term = oval_ecc * (1.35 * (2.0 * n_rad_y * n_rad_y - 1.0) - 0.22 * n_rad_y);
|
|
668
|
+
r_mult += oval_term;
|
|
669
|
+
d_rmult_dtheta += oval_ecc * (5.4 * n_rad_y - 0.22) * tan_theta_y;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// 3. Parent-stem branch collar swelling bosses & UV grain streamline deflection around child branches
|
|
673
|
+
if (parent_collar_amp > 1e-4 && child_collars.length > 0) {
|
|
674
|
+
for (let c_idx = 0; c_idx < child_collars.length; c_idx++) {
|
|
675
|
+
const cc = child_collars[c_idx];
|
|
676
|
+
const dt = t_norm - cc.t_c;
|
|
677
|
+
if (Math.abs(dt) < 3.2 * cc.sigma_t) {
|
|
678
|
+
const theta_c = Math.atan2(
|
|
679
|
+
cc.tx_c * sx + cc.ty_c * sy + cc.tz_c * sz,
|
|
680
|
+
cc.tx_c * rx + cc.ty_c * ry + cc.tz_c * rz
|
|
681
|
+
);
|
|
682
|
+
const dtheta = Math.atan2(Math.sin(theta - theta_c), Math.cos(theta - theta_c));
|
|
683
|
+
const sig_t = dt < 0.0 ? cc.sigma_t * 1.25 : cc.sigma_t * 0.90;
|
|
684
|
+
const u_t = dt / sig_t;
|
|
685
|
+
const u_th = dtheta / cc.sigma_theta;
|
|
686
|
+
const dist2 = u_t * u_t + u_th * u_th;
|
|
687
|
+
if (dist2 < 9.0) {
|
|
688
|
+
const gauss = Math.exp(-0.5 * dist2);
|
|
689
|
+
const b_amp = parent_collar_amp * (0.55 + 0.75 * cc.r_ratio);
|
|
690
|
+
r_mult += b_amp * gauss;
|
|
691
|
+
d_rmult_dtheta += -b_amp * gauss * (u_th / cc.sigma_theta);
|
|
692
|
+
const r_uv2 = dist2 + 0.85;
|
|
693
|
+
u_warp += 0.008 * unwelded * (u_th / r_uv2) * Math.exp(-0.35 * dist2);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// 4. Localized 3D knots, volcanic callus rims, and burls + UV grain streamline deflection
|
|
700
|
+
if (unwelded * tip_roundness > 0.05 && branch_knots.length > 0) {
|
|
701
|
+
for (let k_idx = 0; k_idx < branch_knots.length; k_idx++) {
|
|
702
|
+
const kn = branch_knots[k_idx];
|
|
703
|
+
const dt = t_norm - kn.t_k;
|
|
704
|
+
if (Math.abs(dt) < 3.2 * kn.sigma_t) {
|
|
705
|
+
const dtheta = Math.atan2(Math.sin(theta - kn.theta_k), Math.cos(theta - kn.theta_k));
|
|
706
|
+
const u_t = dt / kn.sigma_t;
|
|
707
|
+
const u_th = dtheta / kn.sigma_theta;
|
|
708
|
+
const rho2 = u_t * u_t + u_th * u_th;
|
|
709
|
+
if (rho2 < 9.0) {
|
|
710
|
+
const k_amp = kn.amp_k * unwelded * tip_roundness;
|
|
711
|
+
if (kn.is_burl) {
|
|
712
|
+
const w_k = Math.exp(-0.55 * rho2);
|
|
713
|
+
r_mult += k_amp * w_k;
|
|
714
|
+
d_rmult_dtheta += -k_amp * w_k * 1.1 * (u_th / kn.sigma_theta);
|
|
715
|
+
} else {
|
|
716
|
+
const exp_r = Math.exp(-0.85 * rho2);
|
|
717
|
+
const w_rim = (0.30 + 1.90 * rho2) * exp_r;
|
|
718
|
+
const dw_drho2 = (1.90 - 0.85 * (0.30 + 1.90 * rho2)) * exp_r;
|
|
719
|
+
r_mult += k_amp * w_rim;
|
|
720
|
+
d_rmult_dtheta += k_amp * dw_drho2 * (2.0 * u_th / kn.sigma_theta);
|
|
721
|
+
if (rho2 < 0.65) {
|
|
722
|
+
vert_ao *= (0.72 + 0.28 * (rho2 / 0.65));
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const r_uv2 = rho2 + 0.85;
|
|
726
|
+
u_warp += 0.009 * unwelded * (u_th / r_uv2) * Math.exp(-0.35 * rho2);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// 5. 3D Geometric Bark Relief & Coupled Tangential Ridge-Pinch
|
|
733
|
+
const u_periodic = k_wrap * (j_wrapped / m_i) + twist_uv * arc_s;
|
|
734
|
+
if (bark_disp > 1e-4) {
|
|
735
|
+
evaluate_bark_relief(
|
|
736
|
+
SCRATCH_RELIEF,
|
|
737
|
+
u_periodic + u_warp,
|
|
738
|
+
v_base,
|
|
739
|
+
species.bark_material,
|
|
740
|
+
(species.seed | 0) + b * 19
|
|
741
|
+
);
|
|
742
|
+
const h_bark = SCRATCH_RELIEF[0];
|
|
743
|
+
const dh_du = SCRATCH_RELIEF[1];
|
|
744
|
+
u_warp += SCRATCH_RELIEF[2] * unwelded * 0.65;
|
|
745
|
+
|
|
746
|
+
r_mult += bark_disp * (h_bark - 0.50) * 1.35;
|
|
747
|
+
d_rmult_dtheta += bark_disp * dh_du * 0.15;
|
|
748
|
+
}
|
|
749
|
+
u_warp = clamp(u_warp, -0.015, 0.015);
|
|
750
|
+
|
|
751
|
+
if (weld_factor > 0.0) {
|
|
752
|
+
if (i === 0) {
|
|
753
|
+
// Asymmetric adaxial (upper crotch) / abaxial (lower support) collar spread at base ring
|
|
754
|
+
const align_parent = n_rad_x * parent_tx + n_rad_y * parent_ty + n_rad_z * parent_tz;
|
|
755
|
+
const spread = align_parent >= 0.0
|
|
756
|
+
? lvl_cfg.collar_spread_upper * align_parent
|
|
757
|
+
: lvl_cfg.collar_spread_lower * (-align_parent);
|
|
758
|
+
r_mult += weld_factor * (0.15 + spread);
|
|
759
|
+
} else {
|
|
760
|
+
// Symmetric collar flare on subsequent rings so Ring i >= 1 stays centered on spine
|
|
761
|
+
r_mult += weld_factor * 0.18;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const r_local = r_node * Math.max(0.25, r_mult);
|
|
766
|
+
let vx = px + r_local * n_rad_x + d_tan * tan_theta_x;
|
|
767
|
+
let vy = py + r_local * n_rad_y + d_tan * tan_theta_y;
|
|
768
|
+
let vz = pz + r_local * n_rad_z + d_tan * tan_theta_z;
|
|
769
|
+
|
|
770
|
+
// Perturb outward normal gently by low-frequency lobe/knot derivative (fine detail comes from normal map)
|
|
771
|
+
const slope = clamp(d_rmult_dtheta / Math.max(0.25, r_mult), -0.24, 0.24);
|
|
772
|
+
|
|
773
|
+
let vnx = n_rad_x - slope * tan_theta_x;
|
|
774
|
+
let vny = n_rad_y - slope * tan_theta_y;
|
|
775
|
+
let vnz = n_rad_z - slope * tan_theta_z;
|
|
776
|
+
const vn_len = Math.sqrt(vnx * vnx + vny * vny + vnz * vnz);
|
|
777
|
+
if (vn_len > 1e-8) {
|
|
778
|
+
vnx /= vn_len;
|
|
779
|
+
vny /= vn_len;
|
|
780
|
+
vnz /= vn_len;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (weld_factor > 0.0) {
|
|
784
|
+
branch_collar_project(
|
|
785
|
+
SCRATCH_COLLAR_POS, 0,
|
|
786
|
+
SCRATCH_COLLAR_NORM, 0,
|
|
787
|
+
vx, vy, vz,
|
|
788
|
+
vnx, vny, vnz,
|
|
789
|
+
emerge_tx, emerge_ty, emerge_tz,
|
|
790
|
+
parent_px, parent_py, parent_pz,
|
|
791
|
+
parent_tx, parent_ty, parent_tz,
|
|
792
|
+
parent_r,
|
|
793
|
+
weld_factor,
|
|
794
|
+
lvl_cfg.collar_normal_blend,
|
|
795
|
+
parent_m,
|
|
796
|
+
parent_flute_amp,
|
|
797
|
+
parent_taper_slope,
|
|
798
|
+
parent_z_min,
|
|
799
|
+
parent_z_max
|
|
800
|
+
);
|
|
801
|
+
if (i === 0) {
|
|
802
|
+
vx = SCRATCH_COLLAR_POS[0];
|
|
803
|
+
vy = SCRATCH_COLLAR_POS[1];
|
|
804
|
+
vz = SCRATCH_COLLAR_POS[2];
|
|
805
|
+
}
|
|
806
|
+
vnx = SCRATCH_COLLAR_NORM[0];
|
|
807
|
+
vny = SCRATCH_COLLAR_NORM[1];
|
|
808
|
+
vnz = SCRATCH_COLLAR_NORM[2];
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const v3 = v_cursor * 3;
|
|
812
|
+
const v2 = v_cursor * 2;
|
|
813
|
+
const v4 = v_cursor * 4;
|
|
814
|
+
|
|
815
|
+
positions[v3] = vx;
|
|
816
|
+
positions[v3 + 1] = vy;
|
|
817
|
+
positions[v3 + 2] = vz;
|
|
818
|
+
|
|
819
|
+
normals[v3] = vnx;
|
|
820
|
+
normals[v3 + 1] = vny;
|
|
821
|
+
normals[v3 + 2] = vnz;
|
|
822
|
+
|
|
823
|
+
uv0[v2] = k_wrap * (j / m_i) + twist_uv * arc_s + u_warp;
|
|
824
|
+
uv0[v2 + 1] = v_base;
|
|
825
|
+
|
|
826
|
+
uv1[v2] = wind_sway;
|
|
827
|
+
uv1[v2 + 1] = wind_phase;
|
|
828
|
+
|
|
829
|
+
colors[v3] = vert_ao;
|
|
830
|
+
colors[v3 + 1] = vert_ao;
|
|
831
|
+
colors[v3 + 2] = vert_ao;
|
|
832
|
+
|
|
833
|
+
joints[v4] = ring_j0;
|
|
834
|
+
joints[v4 + 1] = ring_j1;
|
|
835
|
+
joints[v4 + 2] = ring_j2;
|
|
836
|
+
joints[v4 + 3] = ring_j3;
|
|
837
|
+
|
|
838
|
+
weights[v4] = ring_w0;
|
|
839
|
+
weights[v4 + 1] = ring_w1;
|
|
840
|
+
weights[v4 + 2] = ring_w2;
|
|
841
|
+
weights[v4 + 3] = ring_w3;
|
|
842
|
+
|
|
843
|
+
v_cursor++;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// Recenter non-collar-root rings so their vertex centroid lies on the exact spine point (px, py, pz)
|
|
847
|
+
// while preserving all asymmetric 3D relief (knots, burls, collar bosses, ovalization, bark plates)
|
|
848
|
+
if (i > 0 || !has_meshed_parent) {
|
|
849
|
+
let mean_x = 0.0, mean_y = 0.0, mean_z = 0.0;
|
|
850
|
+
for (let j = 0; j < m_i; j++) {
|
|
851
|
+
const vi3 = (ring_start + j) * 3;
|
|
852
|
+
mean_x += positions[vi3];
|
|
853
|
+
mean_y += positions[vi3 + 1];
|
|
854
|
+
mean_z += positions[vi3 + 2];
|
|
855
|
+
}
|
|
856
|
+
mean_x /= m_i;
|
|
857
|
+
mean_y /= m_i;
|
|
858
|
+
mean_z /= m_i;
|
|
859
|
+
const corr_x = px - mean_x;
|
|
860
|
+
const corr_y = py - mean_y;
|
|
861
|
+
const corr_z = pz - mean_z;
|
|
862
|
+
for (let j = 0; j <= m_i; j++) {
|
|
863
|
+
const vi3 = (ring_start + j) * 3;
|
|
864
|
+
positions[vi3] += corr_x;
|
|
865
|
+
positions[vi3 + 1] += corr_y;
|
|
866
|
+
positions[vi3 + 2] += corr_z;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Record emitted ring centroid and skinning state for child branch attachment
|
|
871
|
+
let r_cx = 0.0, r_cy = 0.0, r_cz = 0.0;
|
|
872
|
+
for (let j = 0; j < m_i; j++) {
|
|
873
|
+
const vi3 = (ring_start + j) * 3;
|
|
874
|
+
r_cx += positions[vi3];
|
|
875
|
+
r_cy += positions[vi3 + 1];
|
|
876
|
+
r_cz += positions[vi3 + 2];
|
|
877
|
+
}
|
|
878
|
+
r_cx /= m_i;
|
|
879
|
+
r_cy /= m_i;
|
|
880
|
+
r_cz /= m_i;
|
|
881
|
+
|
|
882
|
+
current_branch_rings.push({
|
|
883
|
+
cx: r_cx,
|
|
884
|
+
cy: r_cy,
|
|
885
|
+
cz: r_cz,
|
|
886
|
+
r: r_node,
|
|
887
|
+
m: m_i,
|
|
888
|
+
t_norm,
|
|
889
|
+
v_uv: v_base,
|
|
890
|
+
flute_amp: lvl_cfg.flute_amplitude ?? 0.0,
|
|
891
|
+
j0: ring_j0,
|
|
892
|
+
j1: ring_j1,
|
|
893
|
+
j2: ring_j2,
|
|
894
|
+
j3: ring_j3,
|
|
895
|
+
w0: ring_w0,
|
|
896
|
+
w1: ring_w1,
|
|
897
|
+
w2: ring_w2,
|
|
898
|
+
w3: ring_w3
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
if (prev_ring_start >= 0) {
|
|
902
|
+
i_cursor = branch_ring_triangulate(
|
|
903
|
+
indices,
|
|
904
|
+
i_cursor,
|
|
905
|
+
prev_ring_start,
|
|
906
|
+
prev_m,
|
|
907
|
+
ring_start,
|
|
908
|
+
m_i
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
prev_ring_start = ring_start;
|
|
913
|
+
prev_m = m_i;
|
|
914
|
+
|
|
915
|
+
if (i === sample_steps) {
|
|
916
|
+
tip_px = px;
|
|
917
|
+
tip_py = py;
|
|
918
|
+
tip_pz = pz;
|
|
919
|
+
tip_tx = tx;
|
|
920
|
+
tip_ty = ty;
|
|
921
|
+
tip_tz = tz;
|
|
922
|
+
tip_rx = rx;
|
|
923
|
+
tip_ry = ry;
|
|
924
|
+
tip_rz = rz;
|
|
925
|
+
tip_sx = sx;
|
|
926
|
+
tip_sy = sy;
|
|
927
|
+
tip_sz = sz;
|
|
928
|
+
tip_r = r_node;
|
|
929
|
+
tip_arc_s = arc_s;
|
|
930
|
+
tip_v_uv = v_base;
|
|
931
|
+
tip_wind_sway = wind_sway;
|
|
932
|
+
tip_wind_phase = wind_phase;
|
|
933
|
+
tip_j0 = ring_j0;
|
|
934
|
+
tip_j1 = ring_j1;
|
|
935
|
+
tip_j2 = ring_j2;
|
|
936
|
+
tip_j3 = ring_j3;
|
|
937
|
+
tip_w0 = ring_w0;
|
|
938
|
+
tip_w1 = ring_w1;
|
|
939
|
+
tip_w2 = ring_w2;
|
|
940
|
+
tip_w3 = ring_w3;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
meshed_branch_rings.set(b, current_branch_rings);
|
|
945
|
+
|
|
946
|
+
// Close terminal branch tip with a watertight conical shoot-bud apex cap via branch_apex_triangulate
|
|
947
|
+
const apex_start = v_cursor;
|
|
948
|
+
const m_tip = prev_m;
|
|
949
|
+
const cap_ext = tip_r * 1.15;
|
|
950
|
+
const cap_arc_s = tip_arc_s + cap_ext;
|
|
951
|
+
|
|
952
|
+
for (let j = 0; j < m_tip; j++) {
|
|
953
|
+
const theta_mid = ((j + 0.5) / m_tip) * PI2;
|
|
954
|
+
const cos_m = Math.cos(theta_mid);
|
|
955
|
+
const sin_m = Math.sin(theta_mid);
|
|
956
|
+
|
|
957
|
+
const n_rad_x = cos_m * tip_rx + sin_m * tip_sx;
|
|
958
|
+
const n_rad_y = cos_m * tip_ry + sin_m * tip_sy;
|
|
959
|
+
const n_rad_z = cos_m * tip_rz + sin_m * tip_sz;
|
|
960
|
+
|
|
961
|
+
// Outward-tilted shoot-bud cone normal for smooth shading at the branch tip
|
|
962
|
+
let anx = 0.62 * tip_tx + 0.78 * n_rad_x;
|
|
963
|
+
let any = 0.62 * tip_ty + 0.78 * n_rad_y;
|
|
964
|
+
let anz = 0.62 * tip_tz + 0.78 * n_rad_z;
|
|
965
|
+
const an_len = Math.sqrt(anx * anx + any * any + anz * anz);
|
|
966
|
+
if (an_len > 1e-8) {
|
|
967
|
+
anx /= an_len;
|
|
968
|
+
any /= an_len;
|
|
969
|
+
anz /= an_len;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const v3 = v_cursor * 3;
|
|
973
|
+
const v2 = v_cursor * 2;
|
|
974
|
+
const v4 = v_cursor * 4;
|
|
975
|
+
|
|
976
|
+
positions[v3] = tip_px + cap_ext * tip_tx;
|
|
977
|
+
positions[v3 + 1] = tip_py + cap_ext * tip_ty;
|
|
978
|
+
positions[v3 + 2] = tip_pz + cap_ext * tip_tz;
|
|
979
|
+
|
|
980
|
+
normals[v3] = anx;
|
|
981
|
+
normals[v3 + 1] = any;
|
|
982
|
+
normals[v3 + 2] = anz;
|
|
983
|
+
|
|
984
|
+
uv0[v2] = k_wrap * ((j + 0.5) / m_tip) + twist_uv * cap_arc_s;
|
|
985
|
+
uv0[v2 + 1] = clamp(tip_v_uv + 0.01, 0.02, 0.99);
|
|
986
|
+
|
|
987
|
+
uv1[v2] = tip_wind_sway;
|
|
988
|
+
uv1[v2 + 1] = tip_wind_phase;
|
|
989
|
+
|
|
990
|
+
colors[v3] = 1.0;
|
|
991
|
+
colors[v3 + 1] = 1.0;
|
|
992
|
+
colors[v3 + 2] = 1.0;
|
|
993
|
+
|
|
994
|
+
joints[v4] = tip_j0;
|
|
995
|
+
joints[v4 + 1] = tip_j1;
|
|
996
|
+
joints[v4 + 2] = tip_j2;
|
|
997
|
+
joints[v4 + 3] = tip_j3;
|
|
998
|
+
|
|
999
|
+
weights[v4] = tip_w0;
|
|
1000
|
+
weights[v4 + 1] = tip_w1;
|
|
1001
|
+
weights[v4 + 2] = tip_w2;
|
|
1002
|
+
weights[v4 + 3] = tip_w3;
|
|
1003
|
+
|
|
1004
|
+
v_cursor++;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
i_cursor = branch_apex_triangulate(indices, i_cursor, prev_ring_start, m_tip, apex_start);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
if (only_rings) {
|
|
1011
|
+
return /** @type {any} */ (meshed_branch_rings);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Apply analytic crown ambient occlusion onto bark vertex colors
|
|
1015
|
+
tree_mesh_apply_canopy_ao(out, skeleton.crown_center, skeleton.crown_radii, {
|
|
1016
|
+
ao_core_darkening: (species.foliage.ao_core_darkening ?? 0.35) * 0.65,
|
|
1017
|
+
normal_puffiness: 0.0,
|
|
1018
|
+
preserve_existing_color: true
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
out.name = `${species.name || "tree"}_bark_lod${lod_cfg.level}`;
|
|
1022
|
+
out.ensureBounds();
|
|
1023
|
+
out.ensureTangents();
|
|
1024
|
+
|
|
1025
|
+
return out;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Computes the meshed cross-sectional ring centroids, radii, polygon vertex counts, and 4-slot
|
|
1030
|
+
* skinning weights for every retained branch in `skeleton` using the exact same ring synthesis as
|
|
1031
|
+
* `tree_mesh_build_bark`.
|
|
1032
|
+
*
|
|
1033
|
+
* @param {import("../skeleton/TreeSkeleton.js").TreeSkeleton} skeleton
|
|
1034
|
+
* @param {TreeSpeciesConfig} [config]
|
|
1035
|
+
* @param {TreeLODConfig} [lod_config]
|
|
1036
|
+
* @returns {Map<number, Array<{
|
|
1037
|
+
* cx: number, cy: number, cz: number,
|
|
1038
|
+
* r: number, m: number, t_norm: number, flute_amp: number,
|
|
1039
|
+
* j0: number, j1: number, j2: number, j3: number,
|
|
1040
|
+
* w0: number, w1: number, w2: number, w3: number
|
|
1041
|
+
* }>>}
|
|
1042
|
+
*/
|
|
1043
|
+
export function tree_mesh_compute_branch_rings(
|
|
1044
|
+
skeleton,
|
|
1045
|
+
config = new TreeSpeciesConfig(),
|
|
1046
|
+
lod_config = new TreeLODConfig()
|
|
1047
|
+
) {
|
|
1048
|
+
return /** @type {any} */ (tree_mesh_build_bark(skeleton, config, lod_config, new Geometry(), true));
|
|
1049
|
+
}
|