cubik 0.1.0

OpenGL/glium-based multiplayer game engine
Documentation
use crate::cube::generate_cube_collideobj;
use crate::quadoctree::CollisionObj;
use crate::math::{normalize_vector, cross_product, add_vector};
use crate::input::InputListener;
use crate::camera::{Camera, UP};
use crate::collision::check_player_collision;
use crate::quadoctree::QuadOctreeNode;
use glium::glutin::event::{VirtualKeyCode, ElementState, MouseButton};
use glium::Display;
use glium::glutin::dpi::PhysicalPosition;
use serde::{Serialize, Deserialize};
use crate::audio::{SoundData, SoundStream, create_sink, sound_decoder_from_data_looped};
use rodio::Sink;
use crate::interpolation::InterpolationHelper;

const DEFAULT_MOVE_RATE: f32 = 1.28;
const MOUSE_SENSITIVITY: f32 = 1.8;
const GRAVITY: f32 = 1.8;
const JUMP_VELOCITY: f32 = 0.9;
const CLIENT_UPDATE_INTERVAL: f32 = 0.017;
const SERVER_UPDATE_INTERVAL: f32 = 0.15;

pub enum PlayerControlType {	
	MultiplayerServer,
	MultiplayerClient,
	Singleplayer
}

#[derive(Serialize, Deserialize)]
pub enum PlayerControlMessage {
	Server {
		position: [f32; 3],
		yaw: f32,
		is_colliding: bool,
		is_moving: bool
	},
	Client {
		pitch_yaw: (f32, f32),
		input_state: PlayerInputState
	}
}

#[derive(Serialize, Deserialize, Default, Copy, Clone)]
pub struct PlayerInputState {
	pub move_forward: bool,
	pub move_left: bool,
	pub move_right: bool,
	pub move_back: bool,
	pub jump: bool
}

pub struct Player {
	pub control_type: PlayerControlType,

	pub camera: Camera,
	pub player_cube: CollisionObj,
	pub player_cube_offset: [f32; 3],
	pub player_cube_size: [f32; 3],
	pub velocity: [f32; 3],
	pub noclip: bool,
	pub move_rate: f32,

	pub start_position: [f32; 3],

	pub walking_sound: Option<SoundData>,
	walking_sound_sink: Option<Sink>,

	pub is_colliding: bool,
	pub is_moving: bool,

	pub input_state: PlayerInputState,
	net_update_time_count: f32,
	input_changed: bool,

	interpolation: InterpolationHelper<[f32; 3]>
}

impl Player {
	pub fn new(position: [f32; 3], control_type: PlayerControlType, player_cube_offset: [f32; 3], player_cube_size: [f32; 3]) -> Self {
		let player_cube = generate_cube_collideobj(&player_cube_offset, &position, &player_cube_size, 0.);
		let eye_height = player_cube_offset[1] + (player_cube_size[1] * 0.8);
		Self {
			control_type: control_type,
			camera: Camera::new(position, eye_height),
			player_cube_offset: player_cube_offset,
			player_cube_size: player_cube_size,
			player_cube: player_cube,
			start_position: position,
			velocity: [0., 0., 0.],
			move_rate: DEFAULT_MOVE_RATE,
			noclip: false,
			is_colliding: false,
			is_moving: false,
			input_state: Default::default(),
			net_update_time_count: 0.,
			input_changed: false,
			walking_sound: None,
			walking_sound_sink: None,
			interpolation: InterpolationHelper::new()
		}
	}

	pub fn respawn(&mut self) {
		self.camera.position = self.start_position;
	}

	fn input_update(&mut self, time_delta: f32) {
		let move_len = self.move_rate * time_delta;

		let move_dir = if !self.noclip {
			[self.camera.direction[0], 0., self.camera.direction[2]]
		} else {
			// "no clip" mode
			self.camera.direction
		};
		let mut move_vec = [0., 0., 0.0f32];

		let right = normalize_vector(&cross_product(&UP, &move_dir));
		let up = cross_product(&move_dir, &right);
		let direction_perp = cross_product(&move_dir, &up);
		if self.input_state.move_forward { move_vec = add_vector(&move_vec, &move_dir, 1.0); }
		if self.input_state.move_back { move_vec = add_vector(&move_vec, &move_dir, -1.0); }
		if self.input_state.move_left { move_vec = add_vector(&move_vec, &direction_perp, 1.0); }
		if self.input_state.move_right { move_vec = add_vector(&move_vec, &direction_perp, -1.0); }
		self.camera.position = add_vector(&self.camera.position, &move_vec, move_len);

		self.player_cube = generate_cube_collideobj(&self.player_cube_offset, &self.camera.position,
			&self.player_cube_size, -self.camera.pitch_yaw.1);

		self.is_moving = move_vec != [0., 0., 0.0f32];
	}

	fn update_sound(&mut self, sound_stream: Option<&SoundStream>) {
		if let Some(sound_stream) = sound_stream {
			let is_walking = self.is_moving && self.is_colliding;
			if let Some(sound) = self.walking_sound.as_ref() {
				if let Some(sink) = self.walking_sound_sink.as_ref() {
					if !is_walking {
						sink.stop();
						self.walking_sound_sink = None;
					}
				} else if is_walking {
					let sink = create_sink(sound_stream).unwrap();
					sink.append(sound_decoder_from_data_looped(sound).unwrap());
					sink.play();
					self.walking_sound_sink = Some(sink);
				}
			}
		}
	}

