simple growth generation

This commit is contained in:
Evan Peterson 2025-06-01 12:39:33 -04:00
parent 23bac13a1e
commit c8417714cc
Signed by: petersonev
GPG Key ID: 26BC6134519C4FC6
5 changed files with 236 additions and 16 deletions

42
Cargo.lock generated
View File

@ -809,7 +809,7 @@ dependencies = [
"glam",
"itertools 0.14.0",
"libm",
"rand",
"rand 0.8.5",
"rand_distr",
"serde",
"smallvec",
@ -2170,7 +2170,7 @@ checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee"
dependencies = [
"bytemuck",
"libm",
"rand",
"rand 0.8.5",
"serde",
]
@ -3451,8 +3451,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
]
[[package]]
@ -3462,7 +3472,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.3",
]
[[package]]
@ -3474,6 +3494,15 @@ dependencies = [
"getrandom 0.2.16",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.3",
]
[[package]]
name = "rand_distr"
version = "0.4.3"
@ -3481,7 +3510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
dependencies = [
"num-traits",
"rand",
"rand 0.8.5",
]
[[package]]
@ -4111,6 +4140,7 @@ version = "0.1.0"
dependencies = [
"bevy",
"iyes_perf_ui",
"rand 0.9.1",
]
[[package]]

View File

@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
bevy = "0.16"
iyes_perf_ui = "0.5.0"
rand = "0.9.1"
[profile.dev]

View File

@ -1,15 +1,24 @@
use std::f32::consts::PI;
use std::time::{Duration, SystemTime};
use bevy::asset::RenderAssetUsages;
use bevy::pbr::wireframe::{WireframeConfig, WireframePlugin};
use bevy::prelude::*;
use bevy::render::mesh::{PrimitiveTopology, VertexAttributeValues};
use bevy::render::primitives::Aabb;
use bevy::time::common_conditions::on_timer;
mod camera;
mod debugging;
mod tree;
mod tree_rendering;
mod tree_generation;
mod tree_rendering;
#[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
enum RunningGrowthState {
Stopped,
Running,
}
fn main() {
App::new()
@ -17,14 +26,76 @@ fn main() {
.add_plugins(debugging::DebuggingPlugin)
.add_plugins(camera::CameraPlugin)
.add_systems(Startup, setup)
// .add_plugins(WireframePlugin::default())
// .insert_resource(WireframeConfig {
// default_color: Color::BLACK,
// global: true, // or false if you want per-object control
// })
// .add_systems(Update, update_tree)
.add_systems(Update, update_tree_grow_state)
.add_systems(
Update,
update_tree
.run_if(in_state(RunningGrowthState::Running))
.run_if(on_timer(Duration::from_secs_f32(0.2))),
)
.add_plugins(WireframePlugin::default())
.insert_resource(WireframeConfig {
default_color: Color::BLACK,
global: true, // or false if you want per-object control
})
.insert_state(RunningGrowthState::Stopped)
.run();
}
fn update_tree_grow_state(
keyboard: Res<ButtonInput<KeyCode>>,
state: Res<State<RunningGrowthState>>,
mut next_state: ResMut<NextState<RunningGrowthState>>,
) {
if keyboard.just_pressed(KeyCode::KeyR) {
if *state.get() == RunningGrowthState::Running {
next_state.set(RunningGrowthState::Stopped);
return;
}
if *state.get() == RunningGrowthState::Stopped {
next_state.set(RunningGrowthState::Running);
}
}
}
fn update_tree(
mut query: Query<(Entity, &mut tree::Tree, &mut Mesh3d)>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
) {
// info!("{:?}", *state.get());
// if keyboard.just_pressed(KeyCode::KeyR) {
// if *state.get() == RunningGrowthState::Running {
// next_state.set(RunningGrowthState::Stopped);
// return;
// }
// if *state.get() == RunningGrowthState::Stopped {
// next_state.set(RunningGrowthState::Running);
// }
// } else if *state.get() == RunningGrowthState::Stopped {
// return;
// }
info!("here");
for (entity, mut tree, mut mesh) in query {
tree_generation::grow_tree(tree.as_mut());
let t1 = SystemTime::now();
let tree_mesh = tree_rendering::generate_tree(&tree);
let t2 = SystemTime::now();
meshes.remove(&mesh.0);
mesh.0 = meshes.add(tree_mesh);
let t3 = SystemTime::now();
commands.entity(entity).remove::<Aabb>();
let t4 = SystemTime::now();
let generate_duration = t2.duration_since(t1).unwrap();
let mesh_duration = t3.duration_since(t2).unwrap();
let aabb_duration = t4.duration_since(t3).unwrap();
info!("{:?} {:?} {:?}", generate_duration, mesh_duration, aabb_duration);
}
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
@ -71,7 +142,7 @@ fn setup(
},
));
let tree = initial_tree();
let tree = tree_generation::initial_tree();
let tree_mesh = tree_rendering::generate_tree(&tree);
// let normal_material = materials.add(Color::srgb_u8(255, 0, 0));
@ -92,7 +163,7 @@ fn setup(
// alpha_mode: AlphaMode::Blend,
// ..default()
// })),
Transform::from_xyz(0.0, 0.5, 0.0),
// Transform::from_xyz(0.0, 0.5, 0.0),
));
}

