use bevy::prelude::*;
use rand::Rng;
const CONFIGS: [(&str, Cfg); 9] = [
(
"large_tree",
Cfg {
test_case: TestCase::NonUniformTree {
depth: 18,
branch_width: 8,
},
update_filter: UpdateFilter {
probability: 0.5,
min_depth: 0,
max_depth: u32::MAX,
},
},
),
(
"wide_tree",
Cfg {
test_case: TestCase::Tree {
depth: 3,
branch_width: 500,
},
update_filter: UpdateFilter {
probability: 0.5,
min_depth: 0,
max_depth: u32::MAX,
},
},
),
(
"deep_tree",
Cfg {
test_case: TestCase::NonUniformTree {
depth: 25,
branch_width: 2,
},
update_filter: UpdateFilter {
probability: 0.5,
min_depth: 0,
max_depth: u32::MAX,
},
},
),
(
"chain",
Cfg {
test_case: TestCase::Tree {
depth: 2500,
branch_width: 1,
},
update_filter: UpdateFilter {
probability: 0.5,
min_depth: 0,
max_depth: u32::MAX,
},
},
),
(
"update_leaves",
Cfg {
test_case: TestCase::Tree {
depth: 18,
branch_width: 2,
},
update_filter: UpdateFilter {
probability: 0.5,
min_depth: 17,
max_depth: u32::MAX,
},
},
),
(
"update_shallow",
Cfg {
test_case: TestCase::Tree {
depth: 18,
branch_width: 2,
},
update_filter: UpdateFilter {
probability: 0.5,
min_depth: 0,
max_depth: 8,
},
},
),
(
"humanoids_active",
Cfg {
test_case: TestCase::Humanoids {
active: 4000,
inactive: 0,
},
update_filter: UpdateFilter {
probability: 1.0,
min_depth: 0,
max_depth: u32::MAX,
},
},
),
(
"humanoids_inactive",
Cfg {
test_case: TestCase::Humanoids {
active: 10,
inactive: 3990,
},
update_filter: UpdateFilter {
probability: 1.0,
min_depth: 0,
max_depth: u32::MAX,
},
},
),
(
"humanoids_mixed",
Cfg {
test_case: TestCase::Humanoids {
active: 2000,
inactive: 2000,
},
update_filter: UpdateFilter {
probability: 1.0,
min_depth: 0,
max_depth: u32::MAX,
},
},
),
];
fn print_available_configs() {
println!("available configurations:");
for (name, _) in CONFIGS {
println!(" {name}");
}
}
fn main() {
let cfg: Cfg = match std::env::args().nth(1) {
Some(arg) => match CONFIGS.iter().find(|(name, _)| *name == arg) {
Some((name, cfg)) => {
println!("test configuration: {name}");
cfg.clone()
}
None => {
println!("test configuration \"{arg}\" not found.\n");
print_available_configs();
return;
}
},
None => {
println!("missing argument: <test configuration>\n");
print_available_configs();
return;
}
};
println!("\n{cfg:#?}");
App::new()
.insert_resource(cfg)
.add_plugins((MinimalPlugins, TransformPlugin))
.add_systems(Startup, setup)
.add_systems(Update, update)
.run();
}
#[derive(Resource, Debug, Clone)]
struct Cfg {
test_case: TestCase,
update_filter: UpdateFilter,
}
#[allow(unused)]
#[derive(Debug, Clone)]
enum TestCase {
Tree {
depth: u32,
branch_width: u32,
},
NonUniformTree {
depth: u32,
branch_width: u32,
},
Humanoids {
active: u32,
inactive: u32,
},
}
#[derive(Debug, Clone)]
struct UpdateFilter {
min_depth: u32,
max_depth: u32,
probability: f32,
}
#[derive(Component)]
struct UpdateValue(f32);
fn update(time: Res<Time>, mut query: Query<(&mut Transform, &mut UpdateValue)>) {
for (mut t, mut u) in &mut query {
u.0 += time.delta_seconds() * 0.1;
set_translation(&mut t.translation, u.0);
}
}
fn set_translation(translation: &mut Vec3, a: f32) {
translation.x = a.cos() * 32.0;
translation.y = a.sin() * 32.0;
}
fn setup(mut commands: Commands, cfg: Res<Cfg>) {
warn!(include_str!("warning_string.txt"));
let mut cam = Camera2dBundle::default();
cam.transform.translation.z = 100.0;
commands.spawn(cam);
let result = match cfg.test_case {
TestCase::Tree {
depth,
branch_width,
} => {
let tree = gen_tree(depth, branch_width);
spawn_tree(&tree, &mut commands, &cfg.update_filter, default())
}
TestCase::NonUniformTree {
depth,
branch_width,
} => {
let tree = gen_non_uniform_tree(depth, branch_width);
spawn_tree(&tree, &mut commands, &cfg.update_filter, default())
}
TestCase::Humanoids { active, inactive } => {
let mut result = InsertResult::default();
let mut rng = rand::thread_rng();
for _ in 0..active {
result.combine(spawn_tree(
&HUMANOID_RIG,
&mut commands,
&cfg.update_filter,
Transform::from_xyz(
rng.gen::<f32>() * 500.0 - 250.0,
rng.gen::<f32>() * 500.0 - 250.0,
0.0,
),
));
}
for _ in 0..inactive {
result.combine(spawn_tree(
&HUMANOID_RIG,
&mut commands,
&UpdateFilter {
probability: -1.0,
..cfg.update_filter
},
Transform::from_xyz(
rng.gen::<f32>() * 500.0 - 250.0,
rng.gen::<f32>() * 500.0 - 250.0,
0.0,
),
));
}
result
}
};
println!("\n{result:#?}");
}
#[derive(Default, Debug)]
struct InsertResult {
inserted_nodes: usize,
active_nodes: usize,
maximum_depth: usize,
}
impl InsertResult {
fn combine(&mut self, rhs: Self) -> &mut Self {
self.inserted_nodes += rhs.inserted_nodes;
self.active_nodes += rhs.active_nodes;
self.maximum_depth = self.maximum_depth.max(rhs.maximum_depth);
self
}
}
fn spawn_tree(
parent_map: &[usize],
commands: &mut Commands,
update_filter: &UpdateFilter,
root_transform: Transform,
) -> InsertResult {
let count = parent_map.len() + 1;
#[derive(Default, Clone, Copy)]
struct NodeInfo {
child_count: u32,
depth: u32,
}
let mut ents: Vec<Entity> = Vec::with_capacity(count);
let mut node_info: Vec<NodeInfo> = vec![default(); count];
for (i, &parent_idx) in parent_map.iter().enumerate() {
assert!(parent_idx <= i, "invalid spawn order");
node_info[parent_idx].child_count += 1;
}
ents.push(commands.spawn(TransformBundle::from(root_transform)).id());
let mut result = InsertResult::default();
let mut rng = rand::thread_rng();
let mut child_idx: Vec<u16> = vec![0; count];
for (current_idx, &parent_idx) in parent_map.iter().enumerate() {
let current_idx = current_idx + 1;
let sep = child_idx[parent_idx] as f32 / node_info[parent_idx].child_count as f32;
child_idx[parent_idx] += 1;
let depth = node_info[parent_idx].depth + 1;
let info = &mut node_info[current_idx];
info.depth = depth;
result.maximum_depth = result.maximum_depth.max(depth.try_into().unwrap());
let child_entity = {
let mut cmd = commands.spawn_empty();
let update = (rng.gen::<f32>() <= update_filter.probability)
&& (depth >= update_filter.min_depth && depth <= update_filter.max_depth);
if update {
cmd.insert(UpdateValue(sep));
result.active_nodes += 1;
}
let transform = {
let mut translation = Vec3::ZERO;
set_translation(&mut translation, sep);
Transform::from_translation(translation)
};
cmd.insert(TransformBundle::from(transform));
cmd.id()
};
commands
.get_or_spawn(ents[parent_idx])
.add_child(child_entity);
ents.push(child_entity);
}
result.inserted_nodes = ents.len();
result
}
fn gen_tree(depth: u32, branch_width: u32) -> Vec<usize> {
let mut count: usize = 0;
for i in 0..(depth - 1) {
count += TryInto::<usize>::try_into(branch_width.pow(i)).unwrap();
}
(0..count)
.flat_map(|i| std::iter::repeat(i).take(branch_width.try_into().unwrap()))
.collect()
}
fn add_children_non_uniform(
tree: &mut Vec<usize>,
parent: usize,
mut curr_depth: u32,
max_branch_width: u32,
) {
for _ in 0..max_branch_width {
tree.push(parent);
curr_depth = curr_depth.checked_sub(1).unwrap();
if curr_depth == 0 {
return;
}
add_children_non_uniform(tree, tree.len(), curr_depth, max_branch_width);
}
}
fn gen_non_uniform_tree(max_depth: u32, max_branch_width: u32) -> Vec<usize> {
let mut tree = Vec::new();
add_children_non_uniform(&mut tree, 0, max_depth, max_branch_width);
tree
}
const HUMANOID_RIG: [usize; 67] = [
0, 1, 2, 3, 4, 5, 6, 6, 6, 4, 10, 11, 12, 13, 14, 15, 16, 13, 18, 19, 20, 13, 22, 23, 24, 13, 26, 27, 28, 13, 30, 31, 32, 4, 34, 35, 36, 37, 38, 39, 40, 37, 42, 43, 44, 37, 46, 47, 48, 37, 50, 51, 52, 37, 54, 55, 56, 1, 58, 59, 60, 61, 1, 63, 64, 65, 66, ];