1#![allow(
2 clippy::module_name_repetitions,
3 reason = "false positive; TODO: remove after Rust 1.84 is released"
4)]
5
6use alloc::boxed::Box;
7use core::array;
8use core::fmt;
9
10use exhaust::Exhaust;
11use rand::{Rng as _, SeedableRng as _};
12
13use all_is_cubes::arcstr;
14use all_is_cubes::block::{
15 Block, BlockAttributes, BlockCollision, Primitive,
16 Resolution::{self, R16},
17 AIR,
18};
19use all_is_cubes::linking::{BlockModule, BlockProvider, DefaultProvision, GenError, InGenError};
20use all_is_cubes::math::{zo32, Cube, FreeCoordinate, GridAab, GridCoordinate, GridVector, Rgb};
21use all_is_cubes::space::Sky;
22use all_is_cubes::space::{SetCubeError, Space};
23use all_is_cubes::universe::UniverseTransaction;
24use all_is_cubes::util::YieldProgress;
25
26use crate::alg::{array_of_noise, scale_color, voronoi_pattern, NoiseFnExt};
27use crate::{palette, tree};
28
29#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Exhaust)]
34#[non_exhaustive]
35#[allow(missing_docs)]
36pub enum LandscapeBlocks {
37 Grass,
38 GrassBlades {
39 height: GrassHeight,
40 },
41 Dirt,
42 Stone,
43 Log(tree::TreeGrowth),
45 Leaves(tree::TreeGrowth),
46}
47
48#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Exhaust, strum::IntoStaticStr)]
49#[non_exhaustive]
50#[allow(missing_docs)]
51#[repr(u8)]
52pub enum GrassHeight {
53 H1 = 1,
54 H2 = 2,
55 H3 = 3,
56 H4 = 4,
57 H5 = 5,
58 H6 = 6,
59 H7 = 7,
60 H8 = 8,
61}
62impl GrassHeight {
63 fn from_int(i: u8) -> Option<GrassHeight> {
64 match i {
65 0 => None,
66 1 => Some(Self::H1),
67 2 => Some(Self::H2),
68 3 => Some(Self::H3),
69 4 => Some(Self::H4),
70 5 => Some(Self::H5),
71 6 => Some(Self::H6),
72 7 => Some(Self::H7),
73 _ => Some(Self::H8), }
75 }
76}
77
78impl fmt::Display for LandscapeBlocks {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 match self {
81 LandscapeBlocks::Grass => write!(f, "grass"),
82 &LandscapeBlocks::GrassBlades { height } => {
83 write!(f, "grass-blades/{}", height as u8)
84 }
85 LandscapeBlocks::Dirt => write!(f, "dirt"),
86 LandscapeBlocks::Stone => write!(f, "stone"),
87 LandscapeBlocks::Log(growth) => write!(f, "log/{growth}"),
88 LandscapeBlocks::Leaves(growth) => write!(f, "leaves/{growth}"),
89 }
90 }
91}
92
93impl BlockModule for LandscapeBlocks {
94 fn namespace() -> &'static str {
95 "all-is-cubes/landscape"
96 }
97}
98
99impl DefaultProvision<Block> for LandscapeBlocks {
101 fn module_default(self) -> Block {
102 fn color_and_name(color: Rgb, name: &'static str) -> Block {
103 Block::builder()
104 .display_name(name)
105 .color(color.with_alpha_one())
106 .build()
107 }
108
109 fn blades() -> Block {
110 Block::builder()
111 .display_name("Grass Blades")
112 .color(palette::GRASS.with_alpha(zo32(0.1)))
113 .collision(BlockCollision::None)
114 .build()
115 }
116
117 use LandscapeBlocks::*;
118 match self {
119 Grass => color_and_name(palette::GRASS, "Grass"),
120 GrassBlades { height: _ } => blades(),
121 Dirt => color_and_name(palette::DIRT, "Dirt"),
122 Stone => color_and_name(palette::STONE, "Stone"),
123 Log(g) => color_and_name(
124 palette::TREE_BARK * (0.8 + (8. - g.radius() as f32) * 0.1),
125 "Wood",
126 ),
127
128 Leaves(_g) => color_and_name(palette::TREE_LEAVES, "Leaves"),
129 }
130 }
131}
132
133pub async fn install_landscape_blocks(
138 txn: &mut UniverseTransaction,
139 resolution: Resolution,
140 progress: YieldProgress,
141) -> Result<(), GenError> {
142 use LandscapeBlocks::*;
143 let colors = BlockProvider::<LandscapeBlocks>::default();
144 let rng = &mut rand_xoshiro::Xoshiro256Plus::seed_from_u64(123890483921741);
145
146 let mut grass_blade_atom = colors[GrassBlades {
147 height: GrassHeight::H4,
148 }]
149 .clone();
150 if let Primitive::Atom(atom) = grass_blade_atom.primitive_mut() {
152 atom.color = atom.color.to_rgb().with_alpha_one();
153 atom.collision = BlockCollision::None;
154 }
155
156 let blade_color_noise = {
157 let blade_color_noise_v = noise::Value::new(0x2e240365);
158 move |cube: Cube| blade_color_noise_v.at_grid(cube.lower_bounds()) * 0.12 + 1.0
159 };
160 let overhang_noise = array_of_noise(resolution, &noise::Value::new(0), |value| {
161 value * 2.5 + f64::from(resolution) * 0.75
162 });
163 let blade_noise = array_of_noise(
164 resolution.double().unwrap(),
166 &noise::ScalePoint::new(noise::OpenSimplex::new(0x7af8c181)).set_y_scale(0.1),
167 |value| value * (f64::from(resolution) * 1.7) + (f64::from(resolution) * -0.4),
168 );
169
170 let stone_points: Box<[_; 240]> = Box::new(array::from_fn(|_| {
172 (
173 Cube::ORIGIN.aab().random_point(rng),
174 scale_color(colors[Stone].clone(), rng.gen_range(0.9..1.1), 0.02),
175 )
176 }));
177 let stone_pattern = voronoi_pattern(resolution, true, &*stone_points);
178
179 let dirt_points: Box<[_; 1024]> = Box::new(array::from_fn(|_| {
181 (
182 Cube::ORIGIN.aab().random_point(rng),
183 scale_color(colors[Dirt].clone(), rng.gen_range(0.9..1.1), 0.02),
184 )
185 }));
186 let dirt_pattern = voronoi_pattern(resolution, true, &*dirt_points);
187
188 let bark_noise = {
190 let noise = noise::ScalePoint::new(noise::Value::new(0x28711937)).set_y_scale(1. / 4.);
191 move |cube: Cube| noise.at_grid(cube.lower_bounds()) * 0.4 + 0.7
192 };
193
194 let attributes_from = |block: &Block| -> Result<BlockAttributes, InGenError> {
195 Ok(block
196 .evaluate()
197 .map_err(InGenError::other)?
198 .attributes()
199 .clone())
200 };
201
202 BlockProvider::<LandscapeBlocks>::new(progress, |key| {
203 let grass_blades = |txn, height: GrassHeight| -> Result<Block, InGenError> {
204 let height_index = height as GridCoordinate - 1;
205 let noise_section = GridVector::new(
207 height_index.rem_euclid(2),
208 height_index.div_euclid(2).rem_euclid(2),
209 height_index.div_euclid(4).rem_euclid(2),
210 ) * GridCoordinate::from(resolution);
211
212 let ao_fudge = 1.0 + f64::from(height as u8) * 0.15;
218
219 Ok(Block::builder()
220 .attributes(attributes_from(&grass_blade_atom)?)
221 .display_name(arcstr::format!("Grass Blades {}", height as u8))
222 .voxels_fn(resolution, |cube| {
223 let mut cube_for_lookup = cube;
224 cube_for_lookup.y = 0;
225 cube_for_lookup += noise_section;
226 if f64::from(cube.y - height_index) < blade_noise[cube_for_lookup] {
227 scale_color(
228 grass_blade_atom.clone(),
229 blade_color_noise(cube) * ao_fudge,
230 0.02,
231 )
232 } else {
233 AIR
234 }
235 })?
236 .build_txn(txn))
237 };
238
239 Ok(match key {
240 Stone => Block::builder()
241 .attributes(attributes_from(&colors[Stone])?)
242 .voxels_fn(resolution, &stone_pattern)?
243 .build_txn(txn),
244
245 Grass => Block::builder()
246 .attributes(attributes_from(&colors[Grass])?)
247 .voxels_fn(resolution, |cube| {
248 if f64::from(cube.y) >= overhang_noise[cube] {
249 scale_color(colors[Grass].clone(), blade_color_noise(cube), 0.02)
250 } else {
251 dirt_pattern(cube).clone()
252 }
253 })?
254 .build_txn(txn),
255
256 GrassBlades { height } => grass_blades(txn, height)?,
257
258 Dirt => Block::builder()
259 .attributes(attributes_from(&colors[Dirt])?)
260 .voxels_fn(resolution, &dirt_pattern)?
261 .build_txn(txn),
262
263 key @ Log(growth) => {
264 let resolution = R16;
265 let mid = GridCoordinate::from(resolution) / 2;
266 let radius = growth as GridCoordinate;
267 let trunk_box = GridAab::from_lower_upper(
268 [mid - radius, 0, mid - radius],
269 [mid + radius, mid + radius, mid + radius],
270 );
271 let color_block = &colors[key];
272 Block::builder()
273 .attributes(attributes_from(color_block)?)
274 .voxels_fn(resolution, |cube| {
275 if trunk_box.contains_cube(cube) {
276 scale_color(color_block.clone(), bark_noise(cube), 0.05)
278 } else {
279 AIR
280 }
281 })?
282 .build_txn(txn)
283 }
284
285 key @ Leaves(growth) => Block::builder()
286 .attributes(attributes_from(&colors[key])?)
287 .voxels_fn(resolution, |cube| {
288 let radius_vec =
293 cube.map(|c| (c * 2 + 1 - GridCoordinate::from(resolution)).abs() / 2 + 1);
294 let radius = radius_vec
295 .x
296 .abs()
297 .max(radius_vec.y.abs())
298 .max(radius_vec.z.abs());
299
300 let signed_distance_from_edge = radius - growth.radius();
301 let unit_scale_distance =
302 f64::from(signed_distance_from_edge) / f64::from(growth.radius());
303
304 if unit_scale_distance <= 1.0
305 && !rng.gen_bool(
306 ((unit_scale_distance * 4.0).powi(2) / 2.0 + 0.5).clamp(0., 1.),
307 )
308 {
309 &colors[key]
310 } else {
311 &AIR
312 }
313 })?
314 .build_txn(txn),
315 })
316 })
317 .await?
318 .install(txn)?;
319 Ok(())
320}
321
322pub fn wavy_landscape(
340 region: GridAab,
341 space: &mut Space,
342 blocks: &BlockProvider<LandscapeBlocks>,
343 max_slope: FreeCoordinate,
344) -> Result<(), SetCubeError> {
345 let slope_scaled = max_slope / 0.904087;
347 let middle_y = (region.lower_bounds().y + region.upper_bounds().y) / 2;
348
349 let grass_at = grass_placement_function(0x21b5cc6b);
350
351 for x in region.x_range() {
352 for z in region.z_range() {
353 let fx = FreeCoordinate::from(x);
354 let fz = FreeCoordinate::from(z);
355 let terrain_variation = slope_scaled
356 * (((fx / 8.0).sin() + (fz / 8.0).sin()) * 1.0
357 + ((fx / 14.0).sin() + (fz / 14.0).sin()) * 3.0
358 + ((fx / 2.0).sin() + (fz / 2.0).sin()) * 0.6);
359 let surface_y = middle_y + (terrain_variation as GridCoordinate);
360 for y in region.y_range() {
361 let altitude = y - surface_y;
362 use LandscapeBlocks::*;
363 let cube = Cube::new(x, y, z);
364 let block: &Block = if altitude > 1 {
365 continue;
366 } else if altitude == 1 {
367 if let Some(height) = grass_at(cube) {
368 &blocks[GrassBlades { height }]
370 } else {
371 &AIR
372 }
373 } else if altitude == 0 {
374 &blocks[Grass]
375 } else if altitude == -1 {
376 &blocks[Dirt]
377 } else {
378 &blocks[Stone]
379 };
380 space.set(cube, block)?;
381 }
383 }
384 }
385 Ok(())
386}
387
388pub(crate) fn grass_placement_function(seed: u32) -> impl Fn(Cube) -> Option<GrassHeight> {
389 let grass_noise = noise::ScalePoint::new(
390 noise::ScaleBias::new(noise::OpenSimplex::new(seed))
391 .set_bias(1.0) .set_scale(15.0), )
394 .set_scale(0.25);
395
396 move |cube| GrassHeight::from_int(grass_noise.at_cube(cube) as u8)
397}
398
399pub(crate) fn sky_with_grass(sky_color: Rgb) -> Sky {
401 let ground = palette::GRASS.with_alpha_one().reflect(sky_color);
402 Sky::Octants([
403 ground, ground, sky_color, sky_color, ground, ground, sky_color, sky_color, ])
406}