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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
//! First-run game content. (Well, all runs, since we don't have saving yet.)

use macro_rules_attribute::macro_rules_derive;
use paste::paste;

use all_is_cubes::block::Block;
use all_is_cubes::cgmath::{EuclideanSpace as _, Point3};
use all_is_cubes::character::{Character, Spawn};
use all_is_cubes::content::free_editing_starter_inventory;
use all_is_cubes::linking::{BlockProvider, GenError, InGenError};
use all_is_cubes::math::{
    Face6, FaceMap, FreeCoordinate, GridAab, GridCoordinate, GridVector, Rgb, Rgba,
};
use all_is_cubes::space::{LightPhysics, Space};
use all_is_cubes::universe::{Name, URef, Universe, UniverseIndex};
use all_is_cubes::util::YieldProgress;

use crate::fractal::menger_sponge;
use crate::menu::template_menu;
use crate::{atrium::atrium, demo_city, dungeon::demo_dungeon, install_demo_blocks};
use crate::{wavy_landscape, LandscapeBlocks};

/// Generate a `#[test]` function for each element of [`UniverseTemplate`].
/// This macro is used as a derive macro via [`macro_rules_derive`].
macro_rules! generate_template_test {
    (
        $(#[$type_meta:meta])*
        $vis:vis enum $enum_name:ident {
            $(
                $( #[doc = $doc:literal] )*
                $( #[cfg($variant_cfg:meta)] )*
                $variant_name:ident
            ),* $(,)?
        }
    ) => {
        $(
            paste! {
                $( #[cfg($variant_cfg)] )*
                #[test]
                #[allow(non_snake_case)]
                fn [< template_ $variant_name >] () {
                    tests::check_universe_template($enum_name::$variant_name);
                }
            }
        )*
    }
}

/// Selection of initial content for constructing a new [`Universe`].
//
// TODO: Stop using strum, because we will eventually want parameterized templates.
#[derive(
    Clone,
    Debug,
    Eq,
    Hash,
    PartialEq,
    strum::Display,
    strum::EnumString,
    strum::EnumIter,
    strum::IntoStaticStr,
)]
#[strum(serialize_all = "kebab-case")]
#[non_exhaustive]
#[macro_rules_derive(generate_template_test!)]
pub enum UniverseTemplate {
    /// Provides an interactive menu of other templates.
    Menu,

    /// New universe with no contents at all.
    Blank,

    /// Always produces an error, for testing error-handling functionality.
    Fail,

    /// Space with assorted “exhibits” demonstrating or testing various features of All is Cubes.
    DemoCity,

    /// Randomly generated connected rooms.
    /// Someday this might have challenges or become a tutorial.
    Dungeon,

    /// Large space with separate floating islands.
    Islands,

    /// A procedural voxel version of the classic [Sponza] Atrium rendering test scene.
    ///
    /// [Sponza]: https://en.wikipedia.org/wiki/Sponza_Palace
    Atrium,

    /// A procedural voxel version of the classic [Cornell Box] rendering test scene.
    ///
    /// [Cornell Box]: https://en.wikipedia.org/wiki/Cornell_box
    CornellBox,

    /// A [Menger sponge] fractal.
    ///
    /// [Menger sponge]: https://en.wikipedia.org/wiki/Menger_sponge
    MengerSponge,

    /// A test scene containing various shapes and colors to exercise the lighting algorithm.
    LightingBench,

    /// Use entirely random choices.
    ///
    /// TODO: This doesn't yet produce anything even visible — we need more sanity constraints.
    #[cfg(feature = "arbitrary")]
    Random,
    // TODO: add an "nothing, you get a blank editor" option once we have enough editing support.
}

impl UniverseTemplate {
    /// Whether the template should be shown to users.
    /// (This does not control )
    pub fn include_in_lists(&self) -> bool {
        use UniverseTemplate::*;
        match self {
            DemoCity | Dungeon | Atrium | Islands | CornellBox | MengerSponge | LightingBench => {
                true
            }

            // Itself a list of templates!
            Menu => false,

            // More testing than interesting demos.
            Blank | Fail => false,

            #[cfg(feature = "arbitrary")]
            Random => false,
        }
    }

    /// Create a new [`Universe`] based on this template's specifications.
    ///
    /// The `seed` will be used for any randomization which the template performs.
    /// Not all templates have random elements.
    //
    // Design note: u64 was chosen as that both `std::hash::Hasher` and `rand::SeedableRng`
    // agree on this many bits.
    pub async fn build(self, p: YieldProgress, seed: u64) -> Result<Universe, GenError> {
        let mut universe = Universe::new();

        // TODO: Later we want a "module loading" system that can lazily bring in content.
        // For now, unconditionally add all these blocks.
        let [demo_blocks_progress, p] = p.split(0.1);
        install_demo_blocks(&mut universe, demo_blocks_progress).await?;
        p.progress(0.).await;

        let default_space_name: Name = "space".into();

        let mut p = Some(p);
        use UniverseTemplate::*;
        let maybe_space: Option<Result<Space, InGenError>> = match self {
            Menu => Some(template_menu(&mut universe)),
            Blank => None,
            Fail => Some(Err(InGenError::Other(
                "the Fail template always fails to generate".into(),
            ))),
            DemoCity => Some(demo_city(&mut universe, p.take().unwrap()).await),
            Dungeon => Some(demo_dungeon(&mut universe, p.take().unwrap(), seed).await),
            Islands => Some(islands(&mut universe, p.take().unwrap(), 1000).await),
            Atrium => Some(atrium(&mut universe, p.take().unwrap()).await),
            CornellBox => Some(cornell_box()),
            MengerSponge => Some(menger_sponge(&mut universe, 4)),
            LightingBench => Some(all_is_cubes::content::testing::lighting_bench_space(
                &mut universe,
            )),
            #[cfg(feature = "arbitrary")]
            Random => Some(arbitrary_space(&mut universe, p.take().unwrap(), seed).await),
        };

        if let Some(p) = p {
            p.progress(1.0).await;
        }

        // Insert the space and generate the initial character.
        if let Some(space_result) = maybe_space {
            let space_ref =
                insert_generated_space(&mut universe, default_space_name, space_result)?;

            // TODO: "character" is a special default name used for finding the character the
            // player actually uses, and we should replace that or handle it more formally.
            universe.insert("character".into(), Character::spawn_default(space_ref))?;
        }

        Ok(universe)
    }
}

impl Default for UniverseTemplate {
    fn default() -> Self {
        Self::DemoCity
    }
}

/// TODO: This should be a general Universe tool for "insert a generated value or report an error"
/// but for now was written to help out `UniverseTemplate::build`
fn insert_generated_space(
    universe: &mut Universe,
    name: Name,
    result: Result<Space, InGenError>,
) -> Result<URef<Space>, GenError> {
    match result {
        Ok(space) => Ok(universe.insert(name, space)?),
        Err(e) => Err(GenError::failure(e, name)),
    }
}

async fn islands(
    universe: &mut Universe,
    p: YieldProgress,
    diameter: GridCoordinate,
) -> Result<Space, InGenError> {
    let landscape_blocks = BlockProvider::<LandscapeBlocks>::using(universe)?;

    // Set up dimensions
    let bounds = {
        let height = 400;
        let low_corner = diameter / -2;
        GridAab::from_lower_size(
            [low_corner, height / -2, low_corner],
            [diameter, height, diameter],
        )
    };

    let mut space = Space::builder(bounds)
        .spawn({
            let mut spawn = Spawn::default_for_new_space(bounds);
            spawn.set_inventory(free_editing_starter_inventory(true));
            spawn.set_eye_position(bounds.center());
            // TODO: Make this tidier by having a "shrink to centermost point or cube" operation on GridAab
            let cp = bounds.center().map(|c| c as GridCoordinate);
            spawn.set_bounds(GridAab::from_lower_size(
                cp - GridVector::new(30, 30, 30),
                [60, 60, 60],
            ));
            spawn
        })
        .build();

    // Set up grid in which islands are placed
    let island_stride = 50;
    let island_grid = bounds.divide(island_stride);

    for (i, island_pos) in island_grid.interior_iter().enumerate() {
        let cell_bounds = GridAab::from_lower_size(
            Point3::from_vec(island_pos.to_vec() * island_stride),
            [island_stride, island_stride, island_stride],
        )
        .intersection(bounds)
        .expect("island outside space bounds");
        // TODO: randomize island location in cell?
        let occupied_bounds = cell_bounds.expand(FaceMap::repeat(-10).with(Face6::PY, -25));
        wavy_landscape(occupied_bounds, &mut space, &landscape_blocks, 0.5)?;
        p.progress(i as f32 / island_grid.volume() as f32).await;
    }

    Ok(space)
}

#[rustfmt::skip]
fn cornell_box() -> Result<Space, InGenError> {
    // Coordinates are set up based on this dimension because, being blocks, we're not
    // going to *exactly* replicate the original data, but we might want to adjust the
    // scale to something else entirely.
    let box_size = 55;
    // Add one block to all sides for wall thickness.
    let bounds = GridAab::from_lower_size(
        [-1, -1, -1],
        GridVector::new(1, 1, 1) * box_size + GridVector::new(2, 2, 2),
    );
    let mut space = Space::builder(bounds)
        // There shall be no light but that which we make for ourselves!
        .sky_color(Rgb::ZERO)
        .light_physics(LightPhysics::Rays {
            maximum_distance: (box_size * 2).try_into().unwrap_or(u16::MAX),
        })
        .spawn({
            let mut spawn = Spawn::default_for_new_space(bounds);
            spawn.set_inventory(free_editing_starter_inventory(true));
            spawn.set_eye_position(Point3::<FreeCoordinate>::new(0.5, 0.5, 1.6) * box_size.into());
            spawn
        })
        .build();

    let white: Block = Rgba::new(1.0, 1.0, 1.0, 1.0).into();
    let red: Block = Rgba::new(0.57, 0.025, 0.025, 1.0).into();
    let green: Block = Rgba::new(0.025, 0.236, 0.025, 1.0).into();
    let light: Block = Block::builder()
        .display_name("Light")
        .light_emission(Rgb::new(40., 40., 40.))
        .color(Rgba::new(1.0, 1.0, 1.0, 1.0))
        .build();

    // Floor.
    space.fill_uniform(GridAab::from_lower_size([0, -1, 0], [box_size, 1, box_size]), &white)?;
    // Ceiling.
    space.fill_uniform(GridAab::from_lower_size([0, box_size, 0], [box_size, 1, box_size]), &white)?;
    // Light in ceiling.
    space.fill_uniform(GridAab::from_lower_upper([21, box_size, 23], [34, box_size + 1, 33]), &light)?;
    // Back wall.
    space.fill_uniform(GridAab::from_lower_size([0, 0, -1], [box_size, box_size, 1]), &white)?;
    // Right wall (green).
    space.fill_uniform(GridAab::from_lower_size([box_size, 0, 0], [1, box_size, box_size]), &green)?;
    // Left wall (red).
    space.fill_uniform(GridAab::from_lower_size([-1, 0, 0], [1, box_size, box_size]), &red)?;

    // Block #1
    space.fill_uniform(GridAab::from_lower_size([29, 0, 36], [16, 16, 15]), &white)?;
    // Block #2
    space.fill_uniform(GridAab::from_lower_size([10, 0, 13], [18, 33, 15]), &white)?;

    Ok(space)
}

#[cfg(feature = "arbitrary")]
async fn arbitrary_space(
    _: &mut Universe,
    mut progress: YieldProgress,
    seed: u64,
) -> Result<Space, InGenError> {
    use all_is_cubes::cgmath::{Vector3, Zero};
    use arbitrary::{Arbitrary, Error, Unstructured};
    use rand::{RngCore, SeedableRng};

    let mut rng = rand_xoshiro::Xoshiro256Plus::seed_from_u64(seed);
    let mut bytes = [0u8; 16384];
    let mut attempt = 0;
    loop {
        attempt += 1;
        rng.fill_bytes(&mut bytes);
        let r: Result<Space, _> = Arbitrary::arbitrary(&mut Unstructured::new(&bytes));
        match r {
            Ok(mut space) => {
                // Patch spawn position to be reasonable
                let bounds = space.bounds();
                let mut spawn = space.spawn().clone();
                spawn.set_bounds(bounds.expand(FaceMap::repeat(20)));
                spawn.set_eye_position(bounds.center());
                space.set_spawn(spawn);

                // Patch physics to be reasonable
                let mut p = space.physics().clone();
                p.gravity = Vector3::zero(); // won't be a floor
                p.sky_color = p.sky_color * (0.5 / p.sky_color.luminance());
                space.set_physics(p);

                // TODO: These patches are still not enough to get a good result.

                return Ok(space);
            }
            Err(Error::IncorrectFormat) => {
                if attempt >= 1000000 {
                    return Err(InGenError::Other("Out of attempts".into()));
                }
            }
            Err(e) => panic!("{}", e),
        }
        progress = progress.finish_and_cut(0.1).await; // mostly nonsense but we do want to yield
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use all_is_cubes::time::Tick;
    use futures_core::future::BoxFuture;
    use futures_executor::block_on;

    fn _test_build_future_is_send() {
        let _: BoxFuture<'_, _> =
            Box::pin(UniverseTemplate::Atrium.build(YieldProgress::noop(), 0));
    }

    pub(super) fn check_universe_template(template: UniverseTemplate) {
        let result = if let UniverseTemplate::Islands = template {
            // Kludge: the islands template is known to be very slow.
            // We should work on making what it does faster, but for now, let's
            // run a much smaller instance of it for the does-it-succeed test.
            // TODO: This should instead be handled by the template having an official
            // user-configurable size parameter.
            block_on(async {
                let mut universe = Universe::new();
                install_demo_blocks(&mut universe, YieldProgress::noop())
                    .await
                    .unwrap();
                islands(&mut universe, YieldProgress::noop(), 100)
                    .await
                    .unwrap();
            });
            return; // TODO: skipping final test because we didn't create the character
        } else {
            block_on(
                template
                    .clone()
                    .build(YieldProgress::noop(), 0x7f16dfe65954583e),
            )
        };

        if matches!(template, UniverseTemplate::Fail) {
            // The Fail template _should_ return an error
            result.unwrap_err();
        } else {
            let mut u = result.unwrap();

            if template != UniverseTemplate::Blank {
                let _ = u.get_default_character().unwrap().read().unwrap();
            }
            u.step(Tick::arbitrary());
        }
    }
}