#![warn(unused_extern_crates)]
use std;
use env_logger;
use log;
use macro_machines;
use gl_utils;
use gl_utils::*;
use linear_sim;
use linear_sim::*;
pub mod program;
pub mod bounding_volume; pub mod penetration; pub mod simulation;
const LOG_LEVEL : log::LevelFilter = log::LevelFilter::Info;
const LOG_FILENAME : &str = "testbed.log";
const INITIAL_2D_ZOOM : f32 = 1.0;
const INITIAL_3D_POSITION : [f32; 3] = [0.0, 0.0, 2.0];
const INITIAL_CLEAR_COLOR : [f32; 4] = [0.0, 0.0, 0.0, 1.0];
const DEBUG_MAX_STEP : Option <u64> = None;
#[derive(Debug)]
enum ModeGroup {
Simulation,
Penetration,
BoundingVolume
}
impl ModeGroup {
pub(crate) fn states (&self) -> Vec <program::StateId> {
match self {
ModeGroup::Simulation => vec![
program::StateId::SimulationDropTest,
program::StateId::SimulationCollideCapsuleCapsule,
program::StateId::SimulationStackTest,
program::StateId::SimulationBallPitTest,
program::StateId::SimulationHullPitTest
],
ModeGroup::Penetration => vec![
program::StateId::PenetrationCapsuleCapsule,
program::StateId::PenetrationCapsuleCuboid,
program::StateId::PenetrationCuboidCuboid,
program::StateId::PenetrationTriangleTriangle,
program::StateId::PenetrationHullHull
],
ModeGroup::BoundingVolume => vec![
program::StateId::BoundingVolumeObb
]
}
}
pub(crate) const fn from_state (state : program::StateId) -> Self {
match state {
program::StateId::SimulationDropTest |
program::StateId::SimulationCollideCapsuleCapsule |
program::StateId::SimulationStackTest |
program::StateId::SimulationBallPitTest |
program::StateId::SimulationHullPitTest
=> ModeGroup::Simulation,
program::StateId::PenetrationCapsuleCapsule |
program::StateId::PenetrationCapsuleCuboid |
program::StateId::PenetrationCuboidCuboid |
program::StateId::PenetrationTriangleTriangle |
program::StateId::PenetrationHullHull
=> ModeGroup::Penetration,
program::StateId::BoundingVolumeObb
=> ModeGroup::BoundingVolume
}
}
}
impl winit::application::ApplicationHandler for program::Testbed {
fn resumed (&mut self, _event_loop : &winit::event_loop::ActiveEventLoop) { }
fn window_event (&mut self,
_event_loop : &winit::event_loop::ActiveEventLoop,
_window_id : winit::window::WindowId,
event : winit::event::WindowEvent
) {
use winit::{event, keyboard};
let modifiers = Render::modifiers();
if let Some (program_event) = match event {
event::WindowEvent::KeyboardInput {
event: event::KeyEvent {
state: event::ElementState::Pressed, physical_key, ..
},
..
} => match physical_key {
keyboard::PhysicalKey::Code (key_code) => if modifiers.shift_key() {
match key_code {
keyboard::KeyCode::Digit1 =>
(!ModeGroup::Simulation.states().contains (&self.state_id()))
.then_some (program::EventParams::ToSimulationDropTest{}.into()),
keyboard::KeyCode::Digit2 =>
(!ModeGroup::Penetration.states().contains (&self.state_id()))
.then_some (program::EventParams::ToPenetrationCapsuleCapsule{}.into()),
keyboard::KeyCode::Digit3 =>
(!ModeGroup::BoundingVolume.states().contains (&self.state_id()))
.then_some (program::EventParams::ToBoundingVolumeObb{}.into()),
_ => None
}
} else {
let change_state = |
to_state : program::StateId,
event : program::EventParams <'static>
| -> Option <program::Event <'static>> {
let current_state = self.state_id();
let to_group = ModeGroup::from_state (to_state.clone());
(to_group.states().contains (¤t_state) && current_state != to_state)
.then (|| event.into())
};
match key_code {
keyboard::KeyCode::KeyC => {
println!("current camera: {:?}",
self.as_mut().render_context
.viewports()[render::resource::MAIN_VIEWPORT].camera3d()
.unwrap());
None
}
keyboard::KeyCode::Digit1 => change_state (
program::StateId::SimulationDropTest,
program::EventParams::ToSimulationDropTest{}
).or_else (|| change_state (
program::StateId::PenetrationCapsuleCapsule,
program::EventParams::ToPenetrationCapsuleCapsule{}
)).or_else (|| change_state (
program::StateId::BoundingVolumeObb,
program::EventParams::ToBoundingVolumeObb{}
)),
keyboard::KeyCode::Digit2 => change_state (
program::StateId::SimulationCollideCapsuleCapsule,
program::EventParams::ToSimulationCollideCapsuleCapsule{}
).or_else (|| change_state (
program::StateId::PenetrationCapsuleCuboid,
program::EventParams::ToPenetrationCapsuleCuboid{}
)),
keyboard::KeyCode::Digit3 => change_state (
program::StateId::SimulationStackTest,
program::EventParams::ToSimulationStackTest{}
).or_else (|| change_state (
program::StateId::PenetrationCuboidCuboid,
program::EventParams::ToPenetrationCuboidCuboid{}
)),
keyboard::KeyCode::Digit4 => change_state (
program::StateId::SimulationBallPitTest,
program::EventParams::ToSimulationBallPitTest{}
).or_else (|| change_state (
program::StateId::PenetrationTriangleTriangle,
program::EventParams::ToPenetrationTriangleTriangle{}
)),
keyboard::KeyCode::Digit5 => change_state (
program::StateId::SimulationHullPitTest,
program::EventParams::ToSimulationHullPitTest{}
).or_else (|| change_state (
program::StateId::PenetrationHullHull,
program::EventParams::ToPenetrationHullHull{}
)),
keyboard::KeyCode::Tab => match self.state_id() {
program::StateId::PenetrationCapsuleCapsule =>
Some (program::EventParams::NextPenetrationCapsuleCapsule{}.into()),
program::StateId::PenetrationCuboidCuboid =>
Some (program::EventParams::NextPenetrationCuboidCuboid{}.into()),
program::StateId::PenetrationCapsuleCuboid =>
Some (program::EventParams::NextPenetrationCapsuleCuboid{}.into()),
program::StateId::PenetrationTriangleTriangle =>
Some (program::EventParams::NextPenetrationTriangleTriangle{}.into()),
program::StateId::PenetrationHullHull =>
Some (program::EventParams::NextPenetrationHullHull{}.into()),
program::StateId::SimulationCollideCapsuleCapsule =>
Some (program::EventParams::NextSimulationCollideCapsuleCapsule{}.into()),
program::StateId::SimulationBallPitTest =>
Some (program::EventParams::NextSimulationBallPitTest{}.into()),
program::StateId::SimulationHullPitTest =>
Some (program::EventParams::NextSimulationHullPitTest{}.into()),
program::StateId::BoundingVolumeObb =>
Some (program::EventParams::NextBoundingVolumeObb{}.into()),
_ => None
}
keyboard::KeyCode::KeyP => match self.state_data() {
program::StateData::SimulationDropTest { playback, .. } =>
if playback.paused {
Some (program::EventParams::ResumeSimulationDropTest{}.into())
} else {
Some (program::EventParams::PauseSimulationDropTest{}.into())
}
program::StateData::SimulationCollideCapsuleCapsule {
playback, ..
} => if playback.paused {
Some (program::EventParams::ResumeSimulationCollideCapsuleCapsule{}.into())
} else {
Some (program::EventParams::PauseSimulationCollideCapsuleCapsule{}.into())
}
program::StateData::SimulationStackTest { playback, .. } =>
if playback.paused {
Some (program::EventParams::ResumeSimulationStackTest{}.into())
} else {
Some (program::EventParams::PauseSimulationStackTest{}.into())
}
program::StateData::SimulationBallPitTest { playback, .. } =>
if playback.paused {
Some (program::EventParams::ResumeSimulationBallPitTest{}.into())
} else {
Some (program::EventParams::PauseSimulationBallPitTest{}.into())
}
_ => None
}
keyboard::KeyCode::Period => match self.state_data() {
program::StateData::SimulationDropTest { playback, .. } =>
if playback.paused {
Some (program::EventParams::StepSimulationDropTest{}.into())
} else {
Some (program::EventParams::PauseSimulationDropTest{}.into())
}
program::StateData::SimulationCollideCapsuleCapsule {
playback, ..
} => if playback.paused {
Some (program::EventParams::StepSimulationCollideCapsuleCapsule{}.into())
} else {
Some (program::EventParams::PauseSimulationCollideCapsuleCapsule{}.into())
}
program::StateData::SimulationStackTest { playback, .. } =>
if playback.paused {
Some (program::EventParams::StepSimulationStackTest{}.into())
} else {
Some (program::EventParams::PauseSimulationStackTest{}.into())
}
program::StateData::SimulationBallPitTest { playback, .. } =>
if playback.paused {
Some (program::EventParams::StepSimulationBallPitTest{}.into())
} else {
Some (program::EventParams::PauseSimulationBallPitTest{}.into())
}
_ => None
}
keyboard::KeyCode::Comma => match self.state_data() {
program::StateData::SimulationDropTest { playback, .. } =>
if playback.paused {
Some (program::EventParams::StepReverseSimulationDropTest{}.into())
} else {
Some (program::EventParams::PauseSimulationDropTest{}.into())
}
program::StateData::SimulationCollideCapsuleCapsule {
playback, ..
} => if playback.paused {
Some (program::EventParams::StepReverseSimulationCollideCapsuleCapsule{}
.into())
} else {
Some (program::EventParams::PauseSimulationCollideCapsuleCapsule{}.into())
}
program::StateData::SimulationStackTest { playback, .. } =>
if playback.paused {
Some (program::EventParams::StepReverseSimulationStackTest{}.into())
} else {
Some (program::EventParams::PauseSimulationStackTest{}.into())
}
program::StateData::SimulationBallPitTest { playback, .. } =>
if playback.paused {
Some (program::EventParams::StepReverseSimulationBallPitTest{}.into())
} else {
Some (program::EventParams::PauseSimulationBallPitTest{}.into())
}
_ => None
}
keyboard::KeyCode::Backspace => match self.state_id() {
program::StateId::SimulationDropTest =>
Some (program::EventParams::RestartSimulationDropTest{}.into()),
program::StateId::SimulationCollideCapsuleCapsule =>
Some (program::EventParams::RestartSimulationCollideCapsuleCapsule{}
.into()),
program::StateId::SimulationStackTest =>
Some (program::EventParams::RestartSimulationStackTest{}.into()),
program::StateId::SimulationBallPitTest =>
Some (program::EventParams::RestartSimulationBallPitTest{}.into()),
program::StateId::SimulationHullPitTest =>
Some (program::EventParams::RestartSimulationHullPitTest{}.into()),
_ => None
}
_ => None }
}
keyboard::PhysicalKey::Unidentified (native_key_code) => {
log::warn!("keyboard input: unidentified physical keycode: \
{native_key_code:?}");
None
}
}
_ => None
} {
self.handle_event (program_event).unwrap();
} else {
let mut mouse_button_event = None;
let mut mouse_position = (0.0, 0.0);
let mut running = true;
self.as_mut().render_context.demo_handle_winit_window_event (
event, &mut running, &mut mouse_position, &mut mouse_button_event);
self.as_mut().running = running;
}
}
}
fn main() {
println!("linear-sim testbed...");
let log_file = Box::new (std::fs::File::create (LOG_FILENAME).unwrap());
env_logger::Builder::new()
.filter_level (LOG_LEVEL)
.parse_default_env()
.target (env_logger::Target::Pipe (log_file))
.init();
log::info!("initializing glium...");
let (mut event_loop, window, _gl_config, glium_display) =
init::glium_init_gl33core ("linear-sim testbed window");
{ use glium::backend::Facade;
let context = glium_display.get_context();
log::info!("opengl version: {:?}", context.get_opengl_version());
log::info!("opengl profile: {:?}", context.get_opengl_profile());
log::info!("supported glsl version: {:?}", context.get_supported_glsl_version());
log::info!("free video memory: {}MB",
context.get_free_video_memory().unwrap()/1_000_000);
}
program::Testbed::report_sizes();
{ use std::io::Write;
use macro_machines::MachineDotfile;
let mut f = std::fs::File::create ("testbed.dot").unwrap();
f.write_all (program::Testbed::dotfile_hide_actions().as_bytes()).unwrap();
}
object::report_sizes();
system::report_sizes();
collision::report_sizes();
let mut testbed = {
let render_context = Render::new (glium_display, window);
program::Testbed::new (
program::ExtendedState::new (Some (render_context), None, Some (true)).unwrap())
};
println!("Welcome");
println!(" Press 'C' to show current camera state");
let mut fps = 0;
let mut last_t = std::time::Instant::now();
while testbed.as_ref().running {
use winit::platform::pump_events::EventLoopExtPumpEvents;
event_loop.pump_app_events (Some (std::time::Duration::ZERO), &mut testbed);
let debug_break_max_step = |testbed : &mut program::Testbed| {
let system = match testbed.state_data() {
program::StateData::SimulationDropTest { simulation, .. } |
program::StateData::SimulationStackTest { simulation, .. } |
program::StateData::SimulationBallPitTest {
simulation_generation: (simulation, _), ..
} |
program::StateData::SimulationHullPitTest {
simulation_generation: (simulation, _), ..
} |
program::StateData::SimulationCollideCapsuleCapsule {
simulation_generation: (simulation, _), ..
} => &simulation.system,
_ => unreachable!("state id should be checked prior to this match")
};
if let Some (max_step) = DEBUG_MAX_STEP && system.step() >= max_step {
if system.step() > max_step {
log::warn!("system step ({}) > max_step ({})", system.step(), max_step);
}
testbed.as_mut().running = false;
}
};
match testbed.state_id() {
program::StateId::SimulationDropTest => {
testbed.handle_event (program::EventParams::UpdateSimulationDropTest{}.into())
.unwrap();
if cfg!(debug_assertions) {
debug_break_max_step (&mut testbed);
}
}
program::StateId::SimulationCollideCapsuleCapsule => {
testbed.handle_event (
program::EventParams::UpdateSimulationCollideCapsuleCapsule{}.into()
).unwrap();
if cfg!(debug_assertions) {
debug_break_max_step (&mut testbed);
}
}
program::StateId::SimulationStackTest => {
testbed.handle_event (program::EventParams::UpdateSimulationStackTest{}.into())
.unwrap();
if cfg!(debug_assertions) {
debug_break_max_step (&mut testbed);
}
}
program::StateId::SimulationBallPitTest => {
testbed.handle_event (program::EventParams::UpdateSimulationBallPitTest{}.into())
.unwrap();
if cfg!(debug_assertions) {
debug_break_max_step (&mut testbed);
}
}
program::StateId::SimulationHullPitTest => {
testbed.handle_event (program::EventParams::UpdateSimulationHullPitTest{}.into())
.unwrap();
if cfg!(debug_assertions) {
debug_break_max_step (&mut testbed);
}
}
_ => {}
}
use glium::Surface;
let mut glium_frame = testbed.as_mut().render_context.glium_display.draw();
glium_frame.clear_all (
( testbed.as_mut().render_context.clear_color[0],
testbed.as_mut().render_context.clear_color[1],
testbed.as_mut().render_context.clear_color[2],
testbed.as_mut().render_context.clear_color[3]
),
1.0, 0
);
match testbed.state_id() {
program::StateId::PenetrationCapsuleCapsule => {
let e = program::EventParams::DrawPenetrationCapsuleCapsule {
glium_frame: &mut glium_frame
}.into();
testbed.handle_event (e).unwrap();
}
program::StateId::PenetrationCuboidCuboid => {
let e = program::EventParams::DrawPenetrationCuboidCuboid {
glium_frame: &mut glium_frame
}.into();
testbed.handle_event (e).unwrap();
}
program::StateId::PenetrationCapsuleCuboid => {
let e = program::EventParams::DrawPenetrationCapsuleCuboid {
glium_frame: &mut glium_frame
}.into();
testbed.handle_event (e).unwrap();
}
_ => render::Resource::draw_3d (&testbed.as_mut().render_context, &mut glium_frame)
}
render::Resource::draw_2d (&testbed.as_ref().render_context, &mut glium_frame);
glium_frame.finish().unwrap();
let t = std::time::Instant::now();
if (t - last_t).as_secs() >= 1 {
let render = &mut testbed.as_mut().render_context;
let (_width, height) : (u32, u32) = render.window.inner_size().into();
let [_tile_width, tile_height] = render.resource.tile_dimensions (
render::resource::DefaultTilesetId::EasciiAcorn128);
let tile_range = render.resource.draw2d
.viewport_resources_get (render::resource::OVERLAY_VIEWPORT).unwrap()
.draw_indices[0].draw_tiles.as_ref().unwrap().vertex_range.clone();
let fps_row = ((height / tile_height) - 1) as i32;
let tiles = tile::vertices (&format!("FPS: {fps:<6}"), (fps_row, 1));
render.resource.draw2d.tile_2d_vertices
.slice (tile_range.start as usize .. tile_range.end as usize).unwrap()
.write (&tiles);
last_t = t;
fps = 0;
} else {
fps += 1;
}
}
println!("...linear-sim testbed");
}