all_is_cubes/content/
testing.rs1use euclid::vec2;
2use num_traits::Euclid as _;
3use rand::{Rng as _, SeedableRng as _};
4use rand_xoshiro::Xoshiro256Plus;
5
6use crate::block::{self, AIR, Block};
7use crate::character::Spawn;
8use crate::content::{free_editing_starter_inventory, palette};
9use crate::linking::InGenError;
10use crate::math::{Face6, FaceMap, GridAab, GridCoordinate, GridSize, GridSizeCoord, Rgba};
11use crate::space::{LightPhysics, Space, SpacePhysics};
12use crate::universe::Universe;
13use crate::util::YieldProgress;
14
15#[doc(hidden)]
24pub async fn lighting_bench_space(
25 universe: &mut Universe,
26 progress: YieldProgress,
27 requested_space_size: GridSize,
28) -> Result<Space, InGenError> {
29 let layout = LightingBenchLayout::new(requested_space_size)?;
30
31 let mut space = Space::builder(layout.space_bounds())
32 .light_physics(LightPhysics::None)
33 .read_ticket(universe.read_ticket())
34 .spawn({
35 let mut spawn = Spawn::looking_at_space(layout.space_bounds(), [0., 0.5, 1.]);
36 spawn.set_inventory(free_editing_starter_inventory(true));
37 spawn
38 })
39 .build_and_mutate(|m| {
40 m.fill_uniform(
42 m.bounds().shrink(FaceMap::default().with(Face6::PY, layout.yup())).unwrap(),
43 &block::from_color!(0.5, 0.5, 0.5),
44 )
45 })
46 .unwrap();
47
48 let progress = progress.finish_and_cut(0.25).await;
49
50 let section_iter = {
52 let i = layout.section_iter();
53 progress.split_evenly(i.len()).zip(i)
54 };
55 #[expect(
56 clippy::shadow_unrelated,
57 reason = "https://github.com/rust-lang/rust-clippy/issues/11827"
58 )]
59 for (progress, (sx, sz)) in section_iter {
60 {
61 let mut rng = Xoshiro256Plus::seed_from_u64(
66 (sx + sz * i32::from(layout.array_side_lengths.x)) as u64,
67 );
68 let section_bounds = layout.section_bounds(sx, sz);
69 let color = Block::from(Rgba::new(
70 rng.random_range(0.0..=1.0),
71 rng.random_range(0.0..=1.0),
72 rng.random_range(0.0..=1.0),
73 if rng.random_bool(0.125) { 0.5 } else { 1.0 },
74 ));
75 space.mutate(universe.read_ticket(), |m| match rng.random_range(0..3) {
76 0 => {
77 m.fill_uniform(section_bounds, &color).unwrap();
78 }
79 1 => {
80 m.fill_uniform(
81 section_bounds
82 .shrink(FaceMap::default().with(Face6::PY, layout.yup()))
83 .unwrap(),
84 &color,
85 )
86 .unwrap();
87 m.fill_uniform(
88 section_bounds
89 .shrink(FaceMap {
90 nx: 1,
91 ny: 0,
92 nz: 1,
93 px: 1,
94 py: 0,
95 pz: 1,
96 })
97 .unwrap(),
98 &AIR,
99 )
100 .unwrap();
101 }
102 2 => {
103 m.fill(section_bounds, |_| {
104 if rng.random_bool(0.25) {
105 Some(&color)
106 } else {
107 Some(&AIR)
108 }
109 })
110 .unwrap();
111 }
112 _ => unreachable!("rng range"),
113 })
114 }
115 progress.finish().await;
116 }
117
118 space.set_physics(SpacePhysics {
119 light: LightPhysics::Rays {
120 maximum_distance: space.bounds().size().width.max(space.bounds().size().depth) as _,
121 },
122 sky: {
123 let sky_ground = palette::ALMOST_BLACK.to_rgb();
124 let sky_bright = palette::DAY_SKY_COLOR * 2.0;
125 let sky_dim = palette::DAY_SKY_COLOR * 0.5;
126 crate::space::Sky::Octants([
127 sky_ground, sky_ground, sky_bright, sky_bright, sky_ground, sky_ground, sky_dim, sky_dim, ])
132 },
133 ..SpacePhysics::default()
134 });
135 Ok(space)
136}
137
138struct LightingBenchLayout {
145 array_side_lengths: euclid::default::Vector2D<u8>,
146 height: u8,
147}
148
149impl LightingBenchLayout {
150 fn new(requested_space_size: GridSize) -> Result<LightingBenchLayout, InGenError> {
151 let layout = LightingBenchLayout {
152 array_side_lengths: vec2(
153 (requested_space_size.width - u32::from(Self::MARGIN))
154 / u32::from(Self::SECTION_SPACING),
155 (requested_space_size.depth - u32::from(Self::MARGIN))
156 / u32::from(Self::SECTION_SPACING),
157 )
158 .map(saturating_cast),
159 height: saturating_cast(requested_space_size.height),
160 };
161
162 if layout.section_height() < 2 {
163 return Err(InGenError::Other("height too small".into()));
164 }
165
166 Ok(layout)
167 }
168
169 const SECTION_WIDTH: u8 = 6;
171 const MARGIN: u8 = 4;
172 const SECTION_SPACING: u8 = Self::SECTION_WIDTH + Self::MARGIN;
173
174 fn space_bounds(&self) -> GridAab {
175 GridAab::from_lower_upper(
177 [0, -self.ydown() - 1, 0],
178 [
179 i32::from(Self::SECTION_SPACING) * i32::from(self.array_side_lengths.x)
180 + i32::from(Self::MARGIN),
181 1i32.saturating_add_unsigned(self.yup()),
182 i32::from(Self::SECTION_SPACING) * i32::from(self.array_side_lengths.y)
183 + i32::from(Self::MARGIN),
184 ],
185 )
186 }
187
188 fn section_height(&self) -> u8 {
189 self.height.saturating_sub(2)
191 }
192
193 fn yup(&self) -> GridSizeCoord {
195 GridSizeCoord::from(self.section_height()) * 4 / 14
196 }
197 fn ydown(&self) -> GridCoordinate {
199 GridCoordinate::from(self.section_height()).saturating_sub_unsigned(self.yup())
200 }
201
202 fn section_iter(
203 &self,
204 ) -> impl ExactSizeIterator<Item = (GridCoordinate, GridCoordinate)> + use<> {
205 let size = self.array_side_lengths;
206 let size_z = GridCoordinate::from(size.y);
207 let total = GridCoordinate::from(size.x) * size_z;
208 (0..total).map(move |i| i.div_rem_euclid(&size_z))
209 }
210
211 fn section_bounds(&self, sx: GridCoordinate, sz: GridCoordinate) -> GridAab {
213 GridAab::from_lower_size(
214 [
215 i32::from(Self::MARGIN) + sx * i32::from(Self::SECTION_SPACING),
216 -self.ydown() + 1,
217 i32::from(Self::MARGIN) + sz * i32::from(Self::SECTION_SPACING),
218 ],
219 [
220 Self::SECTION_WIDTH.into(),
221 self.section_height().into(),
222 Self::SECTION_WIDTH.into(),
223 ],
224 )
225 }
226}
227
228fn saturating_cast(input: u32) -> u8 {
229 u8::try_from(input).unwrap_or(u8::MAX)
230}