Document tunnel generation

This commit is contained in:
James Melkonian 2022-02-12 13:57:44 -08:00
parent c7ce2b773c
commit 8a02a943f4

View File

@ -29,14 +29,6 @@ pub struct GnarlingFortification {
tunnels: Tunnels, tunnels: Tunnels,
} }
struct Tunnels {
start: Vec3<i32>,
end: Vec3<i32>,
branches: Vec<(Vec3<i32>, Vec3<i32>)>,
path: Vec<Vec3<i32>>,
terminals: Vec<Vec3<i32>>,
}
enum GnarlingStructure { enum GnarlingStructure {
Hut, Hut,
VeloriteHut, VeloriteHut,
@ -115,16 +107,8 @@ impl GnarlingFortification {
} }
let tunnel_length_range = (12.0, 27.0); let tunnel_length_range = (12.0, 27.0);
let (branches, path, terminals) = rrt(start, end, is_valid_edge, tunnel_length_range, rng) let tunnels =
.unwrap_or((Vec::new(), Vec::new(), Vec::new())); Tunnels::new(start, end, is_valid_edge, tunnel_length_range, rng).unwrap_or_default();
let tunnels = Tunnels {
start,
end,
branches,
path,
terminals,
};
let num_points = (wall_radius / 15).max(5); let num_points = (wall_radius / 15).max(5);
let outer_wall_corners = (0..num_points) let outer_wall_corners = (0..num_points)
@ -1933,114 +1917,151 @@ fn harvester_boss<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
.with_asset_expect("common.entity.dungeon.gnarling.harvester", rng) .with_asset_expect("common.entity.dungeon.gnarling.harvester", rng)
} }
#[allow(clippy::type_complexity)] #[derive(Default)]
fn rrt<F>( struct Tunnels {
start: Vec3<i32>, start: Vec3<i32>,
end: Vec3<i32>, end: Vec3<i32>,
is_valid_edge: F, branches: Vec<(Vec3<i32>, Vec3<i32>)>,
radius_range: (f32, f32), path: Vec<Vec3<i32>>,
rng: &mut impl Rng, terminals: Vec<Vec3<i32>>,
) -> Option<(Vec<(Vec3<i32>, Vec3<i32>)>, Vec<Vec3<i32>>, Vec<Vec3<i32>>)> }
where
F: Fn(Vec3<i32>, Vec3<i32>) -> bool,
{
let mut nodes = Vec::new();
let mut node_index: usize = 0;
// HashMap<ChildNode, ParentNode> impl Tunnels {
let mut parents = HashMap::new(); /// Attempts to find a path from a `start` to the `end` using a rapidly
/// exploring random tree (RRT). A point is sampled from an AABB extending
/// slightly beyond the start and end in the x and y axes and below the
/// start in the z axis to slightly below the end. Nodes are stored in a
/// k-d tree for quicker nearest node calculations. A maximum of 7000
/// points are sampled until the tree connects the start to the end. A
/// final path is then reconstructed from the nodes. Returns a `Tunnels`
/// struct of the RRT branches, the complete path, and the location of
/// dead ends. Each branch is a tuple of the start and end locations of
/// each segment. The path is a vector of all the nodes along the
/// complete path from the `start` to the `end`.
fn new<F>(
start: Vec3<i32>,
end: Vec3<i32>,
is_valid_edge: F,
radius_range: (f32, f32),
rng: &mut impl Rng,
) -> Option<Self>
where
F: Fn(Vec3<i32>, Vec3<i32>) -> bool,
{
let mut nodes = Vec::new();
let mut node_index: usize = 0;
let mut kdtree = KdTree::new(); // HashMap<ChildNode, ParentNode>
let start = start.map(|a| (a + 1) as f32); let mut parents = HashMap::new();
let end = end.map(|a| (a + 1) as f32);
let min = Vec3::new(start.x.min(end.x), start.y.min(end.y), start.z.min(end.z)); let mut kdtree = KdTree::new();
let max = Vec3::new(start.x.max(end.x), start.y.max(end.y), start.z.max(end.z)); let startf = start.map(|a| (a + 1) as f32);
let endf = end.map(|a| (a + 1) as f32);
kdtree.add(&[start.x, start.y, start.z], node_index).ok()?; let min = Vec3::new(
nodes.push(start); startf.x.min(endf.x),
node_index += 1; startf.y.min(endf.y),
let mut connect = false; startf.z.min(endf.z),
for _i in 0..7000 {
let radius: f32 = rng.gen_range(radius_range.0..radius_range.1);
let radius_sqrd = radius.powi(2);
if connect {
break;
}
let sampled_point = Vec3::new(
rng.gen_range(min.x - 20.0..max.x + 20.0),
rng.gen_range(min.y - 20.0..max.y + 20.0),
rng.gen_range(min.z - 20.0..max.z - 7.0),
); );
let max = Vec3::new(
startf.x.max(endf.x),
startf.y.max(endf.y),
startf.z.max(endf.z),
);
kdtree
.add(&[startf.x, startf.y, startf.z], node_index)
.ok()?;
nodes.push(startf);
node_index += 1;
let mut connect = false;
for _i in 0..7000 {
let radius: f32 = rng.gen_range(radius_range.0..radius_range.1);
let radius_sqrd = radius.powi(2);
if connect {
break;
}
let sampled_point = Vec3::new(
rng.gen_range(min.x - 20.0..max.x + 20.0),
rng.gen_range(min.y - 20.0..max.y + 20.0),
rng.gen_range(min.z - 20.0..max.z - 7.0),
);
let nearest_index = *kdtree
.nearest_one(
&[sampled_point.x, sampled_point.y, sampled_point.z],
&squared_euclidean,
)
.ok()?
.1 as usize;
let nearest = nodes[nearest_index];
let dist_sqrd = sampled_point.distance_squared(nearest);
let new_point = if dist_sqrd > radius_sqrd {
nearest + (sampled_point - nearest).normalized().map(|a| a * radius)
} else {
sampled_point
};
if is_valid_edge(
nearest.map(|e| e.floor() as i32),
new_point.map(|e| e.floor() as i32),
) {
kdtree
.add(&[new_point.x, new_point.y, new_point.z], node_index)
.ok()?;
nodes.push(new_point);
parents.insert(node_index, nearest_index);
node_index += 1;
}
if new_point.distance_squared(endf) < radius.powi(2) {
connect = true;
}
}
let mut path = Vec::new();
let nearest_index = *kdtree let nearest_index = *kdtree
.nearest_one( .nearest_one(&[endf.x, endf.y, endf.z], &squared_euclidean)
&[sampled_point.x, sampled_point.y, sampled_point.z],
&squared_euclidean,
)
.ok()? .ok()?
.1 as usize; .1 as usize;
let nearest = nodes[nearest_index]; kdtree.add(&[endf.x, endf.y, endf.z], node_index).ok()?;
let dist_sqrd = sampled_point.distance_squared(nearest); nodes.push(endf);
let new_point = if dist_sqrd > radius_sqrd { parents.insert(node_index, nearest_index);
nearest + (sampled_point - nearest).normalized().map(|a| a * radius) path.push(endf);
} else { let mut current_node_index = node_index;
sampled_point while current_node_index > 0 {
}; current_node_index = *parents.get(&current_node_index).unwrap();
if is_valid_edge( path.push(nodes[current_node_index]);
nearest.map(|e| e.floor() as i32),
new_point.map(|e| e.floor() as i32),
) {
kdtree
.add(&[new_point.x, new_point.y, new_point.z], node_index)
.ok()?;
nodes.push(new_point);
parents.insert(node_index, nearest_index);
node_index += 1;
} }
if new_point.distance_squared(end) < radius.powi(2) {
connect = true; let mut terminals = Vec::new();
let last = nodes.len() - 1;
for (node_id, node_pos) in nodes.iter().enumerate() {
if !parents.values().any(|e| e == &node_id) && node_id != 0 && node_id != last {
terminals.push(node_pos.map(|e| e.floor() as i32));
}
} }
}
let mut path = Vec::new(); let branches = parents
let nearest_index = *kdtree .iter()
.nearest_one(&[end.x, end.y, end.z], &squared_euclidean) .map(|(a, b)| {
.ok()? (
.1 as usize; nodes[*a].map(|e| e.floor() as i32),
kdtree.add(&[end.x, end.y, end.z], node_index).ok()?; nodes[*b].map(|e| e.floor() as i32),
nodes.push(end); )
parents.insert(node_index, nearest_index); })
path.push(end); .collect::<Vec<(Vec3<i32>, Vec3<i32>)>>();
let mut current_node_index = node_index; let path = path
while current_node_index > 0 { .iter()
current_node_index = *parents.get(&current_node_index).unwrap(); .map(|a| a.map(|e| e.floor() as i32))
path.push(nodes[current_node_index]); .collect::<Vec<Vec3<i32>>>();
}
let mut terminals = Vec::new(); Some(Self {
let last = nodes.len() - 1; start,
for (node_id, node_pos) in nodes.iter().enumerate() { end,
if !parents.values().any(|e| e == &node_id) && node_id != 0 && node_id != last { branches,
terminals.push(node_pos.map(|e| e.floor() as i32)); path,
} terminals,
}
let branches = parents
.iter()
.map(|(a, b)| {
(
nodes[*a].map(|e| e.floor() as i32),
nodes[*b].map(|e| e.floor() as i32),
)
}) })
.collect::<Vec<(Vec3<i32>, Vec3<i32>)>>(); }
let path = path
.iter()
.map(|a| a.map(|e| e.floor() as i32))
.collect::<Vec<Vec3<i32>>>();
Some((branches, path, terminals))
} }
#[cfg(test)] #[cfg(test)]