yog_cli/design/
worldgen.rs1use yog_worldgen::{Generator, Terrain, preview};
6
7#[derive(yog_data::Schema)]
8pub struct Preview {
9 top: String,
11 slice: String,
13 slice_top: i32,
15 slice_bottom: i32,
16}
17
18fn png(pixels: &[u32], w: u32, h: u32) -> Result<String, String> {
19 let rgba: Vec<u8> = pixels
20 .iter()
21 .flat_map(|&p| [(p >> 16) as u8, (p >> 8) as u8, p as u8, (p >> 24) as u8])
22 .collect();
23 let mut out = Vec::new();
24 let mut enc = png::Encoder::new(&mut out, w, h);
25 enc.set_color(png::ColorType::Rgba);
26 enc.set_depth(png::BitDepth::Eight);
27 enc.set_compression(png::Compression::Fast);
28 let mut writer = enc.write_header().map_err(|e| e.to_string())?;
29 writer.write_image_data(&rgba).map_err(|e| e.to_string())?;
30 writer.finish().map_err(|e| e.to_string())?;
31 Ok(super::raster::png_url(&out))
32}
33
34pub fn render(text: &str, size: u32) -> Result<Preview, String> {
36 let size = size.clamp(16, 1024);
37 let def = yog_data::text::parse_value(text)
38 .map_err(|e| format!("{}:{}: {}", e.at.line, e.at.col, e.message))?;
39 let terrain = def
40 .get(yog_ids::dimension_field::TERRAIN)
41 .ok_or("no `terrain` — this dimension's chunks come from a generator in code")?;
42 let terrain = yog_data::read_value::<Terrain>(terrain).map_err(|ps| {
43 ps.iter()
44 .map(|p| format!("terrain.{}: {}", p.path, p.message))
45 .collect::<Vec<_>>()
46 .join("\n")
47 })?;
48 let int = |k: &str| {
49 def.get(yog_ids::dimension_field::DIMENSION_TYPE)
50 .and_then(|t| t.get(k))
51 .and_then(|v| v.as_i64())
52 .map(|v| v as i32)
53 };
54 let min_y = int("min_y").unwrap_or(-64);
55 let height = int("height").unwrap_or(384);
56 let g = Generator::new(terrain);
57 let half = size as i32 / 2;
58 let top = preview::top_down(
59 &g,
60 preview::Area {
61 x: -half,
62 z: -half,
63 w: size,
64 h: size,
65 },
66 min_y,
67 );
68 let highest = (-half..half).map(|x| g.surface(x, 0)).max().unwrap_or(0);
70 let fluid = g.terrain.fluid.as_ref().map_or(i32::MIN, |f| f.level);
71 let slice_top = (highest.max(fluid) + 4).min(min_y + height - 1);
72 let slice_bottom = (slice_top - 160).max(min_y);
73 let rows = (slice_top - slice_bottom + 1) as u32;
74 let slice = preview::slice(
75 &g,
76 preview::Area {
77 x: -half,
78 z: 0,
79 w: size,
80 h: rows,
81 },
82 slice_top,
83 min_y,
84 );
85 Ok(Preview {
86 top: png(&top, size, size)?,
87 slice: png(&slice, size, (slice_top - slice_bottom + 1) as u32)?,
88 slice_top,
89 slice_bottom,
90 })
91}
92
93#[cfg(test)]
94mod tests {
95 #[test]
96 fn previews_a_dimensions_terrain() {
97 let text = r#"{ kind: "dimension", id: "hills", dimension_type: { min_y: 0, height: 256 },
98 terrain: { seed: 1, base_height: 64, noise: [{ scale: 50, amplitude: 12 }], fill: "@block:minecraft:stone",
99 fluid: { block: "@block:minecraft:water", level: 62 } } }"#;
100 let p = super::render(text, 32).unwrap();
101 assert!(
102 p.top.starts_with(super::super::raster::PNG_URL_PREFIX)
103 && p.slice.starts_with(super::super::raster::PNG_URL_PREFIX)
104 );
105 assert_eq!(p.slice_bottom, 0);
106 assert!(p.slice_top >= 66);
107 let e = super::render(r#"{ kind: "dimension", id: "x" }"#, 32)
108 .err()
109 .unwrap();
110 assert!(e.contains("no `terrain`"), "{e}");
111 }
112}