	fn fix_velocity(&mut self, correction_vec: &[f32; 3]) {
		for i in 0..3 {
			if (correction_vec[i] > 0.00001 && self.velocity[i] < 0.) || (correction_vec[i] < -0.00001 && self.velocity[i] > 0.) {
				self.velocity[i] = 0.;
			}
		}
	}

	fn maybe_jump(&mut self) {
		if !self.input_state.jump { return; }
		self.camera.position[1] += 0.08;
		self.velocity[1] = JUMP_VELOCITY;
	}

	fn collision_gravity_update(&mut self, time_delta: f32, quadoctree: Option<&QuadOctreeNode>) {
		self.is_colliding = false;

		if let Some(quadoctree) = quadoctree {
			if self.noclip { return; }

			let collide_result = check_player_collision(&quadoctree, &self.camera.position, &self.player_cube);

			self.velocity[1] -= GRAVITY * time_delta;

			for poly_collide in &collide_result.polygons {
				self.camera.position = add_vector(&self.camera.position, &poly_collide, 1.);
				self.fix_velocity(&poly_collide);
				if normalize_vector(&poly_collide)[1] > 0.5 {
					self.maybe_jump();
				}
				self.is_colliding = true;
			}
			if let Some(tri_intersect) = collide_result.triangle {
				if self.camera.position[1] < tri_intersect[1] + 0.08 {
					self.camera.position[1] = tri_intersect[1] + 0.02;
					self.fix_velocity(&[0.0, 1.0, 0.0]);
					self.maybe_jump();
					self.is_colliding = true;
				}
			}

			self.camera.position = add_vector(&self.camera.position, &self.velocity, time_delta);
		}
	}

	pub fn update(&mut self, time_delta: f32, quadoctree: Option<&QuadOctreeNode>, sound_stream: Option<&SoundStream>, incoming_msg: Option<PlayerControlMessage>) -> Option<PlayerControlMessage> {
		match self.control_type {
			PlayerControlType::MultiplayerServer => {
				if let Some(incoming_msg) = incoming_msg {
					if let PlayerControlMessage::Client { input_state, pitch_yaw } = incoming_msg {
						self.input_state = input_state;
						self.camera.pitch_yaw = pitch_yaw;
						self.camera.update_direction();
					}
					return None;
				}
				self.input_update(time_delta);
				self.collision_gravity_update(time_delta, quadoctree);
				self.net_update_time_count += time_delta;
				if self.net_update_time_count >= SERVER_UPDATE_INTERVAL {
					self.net_update_time_count = 0.;
					Some(PlayerControlMessage::Server {
						position: self.camera.position,
						yaw: self.camera.pitch_yaw.1,
						is_moving: self.is_moving,
						is_colliding: self.is_colliding
					})
				} else {
					None
				}
			},
			PlayerControlType::MultiplayerClient => {
				if let Some(incoming_msg) = incoming_msg {
					if let PlayerControlMessage::Server { position, is_moving, is_colliding, .. } = incoming_msg {
						self.is_moving = is_moving;
						self.is_colliding = is_colliding;
						self.interpolation.post_update(position);
					}
					return None;
				}
				self.update_sound(sound_stream);
				if let Some(value) = self.interpolation.value(time_delta) {
					self.camera.position = value;
				}
				self.net_update_time_count += time_delta;
				if self.input_changed && self.net_update_time_count >= CLIENT_UPDATE_INTERVAL {
					self.net_update_time_count = 0.;
					self.input_changed = false;
					Some(PlayerControlMessage::Client { input_state: self.input_state, pitch_yaw: self.camera.pitch_yaw })
				} else {
					None
				}
			},
			PlayerControlType::Singleplayer => {
				self.input_update(time_delta);
				self.collision_gravity_update(time_delta, quadoctree);
				self.update_sound(sound_stream);
				None
			}
		}
	}
}

impl InputListener for Player {
	fn handle_key_ev(&mut self, key: Option<VirtualKeyCode>, pressed: bool) -> bool {
		if let Some(key) = key {
			match key {
				VirtualKeyCode::W => self.input_state.move_forward = pressed,
				VirtualKeyCode::A => self.input_state.move_left = pressed,
				VirtualKeyCode::D => self.input_state.move_right = pressed,
				VirtualKeyCode::S => self.input_state.move_back = pressed,
				VirtualKeyCode::Space => self.input_state.jump = pressed,
				VirtualKeyCode::N => {
					if !pressed {
						self.noclip = !self.noclip;
					}
				},
				_ => return false
			}
			self.input_changed = true;
			return true;
		}
		false
	}

	fn handle_mouse_pos_ev(&mut self, new_pos: (f32, f32), display: &Display) -> bool {
		let gl_window = display.gl_window();
		let window = gl_window.window();
		let winsize = window.inner_size();
		let middle = ((winsize.width / 2) as f32, (winsize.height / 2) as f32);

		self.camera.pitch_yaw.1 -= new_pos.0 * MOUSE_SENSITIVITY;
		self.camera.pitch_yaw.0 += (new_pos.1 * MOUSE_SENSITIVITY).min(1.57).max(-1.57);
		self.camera.pitch_yaw.0 = self.camera.pitch_yaw.0.min(1.57).max(-1.57);
		
		self.camera.update_direction();

		window.set_cursor_position(PhysicalPosition::new(middle.0, middle.1)).unwrap();
		self.input_changed = true;
		return true;
	}

	fn handle_mouse_ev(&mut self, _button: MouseButton, _state: ElementState) -> bool {
		false
	}

	fn handle_char_ev(&mut self, _ch: char) -> bool {
		false
	}
}