/// Spawns a new maze floor in response to a SpawnMaze trigger event pub(super) fn spawn_maze( trigger: Trigger, mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, maze_query: Query<(Entity, &Floor, &Maze)>, global_config: Res, ) { let SpawnMaze { floor, config } = trigger.event(); if maze_query.iter().any(|(_, f, _)| f.0 == *floor) { warn!("Floor {} already exists, skipping creation", floor); return; } let maze = match generate_maze(config) { Ok(m) => m, Err(e) => { error!("Failed to generate maze for floor {floor}: {:?}", e); return; } }; // Calculate vertical offset based on floor number let y_offset = match *floor { 1 => 0, // Ground/Initial floor (floor 1) is at y=0 _ => FLOOR_Y_OFFSET, // Other floors are offset vertically } as f32; let entity = commands .spawn(( Name::new(format!("Floor {}", floor)), HexMaze, maze.clone(), Floor(*floor), config.clone(), Transform::from_translation(Vec3::ZERO.with_y(y_offset)), Visibility::Visible, )) .insert_if(CurrentFloor, || *floor == 1) // Only floor 1 gets CurrentFloor .id(); let assets = MazeAssets::new(&mut meshes, &mut materials, &global_config); spawn_maze_tiles( &mut commands, entity, &maze, &assets, config, &global_config, ); } /// Spawns all tiles for a maze as children of the parent maze entity pub fn spawn_maze_tiles( commands: &mut Commands, parent_entity: Entity, maze: &Maze, assets: &MazeAssets, maze_config: &MazeConfig, global_config: &GlobalMazeConfig, ) { commands.entity(parent_entity).with_children(|parent| { for tile in maze.values() { spawn_single_hex_tile( parent, assets, tile, maze_config, global_config, ); } }); } /// Spawns a single hexagonal tile with appropriate transforms and materials pub(super) fn spawn_single_hex_tile( parent: &mut ChildBuilder, assets: &MazeAssets, tile: &HexTile, maze_config: &MazeConfig, global_config: &GlobalMazeConfig, ) { let world_pos = tile.to_vec3(&maze_config.layout); let rotation = match maze_config.layout.orientation { // Pointy hexagons don't need additional rotation (0 degrees) HexOrientation::Pointy => Quat::from_rotation_y(0.0), // Flat-top hexagons need 30 degrees (pi/6) rotation around Y axis HexOrientation::Flat => Quat::from_rotation_y(FRAC_PI_6), }; // Select material based on tile position: start, end, or default let material = match tile.pos() { pos if pos == maze_config.start_pos => assets .custom_materials .get(&RosePine::Pine) .cloned() .unwrap_or_default(), pos if pos == maze_config.end_pos => assets .custom_materials .get(&RosePine::Love) .cloned() .unwrap_or_default(), _ => assets.hex_material.clone(), }; parent .spawn(( Name::new(format!("Hex {}", tile)), Tile, Mesh3d(assets.hex_mesh.clone()), MeshMaterial3d(material), Transform::from_translation(world_pos).with_rotation(rotation), )) .with_children(|parent| { spawn_walls(parent, assets, tile.walls(), global_config) }); } /// Spawns walls around a hexagonal tile based on the walls configuration fn spawn_walls( parent: &mut ChildBuilder, assets: &MazeAssets, walls: &Walls, global_config: &GlobalMazeConfig, ) { // Base rotation for wall alignment (90 degrees counter-clockwise) let z_rotation = Quat::from_rotation_z(-FRAC_PI_2); let y_offset = global_config.height / 2.; for i in 0..6 { if !walls.contains(i) { continue; } // Calculate the angle for this wall // FRAC_PI_3 = 60 deg // Negative because going clockwise // i * 60 produces: 0, 60, 120, 180, 240, 300 let wall_angle = -FRAC_PI_3 * i as f32; // cos(angle) gives x coordinate on unit circle // sin(angle) gives z coordinate on unit circle // Multiply by wall_offset to get actual distance from center let x_offset = global_config.wall_offset() * f32::cos(wall_angle); let z_offset = global_config.wall_offset() * f32::sin(wall_angle); // x: distance along x-axis from center // y: vertical offset from center // z: distance along z-axis from center let pos = Vec3::new(x_offset, y_offset, z_offset); // 1. Rotate around x-axis to align wall with angle // 2. Add FRAC_PI_2 (90) to make wall perpendicular to angle let x_rotation = Quat::from_rotation_x(wall_angle + FRAC_PI_2); let final_rotation = z_rotation * x_rotation; spawn_single_wall(parent, assets, final_rotation, pos); } } /// Spawns a single wall segment with the specified rotation and position fn spawn_single_wall( parent: &mut ChildBuilder, assets: &MazeAssets, rotation: Quat, offset: Vec3, ) { parent.spawn(( Name::new("Wall"), Wall, Mesh3d(assets.wall_mesh.clone()), MeshMaterial3d(assets.wall_material.clone()), Transform::from_translation(offset).with_rotation(rotation), )); }