-
Notifications
You must be signed in to change notification settings - Fork 20
Aabb #182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Aabb #182
Changes from all commits
56c28d0
4a43b68
1037c5a
49c1930
4163172
2763c2e
715357e
3aea322
da6ba5a
167d222
b76e66d
36153bc
3bae7d5
70caa9f
8a7aa94
d315a4d
d68fcfa
8ddee5a
ec9abf5
4405b9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,15 +6,21 @@ use tracing::{debug, error, trace}; | |
|
|
||
| use crate::{net, prediction::PredictedMotion, Net}; | ||
| use common::{ | ||
| collision::BoundingBox, | ||
| force::{ForceParams, GravityMethod}, | ||
| graph::{Graph, NodeId}, | ||
| math, | ||
| node::{DualGraph, Node}, | ||
| node::{Chunk, DualGraph, Node}, | ||
| proto::{self, Character, Command, Component, Position}, | ||
| sanitize_motion_input, | ||
| world::Material, | ||
| worldgen::NodeState, | ||
| Chunks, EntityId, GraphEntities, Step, | ||
| }; | ||
|
|
||
| const CHARACTER_RADIUS: f64 = 0.10_f64; | ||
| const CHARACTER_SLOWDOWN_FACTOR: f32 = 6_f32; | ||
DimpyRed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /// Game state | ||
| pub struct Sim { | ||
| net: Net, | ||
|
|
@@ -26,6 +32,7 @@ pub struct Sim { | |
| pub world: hecs::World, | ||
| pub params: Option<Parameters>, | ||
| pub local_character: Option<Entity>, | ||
| character_velocity: na::Vector4<f32>, // velocity of the character controller that carries over from frame to frame. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, this velocity is computed in a coordinate system relative to the current node the player is associated with. I have personally found it easier to reason about velocity when it was provided in the player's own coordinate system. This way, the velocity doesn't need to be renormalized every frame. The main thing to worry about is that if the player's coordinate system is rotated, the velocity vector would need to be rotated the other way to stay consistent. Having the velocity be relative to the current node works as well, but it requires a lot of care to keep it consistent with the current coordinate system of the player.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see no reason that the player's coordinate system would be rotated such that a rotation of velocity would not also be warranted.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To remain consistent with the current use of hecs, velocity should be added to the |
||
| orientation: na::UnitQuaternion<f32>, | ||
| step: Option<Step>, | ||
|
|
||
|
|
@@ -54,6 +61,7 @@ impl Sim { | |
| world: hecs::World::new(), | ||
| params: None, | ||
| local_character: None, | ||
| character_velocity: na::Vector4::zeros(), | ||
| orientation: na::one(), | ||
| step: None, | ||
|
|
||
|
|
@@ -86,6 +94,8 @@ impl Sim { | |
| self.handle_net(msg); | ||
| } | ||
|
|
||
| self.handle_forces(dt); | ||
|
|
||
| if let Some(step_interval) = self.params.as_ref().map(|x| x.step_interval) { | ||
| self.since_input_sent += dt; | ||
| if let Some(overflow) = self.since_input_sent.checked_sub(step_interval) { | ||
|
|
@@ -135,6 +145,9 @@ impl Sim { | |
| chunk_size: msg.chunk_size, | ||
| meters_to_absolute: msg.meters_to_absolute, | ||
| movement_speed: msg.movement_speed, | ||
| gravity_intensity: msg.gravity_intensity, | ||
| drag_factor: msg.drag_factor, | ||
| gravity_method: msg.gravity_method, | ||
| }); | ||
| // Populate the root node | ||
| populate_fresh_nodes(&mut self.graph); | ||
|
|
@@ -146,8 +159,8 @@ impl Sim { | |
| return; | ||
| } | ||
| self.step = Some(msg.step); | ||
| for &(id, new_pos) in &msg.positions { | ||
| self.update_position(msg.latest_input, id, new_pos); | ||
| for &(id, new_pos, node_transform) in &msg.positions { | ||
| self.update_position(msg.latest_input, id, new_pos, node_transform); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure why, but with some println debugging, I've found situations in which Rather than figuring out the root cause of this discrepancy, I believe the simplest fix would be to do this while fixing the jitter. For prototyping purposes, to simplify development, I would recommend having the client simply send the entire
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think I made it very clear, but node_transform is only not the identity matrix when going from one node to another node. I agree that the Client broadcasting its Position is the proper thing to do right now.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I meant that |
||
| } | ||
| for &(id, orientation) in &msg.character_orientations { | ||
| match self.entity_ids.get(&id) { | ||
|
|
@@ -166,14 +179,29 @@ impl Sim { | |
| } | ||
| } | ||
|
|
||
| fn update_position(&mut self, latest_input: u16, id: EntityId, new_pos: Position) { | ||
| if self.params.as_ref().map_or(false, |x| x.character_id == id) { | ||
| fn update_position( | ||
| &mut self, | ||
| latest_input: u16, | ||
| id: EntityId, | ||
| new_pos: Position, | ||
| node_transform: na::Matrix4<f32>, | ||
| ) { | ||
| let id_is_character = self.params.as_ref().map_or(false, |x| x.character_id == id); | ||
|
|
||
| if id_is_character { | ||
| self.prediction.reconcile(latest_input, new_pos); | ||
| } | ||
|
|
||
| match self.entity_ids.get(&id) { | ||
| None => debug!(%id, "position update for unknown entity"), | ||
| Some(&entity) => match self.world.get_mut::<Position>(entity) { | ||
| Ok(mut pos) => { | ||
| if id_is_character { | ||
| let p0 = node_transform * pos.local * math::origin(); | ||
| let p1 = new_pos.local * math::origin(); | ||
| self.character_velocity = | ||
| math::translate(&p0, &p1) * node_transform * self.character_velocity; | ||
| } | ||
| if pos.node != new_pos.node { | ||
| self.graph_entities.remove(pos.node, entity); | ||
| self.graph_entities.insert(new_pos.node, entity); | ||
|
|
@@ -241,8 +269,24 @@ impl Sim { | |
| } | ||
|
|
||
| fn send_input(&mut self) { | ||
| let (direction, speed) = sanitize_motion_input(self.orientation * self.average_velocity); | ||
| let params = self.params.as_ref().unwrap(); | ||
| let local_velocity = self | ||
| .world | ||
| .get_mut::<Position>(self.local_character.unwrap()) | ||
| .unwrap() | ||
| .local | ||
| .try_inverse() | ||
| .unwrap() | ||
| * self.character_velocity; | ||
| let (direction, speed) = sanitize_motion_input( | ||
| self.orientation * self.average_velocity / CHARACTER_SLOWDOWN_FACTOR // lower player abilities | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose that could work if I made ground drag only apply in the vertical direction. There are two downsides I see to implementing it right now:
average_velocity should definitely be renamed though.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Point (1) can be handled with a noclip toggle, but point (2) makes sense. Although, revealing bugs may be a good thing, as that gives us a chance to fix them. |
||
| + na::Vector3::new( | ||
| local_velocity[0], | ||
| local_velocity[1], | ||
| local_velocity[2], | ||
| ) / params.movement_speed, | ||
| ); | ||
|
|
||
| let generation = self.prediction.push( | ||
| &direction, | ||
| speed | ||
|
|
@@ -263,7 +307,10 @@ impl Sim { | |
| result.local *= self.orientation.to_homogeneous(); | ||
| if let Some(ref params) = self.params { | ||
| // Apply input that hasn't been sent yet | ||
| let (direction, speed) = sanitize_motion_input(self.average_velocity); | ||
| let (direction, speed) = sanitize_motion_input( | ||
| // TODO: Incorparate the persistent velocity into the view calculations. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The simplest way to do this may be to get |
||
| self.average_velocity / CHARACTER_SLOWDOWN_FACTOR, | ||
| ); | ||
| // We multiply by the entire timestep rather than the time so far because | ||
| // self.average_velocity is always over the entire timestep, filling in zeroes for the | ||
| // future. | ||
|
|
@@ -292,6 +339,116 @@ impl Sim { | |
| .despawn(entity) | ||
| .expect("destroyed nonexistent entity"); | ||
| } | ||
|
|
||
| fn handle_forces(&mut self, time: Duration) { | ||
| let params = self.params.as_ref().unwrap(); | ||
|
|
||
| let force_system = ForceParams { | ||
| gravity_intensity: params.gravity_intensity, | ||
| air_drag_factor: params.drag_factor, | ||
| gravity_type: params.gravity_method, | ||
| float_speed: 0.05_f64, | ||
| }; | ||
|
|
||
| // eventually this should be expanded to work on every entity with a physics property, but for now it is just the player | ||
| match self.local_character { | ||
| Some(entity) => match self.world.get_mut::<Position>(entity) { | ||
| Ok(character_position) => { | ||
| // starting with simpler method for testing purposese | ||
| let is_colliding = self.check_collision(*character_position); | ||
|
|
||
| let down_info = &self | ||
| .graph | ||
| .get(character_position.node) | ||
| .as_ref() | ||
| .unwrap() | ||
| .state; | ||
|
|
||
| let character_v4f64 = na::convert::<_, na::Vector4<f64>>( | ||
| character_position.local * math::origin(), | ||
| ); | ||
|
|
||
| let down = -math::lorentz_normalize(&math::orthogonalize( | ||
| down_info.surface().normal(), | ||
| &character_v4f64, | ||
| )); | ||
|
|
||
| let down_local = self.orientation.conjugate() | ||
| * (character_position.local.try_inverse().unwrap() | ||
| * na::convert::<_, na::Vector4<f32>>(-down)) | ||
| .xyz(); | ||
| self.orientation *= na::UnitQuaternion::new( | ||
| na::Vector3::z() * down_local.x * -3.0 * time.as_secs_f32(), | ||
| ); | ||
|
|
||
| let height = down_info.surface().distance_to(&character_v4f64); | ||
|
|
||
| let new_velocity = force_system.apply_forces( | ||
| &na::convert::<_, na::Vector4<f64>>(self.character_velocity), | ||
| &down, | ||
| height, | ||
| is_colliding, | ||
| time.as_secs_f64(), | ||
| ); | ||
|
|
||
| self.character_velocity = math::normalize_vector( | ||
| character_position.local, | ||
| na::convert::<_, na::Vector4<f32>>(new_velocity), | ||
| ); | ||
|
|
||
| for n in self.character_velocity.iter() { | ||
| assert!(!n.is_nan(), "Error, character velocity is not a number"); | ||
| } | ||
| } | ||
| Err(_e) => (), | ||
| }, | ||
| None => (), | ||
| } | ||
| } | ||
|
|
||
| /// checks to see if there are any non-Void voxels near Position. | ||
| // acts as a helper function for handle_forces | ||
| // currently uses a hyperbolic equivilent of an AABB. | ||
| fn check_collision(&self, pos: Position) -> bool { | ||
| let params = self.params.as_ref().unwrap(); | ||
|
|
||
| let bb = BoundingBox::create_aabb( | ||
| pos.node, | ||
| na::convert::<na::Vector4<f32>, na::Vector4<f64>>( | ||
| pos.local * na::Vector4::new(0_f32, 0_f32, 0_f32, 1_f32), | ||
| ), | ||
| CHARACTER_RADIUS, | ||
| &self.graph, | ||
| params.chunk_size, | ||
| ); | ||
|
|
||
| for cbb in bb.bounding_boxes { | ||
| if match self | ||
| .graph | ||
| .get(cbb.node) | ||
| .as_ref() | ||
| .expect("nodes must be populated before collisons can occur") | ||
DimpyRed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| .chunks[cbb.chunk] | ||
| { | ||
| Chunk::Generating => true, | ||
| Chunk::Fresh => true, | ||
| Chunk::Populated { | ||
| surface: _, | ||
| ref voxels, | ||
| } => { | ||
| for voxel_address in cbb.every_voxel() { | ||
| if !matches!(voxels.get(voxel_address as usize), Material::Void) { | ||
| return true; | ||
| } | ||
| } | ||
| false | ||
| } | ||
| } { | ||
| return true; | ||
| } | ||
| } | ||
| false | ||
| } | ||
| } | ||
|
|
||
| /// Simulation details received on connect | ||
|
|
@@ -302,6 +459,9 @@ pub struct Parameters { | |
| /// Absolute units | ||
| pub movement_speed: f32, | ||
| pub character_id: EntityId, | ||
| pub gravity_intensity: f64, | ||
| pub drag_factor: f64, | ||
| pub gravity_method: GravityMethod, | ||
| } | ||
|
|
||
| fn populate_fresh_nodes(graph: &mut DualGraph) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these two constants be part of sim_config?