// -*- coding: utf-8 -*- // // Copyright 2021 Michael Büsch // // Licensed under the Apache License version 2.0 // or the MIT license, at your option. // SPDX-License-Identifier: Apache-2.0 OR MIT // use crate::{gsignal_connect_to_mut, gsigparam, gtk_helpers::*, joystick::Joystick}; use anyhow as ah; use movavg::MovAvg; use physics::prelude::*; use std::cell::RefCell; use std::path::Path; use std::rc::Rc; use std::time::{Duration, Instant}; //use voxel::prelude::*; use graphics::{Landscape3D, Render3D, View3D}; trait RenderTrait: Render3D + View3D + Landscape3D {} impl RenderTrait for voxel::Render {} impl RenderTrait for walleng::Render {} const METERPERPIX_PLANE: PhyType = PhyType::new_f64(0.09); const METERPERPIX_HEIGHT: PhyType = PhyType::new_f64(0.0003); const PERSON_VIEW_HEIGHT: i32 = 0x700; const PLANE_VIEW_HEIGHT: i32 = 0xF00; const PLANE_THRUST_MAX: PhyType = PhyType::new_f64(20.0); const WALKER_WALK_SPEED_KMH: PhyType = PhyType::new_f64(6.0); const WALKER_RUN_SPEED_KMH: PhyType = PhyType::new_f64(20.0); fn pix2m_plane(p: f32) -> PhyType { PhyType::from(p) * METERPERPIX_PLANE } fn m2pix_plane(m: PhyType) -> f32 { *(m / METERPERPIX_PLANE) as f32 } fn pix2m_height(p: f32) -> PhyType { PhyType::from(p) * METERPERPIX_HEIGHT } fn m2pix_height(m: PhyType) -> f32 { *(m / METERPERPIX_HEIGHT) as f32 } fn create_voxel(map_name: &str) -> ah::Result> { let mut map = voxel::Map::open_by_name(Path::new("."), map_name)?; map.inflate(2); map.add_color_lightness_noise(0.015); Ok(Box::new(voxel::Render::new(map))) } fn create_walleng(map_name: &str) -> ah::Result> { let path = Path::new("walleng").join(map_name.to_string() + ".map"); let world = walleng::World::load(&path)?; Ok(Box::new(walleng::Render::new(world))) } pub struct DrawingArea { widget: gtk::DrawingArea, render: Box, surface: Option, world: physics::World, obj_walker: Option, obj_aviation: Option, frame_div: u32, frame_count: u32, fps_count: u32, fps_avg: MovAvg, moving_fwd: bool, moving_bwd: bool, strafing_left: bool, strafing_right: bool, turning_left: bool, turning_right: bool, looking_up: bool, looking_down: bool, jumping: bool, thrust_fact: f32, joystick: Joystick, } impl DrawingArea { pub fn new(widget: gtk::DrawingArea, map_name: &str) -> ah::Result>> { widget.add_events( gdk::EventMask::POINTER_MOTION_MASK | gdk::EventMask::POINTER_MOTION_HINT_MASK | gdk::EventMask::SCROLL_MASK | gdk::EventMask::BUTTON_MOTION_MASK | gdk::EventMask::BUTTON_PRESS_MASK | gdk::EventMask::BUTTON_RELEASE_MASK | gdk::EventMask::KEY_PRESS_MASK | gdk::EventMask::KEY_RELEASE_MASK, ); let render = match 0 { 0 => create_voxel(map_name)?, _ => create_walleng(map_name)?, }; let world = physics::World::new(None); let self_ = Rc::new(RefCell::new(DrawingArea { widget, render, surface: None, world, obj_walker: None, obj_aviation: None, frame_div: 2, frame_count: 0, fps_count: 0, fps_avg: MovAvg::new(), moving_fwd: false, moving_bwd: false, strafing_left: false, strafing_right: false, turning_left: false, turning_right: false, looking_up: false, looking_down: false, jumping: false, thrust_fact: 0.0, joystick: Default::default(), })); if true { self_.borrow_mut().create_walker(); } else { self_.borrow_mut().create_aviation(); } let self2 = Rc::clone(&self_); let dur = Duration::from_millis((1000 / (60 * self_.borrow().frame_div)) as u64); glib::timeout_add_local(dur, move || { if let Ok(mut self_) = self2.try_borrow_mut() { self_.frame_timer(); } glib::ControlFlow::Continue }); let self2 = Rc::clone(&self_); let dur = Duration::from_millis(1000); glib::timeout_add_local(dur, move || { if let Ok(mut self_) = self2.try_borrow_mut() { self_.maintenance_timer(); } glib::ControlFlow::Continue }); Ok(self_) } fn create_walker(&mut self) { let mut obj_walker = physics::Walker::new( physics::kmph_mps(WALKER_WALK_SPEED_KMH), // walk_speed_limit 0.08.into(), // air_trans_acc_fact 0.03.into(), // air_rot_acc_fact ); obj_walker.set_speed_limits(( physics::kmph_mps((-100).into()), physics::kmph_mps(100.into()), )); obj_walker.set_pos_limits(physics::Axis::Z, (0.into(), PhyType::UNLIMITED)); let pos = self.render.get_position(); obj_walker.set_pos(physics::Point::new( pix2m_plane(pos.x()), pix2m_plane(pos.z()), pix2m_height(pos.y()), )); let rot_speed_lim = 200; // deg/s obj_walker.get_rot_move_mut().set_rot_speed_lim( &physics::Rotation::new_rel( physics::radians((-rot_speed_lim).into()), physics::radians((-rot_speed_lim).into()), physics::radians((-rot_speed_lim).into()), ), &physics::Rotation::new_rel( physics::radians(rot_speed_lim.into()), physics::radians(rot_speed_lim.into()), physics::radians(rot_speed_lim.into()), ), ); self.obj_aviation = None; self.obj_walker = Some(obj_walker); } fn create_aviation(&mut self) { let mut obj_aviation: physics::AviationObj = Default::default(); obj_aviation.set_speed_limits(( physics::kmph_mps((-1000).into()), physics::kmph_mps(1000.into()), )); obj_aviation.set_pos_limits(physics::Axis::Z, (0.into(), PhyType::UNLIMITED)); obj_aviation.set_thrust_acc_limits(((-20).into(), 100.into())); let pos = self.render.get_position(); obj_aviation.set_pos(physics::Point::new( pix2m_plane(pos.x()), pix2m_plane(pos.z()), pix2m_height(pos.y()), )); self.obj_walker = None; self.obj_aviation = Some(obj_aviation); } fn maintenance_timer(&mut self) { let fps = self.fps_count; self.fps_count = 0; let fps_avg = self.fps_avg.feed(fps * 100); let phy_object: &dyn PhyObject = if let Some(obj_walker) = self.obj_walker.as_ref() { obj_walker } else if let Some(obj_aviation) = self.obj_aviation.as_ref() { obj_aviation } else { return; }; let pos = phy_object.get_pos(); let rot = phy_object.get_rot(); println!( "{:.1} FPS; pos={:.1}/{:.1}/{:.1}, rot={:.1}/{:.1}/{:.1}", fps_avg as f32 / 100.0, pos.x(), pos.y(), pos.z(), physics::degrees(rot.alpha()), physics::degrees(rot.beta()), physics::degrees(rot.gamma()) ); } fn frame_timer(&mut self) { self.joystick.update(); let have_joystick = self.joystick.get_count() > 0; let joy_state = if have_joystick { Some(self.joystick.get_state(0)) } else { None }; let mut objs: Vec<&mut dyn PhyObject> = Vec::with_capacity(2); let moving_translationally = self.moving_translationally(); if let Some(obj_walker) = self.obj_walker.as_mut() { // Update the physical ground level. obj_walker.set_pos_limits( physics::Axis::Z, ( pix2m_height(self.render.get_terrain_height() + PERSON_VIEW_HEIGHT as f32), PhyType::UNLIMITED, ), ); // Rotational move. let turn_acc = physics::radians(2000.into()); if self.turning_right { obj_walker.turn(physics::walker::TurnDirection::Right(turn_acc)); } else if self.turning_left { obj_walker.turn(physics::walker::TurnDirection::Left(turn_acc)); } else { obj_walker.turn(physics::walker::TurnDirection::Stop); } // Translational move. if self.moving_fwd { obj_walker.move_(physics::walker::MoveDirection::Fwd); } else if self.moving_bwd { obj_walker.move_(physics::walker::MoveDirection::Bwd); } else { obj_walker.move_(physics::walker::MoveDirection::Stop); } // Translational strafe move. if self.strafing_left { obj_walker.strafe(physics::walker::StrafeDirection::Left); } else if self.strafing_right { obj_walker.strafe(physics::walker::StrafeDirection::Right); } else { obj_walker.strafe(physics::walker::StrafeDirection::Stop); } // Jump move. obj_walker.jump( self.jumping, if moving_translationally { 28.into() } else { 23.into() }, // acceleration 0.18.into(), // acc time true, ); // repeat objs.push(obj_walker); } if let Some(obj_aviation) = self.obj_aviation.as_mut() { // Update the physical ground level. obj_aviation.set_pos_limits( physics::Axis::Z, ( pix2m_height(self.render.get_terrain_height() + PLANE_VIEW_HEIGHT as f32), PhyType::UNLIMITED, ), ); // Thrust. if let Some(joy_state) = joy_state { self.thrust_fact = joy_state.get_throttle() as f32; } obj_aviation.set_thrust_acc(PLANE_THRUST_MAX * self.thrust_fact.into()); // Roll. if let Some(joy_state) = joy_state { let fact: PhyType = joy_state.get_stick().x().into(); obj_aviation.set_yoke_roll(physics::radians(90.into()) * fact); } else if self.turning_right || self.turning_left { let mut roll = obj_aviation.get_yoke_roll(); let mul = if self.turning_right { 1.into() } else { (-1).into() }; roll += physics::radians(1.into()) * mul; obj_aviation.set_yoke_roll(roll); } // Pitch. if let Some(joy_state) = joy_state { let fact: PhyType = joy_state.get_stick().y().into(); obj_aviation.set_yoke_pitch(physics::radians(15.into()) * fact); } else if self.moving_fwd || self.moving_bwd { let mut pitch = obj_aviation.get_yoke_pitch(); let mul = if self.moving_bwd { 1.into() } else { (-1).into() }; pitch += physics::radians(1.into()) * mul; obj_aviation.set_yoke_pitch(pitch); } objs.push(obj_aviation); } // Recalculate the physics model. self.world.calc(&mut objs); if self.frame_count % self.frame_div == 0 { if let Some(obj_walker) = self.obj_walker.as_mut() { let phypos = obj_walker.get_pos(); let phyrot = obj_walker.get_rot(); self.render .set_yaw_angle(*physics::degrees(phyrot.gamma()) as f32); self.render.set_position(&graphics::Point3D::new( m2pix_plane(phypos.x()), m2pix_height(phypos.z()), m2pix_plane(phypos.y()), )); } if let Some(obj_aviation) = self.obj_aviation.as_mut() { let phypos = obj_aviation.get_pos(); let yaw = *physics::degrees(obj_aviation.get_yaw()) as f32; let roll = *physics::degrees(obj_aviation.get_roll()) as f32; let pitch = *physics::degrees(obj_aviation.get_pitch()) as f32; //println!("yaw={:.2}*, roll={:.2}*, pitch={:.2}*", yaw, roll, pitch); self.render.set_yaw_angle(yaw); self.render.set_roll_angle(roll); self.render.set_pitch_angle(pitch); self.render.set_position(&graphics::Point3D::new( m2pix_plane(phypos.x()), m2pix_height(phypos.z()), m2pix_plane(phypos.y()), )); } self.redraw(); self.fps_count = self.fps_count.wrapping_add(1); } self.frame_count = self.frame_count.wrapping_add(1); } fn redraw(&self) { self.widget.queue_draw(); } fn draw(&mut self, cairo: cairo::Context) { let screen_width = self.widget.allocated_width() as usize; let screen_height = self.widget.allocated_height() as usize; let (render_width, render_height) = self .render .calc_scene_dimensions(screen_width, screen_height); let (width_diff, height_diff) = (screen_width - render_width, screen_height - render_height); if self.surface.is_none() || self.surface.as_ref().unwrap().width() != render_width as i32 || self.surface.as_ref().unwrap().height() != render_height as i32 { self.surface = None; self.surface = Some( cairo::ImageSurface::create( cairo::Format::ARgb32, render_width as i32, render_height as i32, ) .expect("Failed to create image surface."), ); } if let Some(surface) = &mut self.surface { let render_buf = &mut *surface.data().expect("Failed to get surface data."); let begin = Instant::now(); self.render .render_scene_u8(render_buf, render_width, render_height); if false { println!( "Render dur: {:.2} ms", Instant::now().duration_since(begin).as_micros() as f32 / 1e3 ); } } if let Some(surface) = &self.surface { surface.mark_dirty(); } let begin = Instant::now(); cairo .set_source_surface( self.surface.as_ref().unwrap(), (width_diff / 2) as f64, (height_diff / 2) as f64, ) .expect("Failed to set cairo source."); if let Err(e) = cairo.paint() { println!("Cairo paint failed: {}", e); } if false { println!( "Cairo dur: {:.2} ms", Instant::now().duration_since(begin).as_micros() as f32 / 1e3 ); } } fn handle_key(&mut self, press: bool, event: gdk::EventKey) { let have_joystick = self.joystick.get_count() > 0; if let Some(name) = event.keyval().name() { #[allow(clippy::needless_bool_assign)] match name.as_str() { "Left" => { if press { self.turning_left = true; self.turning_right = false; } else { self.turning_left = false; } } "a" | "A" => { if press { self.turning_left = true; self.turning_right = false; } else { self.turning_left = false; } } "Right" => { if press { self.turning_left = false; self.turning_right = true; } else { self.turning_right = false; } } "d" | "D" => { if press { self.turning_left = false; self.turning_right = true; } else { self.turning_right = false; } } "Up" => { if press { self.moving_fwd = true; self.moving_bwd = false; } else { self.moving_fwd = false; } } "w" | "W" => { if press { if self.obj_walker.is_some() { self.moving_fwd = true; self.moving_bwd = false; } } else { self.moving_fwd = false; } } "Down" => { if press { self.moving_fwd = false; self.moving_bwd = true; } else { self.moving_bwd = false; } } "s" | "S" => { if press { if self.obj_walker.is_some() { self.moving_fwd = false; self.moving_bwd = true; } } else { self.moving_bwd = false; } } "space" => { if press { self.jumping = true; } else { self.jumping = false; } } "Page_Up" => { if press { self.looking_up = true; self.looking_down = false; } else { self.looking_up = false; } } "Page_Down" => { if press { self.looking_down = true; self.looking_up = false; } else { self.looking_down = false; } } "0" => { if press && !have_joystick { self.thrust_fact = 0.0; } } "1" => { if press && !have_joystick { self.thrust_fact = 0.2; } } "2" => { if press && !have_joystick { self.thrust_fact = 0.4; } } "3" => { if press && !have_joystick { self.thrust_fact = 0.6; } } "4" => { if press && !have_joystick { self.thrust_fact = 0.8; } } "5" => { if press && !have_joystick { self.thrust_fact = 1.0; } } "Shift_L" | "Shift_R" => { if let Some(obj_walker) = self.obj_walker.as_mut() { obj_walker.set_walk_speed_limit(if press { physics::kmph_mps(WALKER_RUN_SPEED_KMH) } else { physics::kmph_mps(WALKER_WALK_SPEED_KMH) }); } } _ => { if true { println!( "Key {} {} ignored.", name.as_str(), if press { "press" } else { "release" } ); } } } } } fn moving_translationally(&self) -> bool { self.moving_fwd || self.moving_bwd || self.strafing_left || self.strafing_right } /* fn moving_rotationally(&self) -> bool { self.turning_left || self.turning_right || self.looking_up || self.looking_down } fn moving(&self) -> bool { self.moving_translationally() || self.moving_rotationally() } */ fn gsignal_draw(&mut self, param: &[glib::Value]) -> Option { let _widget = gsigparam!(param[0], gtk::DrawingArea); let cairo = gsigparam!(param[1], cairo::Context); self.draw(cairo); Some(false.to_value()) } fn gsignal_key_press(&mut self, param: &[glib::Value]) -> Option { let _widget = gsigparam!(param[0], gtk::DrawingArea); let event = gsigparam!(param[1], gdk::Event); if let Ok(event) = event.downcast() { self.handle_key(true, event); } Some(false.to_value()) } fn gsignal_key_release(&mut self, param: &[glib::Value]) -> Option { let _widget = gsigparam!(param[0], gtk::DrawingArea); let event = gsigparam!(param[1], gdk::Event); if let Ok(event) = event.downcast() { self.handle_key(false, event); } Some(false.to_value()) } pub fn connect_signals( draw: Rc>, handler_name: &str, ) -> Option { match handler_name { "handler_drawingarea_draw" => Some(gsignal_connect_to_mut!( draw, gsignal_draw, Some(false.to_value()) )), "handler_drawingarea_keypress" => Some(gsignal_connect_to_mut!( draw, gsignal_key_press, Some(false.to_value()) )), "handler_drawingarea_keyrelease" => Some(gsignal_connect_to_mut!( draw, gsignal_key_release, Some(false.to_value()) )), _ => None, } } } // vim: ts=4 sw=4 expandtab