├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── examples
└── voxel.rs
├── shrubbery_logo.png
├── src
├── algorithm_settings.rs
├── attractor.rs
├── attractor_generator_settings.rs
├── branch.rs
├── leaf_classifier.rs
├── lib.rs
├── math_utils.rs
├── shape.rs
├── shrubbery.rs
├── vec.rs
└── voxel.rs
└── voxel_example.png
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
7 | Cargo.lock
8 |
9 | # These are backup files generated by rustfmt
10 | **/*.rs.bk
11 |
12 |
13 | # Added by cargo
14 |
15 | /target
16 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "shrubbery"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | glam = "0.22.0"
10 | rand = "*"
11 |
12 | [dev-dependencies]
13 | kiss3d = "*"
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | shrubbery is dual-licensed under either
2 |
3 | * MIT License (docs/LICENSE-MIT or http://opensource.org/licenses/MIT)
4 | * Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
5 |
6 | at your option.
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # shrubbery
2 |
3 | rust library: Space colonization implementation, for generating trees / shrubbery, with built in voxelization utility.
4 |
5 | ### Screenshot example: voxel.
6 | left: Voxel tree
7 |
8 | right: debug representation of branches + attractors renders as yellow dots.
9 | 
10 |
11 | ## Example code
12 | ```rs
13 | let mut shrubbery = Shrubbery::new(
14 | vec3(0., 0., 0.), // rot position
15 | vec3(0., 1., 0.), // initial growth dir
16 | AlgorithmSettings {
17 | branch_len: 2.0,
18 | leaf_attraction_dist: 6.0,
19 | ..Default::default()
20 | },
21 | AttractorGeneratorSettings::default(),
22 | );
23 |
24 | // spawn particles for tree to grow into
25 | shrubbery.spawn_attractors_from_shape(
26 | vec3(0., 5. + 8.0, 0.),
27 | BoxShape {
28 | x: 15.0,
29 | y: 10.0,
30 | z: 15.,
31 | },
32 | );
33 |
34 | // keep spawning root branches until attractors can be found
35 | shrubbery.build_trunk();
36 |
37 | // grow tree 8 times
38 | (0..8).for_each(|_|shrubbery.grow());
39 |
40 | // make data for the tree as a voxel
41 | let mut voxels = voxelize(shrubbery, VoxelizeSettings::default());
42 |
43 | ```
44 |
45 | ## License
46 |
47 | Shrubbery is free and open source! All code in this repository is dual-licensed under either:
48 |
49 | * MIT License ([LICENSE-MIT](docs/LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
50 | * Apache License, Version 2.0 ([LICENSE-APACHE](docs/LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
51 |
52 | at your option.
53 |
54 | Unless you explicitly state otherwise, any contribution intentionally submitted
55 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
56 | additional terms or conditions.
57 |
--------------------------------------------------------------------------------
/examples/voxel.rs:
--------------------------------------------------------------------------------
1 | use shrubbery::shape::BoxShape;
2 | use shrubbery::voxel::{
3 | drop_leaves, voxelize, BranchRootSizeIncreaser, BranchSizeSetting, LeafSetting, LeafShape,
4 | VoxelType, VoxelizeSettings,
5 | };
6 | use shrubbery::{
7 | algorithm_settings::AlgorithmSettings,
8 | attractor_generator_settings::AttractorGeneratorSettings, prelude::*,
9 | };
10 |
11 | use shrubbery::math::*;
12 |
13 | use kiss3d::event::{Action, Key, WindowEvent};
14 | use kiss3d::light::Light;
15 | use kiss3d::window::Window;
16 |
17 | fn make_shrubbery() -> Shrubbery {
18 | let mut shrubbery = Shrubbery::new(
19 | vec3(0., 0., 0.),
20 | vec3(0., 1., 0.),
21 | AlgorithmSettings {
22 | kill_distance: 2.0,
23 | branch_len: 2.0,
24 | leaf_attraction_dist: 6.0,
25 | min_trunk_height: 3.0,
26 | },
27 | AttractorGeneratorSettings::default(),
28 | );
29 | shrubbery.spawn_attractors_from_shape(
30 | vec3(0., 5. + 8.0, 0.),
31 | BoxShape {
32 | x: 15.0,
33 | y: 10.0,
34 | z: 15.,
35 | },
36 | );
37 | shrubbery.build_trunk();
38 | shrubbery
39 | }
40 |
41 | fn main() {
42 | let mut window = Window::new("that's a fine shrubbery");
43 | window.set_light(Light::StickToCamera);
44 | let mut shrubbery = make_shrubbery();
45 |
46 | // reference kiss3d box models so we can remove them
47 | let mut vis_nodes = vec![];
48 |
49 | let settings = VoxelizeSettings {
50 | branch_size_setting: BranchSizeSetting::Generation {
51 | distances: vec![1.5, 1.0, 1.0, 1.0],
52 | },
53 | branch_root_size_increaser: Some(BranchRootSizeIncreaser {
54 | height: 2.0,
55 | additional_size: 2.0,
56 | }),
57 | leaf_settings: LeafSetting::Shape(LeafShape::Sphere { r: 2.7 }),
58 | };
59 |
60 | while window.render() {
61 | for event in window.events().iter() {
62 | match event.value {
63 | WindowEvent::Key(button, Action::Press, _) => {
64 | // flag to indicate rebuilding the voxels
65 | // process button input: it's dirty
66 | let mut dirty = true;
67 | match button {
68 | Key::R => shrubbery = make_shrubbery(),
69 | Key::G => shrubbery.post_process_gravity(1.0),
70 | Key::T => shrubbery.post_process_spin(3.14 * 0.5),
71 | Key::N => shrubbery.grow(),
72 | _ => dirty = false,
73 | }
74 | if dirty {
75 | build_voxels(&mut shrubbery, &settings, &mut vis_nodes, &mut window);
76 | }
77 | }
78 | _ => {}
79 | }
80 | }
81 |
82 | window.set_line_width(6.0);
83 | for branch in shrubbery.branches.iter() {
84 | let Some(parent_index) = branch.parent_index else {
85 | continue;
86 | };
87 | let p_pos = shrubbery.branches[parent_index].pos;
88 | let from = kiss3d::nalgebra::Point3::new(branch.pos.x, branch.pos.y, branch.pos.z);
89 | let to = kiss3d::nalgebra::Point3::new(p_pos.x, p_pos.y, p_pos.z);
90 |
91 | let mut is_leaf = false;
92 | if let LeafSetting::BranchIsLeaf(classifier) = &settings.leaf_settings {
93 | is_leaf = branch.is_leaf(classifier);
94 | }
95 | let color = if is_leaf {
96 | kiss3d::nalgebra::Point3::new(0.0, 1.0, 0.0)
97 | } else {
98 | kiss3d::nalgebra::Point3::new(0.4, 0.2, 0.0)
99 | };
100 | window.draw_line(&from, &to, &color);
101 | }
102 |
103 | for attractor in shrubbery.attractors.iter() {
104 | let pos =
105 | kiss3d::nalgebra::Point3::new(attractor.pos.x, attractor.pos.y, attractor.pos.z);
106 | window.set_point_size(6.0);
107 | window.draw_point(&pos, &kiss3d::nalgebra::Point3::new(1.0, 1.0, 0.0));
108 | }
109 | }
110 | }
111 |
112 | fn build_voxels(
113 | shrubbery: &mut Shrubbery,
114 | settings: &VoxelizeSettings,
115 | vis_nodes: &mut Vec,
116 | window: &mut Window,
117 | ) {
118 | let mut gen_voxels = voxelize(shrubbery, settings);
119 | drop_leaves(&mut gen_voxels, 0.1);
120 |
121 | vis_nodes
122 | .iter_mut()
123 | .for_each(|mut n| window.remove_node(&mut n));
124 |
125 | for (pos, voxel) in gen_voxels.iter() {
126 | let c_s = 1.0;
127 | let mut c = window.add_cube(c_s, c_s, c_s);
128 | c.append_translation(&kiss3d::nalgebra::Translation3::new(
129 | pos.x as f32 + 40.0,
130 | pos.y as f32,
131 | pos.z as f32,
132 | ));
133 | match voxel {
134 | VoxelType::Air => (),
135 | VoxelType::Branch => {
136 | c.set_color(0.4, 0.2, 0.0);
137 | }
138 | VoxelType::Greenery => {
139 | c.set_color(0.0, 1.0, 0.0);
140 | }
141 | };
142 | vis_nodes.push(c);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/shrubbery_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TanTanDev/shrubbery/d034b98b190f0b2bb83696d71e4bfb866de3b267/shrubbery_logo.png
--------------------------------------------------------------------------------
/src/algorithm_settings.rs:
--------------------------------------------------------------------------------
1 | #[derive(Clone, Copy, Debug, PartialEq)]
2 | pub struct AlgorithmSettings {
3 | pub kill_distance: f32,
4 | pub branch_len: f32,
5 | pub leaf_attraction_dist: f32,
6 | pub min_trunk_height: f32,
7 | }
8 |
9 | impl Default for AlgorithmSettings {
10 | fn default() -> Self {
11 | Self {
12 | kill_distance: 0.3,
13 | branch_len: 0.3,
14 | leaf_attraction_dist: 5.,
15 | min_trunk_height: 1.,
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/attractor.rs:
--------------------------------------------------------------------------------
1 | use glam::Vec3;
2 |
3 | pub struct Attractor {
4 | pub pos: Vec3,
5 | pub reached: bool,
6 | }
7 |
8 | impl Attractor {
9 | pub fn new(pos: Vec3) -> Self {
10 | Self {
11 | pos,
12 | reached: false,
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/attractor_generator_settings.rs:
--------------------------------------------------------------------------------
1 | pub struct AttractorGeneratorSettings {
2 | pub max_leaves: Option,
3 | pub min_leaves: Option,
4 | // a value of 1: will spawn enough leaves to expand the whole area
5 | // 1.0 is minimum recommended value, higher values will yield potentially more branching
6 | // but higher values also generates more leaves, tolling performance
7 | pub density: f32,
8 | }
9 |
10 | impl Default for AttractorGeneratorSettings {
11 | fn default() -> Self {
12 | Self {
13 | max_leaves: Some(500),
14 | min_leaves: Some(30),
15 | density: 1.0,
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/branch.rs:
--------------------------------------------------------------------------------
1 | use glam::Vec3;
2 |
3 | use crate::leaf_classifier::LeafClassifier;
4 |
5 | pub struct Branch {
6 | pub pos: Vec3,
7 | pub parent_index: Option,
8 | pub dir: Vec3,
9 | pub original_dir: Vec3,
10 | // how many attractors are pulling this node
11 | pub attractors_count: i32,
12 | // how man branchings are from this node
13 | pub child_count: i32,
14 | // todo: explain
15 | pub generation: i32,
16 | }
17 |
18 | impl Branch {
19 | // make a new branch based on this branch calculated growth direciton
20 | pub fn next(&self, index: usize, branch_len: f32, is_new_generation: bool) -> Self {
21 | let mut generation = self.generation;
22 | if is_new_generation {
23 | generation += 1;
24 | }
25 | Self {
26 | pos: self.pos + self.dir * branch_len,
27 | parent_index: Some(index),
28 | dir: self.dir,
29 | attractors_count: 0,
30 | original_dir: self.dir,
31 | child_count: 0,
32 | generation,
33 | }
34 | }
35 |
36 | // no child branches: is leaf
37 | pub fn is_leaf(&self, classifier: &LeafClassifier) -> bool {
38 | match classifier {
39 | LeafClassifier::LastBranch => self.child_count == 0,
40 | LeafClassifier::NonRootBranch => self.generation != 0,
41 | }
42 | }
43 |
44 | pub fn reset(&mut self) {
45 | self.attractors_count = 0;
46 | self.dir = self.original_dir;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/leaf_classifier.rs:
--------------------------------------------------------------------------------
1 | #[derive(Copy, Clone, Debug, PartialEq)]
2 | pub enum LeafClassifier {
3 | // branch without children is leaf
4 | LastBranch,
5 | // new "generation" branches are leaves
6 | NonRootBranch,
7 | }
8 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod algorithm_settings;
2 | pub mod attractor;
3 | pub mod attractor_generator_settings;
4 | pub mod branch;
5 | pub mod leaf_classifier;
6 | pub mod shape;
7 | pub mod shrubbery;
8 | pub mod vec;
9 | pub mod voxel;
10 | pub use glam;
11 | pub mod math_utils;
12 |
13 | pub mod prelude {
14 | pub use crate::shrubbery::Shrubbery;
15 | }
16 |
17 | pub mod math {
18 | pub use glam::*;
19 | }
20 |
--------------------------------------------------------------------------------
/src/math_utils.rs:
--------------------------------------------------------------------------------
1 | use glam::{vec2, Vec2, Vec3};
2 |
3 | /// rotate a vec2 position around the origin of 0,0
4 | pub fn rotate_point(pos: Vec2, radians: f32) -> Vec2 {
5 | let (cos_theta, sin_theta) = (radians.cos(), radians.sin());
6 | let out = vec2(
7 | cos_theta * pos.x - sin_theta * pos.y,
8 | sin_theta * pos.x + cos_theta * pos.y,
9 | );
10 | out
11 | }
12 |
13 | /// return the shortest distance from a vec3 to a line with a star and end pos
14 | pub fn dist_to_line(pos: Vec3, line_start: Vec3, line_end: Vec3) -> f32 {
15 | let ab = line_end - line_start;
16 | let ac = pos - line_start;
17 | if ac.dot(ab) <= 0.0 {
18 | return ac.length();
19 | }
20 | let bv = pos - line_end;
21 | if bv.dot(ab) >= 0.0 {
22 | return bv.length();
23 | }
24 | ab.cross(ac).length() / ab.length()
25 | }
26 |
--------------------------------------------------------------------------------
/src/shape.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | algorithm_settings::AlgorithmSettings, attractor::Attractor,
3 | attractor_generator_settings::AttractorGeneratorSettings,
4 | };
5 | use glam::*;
6 | use rand::Rng;
7 |
8 | /// A shape to spawn attractors inside
9 | pub trait Shape {
10 | fn generate(
11 | &self,
12 | pos: Vec3,
13 | attractors: &mut Vec,
14 | algorithm_settings: &AlgorithmSettings,
15 | generator_settings: &AttractorGeneratorSettings,
16 | );
17 | }
18 |
19 | /// x,y,z is total size
20 | pub struct BoxShape {
21 | pub x: f32,
22 | pub y: f32,
23 | pub z: f32,
24 | }
25 |
26 | impl Shape for BoxShape {
27 | fn generate(
28 | &self,
29 | pos: Vec3,
30 | attractors: &mut Vec,
31 | algorithm_settings: &AlgorithmSettings,
32 | generator_settings: &AttractorGeneratorSettings,
33 | ) {
34 | let mut ideal_spacing =
35 | algorithm_settings.leaf_attraction_dist * 0.5 - algorithm_settings.kill_distance * 0.5;
36 | ideal_spacing *= 1.0 / generator_settings.density;
37 |
38 | let x_l = (self.x / ideal_spacing) as i32;
39 | let x_y = (self.y / ideal_spacing) as i32;
40 | let x_z = (self.z / ideal_spacing) as i32;
41 |
42 | let center_shape_offset = -vec3(self.x * 0.5, self.y * 0.5, self.z * 0.5);
43 | let start_pos = vec3(
44 | ideal_spacing * 0.5,
45 | ideal_spacing * 0.5,
46 | ideal_spacing * 0.5,
47 | );
48 |
49 | let scatter_distance = ideal_spacing * 0.5;
50 | let mut rng = rand::thread_rng();
51 |
52 | for x in 0..x_l {
53 | for y in 0..x_y {
54 | for z in 0..x_z {
55 | let jitter = vec3(
56 | x as f32 * ideal_spacing
57 | + rng.gen_range(-scatter_distance..scatter_distance),
58 | y as f32 * ideal_spacing
59 | + rng.gen_range(-scatter_distance..scatter_distance),
60 | z as f32 * ideal_spacing
61 | + rng.gen_range(-scatter_distance..scatter_distance),
62 | );
63 | attractors.push(Attractor::new(
64 | pos + start_pos + center_shape_offset + jitter,
65 | ));
66 | }
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/shrubbery.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | algorithm_settings::AlgorithmSettings,
3 | attractor::Attractor,
4 | attractor_generator_settings::AttractorGeneratorSettings,
5 | branch::Branch,
6 | math_utils::{dist_to_line, rotate_point},
7 | shape::Shape,
8 | };
9 |
10 | use glam::{ivec3, vec2, IVec3, Vec2, Vec3};
11 |
12 | /// a tree/bush, pressentation that you can grow and modify through post processing effect
13 | pub struct Shrubbery {
14 | pub branches: Vec,
15 | pub attractors: Vec,
16 | pub settings: AlgorithmSettings,
17 | pub generator_settings: AttractorGeneratorSettings,
18 | pub min_bounds: Vec3,
19 | pub max_bounds: Vec3,
20 | }
21 |
22 | impl Shrubbery {
23 | pub fn new(
24 | root_pos: Vec3,
25 | initial_dir: Vec3,
26 | settings: AlgorithmSettings,
27 | generator_settings: AttractorGeneratorSettings,
28 | ) -> Self {
29 | let mut branches = Vec::new();
30 | let root = Branch {
31 | pos: root_pos,
32 | parent_index: None,
33 | dir: initial_dir,
34 | attractors_count: 0,
35 | original_dir: initial_dir,
36 | child_count: 0,
37 | generation: 0,
38 | };
39 | branches.push(root);
40 | Self {
41 | branches,
42 | attractors: Vec::new(),
43 | settings,
44 | generator_settings,
45 | min_bounds: Vec3::splat(0f32),
46 | max_bounds: Vec3::splat(0f32),
47 | }
48 | }
49 |
50 | /// return the half size bounds, imagine a quad on the xz axis
51 | pub fn get_plane_half_size(&self) -> f32 {
52 | let size = self.get_bounding_size();
53 | (size.x.max(size.z) as f32 * 0.5).ceil()
54 | }
55 |
56 | /// rturns size of the bounding box
57 | pub fn get_bounding_size(&self) -> IVec3 {
58 | let (min_bounds, max_bounds) = self.get_bounds();
59 | max_bounds - min_bounds
60 | }
61 |
62 | /// returns the min x,y,z and max x,y,z position
63 | pub fn get_bounds(&self) -> (IVec3, IVec3) {
64 | (
65 | ivec3(
66 | self.min_bounds.x.ceil() as i32,
67 | self.min_bounds.y.ceil() as i32,
68 | self.min_bounds.z.ceil() as i32,
69 | ),
70 | ivec3(
71 | self.max_bounds.x.ceil() as i32,
72 | self.max_bounds.y.ceil() as i32,
73 | self.max_bounds.z.ceil() as i32,
74 | ),
75 | )
76 | }
77 |
78 | // expand bounding if branch_pos is outside
79 | pub fn update_bound(min_bounds: &mut Vec3, max_bounds: &mut Vec3, branch_pos: Vec3) {
80 | min_bounds.x = min_bounds.x.min(branch_pos.x);
81 | min_bounds.y = min_bounds.y.min(branch_pos.y);
82 | min_bounds.z = min_bounds.z.min(branch_pos.z);
83 | max_bounds.x = max_bounds.x.max(branch_pos.x);
84 | max_bounds.y = max_bounds.y.max(branch_pos.y);
85 | max_bounds.z = max_bounds.z.max(branch_pos.z);
86 | }
87 |
88 | /// spawn initial branches based on settings.
89 | pub fn build_trunk(&mut self) {
90 | let mut root_end_pos = self.branches[0].pos;
91 | let dir = self.branches[0].dir;
92 | let mut consumed_height = 0.;
93 | // the first root will be as long as it needs to be until it starts gaining attractions
94 | let max_iterations = 1000;
95 | 'a: for _ in 0..max_iterations {
96 | consumed_height += self.settings.branch_len;
97 | root_end_pos += self.settings.branch_len * dir;
98 | for leaf in self.attractors.iter() {
99 | let dist = root_end_pos.distance(leaf.pos);
100 | if dist < self.settings.leaf_attraction_dist {
101 | break 'a;
102 | }
103 | }
104 | }
105 |
106 | self.branches[0].child_count += 1;
107 | let new_branch = self.branches[0].next(0, consumed_height, false);
108 | Self::update_bound(&mut self.min_bounds, &mut self.max_bounds, new_branch.pos);
109 | self.branches.push(new_branch);
110 |
111 | // keep adding branches upwards until we reach the trunk_height
112 | while consumed_height < self.settings.min_trunk_height {
113 | consumed_height += self.settings.branch_len;
114 | let last_index = self.branches.len() - 1;
115 | let new_branch =
116 | self.branches[last_index].next(last_index, self.settings.branch_len, false);
117 | self.branches[last_index].child_count += 1;
118 | Self::update_bound(&mut self.min_bounds, &mut self.max_bounds, new_branch.pos);
119 | self.branches.push(new_branch)
120 | }
121 | }
122 |
123 | /// using space colonization algorithm, spawn new branches
124 | pub fn grow(&mut self) {
125 | for leaf in self.attractors.iter_mut() {
126 | let mut closest_branch: Option = None;
127 | let mut closest_dist = 999999.;
128 | // find shortest signed distance of all branches
129 | for (branch_index, branch) in self.branches.iter_mut().enumerate() {
130 | let dist = leaf.pos.distance(branch.pos);
131 | // is this branch to close to the leaf, discard it
132 | if dist < self.settings.kill_distance {
133 | leaf.reached = true;
134 | closest_branch = None;
135 | break;
136 | }
137 | // to far away to be attracted towards
138 | if dist > self.settings.leaf_attraction_dist {
139 | continue;
140 | }
141 | // record closest branch
142 | if dist < closest_dist {
143 | closest_branch = Some(branch_index);
144 | closest_dist = dist;
145 | }
146 | }
147 | // pull closest branch towards us
148 | if let Some(closest_branch_index) = closest_branch {
149 | let closest_branch_pos = self.branches[closest_branch_index].pos;
150 | let new_branch_dir = leaf.pos - closest_branch_pos;
151 | let new_branch_dir = new_branch_dir.normalize();
152 | self.branches[closest_branch_index].dir += new_branch_dir;
153 | self.branches[closest_branch_index].attractors_count += 1;
154 | }
155 | }
156 | // remove reached leaves
157 | self.attractors.retain(|leaf| !leaf.reached);
158 |
159 | // spawn new branches using previous calculations
160 | let mut to_add = vec![];
161 | for (branch_index, branch) in self
162 | .branches
163 | .iter_mut()
164 | .enumerate()
165 | .filter(|(_, branch)| branch.attractors_count > 0)
166 | {
167 | branch.dir = branch.dir.normalize();
168 | let new_branch = branch.next(branch_index, self.settings.branch_len, true);
169 | branch.child_count += 1;
170 | Self::update_bound(&mut self.min_bounds, &mut self.max_bounds, new_branch.pos);
171 | to_add.push(new_branch);
172 | branch.reset();
173 | }
174 | self.branches.extend(to_add);
175 | }
176 |
177 | /// spawn particles inside provided shape, based on settings
178 | pub fn spawn_attractors_from_shape(&mut self, pos: Vec3, shape: TShape)
179 | where
180 | TShape: Shape,
181 | {
182 | shape.generate(
183 | pos,
184 | &mut self.attractors,
185 | &self.settings,
186 | &self.generator_settings,
187 | );
188 | }
189 |
190 | /// reduce y position of branches, weighted by dist to 0,0 xz.
191 | pub fn post_process_gravity(&mut self, gravity: f32) {
192 | let plane_half_size = self.get_plane_half_size();
193 | for branch in self.branches.iter_mut() {
194 | // branch.
195 | let branch_plane = vec2(branch.pos.x, branch.pos.z);
196 | let root = Vec2::ZERO;
197 | let dist_to_root = branch_plane.distance(root);
198 |
199 | let weight = dist_to_root / plane_half_size;
200 | branch.pos.y -= weight * gravity;
201 | }
202 | }
203 |
204 | /// rotate branch x,z positions around origin: 0,0
205 | pub fn post_process_spin(&mut self, spin_amount: f32) {
206 | let plane_half_size = self.get_plane_half_size();
207 | for branch in self.branches.iter_mut() {
208 | let branch_xz = vec2(branch.pos.x, branch.pos.z);
209 | let root = Vec2::ZERO;
210 | let dist_to_root = branch_xz.distance(root);
211 | let weight = dist_to_root / plane_half_size;
212 |
213 | let y_weight = (branch.pos.y * 0.3).cos() * 0.5 + 0.5;
214 |
215 | let new_xz = rotate_point(branch_xz, spin_amount * weight * y_weight);
216 | branch.pos.x = new_xz.x;
217 | branch.pos.z = new_xz.y;
218 | }
219 | }
220 |
221 | /// returns (distance, index of branch)
222 | pub fn distance_to_branch(&self, pos: Vec3) -> (f32, usize) {
223 | let mut closest = f32::MAX;
224 | let mut index = 0;
225 | for (i, branch) in self.branches.iter().enumerate() {
226 | let Some(parent_index) = branch.parent_index else {
227 | continue;
228 | };
229 |
230 | let d = dist_to_line(pos, self.branches[parent_index].pos, branch.pos);
231 | if d < closest {
232 | closest = d;
233 | index = i;
234 | }
235 | }
236 | (closest, index)
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/src/vec.rs:
--------------------------------------------------------------------------------
1 | use std::ops::*;
2 |
3 | pub type Vector2 = Vector<2>;
4 | pub type Vector3 = Vector<3>;
5 |
6 | #[derive(Copy, Clone, Debug)]
7 | pub struct Vector {
8 | data: [f32; DIMENSIONS],
9 | }
10 |
11 | impl Vector {
12 | pub fn zero() -> Self {
13 | Self {
14 | data: [0f32; DIMENSIONS],
15 | }
16 | }
17 |
18 | pub fn distance(self, other: Self) -> f32 {
19 | (0..DIMENSIONS)
20 | .map(|i| other.data[i] - self.data[i])
21 | .sum::()
22 | .sqrt()
23 | }
24 |
25 | pub fn normalize(self) -> Self {
26 | let inv_len = 1.0 / Self::distance(self, Vector::zero());
27 | self * inv_len
28 | // let d = Self::distance(self, Vec3::ZERO);
29 | // self * d
30 | }
31 | }
32 |
33 | impl Add for Vector {
34 | type Output = Vector;
35 | fn add(self, rhs: Self) -> Self::Output {
36 | let mut out = Vector::zero();
37 | (0..DIMENSIONS).for_each(|i| out.data[i] = self.data[i] + rhs.data[i]);
38 | out
39 | }
40 | }
41 |
42 | impl AddAssign for Vector {
43 | fn add_assign(&mut self, rhs: Self) {
44 | (0..DIMENSIONS).for_each(|i| self.data[i] += rhs.data[i]);
45 | }
46 | }
47 |
48 | impl Mul for Vector {
49 | type Output = Vector;
50 |
51 | fn mul(self, rhs: f32) -> Self::Output {
52 | let mut out = Vector::zero();
53 | (0..DIMENSIONS).for_each(|i| out.data[i] = self.data[i] * rhs);
54 | out
55 | }
56 | }
57 |
58 | impl Sub for Vector {
59 | type Output = Vector;
60 |
61 | fn sub(self, rhs: Self) -> Self::Output {
62 | let mut out = Vector::zero();
63 | (0..DIMENSIONS).for_each(|i| out.data[i] = self.data[i] - rhs.data[i]);
64 | out
65 | }
66 | }
67 |
68 | // impl Vector for Vec3 {
69 | // fn distance(self, other: Self) -> f32 {
70 | // ((other.x - self.x).powi(2) + (other.y - self.y).powi(2) + (other.z - self.z).powi(2))
71 | // .sqrt()
72 | // }
73 |
74 | // fn normalize(self) -> Self {
75 | // let d = Self::distance(self, Vec3::ZERO);
76 | // self * d
77 | // }
78 | // }
79 |
80 | // pub trait Vector:
81 | // Sized
82 | // + Copy
83 | // + Clone
84 | // + Sub