feat(exporters): implement image_dimensions with TDD
This commit is contained in:
@@ -1,5 +1,55 @@
|
|||||||
use lib::{BlockPalette, StructureExporter, VoxelGrid, VoxelType};
|
use lib::{BlockPalette, StructureExporter, VoxelGrid, VoxelType};
|
||||||
|
|
||||||
|
const GAP: u32 = 1;
|
||||||
|
|
||||||
|
// Returns VoxelTypes that (1) are configured in the palette AND (2) appear in the grid.
|
||||||
|
fn active_voxel_types(palette: &BlockPalette, grid: &VoxelGrid) -> Vec<VoxelType> {
|
||||||
|
let mut candidates = vec![VoxelType::Body];
|
||||||
|
if palette.blocks.outline.is_some() {
|
||||||
|
candidates.push(VoxelType::Outline);
|
||||||
|
}
|
||||||
|
if palette.blocks.shadow.is_some() {
|
||||||
|
candidates.push(VoxelType::Shadow);
|
||||||
|
}
|
||||||
|
candidates.retain(|&vt| {
|
||||||
|
(0..grid.depth).any(|z|
|
||||||
|
(0..grid.height).any(|y|
|
||||||
|
(0..grid.width).any(|x| grid.get(x, y, z) == Some(vt))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
candidates
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image_dimensions(grid: &VoxelGrid, palette: &BlockPalette, cell_size: u32) -> (u32, u32) {
|
||||||
|
let label_height = cell_size + 4;
|
||||||
|
let per_layer_height = if grid.height == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
grid.height * (cell_size + GAP) - GAP
|
||||||
|
};
|
||||||
|
let layer_block_height = label_height + GAP + per_layer_height;
|
||||||
|
let total_grid_height = grid.depth * layer_block_height
|
||||||
|
+ grid.depth.saturating_sub(1) * GAP;
|
||||||
|
|
||||||
|
let active = active_voxel_types(palette, grid);
|
||||||
|
let legend_height = if active.is_empty() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
active.len() as u32 * (cell_size + GAP) + GAP
|
||||||
|
};
|
||||||
|
|
||||||
|
let img_width = if grid.width == 0 {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
GAP + grid.width * (cell_size + GAP)
|
||||||
|
};
|
||||||
|
let img_height = total_grid_height
|
||||||
|
+ if legend_height > 0 { GAP + legend_height } else { 0 };
|
||||||
|
|
||||||
|
(img_width, img_height)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct PngExporter {
|
pub struct PngExporter {
|
||||||
palette: BlockPalette,
|
palette: BlockPalette,
|
||||||
cell_size: u32,
|
cell_size: u32,
|
||||||
@@ -20,3 +70,50 @@ impl StructureExporter for PngExporter {
|
|||||||
"png"
|
"png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn palette_full() -> BlockPalette {
|
||||||
|
serde_json::from_str(r#"{
|
||||||
|
"name": "test",
|
||||||
|
"blocks": {
|
||||||
|
"body": "minecraft:stone",
|
||||||
|
"outline": "minecraft:cobblestone",
|
||||||
|
"shadow": "minecraft:gravel"
|
||||||
|
}
|
||||||
|
}"#).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn palette_body_only() -> BlockPalette {
|
||||||
|
serde_json::from_str(r#"{
|
||||||
|
"name": "test",
|
||||||
|
"blocks": { "body": "minecraft:stone" }
|
||||||
|
}"#).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dimensions_single_layer() {
|
||||||
|
let grid = VoxelGrid::new(4, 3, 1);
|
||||||
|
let palette = palette_full();
|
||||||
|
let (w, h) = image_dimensions(&grid, &palette, 16);
|
||||||
|
// width: 1 + 4*(16+1) = 69
|
||||||
|
assert_eq!(w, 69);
|
||||||
|
// label_height = 20, per_layer = 3*17-1 = 50, layer_block = 71
|
||||||
|
// total_grid = 71
|
||||||
|
// legend = 3*17 + 1 = 52 (BUT: all-None grid → 0 active types → legend_height=0)
|
||||||
|
// total_h = 71 + 0 = 71
|
||||||
|
assert_eq!(h, 71);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dimensions_multi_layer() {
|
||||||
|
let g1 = VoxelGrid::new(4, 3, 1);
|
||||||
|
let g3 = VoxelGrid::new(4, 3, 3);
|
||||||
|
let palette = palette_full();
|
||||||
|
let (_, h1) = image_dimensions(&g1, &palette, 16);
|
||||||
|
let (_, h3) = image_dimensions(&g3, &palette, 16);
|
||||||
|
assert!(h3 > h1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user