use crate::foliage::{collect_leaf_points, foliage_mesh_styled};
use crate::mesh::MeshData;
use crate::mesh::branch::{apply_pipe_radius, skeleton_mesh};
use crate::params::TreeParams;
use crate::skeleton::{Skeleton, generate_skeleton};
#[derive(Clone, Debug)]
pub struct TreeMeshes {
pub trunk: MeshData,
pub foliage: MeshData,
pub skeleton: Skeleton,
pub mature_at: f32,
}
pub fn build_tree(params: &TreeParams, age: f32) -> TreeMeshes {
build_aged(&generate_skeleton(params), params, age)
}
pub(crate) fn aged_skeleton(full: &Skeleton, params: &TreeParams, age: f32) -> Skeleton {
let mut skeleton = full.aged(age, ¶ms.growth);
if params.mesh.pipe_radius_enabled {
apply_pipe_radius(
&mut skeleton,
params.mesh.pipe_radius_exponent,
params.mesh.pipe_radius_min,
);
}
skeleton
}
pub(crate) fn build_aged(full: &Skeleton, params: &TreeParams, age: f32) -> TreeMeshes {
let mature_at = full.mature_at(¶ms.growth);
let skeleton = aged_skeleton(full, params, age);
let trunk = skeleton_mesh(&skeleton, params);
let foliage = if params.foliage.enabled {
let points = collect_leaf_points(
&skeleton,
¶ms.foliage,
¶ms.growth,
¶ms.trunk,
params.seed,
);
if points.is_empty() {
MeshData::default()
} else {
foliage_mesh_styled(&points, ¶ms.foliage, params.style)
}
} else {
MeshData::default()
};
TreeMeshes {
trunk,
foliage,
skeleton,
mature_at,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mesh::rings::tests::assert_indices_valid;
use crate::presets::TreePreset;
#[test]
fn build_tree_all_presets_mature() {
for preset in TreePreset::ALL {
let params = preset.params();
for age in [f32::INFINITY, 3.0] {
let out = build_tree(¶ms, age);
let what = format!("{} @ {age}", preset.name());
assert!(!out.trunk.is_empty(), "{what}: empty trunk");
assert_indices_valid(&out.trunk, &what);
assert_indices_valid(&out.foliage, &what);
assert!(out.mature_at.is_finite() && out.mature_at > 0.0);
if age.is_infinite() && params.foliage.enabled && out.skeleton.nodes.len() > 1 {
assert!(!out.foliage.is_empty(), "{what}: mature tree has no leaves");
}
}
}
}
#[test]
fn pipe_radius_path_builds() {
let mut params = TreePreset::Oak.params();
params.mesh.pipe_radius_enabled = true;
params.mesh.smooth_iterations = 2;
params.mesh.adaptive_resolution = true;
let out = build_tree(¶ms, f32::INFINITY);
assert_indices_valid(&out.trunk, "pipe+smooth+adaptive");
}
}