diff --git a/Cargo.lock b/Cargo.lock index b5e5b09..5f7aad3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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]] diff --git a/Cargo.toml b/Cargo.toml index a7300eb..eb40517 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] bevy = "0.16" iyes_perf_ui = "0.5.0" +rand = "0.9.1" [profile.dev] diff --git a/src/main.rs b/src/main.rs index 2fc662c..11b8709 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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>, + state: Res>, + mut next_state: ResMut>, +) { + 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>, +) { + // 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::(); + 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>, @@ -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), )); } diff --git a/src/tree.rs b/src/tree.rs index fbc2140..f80e17c 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -15,6 +15,7 @@ use bevy::{ pub struct Tree { pub age: f32, pub nodes: HashMap, + 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, diff --git a/src/tree_generation.rs b/src/tree_generation.rs index e69de29..8269063 100644 --- a/src/tree_generation.rs +++ b/src/tree_generation.rs @@ -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 = 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::() * TAU; + let random_vector = Vec3::new(random_angle.cos(), 0.0, random_angle.sin()); + + let random_magnitude = rng.sample::(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; + } +}