use std::sync::Arc;
use futures_core::future::BoxFuture;
use instant::Instant;
use all_is_cubes::cgmath::{One as _, Vector3};
use all_is_cubes::drawing::embedded_graphics::{
mono_font::iso_8859_1 as font,
text::{Alignment, Baseline, TextStyleBuilder},
};
use all_is_cubes::block::{self, Block, BlockAttributes, BlockCollision, Resolution::*, AIR};
use all_is_cubes::character::Spawn;
use all_is_cubes::content::palette;
use all_is_cubes::drawing::VoxelBrush;
use all_is_cubes::inv::Slot;
use all_is_cubes::inv::Tool;
use all_is_cubes::linking::{BlockProvider, InGenError};
use all_is_cubes::math::{
Face6, FaceMap, FreeCoordinate, GridAab, GridCoordinate, GridMatrix, GridRotation, GridVector,
Rgb,
};
use all_is_cubes::raycast::Raycaster;
use all_is_cubes::space::{LightPhysics, Space, SpaceBuilder, SpacePhysics};
use all_is_cubes::transaction::Transaction;
use all_is_cubes::universe::Universe;
use all_is_cubes::util::YieldProgress;
use all_is_cubes_ui::logo::logo_text;
use all_is_cubes_ui::vui::{
install_widgets, widgets, Align, Gravity, LayoutGrant, LayoutTree, WidgetTree,
};
use crate::{
clouds::clouds, exhibits::DEMO_CITY_EXHIBITS, noise::NoiseFnExt, space_to_space_copy,
wavy_landscape, DemoBlocks, LandscapeBlocks,
};
pub(crate) async fn demo_city(
universe: &mut Universe,
p: YieldProgress,
) -> Result<Space, InGenError> {
let start_city_time = Instant::now();
let landscape_blocks = BlockProvider::<LandscapeBlocks>::using(universe)?;
let demo_blocks = BlockProvider::<DemoBlocks>::using(universe)?;
use DemoBlocks::*;
use LandscapeBlocks::*;
let [mut ui_blocks_progress, p] = p.split(0.05);
ui_blocks_progress.set_label("UiBlocks");
all_is_cubes_ui::vui::blocks::UiBlocks::new(universe, ui_blocks_progress)
.await
.install(universe)
.unwrap();
let [mut icons_blocks_progress, p] = p.split(0.05);
icons_blocks_progress.set_label("Icons");
all_is_cubes::inv::Icons::new(universe, icons_blocks_progress)
.await
.install(universe)
.unwrap();
let road_radius = CityPlanner::ROAD_RADIUS;
let lamp_position_radius = CityPlanner::LAMP_POSITION_RADIUS;
let exhibit_front_radius = CityPlanner::PLOT_FRONT_RADIUS;
let lamp_spacing = 20;
let sky_height = 30;
let ground_depth = 30; let underground_floor_y = -5;
let radius_xz = 80;
let bounds = GridAab::from_lower_upper(
(-radius_xz, -ground_depth, -radius_xz),
(radius_xz, sky_height, radius_xz),
);
let mut planner = CityPlanner::new(bounds);
let lamp_brush = VoxelBrush::new([
((0, 0, 0), &demo_blocks[LamppostBase]),
((0, 1, 0), &demo_blocks[LamppostSegment]),
((0, 2, 0), &demo_blocks[LamppostTop]),
((0, 3, 0), &demo_blocks[Lamp]),
]);
let mut space = Space::builder(bounds)
.sky_color(Rgb::new(0.9, 0.9, 1.4))
.light_physics(LightPhysics::None) .spawn({
let mut spawn = Spawn::default_for_new_space(bounds);
spawn.set_bounds(GridAab::from_lower_upper(
[-road_radius, 1, 0],
[road_radius + 1, sky_height, 17],
));
let mut inventory = vec![
Tool::RemoveBlock { keep: true }.into(),
Tool::Jetpack { active: false }.into(),
Tool::PushPull.into(),
];
for block in [
&landscape_blocks[Stone],
&demo_blocks[GlassBlock],
&demo_blocks[Lamp],
&demo_blocks[Arrow],
] {
inventory.push(Slot::stack(40, Tool::Block(block.clone())));
}
inventory.push(Slot::stack(
1,
Tool::InfiniteBlocks(demo_blocks[Explosion(0)].clone()),
));
spawn.set_inventory(inventory);
spawn
})
.build();
space.fill_uniform(planner.y_range(-ground_depth, 0), &landscape_blocks[Stone])?;
p.progress(0.1).await;
space.fill_uniform(planner.y_range(0, 1), &landscape_blocks[Grass])?;
p.progress(0.2).await;
{
let grass_noise = noise::ScaleBias::new(noise::OpenSimplex::new(0x21b5cc6b))
.set_bias(0.0)
.set_scale(8.0);
let grass_threshold = 1.2;
space.fill(
GridAab::from_lower_upper((-radius_xz, 1, -radius_xz), (radius_xz, 2, radius_xz)),
|cube| {
if cube.x.abs() <= road_radius || cube.z.abs() <= road_radius {
return None;
}
if grass_noise.at_cube(cube) > grass_threshold * 2. {
Some(&landscape_blocks[GrassBlades { variant: true }])
} else if grass_noise.at_cube(cube) > grass_threshold {
Some(&landscape_blocks[GrassBlades { variant: false }])
} else {
None
}
},
)?;
}
p.progress(0.3).await;
for face in [Face6::PX, Face6::NX, Face6::PZ, Face6::NZ] {
let perpendicular: GridVector = GridRotation::CLOCKWISE.transform(face).normal_vector();
let road_aligned_rotation = GridRotation::from_to(Face6::NZ, face, Face6::PY).unwrap();
let other_side_of_road =
GridRotation::from_basis([Face6::NX, Face6::PY, Face6::NZ]) * road_aligned_rotation;
let rotations = [other_side_of_road, road_aligned_rotation];
let raycaster = Raycaster::new((0.5, 0.5, 0.5), face.normal_vector::<FreeCoordinate>())
.within(space.bounds());
let curb_y = GridVector::unit_y();
for (i, step) in raycaster.enumerate() {
let i = i as GridCoordinate;
for p in -road_radius..=road_radius {
space.set(step.cube_ahead() + perpendicular * p, &demo_blocks[Road])?;
}
if i > road_radius {
for (side, &p) in [-(road_radius + 1), road_radius + 1].iter().enumerate() {
let position = step.cube_ahead() + perpendicular * p + curb_y;
let mut to_compose_with = space[position].clone();
if to_compose_with.clone().unspecialize() != demo_blocks[Curb] {
to_compose_with = AIR;
}
space.set(
position,
block::Composite::new(
demo_blocks[Curb].clone().rotate(rotations[side]),
block::CompositeOperator::Over,
)
.compose_or_replace(to_compose_with),
)?;
}
}
if (i - lamp_position_radius).rem_euclid(lamp_spacing) == 0 {
for p in &[-lamp_position_radius, lamp_position_radius] {
lamp_brush.paint(
&mut space,
step.cube_ahead() + GridVector::new(0, 1, 0) + perpendicular * *p,
)?;
}
}
for p in -road_radius..=road_radius {
for z in underground_floor_y..0 {
space.set(
step.cube_ahead() + perpendicular * p + Vector3::unit_y() * z,
&AIR,
)?;
}
}
if (i - lamp_position_radius).rem_euclid(7) == 0 {
for (side, &p) in [-road_radius, road_radius].iter().enumerate() {
space.set(
step.cube_ahead() + GridVector::new(0, -2, 0) + perpendicular * p,
demo_blocks[Sconce]
.clone()
.rotate(GridRotation::CLOCKWISE * rotations[side]),
)?;
}
}
}
}
p.progress(0.4).await;
let blank_city_time = Instant::now();
log::trace!(
"Blank city took {:.3} s",
blank_city_time
.duration_since(start_city_time)
.as_secs_f32()
);
let [mut landscape_progress, p] = p.split(0.4);
landscape_progress.set_label("Landscape");
landscape_progress.progress(0.0).await;
let landscape_region = GridAab::from_lower_upper(
[-radius_xz, -ground_depth * 8 / 10, -radius_xz],
[-exhibit_front_radius, sky_height, -exhibit_front_radius],
);
space.fill_uniform(landscape_region, AIR)?;
landscape_progress.progress(0.5).await;
wavy_landscape(landscape_region, &mut space, &landscape_blocks, 1.0)?;
planner.occupied_plots.push(landscape_region);
landscape_progress.progress(1.0).await;
if false {
clouds(planner.y_range(sky_height - 2, sky_height), &mut space, 0.1)?;
}
let landscape_time = Instant::now();
log::trace!(
"Landscape took {:.3} s",
landscape_time.duration_since(blank_city_time).as_secs_f32()
);
let [exhibits_progress, final_progress] = p.split(0.8);
let logo_location = space
.bounds()
.abut(Face6::NZ, -3)
.unwrap()
.abut(Face6::PY, -(sky_height - 12))
.unwrap();
install_widgets(
LayoutGrant::new(logo_location),
&LayoutTree::leaf(logo_text()),
)?
.execute(&mut space)?;
planner.occupied_plots.push(logo_location);
'exhibit: for (exhibit, mut exhibit_progress) in DEMO_CITY_EXHIBITS
.iter()
.zip(exhibits_progress.split_evenly(DEMO_CITY_EXHIBITS.len()))
{
exhibit_progress.set_label(format!("Exhibit “{name}”", name = exhibit.name));
exhibit_progress.progress(0.0).await;
let start_exhibit_time = Instant::now();
let exhibit_space = match (exhibit.factory)(exhibit, universe).await {
Ok(s) => s,
Err(error) => {
log::error!(
"Exhibit generation failure.\nExhibit: {name}\nError: {error}",
name = exhibit.name,
error = all_is_cubes::util::ErrorChain(&error),
);
continue 'exhibit;
}
};
exhibit_progress.progress(0.33).await;
let exhibit_footprint = exhibit_space.bounds();
let enclosure_footprint = exhibit_footprint.expand(FaceMap::repeat(1));
let plot_transform = planner
.find_plot(enclosure_footprint)
.expect("Out of city space!");
let plot = exhibit_footprint.transform(plot_transform).unwrap();
let enclosure_at_plot = enclosure_footprint.transform(plot_transform).unwrap();
let enclosure = GridAab::from_lower_upper(
enclosure_at_plot.lower_bounds(),
[
enclosure_at_plot.upper_bounds().x,
1.max(plot.lower_bounds()[1]), enclosure_at_plot.upper_bounds().z,
],
);
space.fill_uniform(enclosure_at_plot, &AIR)?;
space.fill_uniform(enclosure, &demo_blocks[ExhibitBackground])?;
exhibit_progress.progress(0.4).await;
{
let info_resolution = R64;
let exhibit_info_space = draw_exhibit_info(exhibit)?;
let bounds_for_info_voxels = GridAab::from_lower_size(
exhibit_info_space.bounds().lower_bounds(),
GridVector {
x: exhibit_info_space
.bounds()
.size()
.x
.min(enclosure_footprint.size().x * i32::from(info_resolution)),
..exhibit_info_space.bounds().size()
},
);
let info_voxels_widget: WidgetTree = Arc::new(LayoutTree::Stack {
direction: Face6::PZ,
children: vec![
LayoutTree::leaf(widgets::Frame::with_block(demo_blocks[Signboard].clone())),
LayoutTree::leaf(Arc::new(widgets::Voxels::new(
bounds_for_info_voxels,
universe.insert_anonymous(exhibit_info_space),
info_resolution,
BlockAttributes {
display_name: "Exhibit Name".into(),
collision: BlockCollision::None,
..Default::default()
},
))),
],
});
let info_sign_space = info_voxels_widget
.to_space(
SpaceBuilder::default().physics(SpacePhysics::DEFAULT_FOR_BLOCK),
Gravity::new(Align::Center, Align::Center, Align::Low),
)
.unwrap();
let sign_position_in_plot_coordinates = {
GridVector {
x: enclosure_footprint.lower_bounds().x,
y: 0,
z: enclosure_footprint.upper_bounds().z - 1,
}
};
let sign_transform =
plot_transform * GridMatrix::from_translation(sign_position_in_plot_coordinates);
space_to_space_copy(
&info_sign_space,
info_sign_space.bounds(),
&mut space,
sign_transform,
)?;
}
exhibit_progress.progress(0.66).await;
space_to_space_copy(
&exhibit_space,
exhibit_footprint,
&mut space,
plot_transform,
)?;
let exhibit_time = Instant::now().duration_since(start_exhibit_time);
log::trace!(
"{:?} took {:.3} s",
exhibit.name,
exhibit_time.as_secs_f32()
);
exhibit_progress.progress(1.0).await;
}
final_progress.progress(0.5).await;
space.set_physics({
let mut p = space.physics().clone();
p.light = SpacePhysics::default().light;
p
});
final_progress.progress(1.0).await;
Ok(space)
}
#[allow(clippy::type_complexity)]
pub(crate) struct Exhibit {
pub name: &'static str,
pub subtitle: &'static str,
pub factory:
for<'a> fn(&'a Exhibit, &'a mut Universe) -> BoxFuture<'a, Result<Space, InGenError>>,
}
fn draw_exhibit_info(exhibit: &Exhibit) -> Result<Space, InGenError> {
let info_widgets: WidgetTree = Arc::new(LayoutTree::Stack {
direction: Face6::NY,
children: vec![
LayoutTree::leaf(Arc::new(widgets::LargeText {
text: exhibit.name.into(),
font: || &font::FONT_9X18_BOLD,
brush: VoxelBrush::single(Block::from(palette::ALMOST_BLACK)),
text_style: TextStyleBuilder::new()
.alignment(Alignment::Center)
.baseline(Baseline::Middle)
.build(),
})),
LayoutTree::leaf(Arc::new(widgets::LargeText {
text: {
let t = exhibit.subtitle;
if t.is_empty() {
" "
} else {
t
}
}
.into(),
font: || &font::FONT_5X8,
brush: VoxelBrush::single(Block::from(palette::ALMOST_BLACK)),
text_style: TextStyleBuilder::new()
.alignment(Alignment::Center)
.baseline(Baseline::Middle)
.build(),
})),
],
});
let space = info_widgets.to_space(
SpaceBuilder::default().physics(SpacePhysics::DEFAULT_FOR_BLOCK),
Gravity::new(Align::Low, Align::Low, Align::Low),
)?;
Ok(space)
}
#[derive(Clone, Debug, PartialEq)]
struct CityPlanner {
space_bounds: GridAab,
city_radius: GridCoordinate, occupied_plots: Vec<GridAab>,
}
impl CityPlanner {
const ROAD_RADIUS: GridCoordinate = 2;
const LAMP_POSITION_RADIUS: GridCoordinate = Self::ROAD_RADIUS + 2;
const PLOT_FRONT_RADIUS: GridCoordinate = Self::LAMP_POSITION_RADIUS + 2;
const GAP_BETWEEN_PLOTS: GridCoordinate = 1;
pub fn new(space_bounds: GridAab) -> Self {
let city_radius = space_bounds.upper_bounds().x;
let mut occupied_plots = Vec::new();
let road = GridAab::from_lower_upper(
[-Self::LAMP_POSITION_RADIUS, 0, -city_radius],
[Self::LAMP_POSITION_RADIUS + 1, 2, city_radius + 1],
);
occupied_plots.push(road);
occupied_plots.push(
road.transform(GridRotation::CLOCKWISE.to_rotation_matrix())
.unwrap(),
);
Self {
space_bounds,
city_radius,
occupied_plots,
}
}
pub fn find_plot(&mut self, plot_shape: GridAab) -> Option<GridMatrix> {
for d in 0..=self.city_radius {
for street_axis in GridRotation::COUNTERCLOCKWISE.iterate() {
'search: for &left_side in &[false, true] {
let mut transform = GridMatrix::from_translation(GridVector::new(
d,
1,
if left_side {
-Self::PLOT_FRONT_RADIUS - plot_shape.upper_bounds().z
} else {
Self::PLOT_FRONT_RADIUS + plot_shape.upper_bounds().z
},
)) * if left_side {
GridMatrix::one()
} else {
(GridRotation::COUNTERCLOCKWISE * GridRotation::COUNTERCLOCKWISE)
.to_rotation_matrix()
};
transform = street_axis.to_rotation_matrix() * transform;
let transformed = plot_shape
.transform(transform)
.expect("can't happen: city plot transformation failure");
if !self.space_bounds.contains_box(transformed) {
continue 'search;
}
let for_occupancy_check =
transformed.expand(FaceMap::repeat(Self::GAP_BETWEEN_PLOTS));
for occupied in self.occupied_plots.iter() {
if occupied.intersection(for_occupancy_check).is_some() {
continue 'search;
}
}
self.occupied_plots.push(transformed);
return Some(transform);
}
}
}
None
}
fn y_range(&self, lower_y: GridCoordinate, upper_y: GridCoordinate) -> GridAab {
let mut lower = self.space_bounds.lower_bounds();
let mut upper = self.space_bounds.upper_bounds();
lower.y = lower_y;
upper.y = upper_y;
GridAab::from_lower_upper(lower, upper)
}
}