1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! Geospatial → schematic: extrude OSM-style building footprints, and raise
//! terrain from an elevation heightmap. Thin wrappers over `crate::geo`; the
//! caller does the fetching and lat/lon → block projection.
#[diplomat::bridge]
pub mod ffi {
use super::super::schematic::ffi::Schematic;
use super::super::shared::ffi::NucleationError;
/// Namespace for the geodata entry points (no network — data goes in,
/// blocks come out).
#[diplomat::opaque]
pub struct Geo;
impl Geo {
/// Extrude building footprints into a massed schematic. `buildings_json`
/// is a JSON array of objects:
/// `{"polygon": [[x, z], ...], "height": <blocks>, "block": "minecraft:...",
/// "min_y": <optional base, default 1>}`. Footprints are stamped
/// tallest-last, so overlaps keep the tallest occupant per column.
/// `base_block` (empty string = none) lays a ground slab at y=0 under the
/// whole extent. Errors `Parse` on bad JSON, `InvalidArgument` on non-UTF-8.
pub fn extrude_footprints(
buildings_json: &DiplomatStr,
base_block: &DiplomatStr,
name: &DiplomatStr,
) -> Result<Box<Schematic>, NucleationError> {
let json = std::str::from_utf8(buildings_json)
.map_err(|_| NucleationError::InvalidArgument)?;
let base =
std::str::from_utf8(base_block).map_err(|_| NucleationError::InvalidArgument)?;
let name = std::str::from_utf8(name).map_err(|_| NucleationError::InvalidArgument)?;
#[derive(serde::Deserialize)]
struct RawFootprint {
polygon: Vec<(f64, f64)>,
height: i32,
#[serde(default)]
min_y: Option<i32>,
block: String,
}
let raw: Vec<RawFootprint> =
serde_json::from_str(json).map_err(|_| NucleationError::Parse)?;
let footprints: Vec<crate::geo::Footprint> = raw
.into_iter()
.map(|b| crate::geo::Footprint {
polygon: b.polygon,
y_min: b.min_y.unwrap_or(1),
y_max: b.height,
block: b.block,
})
.collect();
let base_opt = if base.is_empty() { None } else { Some(base) };
let s = crate::geo::extrude_footprints(name, &footprints, base_opt);
Ok(Box::new(Schematic(s)))
}
/// Raise terrain from a heightmap. `heights_json` is a flat row-major
/// JSON array of per-column heights (blocks); `width` is columns per row.
/// `surface_blocks_json` is a JSON array of block names — one entry (the
/// same surface everywhere) or one per column, row-major and the same
/// length as `heights`, for elevation/slope banding. Each column's top
/// `surface_depth` blocks are its surface block, the rest are
/// `subsurface_block`. Errors `Parse` on bad JSON, `InvalidArgument` on a
/// non-positive width, empty surface list, or non-UTF-8.
pub fn heightmap_terrain(
heights_json: &DiplomatStr,
width: i32,
surface_blocks_json: &DiplomatStr,
subsurface_block: &DiplomatStr,
surface_depth: i32,
name: &DiplomatStr,
) -> Result<Box<Schematic>, NucleationError> {
let hj = std::str::from_utf8(heights_json)
.map_err(|_| NucleationError::InvalidArgument)?;
let sj = std::str::from_utf8(surface_blocks_json)
.map_err(|_| NucleationError::InvalidArgument)?;
let sub = std::str::from_utf8(subsurface_block)
.map_err(|_| NucleationError::InvalidArgument)?;
let name = std::str::from_utf8(name).map_err(|_| NucleationError::InvalidArgument)?;
if width <= 0 {
return Err(NucleationError::InvalidArgument);
}
let heights: Vec<i32> =
serde_json::from_str(hj).map_err(|_| NucleationError::Parse)?;
let surface_blocks: Vec<String> =
serde_json::from_str(sj).map_err(|_| NucleationError::Parse)?;
if surface_blocks.is_empty() {
return Err(NucleationError::InvalidArgument);
}
let s = crate::geo::heightmap_terrain(
name,
&heights,
width as usize,
&surface_blocks,
sub,
surface_depth,
);
Ok(Box::new(Schematic(s)))
}
}
}