use crate::ecs::camera::components::Projection;
use crate::ecs::graphics::resources::{Atmosphere, Fog, PbrDebugMode};
use crate::ecs::transform::commands::mark_local_transform_dirty;
use crate::ecs::world::World;
use crate::ecs::world::commands::{
spawn_cone_at, spawn_cube_at, spawn_cylinder_at, spawn_plane_at, spawn_sphere_at,
spawn_torus_at,
};
use nalgebra_glm::Vec3;
use super::{Command, ShellState};
pub fn builtin_commands<C>() -> [Command<C>; 36] {
[
Command {
name: "spawn_cube",
description: "Spawn a cube at the specified position",
usage: "spawn_cube <x> <y> <z>",
execute: spawn_cube,
},
Command {
name: "spawn_sphere",
description: "Spawn a sphere at the specified position",
usage: "spawn_sphere <x> <y> <z>",
execute: spawn_sphere,
},
Command {
name: "spawn_cylinder",
description: "Spawn a cylinder at the specified position",
usage: "spawn_cylinder <x> <y> <z>",
execute: spawn_cylinder,
},
Command {
name: "spawn_cone",
description: "Spawn a cone at the specified position",
usage: "spawn_cone <x> <y> <z>",
execute: spawn_cone,
},
Command {
name: "spawn_torus",
description: "Spawn a torus at the specified position",
usage: "spawn_torus <x> <y> <z>",
execute: spawn_torus,
},
Command {
name: "spawn_plane",
description: "Spawn a plane at the specified position",
usage: "spawn_plane <x> <y> <z>",
execute: spawn_plane,
},
Command {
name: "grid",
description: "Toggle grid visibility (on/off)",
usage: "grid <on|off>",
execute: grid,
},
Command {
name: "atmosphere",
description: "Set the atmosphere type",
usage: "atmosphere <none|sky|cloudy|space|nebula|sunset>",
execute: atmosphere,
},
Command {
name: "fov",
description: "Get or set camera field of view in degrees",
usage: "fov [degrees]",
execute: fov,
},
Command {
name: "timescale",
description: "Get or set time scale (1.0 = normal, 0.5 = slow-mo, 2.0 = fast)",
usage: "timescale [value]",
execute: timescale,
},
Command {
name: "fps",
description: "Show current frames per second",
usage: "fps",
execute: fps,
},
Command {
name: "quit",
description: "Exit the application",
usage: "quit",
execute: quit,
},
Command {
name: "pos",
description: "Show current camera position",
usage: "pos",
execute: pos,
},
Command {
name: "tp",
description: "Teleport camera to specified position",
usage: "tp <x> <y> <z>",
execute: teleport,
},
Command {
name: "unlit",
description: "Toggle unlit rendering mode",
usage: "unlit [on|off]",
execute: unlit,
},
Command {
name: "bloom",
description: "Toggle bloom effect, set intensity, or set threshold",
usage: "bloom [on|off|<intensity>|threshold <value>]",
execute: bloom,
},
Command {
name: "rotate",
description: "Rotate an entity by degrees on each axis",
usage: "rotate <entity> <x> <y> <z>",
execute: rotate,
},
Command {
name: "scale",
description: "Scale an entity uniformly or on each axis",
usage: "scale <entity> <factor> OR scale <entity> <x> <y> <z>",
execute: scale,
},
Command {
name: "move",
description: "Move an entity by offset or to absolute position",
usage: "move <entity> <x> <y> <z>",
execute: move_entity,
},
Command {
name: "delete",
description: "Delete an entity from the world",
usage: "delete <entity>",
execute: delete,
},
Command {
name: "inspect",
description: "Show information about an entity",
usage: "inspect <entity>",
execute: inspect,
},
Command {
name: "cull",
description: "Toggle GPU frustum and occlusion culling",
usage: "cull [on|off|gpu on|off|occlusion on|off]",
execute: cull,
},
Command {
name: "bounds",
description: "Toggle bounding volume debug overlay",
usage: "bounds [on|off]",
execute: bounds,
},
Command {
name: "normals",
description: "Toggle vertex normal debug lines",
usage: "normals [on|off]",
execute: normals,
},
Command {
name: "pbr",
description: "Set PBR debug visualization mode",
usage: "pbr [none|basecolor|normal|metallic|roughness|occlusion|emissive|f|g|d|diffuse|specular|miprainbow]",
execute: pbr,
},
Command {
name: "ssao",
description: "Toggle screen-space ambient occlusion",
usage: "ssao [on|off]",
execute: ssao,
},
Command {
name: "ssr",
description: "Toggle screen-space reflections",
usage: "ssr [on|off]",
execute: ssr,
},
Command {
name: "ssgi",
description: "Toggle screen-space global illumination",
usage: "ssgi [on|off]",
execute: ssgi,
},
Command {
name: "fxaa",
description: "Toggle FXAA antialiasing",
usage: "fxaa [on|off]",
execute: fxaa,
},
Command {
name: "fog",
description: "Toggle distance fog or set start/end distance",
usage: "fog [on|off|<start> <end>]",
execute: fog,
},
Command {
name: "exposure",
description: "Get or set exposure multiplier",
usage: "exposure [value]",
execute: exposure,
},
Command {
name: "gamma",
description: "Get or set gamma correction",
usage: "gamma [value]",
execute: gamma,
},
Command {
name: "saturation",
description: "Get or set color saturation",
usage: "saturation [value]",
execute: saturation,
},
Command {
name: "contrast",
description: "Get or set color contrast",
usage: "contrast [value]",
execute: contrast,
},
Command {
name: "fullscreen",
description: "Toggle fullscreen mode",
usage: "fullscreen [on|off]",
execute: fullscreen,
},
Command {
name: "cursor",
description: "Toggle mouse cursor visibility",
usage: "cursor [on|off]",
execute: cursor,
},
]
}
impl<C> ShellState<C> {
pub fn register_builtin_commands(&mut self) {
for command in builtin_commands() {
self.registry.register_builtin(command);
}
}
}
fn spawn_cube<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.len() != 3 {
return "Usage: spawn_cube <x> <y> <z>".to_string();
}
let x: f32 = match args[0].parse() {
Ok(value) => value,
Err(_) => return "Invalid x coordinate".to_string(),
};
let y: f32 = match args[1].parse() {
Ok(value) => value,
Err(_) => return "Invalid y coordinate".to_string(),
};
let z: f32 = match args[2].parse() {
Ok(value) => value,
Err(_) => return "Invalid z coordinate".to_string(),
};
spawn_cube_at(world, Vec3::new(x, y, z));
format!("Spawned cube at ({}, {}, {})", x, y, z)
}
fn spawn_sphere<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.len() != 3 {
return "Usage: spawn_sphere <x> <y> <z>".to_string();
}
let x: f32 = match args[0].parse() {
Ok(value) => value,
Err(_) => return "Invalid x coordinate".to_string(),
};
let y: f32 = match args[1].parse() {
Ok(value) => value,
Err(_) => return "Invalid y coordinate".to_string(),
};
let z: f32 = match args[2].parse() {
Ok(value) => value,
Err(_) => return "Invalid z coordinate".to_string(),
};
spawn_sphere_at(world, Vec3::new(x, y, z));
format!("Spawned sphere at ({}, {}, {})", x, y, z)
}
fn spawn_cylinder<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.len() != 3 {
return "Usage: spawn_cylinder <x> <y> <z>".to_string();
}
let x: f32 = match args[0].parse() {
Ok(value) => value,
Err(_) => return "Invalid x coordinate".to_string(),
};
let y: f32 = match args[1].parse() {
Ok(value) => value,
Err(_) => return "Invalid y coordinate".to_string(),
};
let z: f32 = match args[2].parse() {
Ok(value) => value,
Err(_) => return "Invalid z coordinate".to_string(),
};
spawn_cylinder_at(world, Vec3::new(x, y, z));
format!("Spawned cylinder at ({}, {}, {})", x, y, z)
}
fn spawn_cone<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.len() != 3 {
return "Usage: spawn_cone <x> <y> <z>".to_string();
}
let x: f32 = match args[0].parse() {
Ok(value) => value,
Err(_) => return "Invalid x coordinate".to_string(),
};
let y: f32 = match args[1].parse() {
Ok(value) => value,
Err(_) => return "Invalid y coordinate".to_string(),
};
let z: f32 = match args[2].parse() {
Ok(value) => value,
Err(_) => return "Invalid z coordinate".to_string(),
};
spawn_cone_at(world, Vec3::new(x, y, z));
format!("Spawned cone at ({}, {}, {})", x, y, z)
}
fn spawn_torus<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.len() != 3 {
return "Usage: spawn_torus <x> <y> <z>".to_string();
}
let x: f32 = match args[0].parse() {
Ok(value) => value,
Err(_) => return "Invalid x coordinate".to_string(),
};
let y: f32 = match args[1].parse() {
Ok(value) => value,
Err(_) => return "Invalid y coordinate".to_string(),
};
let z: f32 = match args[2].parse() {
Ok(value) => value,
Err(_) => return "Invalid z coordinate".to_string(),
};
spawn_torus_at(world, Vec3::new(x, y, z));
format!("Spawned torus at ({}, {}, {})", x, y, z)
}
fn spawn_plane<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.len() != 3 {
return "Usage: spawn_plane <x> <y> <z>".to_string();
}
let x: f32 = match args[0].parse() {
Ok(value) => value,
Err(_) => return "Invalid x coordinate".to_string(),
};
let y: f32 = match args[1].parse() {
Ok(value) => value,
Err(_) => return "Invalid y coordinate".to_string(),
};
let z: f32 = match args[2].parse() {
Ok(value) => value,
Err(_) => return "Invalid z coordinate".to_string(),
};
spawn_plane_at(world, Vec3::new(x, y, z));
format!("Spawned plane at ({}, {}, {})", x, y, z)
}
fn grid<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.is_empty() {
let state = if world.resources.graphics.show_grid {
"on"
} else {
"off"
};
return format!("Grid is currently {}", state);
}
match args[0].to_lowercase().as_str() {
"on" | "true" | "1" => {
world.resources.graphics.show_grid = true;
"Grid enabled".to_string()
}
"off" | "false" | "0" => {
world.resources.graphics.show_grid = false;
"Grid disabled".to_string()
}
_ => "Usage: grid <on|off>".to_string(),
}
}
fn atmosphere<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.is_empty() {
let current = match world.resources.graphics.atmosphere {
Atmosphere::None => "none",
Atmosphere::Sky => "sky",
Atmosphere::CloudySky => "cloudy",
Atmosphere::Space => "space",
Atmosphere::Nebula => "nebula",
Atmosphere::Sunset => "sunset",
_ => "other",
};
return format!("Current atmosphere: {}", current);
}
let atmosphere = match args[0].to_lowercase().as_str() {
"none" => Atmosphere::None,
"sky" => Atmosphere::Sky,
"cloudy" | "cloudysky" => Atmosphere::CloudySky,
"space" => Atmosphere::Space,
"nebula" => Atmosphere::Nebula,
"sunset" => Atmosphere::Sunset,
_ => {
return "Unknown atmosphere type. Usage: atmosphere <none|sky|cloudy|space|nebula|sunset>".to_string();
}
};
world.resources.graphics.atmosphere = atmosphere;
format!("Atmosphere set to {}", args[0].to_lowercase())
}
fn fov<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let Some(camera_entity) = world.resources.active_camera else {
return "No active camera".to_string();
};
let Some(camera) = world.core.get_camera_mut(camera_entity) else {
return "Camera component not found".to_string();
};
let Projection::Perspective(perspective) = &mut camera.projection else {
return "Camera is not using perspective projection".to_string();
};
if args.is_empty() {
let fov_degrees = perspective.y_fov_rad.to_degrees();
return format!("Current FOV: {:.1} degrees", fov_degrees);
}
let value: f32 = match args[0].parse() {
Ok(value) => value,
Err(_) => return "Invalid FOV value".to_string(),
};
if !(1.0..=179.0).contains(&value) {
return "FOV must be between 1 and 179 degrees".to_string();
}
perspective.y_fov_rad = value.to_radians();
format!("FOV set to {:.1} degrees", value)
}
fn timescale<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.is_empty() {
return format!(
"Current timescale: {:.2}",
world.resources.window.timing.time_speed
);
}
let scale: f32 = match args[0].parse() {
Ok(value) => value,
Err(_) => return "Invalid timescale value".to_string(),
};
if !(0.0..=10.0).contains(&scale) {
return "Timescale must be between 0.0 and 10.0".to_string();
}
world.resources.window.timing.time_speed = scale;
format!("Timescale set to {:.2}", scale)
}
fn fps<C>(_args: &[&str], world: &mut World, _context: &mut C) -> String {
let timing = &world.resources.window.timing;
format!(
"FPS: {:.1} | Frame time: {:.2}ms | Uptime: {:.1}s",
timing.frames_per_second,
timing.delta_time * 1000.0,
timing.uptime_milliseconds as f32 / 1000.0
)
}
fn quit<C>(_args: &[&str], world: &mut World, _context: &mut C) -> String {
world.resources.window.should_exit = true;
"Exiting...".to_string()
}
fn pos<C>(_args: &[&str], world: &mut World, _context: &mut C) -> String {
let Some(camera_entity) = world.resources.active_camera else {
return "No active camera".to_string();
};
let Some(transform) = world.core.get_local_transform(camera_entity) else {
return "Camera transform not found".to_string();
};
format!(
"Position: ({:.2}, {:.2}, {:.2})",
transform.translation.x, transform.translation.y, transform.translation.z
)
}
fn teleport<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.len() != 3 {
return "Usage: tp <x> <y> <z>".to_string();
}
let Some(camera_entity) = world.resources.active_camera else {
return "No active camera".to_string();
};
let x: f32 = match args[0].parse() {
Ok(value) => value,
Err(_) => return "Invalid x coordinate".to_string(),
};
let y: f32 = match args[1].parse() {
Ok(value) => value,
Err(_) => return "Invalid y coordinate".to_string(),
};
let z: f32 = match args[2].parse() {
Ok(value) => value,
Err(_) => return "Invalid z coordinate".to_string(),
};
let Some(transform) = world.core.get_local_transform_mut(camera_entity) else {
return "Camera transform not found".to_string();
};
transform.translation = Vec3::new(x, y, z);
mark_local_transform_dirty(world, camera_entity);
format!("Teleported to ({:.2}, {:.2}, {:.2})", x, y, z)
}
fn unlit<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.is_empty() {
let state = if world.resources.graphics.unlit_mode {
"on"
} else {
"off"
};
return format!("Unlit mode is currently {}", state);
}
match args[0].to_lowercase().as_str() {
"on" | "true" | "1" => {
world.resources.graphics.unlit_mode = true;
"Unlit mode enabled".to_string()
}
"off" | "false" | "0" => {
world.resources.graphics.unlit_mode = false;
"Unlit mode disabled".to_string()
}
_ => "Usage: unlit [on|off]".to_string(),
}
}
fn bloom<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.is_empty() {
let state = if world.resources.graphics.bloom_enabled {
format!(
"on (intensity: {:.2}, threshold: {:.2})",
world.resources.graphics.bloom_intensity, world.resources.graphics.bloom_threshold
)
} else {
"off".to_string()
};
return format!("Bloom is currently {}", state);
}
match args[0].to_lowercase().as_str() {
"on" | "true" | "1" => {
world.resources.graphics.bloom_enabled = true;
"Bloom enabled".to_string()
}
"off" | "false" | "0" => {
world.resources.graphics.bloom_enabled = false;
"Bloom disabled".to_string()
}
"threshold" => {
if args.len() < 2 {
return format!(
"Bloom threshold is {:.2}",
world.resources.graphics.bloom_threshold
);
}
if let Ok(threshold) = args[1].parse::<f32>() {
if (0.0..=10.0).contains(&threshold) {
world.resources.graphics.bloom_threshold = threshold;
return format!("Bloom threshold set to {:.2}", threshold);
}
return "Bloom threshold must be between 0.0 and 10.0".to_string();
}
"Usage: bloom threshold <value>".to_string()
}
_ => {
if let Ok(intensity) = args[0].parse::<f32>() {
if (0.0..=5.0).contains(&intensity) {
world.resources.graphics.bloom_enabled = true;
world.resources.graphics.bloom_intensity = intensity;
return format!("Bloom intensity set to {:.2}", intensity);
}
return "Bloom intensity must be between 0.0 and 5.0".to_string();
}
"Usage: bloom [on|off|<intensity>|threshold <value>]".to_string()
}
}
}
fn rotate<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.len() != 4 {
return "Usage: rotate <entity> <x> <y> <z>".to_string();
}
let Some(entity) = super::parse_entity(args[0]) else {
return format!("Invalid entity format: {}", args[0]);
};
let x: f32 = match args[1].parse() {
Ok(value) => value,
Err(_) => return "Invalid x rotation".to_string(),
};
let y: f32 = match args[2].parse() {
Ok(value) => value,
Err(_) => return "Invalid y rotation".to_string(),
};
let z: f32 = match args[3].parse() {
Ok(value) => value,
Err(_) => return "Invalid z rotation".to_string(),
};
let Some(transform) = world.core.get_local_transform_mut(entity) else {
return "Entity not found or has no transform".to_string();
};
let rotation_x = nalgebra_glm::quat_angle_axis(x.to_radians(), &Vec3::new(1.0, 0.0, 0.0));
let rotation_y = nalgebra_glm::quat_angle_axis(y.to_radians(), &Vec3::new(0.0, 1.0, 0.0));
let rotation_z = nalgebra_glm::quat_angle_axis(z.to_radians(), &Vec3::new(0.0, 0.0, 1.0));
let rotation = rotation_z * rotation_y * rotation_x;
transform.rotation = rotation * transform.rotation;
mark_local_transform_dirty(world, entity);
format!("Rotated {} by ({}, {}, {}) degrees", args[0], x, y, z)
}
fn scale<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.len() < 2 {
return "Usage: scale <entity> <factor> OR scale <entity> <x> <y> <z>".to_string();
}
let Some(entity) = super::parse_entity(args[0]) else {
return format!("Invalid entity format: {}", args[0]);
};
let factor = if args.len() == 2 {
let value: f32 = match args[1].parse() {
Ok(value) => value,
Err(_) => return "Invalid scale factor".to_string(),
};
Vec3::new(value, value, value)
} else if args.len() == 4 {
let x: f32 = match args[1].parse() {
Ok(value) => value,
Err(_) => return "Invalid x scale".to_string(),
};
let y: f32 = match args[2].parse() {
Ok(value) => value,
Err(_) => return "Invalid y scale".to_string(),
};
let z: f32 = match args[3].parse() {
Ok(value) => value,
Err(_) => return "Invalid z scale".to_string(),
};
Vec3::new(x, y, z)
} else {
return "Usage: scale <entity> <factor> OR scale <entity> <x> <y> <z>".to_string();
};
let new_scale = {
let Some(transform) = world.core.get_local_transform_mut(entity) else {
return "Entity not found or has no transform".to_string();
};
transform.scale = transform.scale.component_mul(&factor);
transform.scale
};
mark_local_transform_dirty(world, entity);
format!(
"Scaled {} to ({:.2}, {:.2}, {:.2})",
args[0], new_scale.x, new_scale.y, new_scale.z
)
}
fn move_entity<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.len() != 4 {
return "Usage: move <entity> <x> <y> <z>".to_string();
}
let Some(entity) = super::parse_entity(args[0]) else {
return format!("Invalid entity format: {}", args[0]);
};
let x: f32 = match args[1].parse() {
Ok(value) => value,
Err(_) => return "Invalid x position".to_string(),
};
let y: f32 = match args[2].parse() {
Ok(value) => value,
Err(_) => return "Invalid y position".to_string(),
};
let z: f32 = match args[3].parse() {
Ok(value) => value,
Err(_) => return "Invalid z position".to_string(),
};
let Some(transform) = world.core.get_local_transform_mut(entity) else {
return "Entity not found or has no transform".to_string();
};
transform.translation = Vec3::new(x, y, z);
mark_local_transform_dirty(world, entity);
format!("Moved {} to ({:.2}, {:.2}, {:.2})", args[0], x, y, z)
}
fn delete<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.is_empty() {
return "Usage: delete <entity>".to_string();
}
let Some(entity) = super::parse_entity(args[0]) else {
return format!("Invalid entity format: {}", args[0]);
};
if world.core.get_local_transform(entity).is_none() {
return "Entity not found".to_string();
}
world.despawn_entities(&[entity]);
format!("Deleted {}", args[0])
}
fn inspect<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.is_empty() {
return "Usage: inspect <entity>".to_string();
}
let Some(entity) = super::parse_entity(args[0]) else {
return format!("Invalid entity format: {}", args[0]);
};
let mut info = format!("Entity: {}\n", args[0]);
if let Some(name) = world.core.get_name(entity) {
info.push_str(&format!("Name: {}\n", name.0));
}
if let Some(transform) = world.core.get_local_transform(entity) {
info.push_str(&format!(
"Position: ({:.2}, {:.2}, {:.2})\n",
transform.translation.x, transform.translation.y, transform.translation.z
));
info.push_str(&format!(
"Scale: ({:.2}, {:.2}, {:.2})\n",
transform.scale.x, transform.scale.y, transform.scale.z
));
} else {
return "Entity not found".to_string();
}
if let Some(visibility) = world.core.get_visibility(entity) {
info.push_str(&format!("Visible: {}\n", visibility.visible));
}
info
}
fn parse_bool(value: &str) -> Option<bool> {
match value.to_lowercase().as_str() {
"on" | "true" | "1" => Some(true),
"off" | "false" | "0" => Some(false),
_ => None,
}
}
fn cull<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let graphics = &mut world.resources.graphics;
if args.is_empty() {
return format!(
"GPU culling: {} | Occlusion: {}",
if graphics.gpu_culling_enabled {
"on"
} else {
"off"
},
if graphics.occlusion_culling_enabled {
"on"
} else {
"off"
}
);
}
match args[0].to_lowercase().as_str() {
"gpu" => {
let Some(value) = args.get(1).and_then(|raw| parse_bool(raw)) else {
return "Usage: cull gpu <on|off>".to_string();
};
graphics.gpu_culling_enabled = value;
format!("GPU culling {}", if value { "enabled" } else { "disabled" })
}
"occlusion" => {
let Some(value) = args.get(1).and_then(|raw| parse_bool(raw)) else {
return "Usage: cull occlusion <on|off>".to_string();
};
graphics.occlusion_culling_enabled = value;
format!(
"Occlusion culling {}",
if value { "enabled" } else { "disabled" }
)
}
other => {
let Some(value) = parse_bool(other) else {
return "Usage: cull [on|off|gpu on|off|occlusion on|off]".to_string();
};
graphics.gpu_culling_enabled = value;
graphics.occlusion_culling_enabled = value;
format!("Culling {}", if value { "enabled" } else { "disabled" })
}
}
}
fn bounds<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let graphics = &mut world.resources.graphics;
if args.is_empty() {
return format!(
"Bounding volumes: {}",
if graphics.show_bounding_volumes {
"on"
} else {
"off"
}
);
}
let Some(value) = parse_bool(args[0]) else {
return "Usage: bounds [on|off]".to_string();
};
graphics.show_bounding_volumes = value;
format!(
"Bounding volumes {}",
if value { "enabled" } else { "disabled" }
)
}
fn normals<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let graphics = &mut world.resources.graphics;
if args.is_empty() {
return format!(
"Normals: {}",
if graphics.show_normals { "on" } else { "off" }
);
}
let Some(value) = parse_bool(args[0]) else {
return "Usage: normals [on|off]".to_string();
};
graphics.show_normals = value;
format!(
"Normal debug {}",
if value { "enabled" } else { "disabled" }
)
}
fn pbr<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.is_empty() {
return format!(
"PBR debug mode: {}",
world.resources.graphics.pbr_debug_mode.name()
);
}
let mode = match args[0].to_lowercase().as_str() {
"none" | "off" => PbrDebugMode::None,
"basecolor" | "base" | "color" => PbrDebugMode::BaseColor,
"normal" | "normals" => PbrDebugMode::Normal,
"metallic" | "metal" => PbrDebugMode::Metallic,
"roughness" | "rough" => PbrDebugMode::Roughness,
"occlusion" | "ao" => PbrDebugMode::Occlusion,
"emissive" | "emit" => PbrDebugMode::Emissive,
"f" | "fresnel" => PbrDebugMode::F,
"g" | "geometry" => PbrDebugMode::G,
"d" | "distribution" => PbrDebugMode::D,
"diffuse" => PbrDebugMode::Diffuse,
"specular" | "spec" => PbrDebugMode::Specular,
"miprainbow" | "mip" | "rainbow" => PbrDebugMode::MipRainbow,
_ => {
return "Usage: pbr [none|basecolor|normal|metallic|roughness|occlusion|emissive|f|g|d|diffuse|specular|miprainbow]".to_string();
}
};
world.resources.graphics.pbr_debug_mode = mode;
format!("PBR debug mode: {}", mode.name())
}
fn ssao<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let graphics = &mut world.resources.graphics;
if args.is_empty() {
return format!("SSAO: {}", if graphics.ssao_enabled { "on" } else { "off" });
}
let Some(value) = parse_bool(args[0]) else {
return "Usage: ssao [on|off]".to_string();
};
graphics.ssao_enabled = value;
format!("SSAO {}", if value { "enabled" } else { "disabled" })
}
fn ssr<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let graphics = &mut world.resources.graphics;
if args.is_empty() {
return format!("SSR: {}", if graphics.ssr_enabled { "on" } else { "off" });
}
let Some(value) = parse_bool(args[0]) else {
return "Usage: ssr [on|off]".to_string();
};
graphics.ssr_enabled = value;
format!("SSR {}", if value { "enabled" } else { "disabled" })
}
fn ssgi<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let graphics = &mut world.resources.graphics;
if args.is_empty() {
return format!("SSGI: {}", if graphics.ssgi_enabled { "on" } else { "off" });
}
let Some(value) = parse_bool(args[0]) else {
return "Usage: ssgi [on|off]".to_string();
};
graphics.ssgi_enabled = value;
format!("SSGI {}", if value { "enabled" } else { "disabled" })
}
fn fxaa<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let graphics = &mut world.resources.graphics;
if args.is_empty() {
return format!("FXAA: {}", if graphics.fxaa_enabled { "on" } else { "off" });
}
let Some(value) = parse_bool(args[0]) else {
return "Usage: fxaa [on|off]".to_string();
};
graphics.fxaa_enabled = value;
format!("FXAA {}", if value { "enabled" } else { "disabled" })
}
fn fog<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
if args.is_empty() {
return match world.resources.graphics.fog {
Some(fog) => format!("Fog: on (start {:.1}, end {:.1})", fog.start, fog.end),
None => "Fog: off".to_string(),
};
}
if args.len() >= 2
&& let (Ok(start), Ok(end)) = (args[0].parse::<f32>(), args[1].parse::<f32>())
{
let mut fog = world.resources.graphics.fog.unwrap_or_default();
fog.start = start;
fog.end = end.max(start + 0.01);
world.resources.graphics.fog = Some(fog);
return format!("Fog start {:.1}, end {:.1}", fog.start, fog.end);
}
let Some(value) = parse_bool(args[0]) else {
return "Usage: fog [on|off|<start> <end>]".to_string();
};
if value {
if world.resources.graphics.fog.is_none() {
world.resources.graphics.fog = Some(Fog::default());
}
"Fog enabled".to_string()
} else {
world.resources.graphics.fog = None;
"Fog disabled".to_string()
}
}
fn exposure<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let grading = &mut world.resources.graphics.color_grading;
if args.is_empty() {
return format!("Exposure: {:.3}", grading.exposure);
}
let Ok(value) = args[0].parse::<f32>() else {
return "Usage: exposure [value]".to_string();
};
grading.exposure = value.max(0.0);
format!("Exposure set to {:.3}", grading.exposure)
}
fn gamma<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let grading = &mut world.resources.graphics.color_grading;
if args.is_empty() {
return format!("Gamma: {:.2}", grading.gamma);
}
let Ok(value) = args[0].parse::<f32>() else {
return "Usage: gamma [value]".to_string();
};
grading.gamma = value.clamp(0.1, 10.0);
format!("Gamma set to {:.2}", grading.gamma)
}
fn saturation<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let grading = &mut world.resources.graphics.color_grading;
if args.is_empty() {
return format!("Saturation: {:.2}", grading.saturation);
}
let Ok(value) = args[0].parse::<f32>() else {
return "Usage: saturation [value]".to_string();
};
grading.saturation = value.clamp(0.0, 4.0);
format!("Saturation set to {:.2}", grading.saturation)
}
fn contrast<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let grading = &mut world.resources.graphics.color_grading;
if args.is_empty() {
return format!("Contrast: {:.2}", grading.contrast);
}
let Ok(value) = args[0].parse::<f32>() else {
return "Usage: contrast [value]".to_string();
};
grading.contrast = value.clamp(0.0, 4.0);
format!("Contrast set to {:.2}", grading.contrast)
}
fn fullscreen<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let graphics = &mut world.resources.graphics;
if args.is_empty() {
return format!(
"Fullscreen: {}",
if graphics.use_fullscreen { "on" } else { "off" }
);
}
let Some(value) = parse_bool(args[0]) else {
return "Usage: fullscreen [on|off]".to_string();
};
graphics.use_fullscreen = value;
format!("Fullscreen {}", if value { "enabled" } else { "disabled" })
}
fn cursor<C>(args: &[&str], world: &mut World, _context: &mut C) -> String {
let graphics = &mut world.resources.graphics;
if args.is_empty() {
return format!(
"Cursor: {}",
if graphics.show_cursor { "on" } else { "off" }
);
}
let Some(value) = parse_bool(args[0]) else {
return "Usage: cursor [on|off]".to_string();
};
graphics.show_cursor = value;
format!("Cursor {}", if value { "shown" } else { "hidden" })
}