feat(dev-tools): add maze orientation toggle

This commit is contained in:
Kristofers Solo 2024-12-08 19:30:58 +02:00
parent 1a0a859fec
commit 8ef2db1d48
7 changed files with 99 additions and 62 deletions

View File

@ -1,7 +1,7 @@
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
use bevy::{prelude::*, window::PrimaryWindow}; use bevy::{prelude::*, window::PrimaryWindow};
use hexx::Hex; use hexx::{Hex, HexOrientation};
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use crate::maze::{events::RecreateMazeEvent, MazeConfig, MazePluginLoaded}; use crate::maze::{events::RecreateMazeEvent, MazeConfig, MazePluginLoaded};
@ -34,12 +34,22 @@ pub(crate) fn maze_controls_ui(world: &mut World) {
changed |= add_drag_value_control(ui, "Radius:", &mut maze_config.radius, 1.0, 1..=100); changed |= add_drag_value_control(ui, "Radius:", &mut maze_config.radius, 1.0, 1..=100);
changed |= changed |=
add_drag_value_control(ui, "Height:", &mut maze_config.height, 0.5, 1.0..=50.0); add_drag_value_control(ui, "Height:", &mut maze_config.height, 0.5, 1.0..=50.0);
changed |= add_drag_value_control(
ui,
"Hex Size:",
&mut maze_config.hex_size,
1.0,
1.0..=100.0,
);
changed |= add_orientation_control(ui, &mut maze_config.layout.orientation);
changed |= add_position_control(ui, "Start Position:", &mut maze_config.start_pos); changed |= add_position_control(ui, "Start Position:", &mut maze_config.start_pos);
changed |= add_position_control(ui, "End Position:", &mut maze_config.end_pos); changed |= add_position_control(ui, "End Position:", &mut maze_config.end_pos);
// Trigger recreation if any value changed // Trigger recreation if any value changed
if changed { if changed {
maze_config.update();
if let Some(mut event_writer) = if let Some(mut event_writer) =
world.get_resource_mut::<Events<RecreateMazeEvent>>() world.get_resource_mut::<Events<RecreateMazeEvent>>()
{ {
@ -115,3 +125,19 @@ fn add_seed_control(ui: &mut Ui, seed: &mut u64) -> bool {
changed changed
} }
fn add_orientation_control(ui: &mut Ui, orientation: &mut HexOrientation) -> bool {
let mut changed = false;
ui.horizontal(|ui| {
ui.label("Orientation:");
let response = ui.radio_value(orientation, HexOrientation::Flat, "Flat");
changed |= response.changed();
let response = ui.radio_value(orientation, HexOrientation::Pointy, "Pointy");
changed |= response.changed();
});
changed
}

View File

@ -1,8 +1,10 @@
use super::MazeConfig;
use bevy::prelude::*;
use std::f32::consts::FRAC_PI_2; use std::f32::consts::FRAC_PI_2;
use bevy::prelude::*; const WALL_OVERLAP_MODIFIER: f32 = 1.25;
const HEX_SIDES: usize = 6;
use super::{resources::WALL_SIZE, MazeConfig}; const WHITE_EMISSION_INTENSITY: f32 = 10.;
pub(crate) struct MazeAssets { pub(crate) struct MazeAssets {
pub(crate) hex_mesh: Handle<Mesh>, pub(crate) hex_mesh: Handle<Mesh>,
@ -11,22 +13,27 @@ pub(crate) struct MazeAssets {
pub(crate) wall_material: Handle<StandardMaterial>, pub(crate) wall_material: Handle<StandardMaterial>,
} }
pub(crate) fn create_base_assets( impl MazeAssets {
meshes: &mut ResMut<Assets<Mesh>>, pub(crate) fn new(
materials: &mut ResMut<Assets<StandardMaterial>>, meshes: &mut ResMut<Assets<Mesh>>,
config: &MazeConfig, materials: &mut ResMut<Assets<StandardMaterial>>,
) -> MazeAssets { config: &MazeConfig,
MazeAssets { ) -> MazeAssets {
hex_mesh: meshes.add(generate_hex_mesh(config.size, config.height)), MazeAssets {
wall_mesh: meshes.add(generate_square_mesh(config.size)), hex_mesh: meshes.add(generate_hex_mesh(config.hex_size, config.height)),
hex_material: materials.add(white_material()), wall_mesh: meshes.add(generate_square_mesh(
wall_material: materials.add(Color::BLACK), config.hex_size + config.wall_size() / WALL_OVERLAP_MODIFIER,
config.wall_size(),
)),
hex_material: materials.add(white_material()),
wall_material: materials.add(Color::BLACK),
}
} }
} }
fn generate_hex_mesh(radius: f32, depth: f32) -> Mesh { fn generate_hex_mesh(radius: f32, depth: f32) -> Mesh {
let hexagon = RegularPolygon { let hexagon = RegularPolygon {
sides: 6, sides: HEX_SIDES,
circumcircle: Circle::new(radius), circumcircle: Circle::new(radius),
}; };
let prism_shape = Extrusion::new(hexagon, depth); let prism_shape = Extrusion::new(hexagon, depth);
@ -35,8 +42,8 @@ fn generate_hex_mesh(radius: f32, depth: f32) -> Mesh {
Mesh::from(prism_shape).rotated_by(rotation) Mesh::from(prism_shape).rotated_by(rotation)
} }
fn generate_square_mesh(depth: f32) -> Mesh { fn generate_square_mesh(depth: f32, wall_size: f32) -> Mesh {
let square = Rectangle::new(WALL_SIZE, WALL_SIZE); let square = Rectangle::new(wall_size, wall_size);
let rectangular_prism = Extrusion::new(square, depth); let rectangular_prism = Extrusion::new(square, depth);
let rotation = Quat::from_rotation_x(FRAC_PI_2); let rotation = Quat::from_rotation_x(FRAC_PI_2);
@ -44,10 +51,14 @@ fn generate_square_mesh(depth: f32) -> Mesh {
} }
fn white_material() -> StandardMaterial { fn white_material() -> StandardMaterial {
let val = 10.;
StandardMaterial { StandardMaterial {
base_color: Color::WHITE, base_color: Color::WHITE,
emissive: LinearRgba::new(val, val, val, val), emissive: LinearRgba::new(
WHITE_EMISSION_INTENSITY,
WHITE_EMISSION_INTENSITY,
WHITE_EMISSION_INTENSITY,
WHITE_EMISSION_INTENSITY,
),
..default() ..default()
} }
} }

View File

@ -5,7 +5,6 @@ use bevy::{
use super::{ use super::{
events::RecreateMazeEvent, events::RecreateMazeEvent,
resources::Layout,
systems::{self, recreation::handle_maze_recreation_event}, systems::{self, recreation::handle_maze_recreation_event},
MazeConfig, MazePluginLoaded, MazeConfig, MazePluginLoaded,
}; };
@ -16,7 +15,6 @@ pub(crate) struct MazePlugin;
impl Plugin for MazePlugin { impl Plugin for MazePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.init_resource::<MazeConfig>() app.init_resource::<MazeConfig>()
.init_resource::<Layout>()
.add_event::<RecreateMazeEvent>() .add_event::<RecreateMazeEvent>()
.add_systems(Update, handle_maze_recreation_event); .add_systems(Update, handle_maze_recreation_event);
} }

View File

@ -9,7 +9,6 @@ use thiserror::Error;
#[reflect(Resource)] #[reflect(Resource)]
pub struct MazePluginLoaded; pub struct MazePluginLoaded;
pub(crate) const WALL_SIZE: f32 = 1.0;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum MazeConfigError { pub enum MazeConfigError {
#[error("Failed to convert radius from u32 to i32: {0}")] #[error("Failed to convert radius from u32 to i32: {0}")]
@ -21,17 +20,19 @@ pub enum MazeConfigError {
pub struct MazeConfig { pub struct MazeConfig {
pub radius: u32, pub radius: u32,
pub height: f32, pub height: f32,
pub size: f32, pub hex_size: f32,
pub start_pos: Hex, pub start_pos: Hex,
pub end_pos: Hex, pub end_pos: Hex,
pub seed: u64, pub seed: u64,
pub layout: HexLayout,
} }
impl MazeConfig { impl MazeConfig {
fn new( fn new(
radius: u32, radius: u32,
height: f32, height: f32,
size: f32, hex_size: f32,
orientation: HexOrientation,
seed: Option<u64>, seed: Option<u64>,
) -> Result<Self, MazeConfigError> { ) -> Result<Self, MazeConfigError> {
let seed = seed.unwrap_or_else(|| thread_rng().gen()); let seed = seed.unwrap_or_else(|| thread_rng().gen());
@ -43,40 +44,50 @@ impl MazeConfig {
debug!("Start pos: ({},{})", start_pos.x, start_pos.y); debug!("Start pos: ({},{})", start_pos.x, start_pos.y);
debug!("End pos: ({},{})", end_pos.x, end_pos.y); debug!("End pos: ({},{})", end_pos.x, end_pos.y);
let layout = HexLayout {
orientation,
hex_size: Vec2::splat(hex_size),
..default()
};
Ok(Self { Ok(Self {
radius: radius as u32, radius: radius as u32,
height, height,
size, hex_size,
start_pos, start_pos,
end_pos, end_pos,
seed, seed,
layout,
}) })
} }
pub fn new_unchecked(radius: u32, height: f32, hex_size: f32, seed: Option<u64>) -> Self { pub fn new_unchecked(
Self::new(radius, height, hex_size, seed) radius: u32,
height: f32,
hex_size: f32,
orientation: HexOrientation,
seed: Option<u64>,
) -> Self {
Self::new(radius, height, hex_size, orientation, seed)
.expect("Failed to create MazeConfig with supposedly safe values") .expect("Failed to create MazeConfig with supposedly safe values")
} }
pub fn wall_size(&self) -> f32 {
self.hex_size / 6.
}
pub fn wall_offset(&self) -> f32 {
self.hex_size - self.wall_size()
}
pub fn update(&mut self) {
self.layout.hex_size = Vec2::splat(self.hex_size);
}
} }
impl Default for MazeConfig { impl Default for MazeConfig {
fn default() -> Self { fn default() -> Self {
Self::new_unchecked(7, 20., 6., None) Self::new_unchecked(7, 20., 6., HexOrientation::Flat, None)
}
}
#[derive(Debug, Reflect, Resource, Deref, DerefMut, Clone)]
#[reflect(Resource)]
pub struct Layout(pub HexLayout);
impl FromWorld for Layout {
fn from_world(world: &mut World) -> Self {
let config = world.resource::<MazeConfig>();
Self(HexLayout {
orientation: HexOrientation::Flat,
hex_size: Vec2::splat(config.size),
..default()
})
} }
} }

View File

@ -1,8 +1,6 @@
use bevy::prelude::*; use bevy::prelude::*;
use crate::maze::{ use crate::maze::{components::MazeFloor, events::RecreateMazeEvent, MazeConfig};
components::MazeFloor, events::RecreateMazeEvent, resources::Layout, MazeConfig,
};
use super::setup::setup_maze; use super::setup::setup_maze;
@ -11,13 +9,12 @@ pub(crate) fn handle_maze_recreation_event(
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
config: Res<MazeConfig>, config: Res<MazeConfig>,
layout: Res<Layout>,
query: Query<(Entity, &MazeFloor)>, query: Query<(Entity, &MazeFloor)>,
mut event_reader: EventReader<RecreateMazeEvent>, mut event_reader: EventReader<RecreateMazeEvent>,
) { ) {
for event in event_reader.read() { for event in event_reader.read() {
despawn_floor(&mut commands, &query, event.floor); despawn_floor(&mut commands, &query, event.floor);
setup_maze(&mut commands, &mut meshes, &mut materials, &config, &layout); setup_maze(&mut commands, &mut meshes, &mut materials, &config);
} }
} }

View File

@ -1,9 +1,7 @@
use bevy::prelude::*; use bevy::prelude::*;
use hexlab::{GeneratorType, MazeBuilder}; use hexlab::{GeneratorType, MazeBuilder};
use crate::maze::{ use crate::maze::{assets::MazeAssets, components::MazeFloor, MazeConfig};
assets::create_base_assets, components::MazeFloor, resources::Layout, MazeConfig,
};
use super::spawn::spawn_single_hex_tile; use super::spawn::spawn_single_hex_tile;
@ -12,9 +10,8 @@ pub(crate) fn setup(
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
config: Res<MazeConfig>, config: Res<MazeConfig>,
layout: Res<Layout>,
) { ) {
setup_maze(&mut commands, &mut meshes, &mut materials, &config, &layout); setup_maze(&mut commands, &mut meshes, &mut materials, &config);
} }
pub(super) fn setup_maze( pub(super) fn setup_maze(
@ -22,7 +19,6 @@ pub(super) fn setup_maze(
meshes: &mut ResMut<Assets<Mesh>>, meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<StandardMaterial>>, materials: &mut ResMut<Assets<StandardMaterial>>,
config: &MazeConfig, config: &MazeConfig,
layout: &Layout,
) { ) {
let maze = MazeBuilder::new() let maze = MazeBuilder::new()
.with_radius(config.radius) .with_radius(config.radius)
@ -31,7 +27,7 @@ pub(super) fn setup_maze(
.build() .build()
.expect("Something went wrong while creating maze"); .expect("Something went wrong while creating maze");
let assets = create_base_assets(meshes, materials, config); let assets = MazeAssets::new(meshes, materials, config);
commands commands
.spawn(( .spawn((
Name::new("Floor"), Name::new("Floor"),
@ -43,7 +39,7 @@ pub(super) fn setup_maze(
)) ))
.with_children(|parent| { .with_children(|parent| {
for tile in maze.values() { for tile in maze.values() {
spawn_single_hex_tile(parent, &assets, tile, &layout.0, &config) spawn_single_hex_tile(parent, &assets, tile, &config)
} }
}); });
} }

View File

@ -7,7 +7,6 @@ use hexx::HexOrientation;
use crate::maze::{ use crate::maze::{
assets::MazeAssets, assets::MazeAssets,
components::{MazeTile, MazeWall}, components::{MazeTile, MazeWall},
resources::WALL_SIZE,
MazeConfig, MazeConfig,
}; };
@ -15,11 +14,10 @@ pub(super) fn spawn_single_hex_tile(
parent: &mut ChildBuilder, parent: &mut ChildBuilder,
assets: &MazeAssets, assets: &MazeAssets,
tile: &HexTile, tile: &HexTile,
layout: &HexLayout,
config: &MazeConfig, config: &MazeConfig,
) { ) {
let world_pos = tile.to_vec3(layout); let world_pos = tile.to_vec3(&config.layout);
let rotation = match layout.orientation { let rotation = match config.layout.orientation {
HexOrientation::Pointy => Quat::from_rotation_y(0.0), HexOrientation::Pointy => Quat::from_rotation_y(0.0),
HexOrientation::Flat => Quat::from_rotation_y(FRAC_PI_6), // 30 degrees rotation HexOrientation::Flat => Quat::from_rotation_y(FRAC_PI_6), // 30 degrees rotation
}; };
@ -49,8 +47,8 @@ fn spawn_walls(parent: &mut ChildBuilder, assets: &MazeAssets, config: &MazeConf
let wall_angle = -FRAC_PI_3 * i as f32; let wall_angle = -FRAC_PI_3 * i as f32;
let x_offset = (config.size - WALL_SIZE) * f32::cos(wall_angle); let x_offset = config.wall_offset() * f32::cos(wall_angle);
let z_offset = (config.size - WALL_SIZE) * f32::sin(wall_angle); let z_offset = config.wall_offset() * f32::sin(wall_angle);
let pos = Vec3::new(x_offset, y_offset, z_offset); let pos = Vec3::new(x_offset, y_offset, z_offset);
let x_rotation = Quat::from_rotation_x(wall_angle + FRAC_PI_2); let x_rotation = Quat::from_rotation_x(wall_angle + FRAC_PI_2);