use std::fmt;
use itertools::Itertools as _;
use petgraph::visit::EdgeRef as _;
use all_is_cubes::block::{self, Block, AIR};
use all_is_cubes::cgmath::Vector3;
use all_is_cubes::linking::{BlockProvider, InGenError};
use all_is_cubes::math::{
Face6, FaceMap, GridAab, GridArray, GridCoordinate, GridPoint, GridRotation, GridVector,
};
use all_is_cubes::space::SpaceTransaction;
use crate::LandscapeBlocks::{self, Leaves, Log};
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, exhaust::Exhaust)]
#[allow(clippy::exhaustive_enums)]
pub enum TreeGrowth {
Sapling = 1, G2, G3,
G4,
G5,
G6,
G7,
Block,
}
impl fmt::Display for TreeGrowth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", *self as GridCoordinate)
}
}
impl TreeGrowth {
pub fn from_radius(index: GridCoordinate) -> Self {
match index {
i if i <= 1 => Self::Sapling,
2 => Self::G2,
3 => Self::G3,
4 => Self::G4,
5 => Self::G5,
6 => Self::G6,
7 => Self::G7,
_ => Self::Block,
}
}
pub fn radius(self) -> GridCoordinate {
self as i32
}
}
pub(crate) fn make_log(
blocks: &BlockProvider<LandscapeBlocks>,
directions: FaceMap<Option<TreeGrowth>>,
leaves: Option<TreeGrowth>,
) -> Block {
let mut parts: Vec<Block> = directions
.iter()
.map(|(face, &growth)| {
let Some(growth) = growth else { return AIR; };
blocks[Log(growth)].clone().rotate(
GridRotation::from_to(Face6::NY, face, Face6::PX)
.or_else(|| GridRotation::from_to(Face6::NY, face, Face6::PZ))
.unwrap(),
)
})
.collect();
if let Some(leaves_growth) = leaves {
parts.push(blocks[Leaves(leaves_growth)].clone());
}
block::Composite::stack(
AIR,
parts
.into_iter()
.map(|block| block::Composite::new(block, block::CompositeOperator::Over)),
)
}
pub(crate) fn make_tree(
blocks: &BlockProvider<LandscapeBlocks>,
rng: &mut impl rand::Rng,
root: GridPoint,
bounds: GridAab,
) -> Result<SpaceTransaction, InGenError> {
let mut graph = Growph::new(bounds.expand(FaceMap::default().with(Face6::NY, 1)));
*graph.edge_mut(root, Face6::NY).unwrap() = Some(TreeGrowth::Sapling);
for cube in bounds.abut(Face6::PY, -1).unwrap().interior_iter() {
if let Some(l) = graph.leaves_mut(cube) {
*l = Some(TreeGrowth::Block);
}
}
let mut paths = 0;
const MAX_STEP_COST: i32 = 30;
const BASE_DISTANCE_COST: i32 = 10;
while let Some((_, path)) = petgraph::algo::astar(
&graph,
root,
|cube| {
cube != root
&& matches!(graph.leaves(cube), Some(Some(_)))
&& graph.neighbor_edges(cube) == FaceMap::default()
},
|edge_ref| {
let relative_y = ((f64::from(edge_ref.target().y - bounds.lower_bounds().y) + 0.5)
/ f64::from(bounds.size().y))
.clamp(0.0, 1.0);
let is_currently_on_existing_branch =
graph.neighbor_edges(edge_ref.source()) != FaceMap::default();
let would_enter_branch = graph.neighbor_edges(edge_ref.target()) != FaceMap::default();
if !is_currently_on_existing_branch && would_enter_branch {
MAX_STEP_COST
} else if edge_ref.growth.is_some() && rng.gen_bool(1.0 - relative_y) {
BASE_DISTANCE_COST / 2
} else {
BASE_DISTANCE_COST
}
},
|node| {
let distance = node - root;
(distance.x.abs() + distance.y.abs() + distance.z.abs()) * MAX_STEP_COST
},
) {
for (a, b) in path.into_iter().tuple_windows() {
let face = Face6::try_from(b - a).unwrap();
*graph.edge_mut(a, face).unwrap() = Some(TreeGrowth::Sapling);
graph.data[a].flow = graph.data[a].flow.saturating_add(1);
}
paths += 1;
if paths > 50 {
log::warn!("aborting branch building");
break;
}
}
graph.data[root - GridVector::unit_y()].flow = graph.data[root].flow;
for cube in graph.bounds().interior_iter() {
for face in Face6::ALL {
let neighbor = cube + face.normal_vector();
if graph.bounds().contains_cube(neighbor) {
let flow1 = graph.data[cube].flow;
let flow2 = graph.data[neighbor].flow;
let flow = flow1.min(flow2);
if let Some(edge_growth) = graph.edge_mut(cube, face) {
if edge_growth.is_some() {
let size = (f64::from(flow) * 1.1).sqrt() as GridCoordinate;
*edge_growth = Some(TreeGrowth::from_radius(size));
}
}
}
}
}
let mut txn = SpaceTransaction::default();
for (cube, log) in graph.logs(blocks) {
if log != AIR && bounds.contains_cube(cube) {
txn.set_overwrite(cube, log);
}
}
Ok(txn)
}
use graph::Growph;
mod graph {
use super::*;
pub(crate) struct Growph {
pub(crate) data: GridArray<GrowphCell>,
}
impl Growph {
pub fn new(bounds: GridAab) -> Self {
Self {
data: GridArray::from_fn(bounds, |_| GrowphCell::new()),
}
}
pub fn bounds(&self) -> GridAab {
self.data.bounds()
}
pub fn edge(&self, cube: GridPoint, neighbor_face: Face6) -> Option<Option<TreeGrowth>> {
if neighbor_face.is_negative() {
self.data
.get(cube + neighbor_face.normal_vector())
.map(|cell| cell.pos_neighbors[neighbor_face.axis_number()])
} else {
self.data
.get(cube)
.map(|cell| cell.pos_neighbors[neighbor_face.axis_number()])
}
}
pub fn edge_mut(
&mut self,
cube: GridPoint,
neighbor_face: Face6,
) -> Option<&mut Option<TreeGrowth>> {
if neighbor_face.is_negative() {
self.data
.get_mut(cube + neighbor_face.normal_vector())
.map(|cell| &mut cell.pos_neighbors[neighbor_face.axis_number()])
} else {
self.data
.get_mut(cube)
.map(|cell| &mut cell.pos_neighbors[neighbor_face.axis_number()])
}
}
pub fn neighbor_edges(&self, cube: GridPoint) -> FaceMap<Option<TreeGrowth>> {
FaceMap::from_fn(|face| self.edge(cube, face).flatten())
}
pub fn leaves(&self, cube: GridPoint) -> Option<Option<TreeGrowth>> {
self.data.get(cube).map(|cell| cell.leaves)
}
pub fn leaves_mut(&mut self, cube: GridPoint) -> Option<&mut Option<TreeGrowth>> {
self.data.get_mut(cube).map(|cell| &mut cell.leaves)
}
pub fn logs<'a>(
&'a self,
blocks: &'a BlockProvider<LandscapeBlocks>,
) -> impl Iterator<Item = (GridPoint, Block)> + 'a {
self.bounds().interior_iter().map(|cube| {
(
cube,
make_log(
blocks,
self.neighbor_edges(cube),
self.leaves(cube).flatten(),
),
)
})
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct GrowphCell {
pos_neighbors: Vector3<Option<TreeGrowth>>,
leaves: Option<TreeGrowth>,
pub(crate) flow: u16,
}
impl GrowphCell {
fn new() -> Self {
Self {
pos_neighbors: Vector3::new(None, None, None),
leaves: None,
flow: 0,
}
}
}
impl petgraph::visit::GraphBase for Growph {
type NodeId = GridPoint;
type EdgeId = (GridPoint, usize); }
impl petgraph::visit::Data for Growph {
type NodeWeight = Option<TreeGrowth>;
type EdgeWeight = Option<TreeGrowth>;
}
impl<'g> petgraph::visit::IntoEdgeReferences for &'g Growph {
type EdgeRef = GrowphEdgeRef;
type EdgeReferences = Box<dyn Iterator<Item = GrowphEdgeRef> + 'g>;
fn edge_references(self) -> Self::EdgeReferences {
todo!()
}
}
impl<'g> petgraph::visit::IntoEdges for &'g Growph {
type Edges = Box<dyn Iterator<Item = GrowphEdgeRef> + 'g>;
fn edges(self, cube: GridPoint) -> Self::Edges {
Box::new(
self.neighbor_edges(cube)
.map(|face, growth| GrowphEdgeRef::from_face(cube, face, growth))
.into_values()
.into_iter()
.filter(|edge| {
self.bounds().contains_cube(edge.source())
&& self.bounds().contains_cube(edge.target())
}),
)
}
}
impl<'g> petgraph::visit::IntoNeighbors for &'g Growph {
type Neighbors = Box<dyn Iterator<Item = GridPoint> + 'g>;
fn neighbors(self, _node: Self::NodeId) -> Self::Neighbors {
todo!()
}
}
impl petgraph::visit::Visitable for Growph {
type Map = VisitMap;
fn visit_map(&self) -> Self::Map {
VisitMap::new(self.bounds())
}
fn reset_map(&self, map: &mut Self::Map) {
*map = self.visit_map()
}
}
#[derive(Clone, Copy, Debug)]
pub struct GrowphEdgeRef {
cube: GridPoint,
face: Face6,
pub growth: Option<TreeGrowth>,
}
impl GrowphEdgeRef {
fn from_face(cube: GridPoint, face: Face6, growth: Option<TreeGrowth>) -> Self {
Self { cube, face, growth }
}
}
impl petgraph::visit::EdgeRef for GrowphEdgeRef {
type NodeId = GridPoint;
type EdgeId = (GridPoint, usize);
type Weight = Option<TreeGrowth>;
fn source(&self) -> Self::NodeId {
self.cube
}
fn target(&self) -> Self::NodeId {
self.cube + self.face.normal_vector()
}
fn weight(&self) -> &Self::Weight {
&self.growth
}
fn id(&self) -> Self::EdgeId {
(
if self.face.is_negative() {
self.target()
} else {
self.source()
},
self.face.axis_number(),
)
}
}
#[derive(Clone, Debug)]
pub struct VisitMap(GridArray<bool>);
impl VisitMap {
fn new(bounds: GridAab) -> VisitMap {
Self(GridArray::from_fn(bounds, |_| false))
}
}
impl petgraph::visit::VisitMap<GridPoint> for VisitMap {
fn visit(&mut self, node: GridPoint) -> bool {
!std::mem::replace(
self.0.get_mut(node).expect("node coordinates out of range"),
true,
)
}
fn is_visited(&self, &node: &GridPoint) -> bool {
self.0[node]
}
}
}