View File

@ -15,6 +15,7 @@ use bevy::{
pub struct Tree {
pub age: f32,
pub nodes: HashMap<u32, TreeNode>,
next_node_id: u32,
}
#[derive(Debug, Copy, Clone)]
@ -42,11 +43,26 @@ impl Default for Tree {
Tree {
age: 0.,
nodes: HashMap::new(),
next_node_id: 0,
}
}
}
impl Tree {
pub fn add_node(&mut self, mut tree_node: TreeNode) {
tree_node.id = self.next_node_id;
self.nodes.insert(self.next_node_id, tree_node);
self.next_node_id += 1;
}
pub fn get_node(&self, id: u32) -> Option<&TreeNode> {
self.nodes.get(&id)
}
pub fn get_node_mut(&mut self, id: u32) -> Option<&mut TreeNode> {
self.nodes.get_mut(&id)
}
pub fn get_accumulated_transform(&self, id: u32) -> Transform {
let mut transform = Transform::IDENTITY;
let mut curr_node = &self.nodes[&id];
@ -55,14 +71,13 @@ impl Tree {
break;
}
curr_node = &self.nodes[&curr_node.parent_id];
info!("transform: {:?}", curr_node);
// info!("transform: {:?}", curr_node);
transform = curr_node.get_transform() * transform;
}
transform
}
}
impl TreeNode {
pub fn new() -> Self {
TreeNode {
@ -76,6 +91,10 @@ impl TreeNode {
}
}
pub fn is_root(&self) -> bool {
return self.id == self.parent_id
}
pub fn get_transform(&self) -> Transform {
let mut transform = Transform::IDENTITY;
transform.rotate_local_y(self.rotation);
@ -104,6 +123,13 @@ impl TreeNode {
)
}
pub fn is_meristem(&self) -> bool {
matches!(
self.kind,
TreeNodeKind::EndMeristem | TreeNodeKind::EndBranch
)
}
pub fn is_extension(&self) -> bool {
matches!(
self.kind,

View File

@ -0,0 +1,92 @@
use std::f32::consts::TAU;
use crate::tree::{self, Tree, TreeNode, TreeNodeKind};
use bevy::prelude::*;
use rand::{distr::StandardUniform, Rng};
pub fn initial_tree() -> tree::Tree {
let mut tree = tree::Tree::default();
tree.add_node(tree::TreeNode {
id: 0,
kind: tree::TreeNodeKind::EndMeristem,
parent_id: 0,
radius: 0.05,
offset: 0.5,
angle: 0.,
rotation: 0.,
});
tree
}
const MAX_MERISTEM_LENGTH: f32 = 1.;
pub fn grow_tree(tree: &mut tree::Tree) {
let meristems_ids: Vec<u32> = tree
.nodes
.iter()
.filter(|(_, n)| n.is_meristem())
.map(|(i, _)| *i)
.collect();
for meristem_id in meristems_ids {
grow_meristem(meristem_id, tree);
}
}
pub fn grow_meristem(node_id: u32, tree: &mut Tree) {
let mut rng = rand::rng();
let accumulated_transform = tree.get_accumulated_transform(node_id);
let grow_factor = 0.001;
let node = tree.get_node_mut(node_id).unwrap();
node.offset += 0.1;
let original_rotation = node.get_rotation().rotation;
let original_vector = original_rotation * Vec3::Y;
let accumulated_rotation_inverse = accumulated_transform.rotation.inverse();
let up_vector = accumulated_rotation_inverse * Vec3::Y;
let towards_up_vector = up_vector - original_vector;
let random_angle = rng.random::<f32>() * TAU;
let random_vector = Vec3::new(random_angle.cos(), 0.0, random_angle.sin());
let random_magnitude = rng.sample::<f32, StandardUniform>(StandardUniform) - 0.5;
let new_vector = (original_vector + towards_up_vector*0.05 + random_vector*random_magnitude*0.1).normalize();
let r = new_vector.length();
let inclination = (new_vector.y / r).acos();
let azimuth = new_vector.x.atan2(new_vector.z);
node.angle = inclination;
node.rotation = azimuth;
if node.offset > MAX_MERISTEM_LENGTH + node.radius {
let new_meristem_length = node.offset - MAX_MERISTEM_LENGTH;
let new_meristem_radius = node.radius * 0.5;
node.offset -= new_meristem_length;
node.kind = TreeNodeKind::Extension;
let parent_id = node.id;
let angle = node.angle;
let rotation = node.rotation;
tree.add_node(TreeNode {
id: 0,
kind: TreeNodeKind::EndMeristem,
parent_id: parent_id,
radius: new_meristem_radius,
offset: new_meristem_length,
angle: angle,
rotation: rotation,
});
}
let mut growth_node_id = node_id;
loop {
let node = tree.get_node_mut(growth_node_id).unwrap();
node.radius = (node.radius.powi(2) + grow_factor).sqrt();
if node.is_root() {
break;
}
growth_node_id = node.parent_id;
}
}