use crate::block_state::BlockState;
use crate::building::{PolygonPrism, Shape};
use crate::universal_schematic::UniversalSchematic;
pub struct Footprint {
pub polygon: Vec<(f64, f64)>,
pub y_min: i32,
pub y_max: i32,
pub block: String,
}
pub fn extrude_footprints(
name: &str,
footprints: &[Footprint],
base_block: Option<&str>,
) -> UniversalSchematic {
let mut s = UniversalSchematic::new(name.to_string());
if let Some(base) = base_block.filter(|b| !b.is_empty()) {
let (mut lo_x, mut lo_z) = (f64::INFINITY, f64::INFINITY);
let (mut hi_x, mut hi_z) = (f64::NEG_INFINITY, f64::NEG_INFINITY);
for f in footprints {
for &(x, z) in &f.polygon {
lo_x = lo_x.min(x);
lo_z = lo_z.min(z);
hi_x = hi_x.max(x);
hi_z = hi_z.max(z);
}
}
if lo_x.is_finite() {
let bs = BlockState::new(base);
for x in lo_x.floor() as i32..=hi_x.ceil() as i32 {
for z in lo_z.floor() as i32..=hi_z.ceil() as i32 {
s.set_block(x, 0, z, &bs);
}
}
}
}
let mut order: Vec<usize> = (0..footprints.len()).collect();
order.sort_by_key(|&i| footprints[i].y_max);
for &i in &order {
let f = &footprints[i];
if f.polygon.len() < 3 {
continue;
}
let bs = BlockState::new(f.block.as_str());
let prism = PolygonPrism::new(f.polygon.clone(), f.y_min, f.y_max);
prism.for_each_point(|x, y, z| {
s.set_block(x, y, z, &bs);
});
}
s
}
pub fn heightmap_terrain(
name: &str,
heights: &[i32],
width: usize,
surface_blocks: &[String],
subsurface_block: &str,
surface_depth: i32,
) -> UniversalSchematic {
let mut s = UniversalSchematic::new(name.to_string());
if width == 0 || surface_blocks.is_empty() {
return s;
}
let sub = BlockState::new(subsurface_block);
let surface_depth = surface_depth.max(1);
let uniform = surface_blocks.len() == 1;
let depth = heights.len() / width;
for gz in 0..depth {
for gx in 0..width {
let idx = gz * width + gx;
let h = heights[idx].max(0);
let surf = if uniform {
BlockState::new(surface_blocks[0].as_str())
} else {
BlockState::new(surface_blocks[idx.min(surface_blocks.len() - 1)].as_str())
};
for y in 0..=h {
let bs = if y > h - surface_depth { &surf } else { &sub };
s.set_block(gx as i32, y, gz as i32, bs);
}
}
}
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn footprints_extrude_tallest_wins() {
let fps = vec![
Footprint {
polygon: vec![(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)],
y_min: 1,
y_max: 3,
block: "minecraft:bricks".into(),
},
Footprint {
polygon: vec![(4.0, 4.0), (7.0, 4.0), (7.0, 7.0), (4.0, 7.0)],
y_min: 1,
y_max: 20,
block: "minecraft:quartz_block".into(),
},
];
let s = extrude_footprints("t", &fps, Some("minecraft:gray_concrete"));
assert_eq!(s.get_block(0, 0, 0).unwrap().name, "minecraft:gray_concrete");
assert_eq!(s.get_block(1, 2, 1).unwrap().name, "minecraft:bricks");
assert_eq!(s.get_block(5, 2, 5).unwrap().name, "minecraft:quartz_block");
assert_eq!(s.get_block(5, 18, 5).unwrap().name, "minecraft:quartz_block");
}
#[test]
fn heightmap_caps_with_surface() {
let s = heightmap_terrain(
"t",
&[2, 2, 5, 5],
2,
&["minecraft:grass_block".to_string()],
"minecraft:stone",
1,
);
assert_eq!(s.get_block(0, 2, 0).unwrap().name, "minecraft:grass_block");
assert_eq!(s.get_block(0, 1, 0).unwrap().name, "minecraft:stone");
assert_eq!(s.get_block(0, 5, 1).unwrap().name, "minecraft:grass_block");
assert!(
s.get_block(0, 3, 0).map_or(true, |b| b.name == "minecraft:air"),
"nothing solid above the column top"
);
}
#[test]
fn heightmap_per_column_surface_bands() {
let s = heightmap_terrain(
"t",
&[3, 3],
2,
&["minecraft:snow_block".to_string(), "minecraft:gravel".to_string()],
"minecraft:stone",
1,
);
assert_eq!(s.get_block(0, 3, 0).unwrap().name, "minecraft:snow_block");
assert_eq!(s.get_block(1, 3, 0).unwrap().name, "minecraft:gravel");
assert_eq!(s.get_block(0, 0, 0).unwrap().name, "minecraft:stone");
}
}