From 4a43b683265d0fd6c1adab3a887aaa479c20aa47 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 15 Jun 2021 14:28:10 -0700 Subject: [PATCH 01/17] Made a few refactors to make the linter happy --- common/src/dodeca.rs | 2 +- server/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/dodeca.rs b/common/src/dodeca.rs index d2ce50cb..6fca5973 100644 --- a/common/src/dodeca.rs +++ b/common/src/dodeca.rs @@ -157,7 +157,7 @@ lazy_static! { let cosh_distance = (REFLECTIONS[i] * REFLECTIONS[j])[(3, 3)]; // Possile cosh_distances: 1, 4.23606 = 2+sqrt(5), 9.47213 = 5+2*sqrt(5), 12.70820 = 6+3*sqrt(5); // < 2.0 indicates identical faces; < 5.0 indicates adjacent faces; > 5.0 indicates non-adjacent faces - result[i][j] = cosh_distance >= 2.0 && cosh_distance < 5.0; + result[i][j] = (2.0..5.0).contains(&cosh_distance); } } result diff --git a/server/src/lib.rs b/server/src/lib.rs index 752e6d94..6135603b 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -65,8 +65,8 @@ impl Server { let mut client_events = client_events.fuse(); loop { select! { - _ = ticks.next() => { self.on_step() } - conn = incoming.select_next_some() => { self.on_connect(conn, client_events_send.clone()); } + _ = ticks.next() => self.on_step(), + conn = incoming.select_next_some() => {self.on_connect(conn, client_events_send.clone()); } e = client_events.select_next_some() => { self.on_client_event(e.0, e.1); } } } From 1037c5a6113df57941b18735441b8bc0dede4927 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 15 Jun 2021 16:44:19 -0700 Subject: [PATCH 02/17] added AABB masking --- common/src/collision.rs | 109 ++++++++++++++++++++++++++++++++++++++++ common/src/dodeca.rs | 24 +++++++++ 2 files changed, 133 insertions(+) create mode 100644 common/src/collision.rs diff --git a/common/src/collision.rs b/common/src/collision.rs new file mode 100644 index 00000000..ef2032bc --- /dev/null +++ b/common/src/collision.rs @@ -0,0 +1,109 @@ +use crate::node::{DualGraph, VoxelData}; +use crate::{ + dodeca::{Side, Vertex}, + graph::NodeId, + world::Material, + Plane, +}; + +#[allow(dead_code)] + +/* +The set of voxels that a collision body covers within a chunk +*/ + +pub struct ChunkBoundingBox { + node: graph::NodeID, + chunk: dodeca::Vertex, + min_xyz na::Vector3, + max_xyz na::Vector3, + } + + +/* +The set of voxels that a collision body covers. +*/ +pub struct BoundingBox { + Vec boundingBoxes, +} + + +// Does not figure out which chunk it is in automatically +impl BoundingBox { + pub fn createAABB(graph::NodeID startNode, dodeca::Vertex startChunk, Vector4 position, f64 radius, graph: &DualGraph) -> Self { + boundingBoxes = Vec::new(); + mut Option(ChunkBoundingBox) result; + let sides[dodeca::Side; 3] = startChunk.canonical_sides(); + + // get BBs for the chunks within the node. + for v in Vertex { + addSubBB(boundingBoxes, getChunkBoundingBox( startNode, v, position, radius)); + } + + // get BBs for foreign chunks sharing a dodeca side + for side in sides { + let node = DualGraph.neighbor(startNode, side); // possible crash if ungenerated node is looked at. + + for v in side.vertices() { + addSubBB(boundingBoxes, getChunkBoundingBox( node, v, position, radius)); + } + } + + let oppositeNode = DualGraph.neighbor( DualGraph.neighbor( DualGraph.neighbor(startNode, sides[0]), sides[1]) ); + + // get BBs for the chunks sharing an edge + for side in sides { + let node = DualGraph.neighbor(oppositeNode, side); + + addSubBB(boundingBoxes, getChunkBoundingBox( node, Vertex::from_sides(sides[0], sides[1], sides[2]), position, radius )); + } + + // get BB for chunk sharing only a vertex. + addSubBB(boundingBoxes, getChunkBoundingBox(oppositeNode, Vertex::from_sides(sides[0], sides[1], sides[2]), position, radius)); + + self + } + + // adds a sub-bounding box to a list if it exists + pub fn addSubBB (list: &Vector3, result: Option(ChunkBoundingBox) ) { + if result.is_some() list.push(result.unwrap()); + } + +} + + + +// translatedPosition should be the object position in the node coordinates of the chunk. +// node can be easily factored out if it stops being convienent. +impl ChunkBoundingBox { + pub fn getChunkBoundingBox (graph::NodeID node, dodeca::Vertex chunk, Vector4 translatedPosition, f64 radius) -> Option { + let na::Vector3. euclideanPosition = (chunk.chunk_to_node().try_inverse().unwrap() * translatedPosition).xyz; + let mut na::Vector3 min_xyz = na::Vector3::new(); + let mut na::Vector max_xyz = na::Vector3::new(); + let i32 dimension = 12; // should unhardcode this asap + + // verify at least one box corner is within the chunk + if translatedPosition.iter().all(|n| n + radius > 0 && n - radius < 1) { + min_xyz.x = (translatedPosition.x.min(0) * dimension ).floor() as i32; + max_xyz.x = (translatedPosition.x.max(1) * dimension ).ceil() as i32; + + min_xyz.y = (translatedPosition.y.min(0) * dimension ).floor() as i32; + max_xyz.y = (translatedPosition.y.max(1) * dimension ).ceil() as i32; + + min_xyz.z = (translatedPosition.z.min(0) * dimension ).floor() as i32; + max_xyz.z = (translatedPosition.z.max(1) * dimension ).ceil() as i32; + + Some ( + ChunkBoundingBox { + node: node, + chunk: chunk, + min_xyz: clone(min_xyz), + max_xyz: clone(max_xyz), + } + ) + } + + else None(); + + } +} \ No newline at end of file diff --git a/common/src/dodeca.rs b/common/src/dodeca.rs index 6fca5973..8921c257 100644 --- a/common/src/dodeca.rs +++ b/common/src/dodeca.rs @@ -43,6 +43,10 @@ impl Side { ADJACENT[self as usize][other as usize] } + pub fn vertices(self) -> [Vertex; 5] { + SIDE_VERTICES[self as usize] + } + /// Reflection across this side #[inline] pub fn reflection(self) -> &'static na::Matrix4 { @@ -238,6 +242,26 @@ lazy_static! { result }; + static ref SIDE_VERTICES: [[Vertex; 5]; SIDE_COUNT] = { + let mut result_list = vec![Vec::new(); SIDE_COUNT]; + + for a in Vertex::iter() { + let sides = a.canonical_sides(); + result_list[sides[0] as usize].push(a); + result_list[sides[1] as usize].push(a); + result_list[sides[2] as usize].push(a); + } + let mut out: [[Vertex; 5]; SIDE_COUNT] = [[Vertex::A; 5]; SIDE_COUNT]; // dummy fill + + for a in 0..5 { + for b in 0..SIDE_COUNT { + out[a][b] = result_list[a][b]; + } + + } + out + }; + /// Whether the determinant of the cube-to-node transform is negative static ref CHUNK_TO_NODE_PARITY: [bool; VERTEX_COUNT] = { let mut result = [false; VERTEX_COUNT]; From 49c19308b84f880e179a242153f7c8ca5083d56e Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 22 Jul 2021 12:38:49 -0700 Subject: [PATCH 03/17] preliminary tested AABB --- common/src/collision.rs | 430 +++++++++++++++++++++++++++++++++------- common/src/dodeca.rs | 15 +- common/src/graph.rs | 14 ++ common/src/lib.rs | 1 + 4 files changed, 386 insertions(+), 74 deletions(-) diff --git a/common/src/collision.rs b/common/src/collision.rs index ef2032bc..8d18d767 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -1,109 +1,405 @@ -use crate::node::{DualGraph, VoxelData}; +use crate::node::{DualGraph}; use crate::{ - dodeca::{Side, Vertex}, + dodeca::{Vertex}, graph::NodeId, - world::Material, - Plane, }; #[allow(dead_code)] - /* The set of voxels that a collision body covers within a chunk */ - +#[derive(PartialEq, Clone, Debug, Copy)] pub struct ChunkBoundingBox { - node: graph::NodeID, - chunk: dodeca::Vertex, - min_xyz na::Vector3, - max_xyz na::Vector3, - } + node: NodeId, + chunk: Vertex, + min_xyz: na::Vector3, + max_xyz: na::Vector3, + dimension: u8, +} +pub struct VoxelAddress { + node: NodeId, + chunk: Vertex, + index: i32, +} /* The set of voxels that a collision body covers. */ pub struct BoundingBox { - Vec boundingBoxes, + bounding_boxes: Vec, } - // Does not figure out which chunk it is in automatically +// The pattern I use to unwrap the results of neighbor is kind of awkward. impl BoundingBox { - pub fn createAABB(graph::NodeID startNode, dodeca::Vertex startChunk, Vector4 position, f64 radius, graph: &DualGraph) -> Self { - boundingBoxes = Vec::new(); - mut Option(ChunkBoundingBox) result; - let sides[dodeca::Side; 3] = startChunk.canonical_sides(); + pub fn create_aabb( + start_node: NodeId, + start_chunk: Vertex, + position: na::Vector4, + radius: f64, + graph: &DualGraph, + dimension: u8, + ) -> Self { + assert!( + radius <= 1.0, + "Error: the radius of a bounding box may not exceed 1 absolute unit." + ); + let mut bounding_boxes = Vec::::new(); + let sides = start_chunk.canonical_sides(); // get BBs for the chunks within the node. - for v in Vertex { - addSubBB(boundingBoxes, getChunkBoundingBox( startNode, v, position, radius)); + for v in Vertex::iter() { + Self::add_sub_bb( + &mut bounding_boxes, + ChunkBoundingBox::get_chunk_bounding_box( + start_node, v, position, radius, dimension, + ), + ); } // get BBs for foreign chunks sharing a dodeca side - for side in sides { - let node = DualGraph.neighbor(startNode, side); // possible crash if ungenerated node is looked at. - - for v in side.vertices() { - addSubBB(boundingBoxes, getChunkBoundingBox( node, v, position, radius)); + for side in sides.iter() { + if let Some(node) = graph.neighbor(start_node, *side) { + let translated_position = side.reflection() * position; + for v in side.vertices().iter() { + Self::add_sub_bb( + &mut bounding_boxes, + ChunkBoundingBox::get_chunk_bounding_box( + node, + *v, + translated_position, + radius, + dimension, + ), + ); + } } } - let oppositeNode = DualGraph.neighbor( DualGraph.neighbor( DualGraph.neighbor(startNode, sides[0]), sides[1]) ); - - // get BBs for the chunks sharing an edge - for side in sides { - let node = DualGraph.neighbor(oppositeNode, side); + if let Some(opposite_node) = graph.opposing_node(start_node, sides) { + let opposite_position = + sides[0].reflection() * sides[1].reflection() * sides[2].reflection() * position; - addSubBB(boundingBoxes, getChunkBoundingBox( node, Vertex::from_sides(sides[0], sides[1], sides[2]), position, radius )); - } + // get BBs for the chunks sharing an edge + for side in sides.iter() { + let node = graph.neighbor(opposite_node, *side); + let translated_position = side.reflection() * opposite_position; + + Self::add_sub_bb( + &mut bounding_boxes, + ChunkBoundingBox::get_chunk_bounding_box( + node.unwrap(), + Vertex::from_sides(sides[0], sides[1], sides[2]).unwrap(), // pretty sure this is pointless + translated_position, + radius, + dimension, + ), + ); + } - // get BB for chunk sharing only a vertex. - addSubBB(boundingBoxes, getChunkBoundingBox(oppositeNode, Vertex::from_sides(sides[0], sides[1], sides[2]), position, radius)); + // get BB for chunk sharing only a vertex. + Self::add_sub_bb( + &mut bounding_boxes, + ChunkBoundingBox::get_chunk_bounding_box( + opposite_node, + Vertex::from_sides(sides[0], sides[1], sides[2]).unwrap(), + opposite_position, + radius, + dimension, + ), + ); + } - self + BoundingBox { bounding_boxes } } // adds a sub-bounding box to a list if it exists - pub fn addSubBB (list: &Vector3, result: Option(ChunkBoundingBox) ) { - if result.is_some() list.push(result.unwrap()); - } + pub fn add_sub_bb(list: &mut Vec, result: Option) { + if let Some(x) = result { + list.push(x); + } + } + pub fn every_voxel_address<'a>(&'a self) -> impl Iterator + 'a { + self.bounding_boxes.iter().flat_map(|cbb| { + cbb.every_voxel().map(move |index| VoxelAddress { + node: cbb.node, + chunk: cbb.chunk, + index, + }) + }) + } } - - -// translatedPosition should be the object position in the node coordinates of the chunk. +// translated_position should be the object position in the node coordinates of the chunk. // node can be easily factored out if it stops being convienent. impl ChunkBoundingBox { - pub fn getChunkBoundingBox (graph::NodeID node, dodeca::Vertex chunk, Vector4 translatedPosition, f64 radius) -> Option { - let na::Vector3. euclideanPosition = (chunk.chunk_to_node().try_inverse().unwrap() * translatedPosition).xyz; - let mut na::Vector3 min_xyz = na::Vector3::new(); - let mut na::Vector max_xyz = na::Vector3::new(); - let i32 dimension = 12; // should unhardcode this asap + pub fn get_chunk_bounding_box( + node: NodeId, + chunk: Vertex, + translated_position: na::Vector4, + radius: f64, + dimension: u8, + ) -> Option { + let euclidean_position = + (chunk.chunk_to_node().try_inverse().unwrap() * translated_position).xyz(); + let mut min_xyz = na::Vector3::::new(0_i32, 0_i32, 0_i32); + let mut max_xyz = na::Vector3::::new(0_i32, 0_i32, 0_i32); // verify at least one box corner is within the chunk - if translatedPosition.iter().all(|n| n + radius > 0 && n - radius < 1) { - min_xyz.x = (translatedPosition.x.min(0) * dimension ).floor() as i32; - max_xyz.x = (translatedPosition.x.max(1) * dimension ).ceil() as i32; - - min_xyz.y = (translatedPosition.y.min(0) * dimension ).floor() as i32; - max_xyz.y = (translatedPosition.y.max(1) * dimension ).ceil() as i32; - - min_xyz.z = (translatedPosition.z.min(0) * dimension ).floor() as i32; - max_xyz.z = (translatedPosition.z.max(1) * dimension ).ceil() as i32; - - Some ( - ChunkBoundingBox { - node: node, - chunk: chunk, - min_xyz: clone(min_xyz), - max_xyz: clone(max_xyz), + if euclidean_position + .iter() + .all(|n| n + radius > 0_f64 && n - radius < 1_f64) + { + min_xyz.x = (euclidean_position.x.min(0_f64) * dimension as f64).floor() as i32; + max_xyz.x = (euclidean_position.x.max(1_f64) * dimension as f64).ceil() as i32; + + min_xyz.y = (euclidean_position.y.min(0_f64) * dimension as f64).floor() as i32; + max_xyz.y = (euclidean_position.y.max(1_f64) * dimension as f64).ceil() as i32; + + min_xyz.z = (euclidean_position.z.min(0_f64) * dimension as f64).floor() as i32; + max_xyz.z = (euclidean_position.z.max(1_f64) * dimension as f64).ceil() as i32; + + Some(ChunkBoundingBox { + node, + chunk, + min_xyz, + max_xyz, + dimension, + }) + } else { + None + } + } + + // returns the index (single number) of every voxel contained inside + pub fn every_voxel<'b>(&'b self) -> impl Iterator + 'b { + (self.min_xyz[2]..self.max_xyz[2]).flat_map(move |z| { + (self.min_xyz[1]..self.max_xyz[1]).flat_map(move |y| { + (self.min_xyz[0]..self.max_xyz[0]).map(move |x| { + x + (self.dimension as i32) * y + (self.dimension as i32).pow(2) * z + }) + }) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::node::{DualGraph, Node}; + use crate::Chunks; + use crate::{graph::Graph, proto::Position, traversal::ensure_nearby}; + use approx::*; + + const CHUNK_SIZE: u8 = 12; // might want to test with multiple values in the future. + static CHUNK_SIZE_F: f64 = CHUNK_SIZE as f64; + + // place a small bounding box near the center of the node. There should be exactly 20 chunks in contact. + #[test] + fn proper_chunks_20() { + let mut graph = Graph::new(); + ensure_nearby(&mut graph, &Position::origin(), 4.0); + + let central_chunk = Vertex::A; // arbitrary vertex + let position = central_chunk.chunk_to_node() + * na::Vector4::new( + 0.4 / CHUNK_SIZE_F, + 0.4 / CHUNK_SIZE_F, + 0.4 / CHUNK_SIZE_F, + 1.0, + ); + + let bb = BoundingBox::create_aabb( + NodeId::ROOT, + central_chunk, + position, + 2.0 / CHUNK_SIZE_F, + &graph, + CHUNK_SIZE, + ); + + assert_eq!(bb.bounding_boxes.len(), 20); + } + + // place a small bounding box in the center of a chunk. There should be exactly 1 chunk in contact. + #[test] + fn proper_chunks_1() { + let mut graph = Graph::new(); + ensure_nearby(&mut graph, &Position::origin(), 4.0); + + let central_chunk = Vertex::A; // arbitrary vertex + let position = central_chunk.chunk_to_node() * na::Vector4::new(0.5, 0.5, 0.5, 1.0); + + let bb = BoundingBox::create_aabb( + NodeId::ROOT, + central_chunk, + position, + 1.0 / CHUNK_SIZE_F, + &graph, + CHUNK_SIZE, + ); + + assert_eq!(bb.bounding_boxes.len(), 1); + } + + // place a small bounding box next to a dodecaherdral vertex. There should be exactly 8 chunks in contact. + #[test] + fn proper_chunks_8() { + let mut graph = Graph::new(); + ensure_nearby(&mut graph, &Position::origin(), 4.0); + + let central_chunk = Vertex::K; // arbitrary vertex + let position = central_chunk.chunk_to_node() + * na::Vector4::new( + 1.0 - 0.4 / CHUNK_SIZE_F, + 1.0 - 0.4 / CHUNK_SIZE_F, + 1.0 - 0.4 / CHUNK_SIZE_F, + 1.0, + ); + + let bb = BoundingBox::create_aabb( + NodeId::ROOT, + central_chunk, + position, + 2.0 / CHUNK_SIZE_F, + &graph, + CHUNK_SIZE, + ); + + assert_eq!(bb.bounding_boxes.len(), 8); + } + + // place a small bounding box on the center of a dodecaherdral face. There should be exactly 10 chunks in contact. + #[test] + fn proper_chunks_10() { + let mut graph = Graph::new(); + ensure_nearby(&mut graph, &Position::origin(), 4.0); + + let central_chunk = Vertex::D; // arbitrary vertex + let position = central_chunk.chunk_to_node() + * na::Vector4::new( + 1.0 - 0.4 / CHUNK_SIZE_F, + 1.0 - 0.4 / CHUNK_SIZE_F, + 0.4 / CHUNK_SIZE_F, + 1.0, + ); + + let bb = BoundingBox::create_aabb( + NodeId::ROOT, + central_chunk, + position, + 2.0 / CHUNK_SIZE_F, + &graph, + CHUNK_SIZE, + ); + + assert_eq!(bb.bounding_boxes.len(), 10); + } + + // place a small bounding box right between the center of a dodecaherdral face and the node center. There should be exactly 5 chunks in contact. + #[test] + fn proper_chunks_5() { + let mut graph = Graph::new(); + ensure_nearby(&mut graph, &Position::origin(), 4.0); + + let central_chunk = Vertex::E; // arbitrary vertex + let position = central_chunk.chunk_to_node() + * na::Vector4::new(0.4 / CHUNK_SIZE_F, 0.4 / CHUNK_SIZE_F, 0.5, 1.0); + + let bb = BoundingBox::create_aabb( + NodeId::ROOT, + central_chunk, + position, + 2.0 / CHUNK_SIZE_F, + &graph, + CHUNK_SIZE, + ); + + assert_eq!(bb.bounding_boxes.len(), 5); + } + + // place bounding boxes in a variety of places with a variety of sizes and make sure the amount of voxels contained within are roughly what you would + // expect in a euclidean bounding box of the same radius. + #[test] + fn reasonable_voxel_count() { + let margin_of_error = 3.0; // three times more voxels than what would be expected. + let radi_to_test = 6; // higher number means more test precision. + + let mut graph = Graph::new(); + ensure_nearby(&mut graph, &Position::origin(), 4.0); + + let central_chunk = Vertex::B; // arbitrary vertex + + for x in 0..CHUNK_SIZE { + for r in 1..radi_to_test { + let radius = (r / radi_to_test) as f64; + let x_f64 = x as f64; + let expected_voxel_count = (radius * 2.0).powf(3.0); + let position = central_chunk.chunk_to_node() + * na::Vector4::new( + x_f64 / CHUNK_SIZE_F, + x_f64 / CHUNK_SIZE_F, + x_f64 / CHUNK_SIZE_F, + 1.0, + ); + + let bb = BoundingBox::create_aabb( + NodeId::ROOT, + central_chunk, + position, + radius, + &graph, + CHUNK_SIZE, + ); + + let mut actual_voxels = 0; + for address in bb.every_voxel_address() { + actual_voxels += 1; } - ) + + assert!(actual_voxels <= (expected_voxel_count * margin_of_error).ceil() as i32); + assert!(actual_voxels >= (expected_voxel_count / margin_of_error).floor() as i32); + } } - - else None(); - - } -} \ No newline at end of file + } + + // places a bounding box at a certain [x,y,z], and verifies that the voxel at [x, y, z] is contained within the bounding box. + #[test] + fn internal_coordinates() { + let mut graph = Graph::new(); + ensure_nearby(&mut graph, &Position::origin(), 4.0); + + let central_chunk = Vertex::B; // arbitrary vertex + let chunk_coords = na::Vector3::new(2_i32, 6_i32, 9_i32); + + let position = central_chunk.chunk_to_node() + * na::Vector4::new( + (chunk_coords[0] as f64) / CHUNK_SIZE_F, + (chunk_coords[1] as f64) / CHUNK_SIZE_F, + (chunk_coords[2] as f64) / CHUNK_SIZE_F, + 1.0, + ); + + let bb = BoundingBox::create_aabb( + NodeId::ROOT, + central_chunk, + position, + 3.0 / CHUNK_SIZE_F, + &graph, + CHUNK_SIZE, + ); + + let expected_index = chunk_coords[0] * (CHUNK_SIZE as i32).pow(2) + + chunk_coords[1] * (CHUNK_SIZE as i32) + + chunk_coords[2]; + + for address in bb.every_voxel_address() { + if expected_index == address.index { + return; + } + } + panic!(); + } +} diff --git a/common/src/dodeca.rs b/common/src/dodeca.rs index 8921c257..53cb3b6f 100644 --- a/common/src/dodeca.rs +++ b/common/src/dodeca.rs @@ -242,20 +242,21 @@ lazy_static! { result }; + /// The 5 vertices incedent on a side static ref SIDE_VERTICES: [[Vertex; 5]; SIDE_COUNT] = { let mut result_list = vec![Vec::new(); SIDE_COUNT]; - for a in Vertex::iter() { - let sides = a.canonical_sides(); - result_list[sides[0] as usize].push(a); - result_list[sides[1] as usize].push(a); - result_list[sides[2] as usize].push(a); + for v in Vertex::iter() { + let sides = v.canonical_sides(); + result_list[sides[0] as usize].push(v); + result_list[sides[1] as usize].push(v); + result_list[sides[2] as usize].push(v); } - let mut out: [[Vertex; 5]; SIDE_COUNT] = [[Vertex::A; 5]; SIDE_COUNT]; // dummy fill + let mut out: [[Vertex; 5]; SIDE_COUNT] = [[Vertex::A; 5]; SIDE_COUNT]; for a in 0..5 { for b in 0..SIDE_COUNT { - out[a][b] = result_list[a][b]; + out[b][a] = result_list[b][a]; // flipped some things } } diff --git a/common/src/graph.rs b/common/src/graph.rs index 4c01e547..75e0f290 100644 --- a/common/src/graph.rs +++ b/common/src/graph.rs @@ -199,6 +199,20 @@ impl Graph { id } + /// find the node opposite "node" along "sides". + // This can be improved to return None in less situations + pub fn opposing_node(&self, node: NodeId, sides: [Side; 3]) -> Option { + if let Some(n1) = self.neighbor(node, sides[0]) { + if let Some(n2) = self.neighbor(n1, sides[1]) { + self.neighbor(n2, sides[2]) + } else { + None + } + } else { + None + } + } + /// Ensure all shorter neighbors of a not-yet-created child node exist and return them fn populate_shorter_neighbors_of_child( &mut self, diff --git a/common/src/lib.rs b/common/src/lib.rs index 003833bd..73ac74ac 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -8,6 +8,7 @@ mod id; mod chunks; pub mod codec; +pub mod collision; pub mod cursor; pub mod dodeca; pub mod graph; From 41631723acc7e67db41a771846c3a6fc0ae4ddb5 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 20 Jan 2022 13:50:46 -0800 Subject: [PATCH 04/17] Tweaks, bug fixes, and better testing of AABBs --- common/src/collision.rs | 134 ++++++++++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 39 deletions(-) diff --git a/common/src/collision.rs b/common/src/collision.rs index 8d18d767..4dc7ae16 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -1,8 +1,5 @@ -use crate::node::{DualGraph}; -use crate::{ - dodeca::{Vertex}, - graph::NodeId, -}; +use crate::node::DualGraph; +use crate::{dodeca::Vertex, graph::NodeId}; #[allow(dead_code)] /* @@ -30,12 +27,28 @@ pub struct BoundingBox { bounding_boxes: Vec, } +// from a node coordinate in an arbitrary node, returns the which chunk the point would reside in +pub fn chunk_from_location(location: na::Vector4) -> Option { + for v in Vertex::iter() { + let pos = (v.chunk_to_node().try_inverse().unwrap() * location).xyz(); + if (pos.x >= 0_f64) + && (pos.x <= 1_f64) + && (pos.y >= 0_f64) + && (pos.y <= 1_f64) + && (pos.z >= 0_f64) + && (pos.z <= 1_f64) + { + return Some(v); + } + } + None +} + // Does not figure out which chunk it is in automatically // The pattern I use to unwrap the results of neighbor is kind of awkward. impl BoundingBox { pub fn create_aabb( start_node: NodeId, - start_chunk: Vertex, position: na::Vector4, radius: f64, graph: &DualGraph, @@ -45,6 +58,9 @@ impl BoundingBox { radius <= 1.0, "Error: the radius of a bounding box may not exceed 1 absolute unit." ); + + let start_chunk = + chunk_from_location(position).expect("Error: cannot find chunk for given position."); let mut bounding_boxes = Vec::::new(); let sides = start_chunk.canonical_sides(); @@ -130,6 +146,33 @@ impl BoundingBox { }) }) } + + pub fn display_summary(&self) { + let number_of_chunk_bouding_boxes = { + let mut n = 0_i32; + for cbb in self.bounding_boxes.iter() { + n += 1; + } + n + }; + println!( + "Bounding box spanning {} chunks:", + number_of_chunk_bouding_boxes + ); + for cbb in self.bounding_boxes.iter() { + println!("\tA chunk"); + //println!("\t\twith node id {}", cbb.node); // can't easily display node id + println!( + "\t\twith bounding box stretching from ({}, {}, {}) to ({}, {}, {})", + cbb.min_xyz[0], + cbb.min_xyz[1], + cbb.min_xyz[2], + cbb.max_xyz[0], + cbb.max_xyz[1], + cbb.max_xyz[2] + ); + } + } } // translated_position should be the object position in the node coordinates of the chunk. @@ -147,20 +190,28 @@ impl ChunkBoundingBox { let mut min_xyz = na::Vector3::::new(0_i32, 0_i32, 0_i32); let mut max_xyz = na::Vector3::::new(0_i32, 0_i32, 0_i32); + // It's important to note that euclidean_position is measured as chunk lengths, and radius is measured in absolute units. + // By coicidence, an absolute unit is aproximately a chunk's diameter, and only because of that there is no unit conversion here. + // verify at least one box corner is within the chunk if euclidean_position .iter() .all(|n| n + radius > 0_f64 && n - radius < 1_f64) { - min_xyz.x = (euclidean_position.x.min(0_f64) * dimension as f64).floor() as i32; - max_xyz.x = (euclidean_position.x.max(1_f64) * dimension as f64).ceil() as i32; - - min_xyz.y = (euclidean_position.y.min(0_f64) * dimension as f64).floor() as i32; - max_xyz.y = (euclidean_position.y.max(1_f64) * dimension as f64).ceil() as i32; - - min_xyz.z = (euclidean_position.z.min(0_f64) * dimension as f64).floor() as i32; - max_xyz.z = (euclidean_position.z.max(1_f64) * dimension as f64).ceil() as i32; - + min_xyz.x = + ((euclidean_position.x - radius).max(0_f64) * dimension as f64).floor() as i32; + max_xyz.x = + ((euclidean_position.x + radius).min(1_f64) * dimension as f64).ceil() as i32; + + min_xyz.y = + ((euclidean_position.y - radius).max(0_f64) * dimension as f64).floor() as i32; + max_xyz.y = + ((euclidean_position.y + radius).min(1_f64) * dimension as f64).ceil() as i32; + + min_xyz.z = + ((euclidean_position.z - radius).max(0_f64) * dimension as f64).floor() as i32; + max_xyz.z = + ((euclidean_position.z + radius).min(1_f64) * dimension as f64).ceil() as i32; Some(ChunkBoundingBox { node, chunk, @@ -213,7 +264,6 @@ mod tests { let bb = BoundingBox::create_aabb( NodeId::ROOT, - central_chunk, position, 2.0 / CHUNK_SIZE_F, &graph, @@ -234,7 +284,6 @@ mod tests { let bb = BoundingBox::create_aabb( NodeId::ROOT, - central_chunk, position, 1.0 / CHUNK_SIZE_F, &graph, @@ -261,7 +310,6 @@ mod tests { let bb = BoundingBox::create_aabb( NodeId::ROOT, - central_chunk, position, 2.0 / CHUNK_SIZE_F, &graph, @@ -280,20 +328,20 @@ mod tests { let central_chunk = Vertex::D; // arbitrary vertex let position = central_chunk.chunk_to_node() * na::Vector4::new( - 1.0 - 0.4 / CHUNK_SIZE_F, - 1.0 - 0.4 / CHUNK_SIZE_F, - 0.4 / CHUNK_SIZE_F, + 1.0 - 0.5 / CHUNK_SIZE_F, + 0.25 / CHUNK_SIZE_F, + 0.25 / CHUNK_SIZE_F, 1.0, ); let bb = BoundingBox::create_aabb( NodeId::ROOT, - central_chunk, position, 2.0 / CHUNK_SIZE_F, &graph, CHUNK_SIZE, ); + bb.display_summary(); assert_eq!(bb.bounding_boxes.len(), 10); } @@ -310,7 +358,6 @@ mod tests { let bb = BoundingBox::create_aabb( NodeId::ROOT, - central_chunk, position, 2.0 / CHUNK_SIZE_F, &graph, @@ -325,7 +372,7 @@ mod tests { #[test] fn reasonable_voxel_count() { let margin_of_error = 3.0; // three times more voxels than what would be expected. - let radi_to_test = 6; // higher number means more test precision. + let radi_to_test = CHUNK_SIZE; // higher number means more test precision. Try to keep it a divisor of CHUNK_SIZE_F. let mut graph = Graph::new(); ensure_nearby(&mut graph, &Position::origin(), 4.0); @@ -334,9 +381,17 @@ mod tests { for x in 0..CHUNK_SIZE { for r in 1..radi_to_test { - let radius = (r / radi_to_test) as f64; + let radius = (r as f64) / (radi_to_test as f64); let x_f64 = x as f64; - let expected_voxel_count = (radius * 2.0).powf(3.0); + // Getting the correct estimation for the number of voxels can be tricky + let expected_voxel_count = (radius * 2.0 * CHUNK_SIZE_F).powf(3.0); // value to display + let minimum_expected_voxel_count = + (((radius * 2.0 * CHUNK_SIZE_F) - 1_f64).powf(3.0) / margin_of_error).floor() + as i32; + let maximum_expected_voxel_count = + (((radius * 2.0 * CHUNK_SIZE_F) + 1_f64).powf(3.0) * margin_of_error).ceil() + as i32; + let position = central_chunk.chunk_to_node() * na::Vector4::new( x_f64 / CHUNK_SIZE_F, @@ -345,22 +400,22 @@ mod tests { 1.0, ); - let bb = BoundingBox::create_aabb( - NodeId::ROOT, - central_chunk, - position, - radius, - &graph, - CHUNK_SIZE, - ); + let bb = + BoundingBox::create_aabb(NodeId::ROOT, position, radius, &graph, CHUNK_SIZE); let mut actual_voxels = 0; for address in bb.every_voxel_address() { actual_voxels += 1; } - assert!(actual_voxels <= (expected_voxel_count * margin_of_error).ceil() as i32); - assert!(actual_voxels >= (expected_voxel_count / margin_of_error).floor() as i32); + println!( + "actual_voxels for reasonable_voxel_count: {} vs {}(min), {}(expected), {}(max)", + actual_voxels, minimum_expected_voxel_count, expected_voxel_count, maximum_expected_voxel_count + ); + bb.display_summary(); + println!("x_f64 is {} radius is {}", x_f64, radius); + assert!(actual_voxels >= minimum_expected_voxel_count); + assert!(actual_voxels <= maximum_expected_voxel_count); } } } @@ -384,16 +439,17 @@ mod tests { let bb = BoundingBox::create_aabb( NodeId::ROOT, - central_chunk, position, 3.0 / CHUNK_SIZE_F, &graph, CHUNK_SIZE, ); - let expected_index = chunk_coords[0] * (CHUNK_SIZE as i32).pow(2) + bb.display_summary(); + + let expected_index = chunk_coords[0] + chunk_coords[1] * (CHUNK_SIZE as i32) - + chunk_coords[2]; + + chunk_coords[2] * (CHUNK_SIZE as i32).pow(2); for address in bb.every_voxel_address() { if expected_index == address.index { From 2763c2e639b279dfefe6095d009944dbceab1db2 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 25 Mar 2022 20:45:10 -0700 Subject: [PATCH 05/17] added velocity, gravity, and soft collisions --- client/src/sim.rs | 198 ++++++++++++++++++++++++++++++++++++++-- common/src/collision.rs | 46 +++++----- common/src/force.rs | 38 ++++++++ common/src/lib.rs | 1 + common/src/math.rs | 66 ++++++++++++++ common/src/proto.rs | 3 +- common/src/worldgen.rs | 16 +++- server/src/sim.rs | 16 +++- 8 files changed, 350 insertions(+), 34 deletions(-) create mode 100644 common/src/force.rs diff --git a/client/src/sim.rs b/client/src/sim.rs index ec36301c..8ea58150 100644 --- a/client/src/sim.rs +++ b/client/src/sim.rs @@ -6,15 +6,21 @@ use tracing::{debug, error, trace}; use crate::{net, prediction::PredictedMotion, Net}; use common::{ + collision::BoundingBox, + force, 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.025_f64; +const CHARACTER_SLOWDOWN_FACTOR: f32 = 6_f32; + /// Game state pub struct Sim { net: Net, @@ -26,6 +32,7 @@ pub struct Sim { pub world: hecs::World, pub params: Option, pub local_character: Option, + character_velocity: na::Vector4, // velocity of the character controller that carries over from frame to frame. orientation: na::UnitQuaternion, step: Option, @@ -54,6 +61,7 @@ impl Sim { world: hecs::World::new(), params: None, local_character: None, + character_velocity: na::Vector4::new(0_f32, 0_f32, 0_f32, 0_f32), 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) { @@ -146,8 +156,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); } for &(id, orientation) in &msg.character_orientations { match self.entity_ids.get(&id) { @@ -166,14 +176,59 @@ 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, + ) { + 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::(entity) { Ok(mut pos) => { + if id_is_character { + let previous_charvel = self.character_velocity; + + // velocity-related stuff. + let m = math::mip(&self.character_velocity, &self.character_velocity); + + let (v_mag, v_norm) = match m <= 0_f32 { + true => (0_f32, new_pos.local * na::Vector4::new(1_f32, 0_f32, 0_f32, 0_f32)), + false => { + let v_mag = m.sqrt(); + let v_norm = self.character_velocity / v_mag; + (v_mag, v_norm) + }, + }; + + let p0 = pos.local + * node_transform.try_inverse().unwrap_or(na::Matrix4::zeros()) + * math::origin(); + + let p1 = new_pos.local * math::origin(); + + assert!((math::mip(&p0, &p0) < 0.0), "Error, p0 is not in the format of a point" ); + assert!((math::mip(&p1, &p1) < 0.0), "Error, p1 is not in the format of a point" ); + assert!((math::mip(&v_norm, &v_norm) >= 0.0), "Error, v_norm is not in the format of a vector"); + + self.character_velocity = math::translate_normal( + p0, + p1, + v_norm, + ) * v_mag; + + for n in self.character_velocity.iter() { + assert!(!n.is_nan(), "Error, character velocity is not a number. character_velocity was {}. v_mag is {}. + p0 is {}. p1 is {}. Their difference is {}.", previous_charvel, v_mag, p0, p1, (p1 - p0)); + } + } if pos.node != new_pos.node { self.graph_entities.remove(pos.node, entity); self.graph_entities.insert(new_pos.node, entity); @@ -241,8 +296,16 @@ 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 (direction, speed) = sanitize_motion_input( + self.orientation * self.average_velocity / CHARACTER_SLOWDOWN_FACTOR // lower player abilities + + na::Vector3::new( + self.character_velocity[0], + self.character_velocity[1], + self.character_velocity[2], + ) / params.movement_speed, + ); + let generation = self.prediction.push( &direction, speed @@ -263,7 +326,17 @@ 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 gravity into the view calculations. + self.average_velocity / CHARACTER_SLOWDOWN_FACTOR, + /*+ self.orientation + * na::Vector3::new( + self.character_velocity[0], + self.character_velocity[1], + self.character_velocity[2], + ) + / params.movement_speed,*/ + ); // 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 +365,117 @@ impl Sim { .despawn(entity) .expect("destroyed nonexistent entity"); } + + fn handle_forces(&mut self, time: Duration) { + // 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::(entity) { + Ok(character_position) => { + // starting with simplier 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>( + character_position.local * math::origin(), + ); + + let down_temp = math::lorentz_normalize(&math::orthogonalize( + down_info.surface().normal(), + &character_v4f64, + )); + for n in down_temp.iter() { + assert!(!n.is_nan(), "error, down direction is not a nunber"); + } + + // the meaning of the surface plane varies based on which side of it you are + let down = match down_info.is_sky() { + true => down_temp, + false => -down_temp, + }; + + // let is_colliding = !down_info.is_sky(); + + let height = down_info.surface().distance_to(&character_v4f64); + + let acceleration_total = force::gravity(&down, height) + // currently not doing drag or bouyant as added forces. + // + force::normal_buoyant(&down, height, is_colliding) + /*+ force::normal_drag( + is_colliding, + na::convert::<_, na::Vector4>(self.character_velocity), + )*/; + + if is_colliding { + self.character_velocity = na::convert::<_, na::Vector4>(-down * 0.05_f64); + } else { + self.character_velocity *= 0.70_f32.powf(time.as_secs_f32()); + } + + self.character_velocity += + na::convert::<_, na::Vector4>(acceleration_total) * time.as_secs_f32(); + + self.character_velocity = math::normalize_vector(character_position.local, self.character_velocity); + + for n in self.character_velocity.iter() { + assert!(!n.is_nan(), "Error, character velocity is not a number"); + } + } + Err(_e) => (), + }, + None => (), + } + } + + // helper function for handle_forces + 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>( + 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") + .chunks[cbb.chunk] + { + Chunk::Generating => true, + Chunk::Fresh => true, + Chunk::Populated { + surface: _, + ref voxels, + } => { + for voxel_address in cbb.every_voxel() { + if match voxels.get(voxel_address as usize) { + Material::Void => false, + _ => true, + } { + return true; // I think this is correct + } + } + false + } + } { + return true; + } + } + false + } } /// Simulation details received on connect diff --git a/common/src/collision.rs b/common/src/collision.rs index 4dc7ae16..0e6ce030 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -7,24 +7,24 @@ The set of voxels that a collision body covers within a chunk */ #[derive(PartialEq, Clone, Debug, Copy)] pub struct ChunkBoundingBox { - node: NodeId, - chunk: Vertex, - min_xyz: na::Vector3, - max_xyz: na::Vector3, - dimension: u8, + pub node: NodeId, + pub chunk: Vertex, + pub min_xyz: na::Vector3, + pub max_xyz: na::Vector3, + pub dimension: u8, } pub struct VoxelAddress { - node: NodeId, - chunk: Vertex, - index: i32, + pub node: NodeId, + pub chunk: Vertex, + pub index: u32, } /* The set of voxels that a collision body covers. */ pub struct BoundingBox { - bounding_boxes: Vec, + pub bounding_boxes: Vec, } // from a node coordinate in an arbitrary node, returns the which chunk the point would reside in @@ -150,7 +150,7 @@ impl BoundingBox { pub fn display_summary(&self) { let number_of_chunk_bouding_boxes = { let mut n = 0_i32; - for cbb in self.bounding_boxes.iter() { + for _cbb in self.bounding_boxes.iter() { n += 1; } n @@ -187,8 +187,8 @@ impl ChunkBoundingBox { ) -> Option { let euclidean_position = (chunk.chunk_to_node().try_inverse().unwrap() * translated_position).xyz(); - let mut min_xyz = na::Vector3::::new(0_i32, 0_i32, 0_i32); - let mut max_xyz = na::Vector3::::new(0_i32, 0_i32, 0_i32); + let mut min_xyz = na::Vector3::::new(0_u32, 0_u32, 0_u32); + let mut max_xyz = na::Vector3::::new(0_u32, 0_u32, 0_u32); // It's important to note that euclidean_position is measured as chunk lengths, and radius is measured in absolute units. // By coicidence, an absolute unit is aproximately a chunk's diameter, and only because of that there is no unit conversion here. @@ -199,19 +199,19 @@ impl ChunkBoundingBox { .all(|n| n + radius > 0_f64 && n - radius < 1_f64) { min_xyz.x = - ((euclidean_position.x - radius).max(0_f64) * dimension as f64).floor() as i32; + ((euclidean_position.x - radius).max(0_f64) * dimension as f64).floor() as u32; max_xyz.x = - ((euclidean_position.x + radius).min(1_f64) * dimension as f64).ceil() as i32; + ((euclidean_position.x + radius).min(1_f64) * dimension as f64).ceil() as u32; min_xyz.y = - ((euclidean_position.y - radius).max(0_f64) * dimension as f64).floor() as i32; + ((euclidean_position.y - radius).max(0_f64) * dimension as f64).floor() as u32; max_xyz.y = - ((euclidean_position.y + radius).min(1_f64) * dimension as f64).ceil() as i32; + ((euclidean_position.y + radius).min(1_f64) * dimension as f64).ceil() as u32; min_xyz.z = - ((euclidean_position.z - radius).max(0_f64) * dimension as f64).floor() as i32; + ((euclidean_position.z - radius).max(0_f64) * dimension as f64).floor() as u32; max_xyz.z = - ((euclidean_position.z + radius).min(1_f64) * dimension as f64).ceil() as i32; + ((euclidean_position.z + radius).min(1_f64) * dimension as f64).ceil() as u32; Some(ChunkBoundingBox { node, chunk, @@ -225,11 +225,11 @@ impl ChunkBoundingBox { } // returns the index (single number) of every voxel contained inside - pub fn every_voxel<'b>(&'b self) -> impl Iterator + 'b { + pub fn every_voxel<'b>(&'b self) -> impl Iterator + 'b { (self.min_xyz[2]..self.max_xyz[2]).flat_map(move |z| { (self.min_xyz[1]..self.max_xyz[1]).flat_map(move |y| { (self.min_xyz[0]..self.max_xyz[0]).map(move |x| { - x + (self.dimension as i32) * y + (self.dimension as i32).pow(2) * z + x + (self.dimension as u32) * y + (self.dimension as u32).pow(2) * z }) }) }) @@ -427,7 +427,7 @@ mod tests { ensure_nearby(&mut graph, &Position::origin(), 4.0); let central_chunk = Vertex::B; // arbitrary vertex - let chunk_coords = na::Vector3::new(2_i32, 6_i32, 9_i32); + let chunk_coords = na::Vector3::new(2_u32, 6_u32, 9_u32); let position = central_chunk.chunk_to_node() * na::Vector4::new( @@ -448,8 +448,8 @@ mod tests { bb.display_summary(); let expected_index = chunk_coords[0] - + chunk_coords[1] * (CHUNK_SIZE as i32) - + chunk_coords[2] * (CHUNK_SIZE as i32).pow(2); + + chunk_coords[1] * (CHUNK_SIZE as u32) + + chunk_coords[2] * (CHUNK_SIZE as u32).pow(2); for address in bb.every_voxel_address() { if expected_index == address.index { diff --git a/common/src/force.rs b/common/src/force.rs new file mode 100644 index 00000000..be448257 --- /dev/null +++ b/common/src/force.rs @@ -0,0 +1,38 @@ +// use crate::Plane; + +// this module covers forces like gravity and the normal force. + +// 1/2 absolute units per second squared of acceleration; it will take two seconds for something at rest to fall the length of one chunk. +const GRAVITY_INTENSITY: f64 = 0.50_f64; + +const BUOYANT_INTENSITY: f64 = GRAVITY_INTENSITY * 2.5_f64; + +// 1000 inverse absolute units of drag acceleration; The equilibruim speed of something under all three forces will be 0.1 absolute units a second. +const GROUND_DRAG_INTENSITY: f64 = 10000_f64; +const AIR_DRAG_INTENSITY: f64 = GROUND_DRAG_INTENSITY / 15_f64; + +// gravity pulls downward +pub fn gravity(down: &na::Vector4, height: f64) -> na::Vector4 { + // return down * GRAVITY_INTENSITY; + // versoin of gravity that conforms to divergence theorem + return down * GRAVITY_INTENSITY / height.cosh().powi(2); +} + +// currently hard collions aren't possible, so you will just "float" to the top of the ground. +pub fn normal_buoyant(down: &na::Vector4, height: f64, is_collsion: bool) -> na::Vector4 { + if is_collsion { + return down * -BUOYANT_INTENSITY / height.cosh().powi(2); + } + return na::Vector4::zeros(); +} + +// when sinking in to the ground, you will expereince drag which acts agaist whichever way you are moving +// and which gains strength proportional to the speed cubed. +pub fn normal_drag(is_collsion: bool, current_velocity: na::Vector4) -> na::Vector4 { + let speed_squared = current_velocity.norm().powi(2); + + if is_collsion { + return -(GROUND_DRAG_INTENSITY * speed_squared).min(1_f64) * current_velocity; + } + return -(AIR_DRAG_INTENSITY * speed_squared).min(1_f64) * current_velocity; +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 73ac74ac..1cdb8b5e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -11,6 +11,7 @@ pub mod codec; pub mod collision; pub mod cursor; pub mod dodeca; +pub mod force; pub mod graph; mod graph_entities; pub mod lru_slab; diff --git a/common/src/math.rs b/common/src/math.rs index 16b2739a..c623655e 100644 --- a/common/src/math.rs +++ b/common/src/math.rs @@ -105,6 +105,41 @@ pub fn renormalize_isometry(m: &na::Matrix4) -> na::Matrix4 translate_along(&direction, boost_length) * rotation.to_homogeneous() } +/// returns the result of translating normal vector v0 from p0 to p1 +pub fn translate_normal( + p0: na::Vector4, + p1: na::Vector4, + v0: na::Vector4, +) -> na::Vector4 { + let d = distance(&p0, &p1); + if !(d >= na::zero()) {return v0;} + + let v1 = (v0 * d.cosh() + p1 * d.sinh()); + // need to do a little processing to increase numerical stability + // return v1 + p1 * mip(&p1, &v1); + return v1; +} + +/// normalizes vector v with repect to translation matrix t +pub fn normalize_vector( + t: na::Matrix4, + v: na::Vector4, +) -> na::Vector4 { + let p = t * origin(); + let m = mip(&v, &v); + if (m <= na::zero()) {return t * na::Vector4::::new(na::one(), na::zero(), na::zero(), na::zero());} + + let v_mag = m.sqrt(); + let v_norm = v/v_mag; + + return (v_norm + p * mip(&p, &v_norm)) * v_mag; +} + +// make a orthogonal to b +pub fn orthogonalize(a: &na::Vector4, b: &na::Vector4) -> na::Vector4 { + return a - b * (mip(a, b) / mip(b, b)); +} + #[rustfmt::skip] fn renormalize_rotation_reflection(m: &na::Matrix3) -> na::Matrix3 { let zv = m.index((.., 2)).normalize(); @@ -230,6 +265,12 @@ mod tests { assert_abs_diff_eq!(distance(&p, &m) * 2.0, distance(&p, &q), epsilon = 1e-5); } + #[test] + fn distance_identity() { + let a = na::Vector4::new(0.2, 0.0, 0.3, 1.0); + assert_abs_diff_eq!(distance(&a, &a), 0.0, epsilon = 1e-5); + } + #[test] fn renormalize_translation() { let mat = translate( @@ -248,4 +289,29 @@ mod tests { 0.0, 0.0, 0.0, 1.0); assert_abs_diff_eq!(renormalize_isometry(&mat), mat, epsilon = 1e-5); } + + #[test] + fn orthogonalize_example() { + let vec1 = na::Vector4::new(-0.5, -0.5, 0.0, 1.0); + let vec2 = na::Vector4::new(0.3, -0.7, 0.0, 1.0); + let orth = orthogonalize(&vec1, &vec2); + + assert_abs_diff_eq!(mip(&vec2, &orth), 0.0, epsilon = 1e-5); + } +/* + #[test] + fn translate_normal_identity() { + let p = na::Vector4::new(-0.03635, 0.95129, 0.0, 1.38068); + let norm = na::Vector4::new(1.0, 0.0, 0.0, 0.0); + assert_abs_diff_eq!(translate_normal(p, p, norm), norm, epsilon = 1e-5); + }*/ + + fn translate_normal_still_normal() { + let p1 = na::Vector4::new(-0.03635, 0.95129, 0.0, 1.38068); + let p0 = origin(); + let norm = na::Vector4::new(1.0, 0.0, 0.0, 0.0); + let trans = translate_normal(p0, p1, norm); + assert_abs_diff_eq!( mip(&trans, &trans), 1.0, epsilon = 1e-5); + assert_abs_diff_eq!( mip(&trans, &p1), 0.0, epsilon = 1e-5); + } } diff --git a/common/src/proto.rs b/common/src/proto.rs index 208b12b5..af2a8606 100644 --- a/common/src/proto.rs +++ b/common/src/proto.rs @@ -39,7 +39,8 @@ pub struct StateDelta { pub step: Step, /// Highest input generation received prior to `step` pub latest_input: u16, - pub positions: Vec<(EntityId, Position)>, + /// the last field is the transform required to go from the previous node to the current one. + pub positions: Vec<(EntityId, Position, na::Matrix4)>, pub character_orientations: Vec<(EntityId, na::UnitQuaternion)>, } diff --git a/common/src/worldgen.rs b/common/src/worldgen.rs index ad3249bf..4cc25130 100644 --- a/common/src/worldgen.rs +++ b/common/src/worldgen.rs @@ -75,7 +75,7 @@ impl NodeState { road_state: NodeStateRoad::ROOT, spice: 0, enviro: EnviroFactors { - max_elevation: 0.0, + max_elevation: -0.0, temperature: 0.0, rainfall: 0.0, blockiness: 0.0, @@ -128,6 +128,20 @@ impl NodeState { enviro, } } + + pub fn surface(&self) -> Plane { + return self.surface; + } + + // if the node lies above the ground-plane or not + pub fn is_sky(&self) -> bool { + match self.kind { + Land => false, + DeepLand => false, + Sky => true, + DeepSky => true, + } + } } struct VoxelCoords { diff --git a/server/src/sim.rs b/server/src/sim.rs index 56a77016..f016baf6 100644 --- a/server/src/sim.rs +++ b/server/src/sim.rs @@ -109,7 +109,12 @@ impl Sim { let _guard = span.enter(); // Simulate - for (_, (ch, pos)) in self.world.query::<(&Character, &mut Position)>().iter() { + let mut node_transforms = FxHashMap::>::default(); + for (_, (ch, pos, id)) in self + .world + .query::<(&Character, &mut Position, &EntityId)>() + .iter() + { let next_xf = pos.local * math::translate_along(&ch.direction, ch.speed / self.cfg.rate as f32); pos.local = math::renormalize_isometry(&next_xf); @@ -117,6 +122,7 @@ impl Sim { if next_node != pos.node { pos.node = next_node; pos.local = transition_xf * pos.local; + node_transforms.insert(*id, transition_xf); } ensure_nearby(&mut self.graph, pos, f64::from(self.cfg.view_distance)); } @@ -157,7 +163,13 @@ impl Sim { .world .query::<(&EntityId, &Position)>() .iter() - .map(|(_, (&id, &position))| (id, position)) + .map(|(_, (&id, &position))| { + ( + id, + position, + *node_transforms.get(&id).unwrap_or(&na::Matrix4::identity()), + ) + }) .collect(), character_orientations: self .world From 715357e53d5f94a46e481fff2fe470e62f800016 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 25 Mar 2022 20:45:10 -0700 Subject: [PATCH 06/17] added velocity, gravity, and soft collisions --- client/src/sim.rs | 209 ++++++++++++++++++++++++++++++++++++++-- common/src/collision.rs | 76 ++++++++++----- common/src/force.rs | 38 ++++++++ common/src/lib.rs | 1 + common/src/math.rs | 66 +++++++++++++ common/src/proto.rs | 3 +- common/src/worldgen.rs | 16 ++- server/src/sim.rs | 16 ++- 8 files changed, 391 insertions(+), 34 deletions(-) create mode 100644 common/src/force.rs diff --git a/client/src/sim.rs b/client/src/sim.rs index ec36301c..953c6125 100644 --- a/client/src/sim.rs +++ b/client/src/sim.rs @@ -6,15 +6,21 @@ use tracing::{debug, error, trace}; use crate::{net, prediction::PredictedMotion, Net}; use common::{ + collision::BoundingBox, + force, 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; + /// Game state pub struct Sim { net: Net, @@ -26,6 +32,7 @@ pub struct Sim { pub world: hecs::World, pub params: Option, pub local_character: Option, + character_velocity: na::Vector4, // velocity of the character controller that carries over from frame to frame. orientation: na::UnitQuaternion, step: Option, @@ -54,6 +61,7 @@ impl Sim { world: hecs::World::new(), params: None, local_character: None, + character_velocity: na::Vector4::new(0_f32, 0_f32, 0_f32, 0_f32), 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) { @@ -146,8 +156,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); } for &(id, orientation) in &msg.character_orientations { match self.entity_ids.get(&id) { @@ -166,14 +176,59 @@ 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, + ) { + 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::(entity) { Ok(mut pos) => { + if id_is_character { + let previous_charvel = self.character_velocity; + + // velocity-related stuff. + let m = math::mip(&self.character_velocity, &self.character_velocity); + + let (v_mag, v_norm) = match m <= 0_f32 { + true => (0_f32, new_pos.local * na::Vector4::new(1_f32, 0_f32, 0_f32, 0_f32)), + false => { + let v_mag = m.sqrt(); + let v_norm = self.character_velocity / v_mag; + (v_mag, v_norm) + }, + }; + + let p0 = pos.local + * node_transform.try_inverse().unwrap_or(na::Matrix4::zeros()) + * math::origin(); + + let p1 = new_pos.local * math::origin(); + + assert!((math::mip(&p0, &p0) < 0.0), "Error, p0 is not in the format of a point" ); + assert!((math::mip(&p1, &p1) < 0.0), "Error, p1 is not in the format of a point" ); + assert!((math::mip(&v_norm, &v_norm) >= 0.0), "Error, v_norm is not in the format of a vector"); + + self.character_velocity = math::translate_normal( + p0, + p1, + v_norm, + ) * v_mag; + + for n in self.character_velocity.iter() { + assert!(!n.is_nan(), "Error, character velocity is not a number. character_velocity was {}. v_mag is {}. + p0 is {}. p1 is {}. Their difference is {}.", previous_charvel, v_mag, p0, p1, (p1 - p0)); + } + } if pos.node != new_pos.node { self.graph_entities.remove(pos.node, entity); self.graph_entities.insert(new_pos.node, entity); @@ -241,8 +296,16 @@ 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 (direction, speed) = sanitize_motion_input( + self.orientation * self.average_velocity / CHARACTER_SLOWDOWN_FACTOR // lower player abilities + + na::Vector3::new( + self.character_velocity[0], + self.character_velocity[1], + self.character_velocity[2], + ) / params.movement_speed, + ); + let generation = self.prediction.push( &direction, speed @@ -263,7 +326,17 @@ 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 gravity into the view calculations. + self.average_velocity / CHARACTER_SLOWDOWN_FACTOR, + /*+ self.orientation + * na::Vector3::new( + self.character_velocity[0], + self.character_velocity[1], + self.character_velocity[2], + ) + / params.movement_speed,*/ + ); // 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 +365,128 @@ impl Sim { .despawn(entity) .expect("destroyed nonexistent entity"); } + + fn handle_forces(&mut self, time: Duration) { + // 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::(entity) { + Ok(character_position) => { + // starting with simplier 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>( + character_position.local * math::origin(), + ); + + let mut down_temp = math::lorentz_normalize(&math::orthogonalize( + down_info.surface().normal(), + &character_v4f64, + )); + for n in down_temp.iter() { + assert!(!n.is_nan(), "error, down direction is not a nunber"); + } + + let down_local = self.orientation.conjugate() * (character_position.local.try_inverse().unwrap() * na::convert::<_, na::Vector4>(down_temp)).xyz(); + self.orientation *= na::UnitQuaternion::new(na::Vector3::y().cross(&down_local) * 0.01); + + // the meaning of the surface plane varies based on which side of it you are + let down = match down_info.is_sky() { + true => down_temp, + false => -down_temp, + }; + + + + // let is_colliding = !down_info.is_sky(); + + let height = down_info.surface().distance_to(&character_v4f64); + + // let is_colliding = height < 0.04_f64; + + let acceleration_total = force::gravity(&down, height) + // currently not doing drag or bouyant as added forces. + // + force::normal_buoyant(&down, height, is_colliding) + /*+ force::normal_drag( + is_colliding, + na::convert::<_, na::Vector4>(self.character_velocity), + )*/; + + if is_colliding { + self.character_velocity = na::convert::<_, na::Vector4>(down * -0.05_f64); + } else { + self.character_velocity *= 0.90_f32.powf(time.as_secs_f32()); + self.character_velocity += + na::convert::<_, na::Vector4>(acceleration_total) * time.as_secs_f32(); + } + + + + self.character_velocity = math::normalize_vector(character_position.local, self.character_velocity); + + for n in self.character_velocity.iter() { + assert!(!n.is_nan(), "Error, character velocity is not a number"); + } + } + Err(_e) => (), + }, + None => (), + } + } + + // helper function for handle_forces + 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>( + pos.local * na::Vector4::new(0_f32, 0_f32, 0_f32, 1_f32), + ), + CHARACTER_RADIUS, + &self.graph, + params.chunk_size, + ); + + // bb.display_summary(); + + for cbb in bb.bounding_boxes { + if match self + .graph + .get(cbb.node) + .as_ref() + .expect("nodes must be populated before collisons can occur") + .chunks[cbb.chunk] + { + Chunk::Generating => true, + Chunk::Fresh => true, + Chunk::Populated { + surface: _, + ref voxels, + } => { + for voxel_address in cbb.every_voxel() { + if match voxels.get(voxel_address as usize) { + Material::Void => false, + _ => true, + } { + return true; // I think this is correct + } + } + false + } + } { + return true; + } + } + // println!("No collsion."); + false + } } /// Simulation details received on connect diff --git a/common/src/collision.rs b/common/src/collision.rs index 4dc7ae16..dbadd5fa 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -7,24 +7,24 @@ The set of voxels that a collision body covers within a chunk */ #[derive(PartialEq, Clone, Debug, Copy)] pub struct ChunkBoundingBox { - node: NodeId, - chunk: Vertex, - min_xyz: na::Vector3, - max_xyz: na::Vector3, - dimension: u8, + pub node: NodeId, + pub chunk: Vertex, + pub min_xyz: na::Vector3, + pub max_xyz: na::Vector3, + pub dimension: u8, } pub struct VoxelAddress { - node: NodeId, - chunk: Vertex, - index: i32, + pub node: NodeId, + pub chunk: Vertex, + pub index: u32, } /* The set of voxels that a collision body covers. */ pub struct BoundingBox { - bounding_boxes: Vec, + pub bounding_boxes: Vec, } // from a node coordinate in an arbitrary node, returns the which chunk the point would reside in @@ -150,7 +150,7 @@ impl BoundingBox { pub fn display_summary(&self) { let number_of_chunk_bouding_boxes = { let mut n = 0_i32; - for cbb in self.bounding_boxes.iter() { + for _cbb in self.bounding_boxes.iter() { n += 1; } n @@ -187,8 +187,8 @@ impl ChunkBoundingBox { ) -> Option { let euclidean_position = (chunk.chunk_to_node().try_inverse().unwrap() * translated_position).xyz(); - let mut min_xyz = na::Vector3::::new(0_i32, 0_i32, 0_i32); - let mut max_xyz = na::Vector3::::new(0_i32, 0_i32, 0_i32); + let mut min_xyz = na::Vector3::::new(0_u32, 0_u32, 0_u32); + let mut max_xyz = na::Vector3::::new(0_u32, 0_u32, 0_u32); // It's important to note that euclidean_position is measured as chunk lengths, and radius is measured in absolute units. // By coicidence, an absolute unit is aproximately a chunk's diameter, and only because of that there is no unit conversion here. @@ -199,19 +199,19 @@ impl ChunkBoundingBox { .all(|n| n + radius > 0_f64 && n - radius < 1_f64) { min_xyz.x = - ((euclidean_position.x - radius).max(0_f64) * dimension as f64).floor() as i32; + ((euclidean_position.x - radius).max(0_f64) * dimension as f64).floor() as u32; max_xyz.x = - ((euclidean_position.x + radius).min(1_f64) * dimension as f64).ceil() as i32; + ((euclidean_position.x + radius).min(1_f64) * dimension as f64).ceil() as u32; min_xyz.y = - ((euclidean_position.y - radius).max(0_f64) * dimension as f64).floor() as i32; + ((euclidean_position.y - radius).max(0_f64) * dimension as f64).floor() as u32; max_xyz.y = - ((euclidean_position.y + radius).min(1_f64) * dimension as f64).ceil() as i32; + ((euclidean_position.y + radius).min(1_f64) * dimension as f64).ceil() as u32; min_xyz.z = - ((euclidean_position.z - radius).max(0_f64) * dimension as f64).floor() as i32; + ((euclidean_position.z - radius).max(0_f64) * dimension as f64).floor() as u32; max_xyz.z = - ((euclidean_position.z + radius).min(1_f64) * dimension as f64).ceil() as i32; + ((euclidean_position.z + radius).min(1_f64) * dimension as f64).ceil() as u32; Some(ChunkBoundingBox { node, chunk, @@ -225,11 +225,20 @@ impl ChunkBoundingBox { } // returns the index (single number) of every voxel contained inside - pub fn every_voxel<'b>(&'b self) -> impl Iterator + 'b { + /*pub fn every_voxel<'b>(&'b self) -> impl Iterator + 'b { (self.min_xyz[2]..self.max_xyz[2]).flat_map(move |z| { (self.min_xyz[1]..self.max_xyz[1]).flat_map(move |y| { (self.min_xyz[0]..self.max_xyz[0]).map(move |x| { - x + (self.dimension as i32) * y + (self.dimension as i32).pow(2) * z + x + (self.dimension as u32) * y + (self.dimension as u32).pow(2) * z + }) + }) + }) + }*/ + pub fn every_voxel<'b>(&'b self) -> impl Iterator + 'b { + (self.min_xyz[2]..self.max_xyz[2]).flat_map(move |z| { + (self.min_xyz[1]..self.max_xyz[1]).flat_map(move |y| { + (self.min_xyz[0]..self.max_xyz[0]).map(move |x| { + z + (self.dimension as u32) * y + (self.dimension as u32).pow(2) * x }) }) }) @@ -420,6 +429,27 @@ mod tests { } } + // eunsures that chunk_from_location has expected behavior + #[test] + fn chunk_from_location_proper_chunk() { + let central_chunk = Vertex::F; // arbitrary vertex + let chunk_coords = na::Vector3::new(1_u32, 1_u32, 1_u32); + + let position = central_chunk.chunk_to_node() + * na::Vector4::new( + (chunk_coords[0] as f64) / CHUNK_SIZE_F, + (chunk_coords[1] as f64) / CHUNK_SIZE_F, + (chunk_coords[2] as f64) / CHUNK_SIZE_F, + 1.0, + ); + + assert_eq!( central_chunk, chunk_from_location(position).unwrap()); + + + } + + + // places a bounding box at a certain [x,y,z], and verifies that the voxel at [x, y, z] is contained within the bounding box. #[test] fn internal_coordinates() { @@ -427,7 +457,7 @@ mod tests { ensure_nearby(&mut graph, &Position::origin(), 4.0); let central_chunk = Vertex::B; // arbitrary vertex - let chunk_coords = na::Vector3::new(2_i32, 6_i32, 9_i32); + let chunk_coords = na::Vector3::new(2_u32, 6_u32, 9_u32); let position = central_chunk.chunk_to_node() * na::Vector4::new( @@ -448,8 +478,8 @@ mod tests { bb.display_summary(); let expected_index = chunk_coords[0] - + chunk_coords[1] * (CHUNK_SIZE as i32) - + chunk_coords[2] * (CHUNK_SIZE as i32).pow(2); + + chunk_coords[1] * (CHUNK_SIZE as u32) + + chunk_coords[2] * (CHUNK_SIZE as u32).pow(2); for address in bb.every_voxel_address() { if expected_index == address.index { diff --git a/common/src/force.rs b/common/src/force.rs new file mode 100644 index 00000000..be448257 --- /dev/null +++ b/common/src/force.rs @@ -0,0 +1,38 @@ +// use crate::Plane; + +// this module covers forces like gravity and the normal force. + +// 1/2 absolute units per second squared of acceleration; it will take two seconds for something at rest to fall the length of one chunk. +const GRAVITY_INTENSITY: f64 = 0.50_f64; + +const BUOYANT_INTENSITY: f64 = GRAVITY_INTENSITY * 2.5_f64; + +// 1000 inverse absolute units of drag acceleration; The equilibruim speed of something under all three forces will be 0.1 absolute units a second. +const GROUND_DRAG_INTENSITY: f64 = 10000_f64; +const AIR_DRAG_INTENSITY: f64 = GROUND_DRAG_INTENSITY / 15_f64; + +// gravity pulls downward +pub fn gravity(down: &na::Vector4, height: f64) -> na::Vector4 { + // return down * GRAVITY_INTENSITY; + // versoin of gravity that conforms to divergence theorem + return down * GRAVITY_INTENSITY / height.cosh().powi(2); +} + +// currently hard collions aren't possible, so you will just "float" to the top of the ground. +pub fn normal_buoyant(down: &na::Vector4, height: f64, is_collsion: bool) -> na::Vector4 { + if is_collsion { + return down * -BUOYANT_INTENSITY / height.cosh().powi(2); + } + return na::Vector4::zeros(); +} + +// when sinking in to the ground, you will expereince drag which acts agaist whichever way you are moving +// and which gains strength proportional to the speed cubed. +pub fn normal_drag(is_collsion: bool, current_velocity: na::Vector4) -> na::Vector4 { + let speed_squared = current_velocity.norm().powi(2); + + if is_collsion { + return -(GROUND_DRAG_INTENSITY * speed_squared).min(1_f64) * current_velocity; + } + return -(AIR_DRAG_INTENSITY * speed_squared).min(1_f64) * current_velocity; +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 73ac74ac..1cdb8b5e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -11,6 +11,7 @@ pub mod codec; pub mod collision; pub mod cursor; pub mod dodeca; +pub mod force; pub mod graph; mod graph_entities; pub mod lru_slab; diff --git a/common/src/math.rs b/common/src/math.rs index 16b2739a..c623655e 100644 --- a/common/src/math.rs +++ b/common/src/math.rs @@ -105,6 +105,41 @@ pub fn renormalize_isometry(m: &na::Matrix4) -> na::Matrix4 translate_along(&direction, boost_length) * rotation.to_homogeneous() } +/// returns the result of translating normal vector v0 from p0 to p1 +pub fn translate_normal( + p0: na::Vector4, + p1: na::Vector4, + v0: na::Vector4, +) -> na::Vector4 { + let d = distance(&p0, &p1); + if !(d >= na::zero()) {return v0;} + + let v1 = (v0 * d.cosh() + p1 * d.sinh()); + // need to do a little processing to increase numerical stability + // return v1 + p1 * mip(&p1, &v1); + return v1; +} + +/// normalizes vector v with repect to translation matrix t +pub fn normalize_vector( + t: na::Matrix4, + v: na::Vector4, +) -> na::Vector4 { + let p = t * origin(); + let m = mip(&v, &v); + if (m <= na::zero()) {return t * na::Vector4::::new(na::one(), na::zero(), na::zero(), na::zero());} + + let v_mag = m.sqrt(); + let v_norm = v/v_mag; + + return (v_norm + p * mip(&p, &v_norm)) * v_mag; +} + +// make a orthogonal to b +pub fn orthogonalize(a: &na::Vector4, b: &na::Vector4) -> na::Vector4 { + return a - b * (mip(a, b) / mip(b, b)); +} + #[rustfmt::skip] fn renormalize_rotation_reflection(m: &na::Matrix3) -> na::Matrix3 { let zv = m.index((.., 2)).normalize(); @@ -230,6 +265,12 @@ mod tests { assert_abs_diff_eq!(distance(&p, &m) * 2.0, distance(&p, &q), epsilon = 1e-5); } + #[test] + fn distance_identity() { + let a = na::Vector4::new(0.2, 0.0, 0.3, 1.0); + assert_abs_diff_eq!(distance(&a, &a), 0.0, epsilon = 1e-5); + } + #[test] fn renormalize_translation() { let mat = translate( @@ -248,4 +289,29 @@ mod tests { 0.0, 0.0, 0.0, 1.0); assert_abs_diff_eq!(renormalize_isometry(&mat), mat, epsilon = 1e-5); } + + #[test] + fn orthogonalize_example() { + let vec1 = na::Vector4::new(-0.5, -0.5, 0.0, 1.0); + let vec2 = na::Vector4::new(0.3, -0.7, 0.0, 1.0); + let orth = orthogonalize(&vec1, &vec2); + + assert_abs_diff_eq!(mip(&vec2, &orth), 0.0, epsilon = 1e-5); + } +/* + #[test] + fn translate_normal_identity() { + let p = na::Vector4::new(-0.03635, 0.95129, 0.0, 1.38068); + let norm = na::Vector4::new(1.0, 0.0, 0.0, 0.0); + assert_abs_diff_eq!(translate_normal(p, p, norm), norm, epsilon = 1e-5); + }*/ + + fn translate_normal_still_normal() { + let p1 = na::Vector4::new(-0.03635, 0.95129, 0.0, 1.38068); + let p0 = origin(); + let norm = na::Vector4::new(1.0, 0.0, 0.0, 0.0); + let trans = translate_normal(p0, p1, norm); + assert_abs_diff_eq!( mip(&trans, &trans), 1.0, epsilon = 1e-5); + assert_abs_diff_eq!( mip(&trans, &p1), 0.0, epsilon = 1e-5); + } } diff --git a/common/src/proto.rs b/common/src/proto.rs index 208b12b5..af2a8606 100644 --- a/common/src/proto.rs +++ b/common/src/proto.rs @@ -39,7 +39,8 @@ pub struct StateDelta { pub step: Step, /// Highest input generation received prior to `step` pub latest_input: u16, - pub positions: Vec<(EntityId, Position)>, + /// the last field is the transform required to go from the previous node to the current one. + pub positions: Vec<(EntityId, Position, na::Matrix4)>, pub character_orientations: Vec<(EntityId, na::UnitQuaternion)>, } diff --git a/common/src/worldgen.rs b/common/src/worldgen.rs index ad3249bf..4cc25130 100644 --- a/common/src/worldgen.rs +++ b/common/src/worldgen.rs @@ -75,7 +75,7 @@ impl NodeState { road_state: NodeStateRoad::ROOT, spice: 0, enviro: EnviroFactors { - max_elevation: 0.0, + max_elevation: -0.0, temperature: 0.0, rainfall: 0.0, blockiness: 0.0, @@ -128,6 +128,20 @@ impl NodeState { enviro, } } + + pub fn surface(&self) -> Plane { + return self.surface; + } + + // if the node lies above the ground-plane or not + pub fn is_sky(&self) -> bool { + match self.kind { + Land => false, + DeepLand => false, + Sky => true, + DeepSky => true, + } + } } struct VoxelCoords { diff --git a/server/src/sim.rs b/server/src/sim.rs index 56a77016..f016baf6 100644 --- a/server/src/sim.rs +++ b/server/src/sim.rs @@ -109,7 +109,12 @@ impl Sim { let _guard = span.enter(); // Simulate - for (_, (ch, pos)) in self.world.query::<(&Character, &mut Position)>().iter() { + let mut node_transforms = FxHashMap::>::default(); + for (_, (ch, pos, id)) in self + .world + .query::<(&Character, &mut Position, &EntityId)>() + .iter() + { let next_xf = pos.local * math::translate_along(&ch.direction, ch.speed / self.cfg.rate as f32); pos.local = math::renormalize_isometry(&next_xf); @@ -117,6 +122,7 @@ impl Sim { if next_node != pos.node { pos.node = next_node; pos.local = transition_xf * pos.local; + node_transforms.insert(*id, transition_xf); } ensure_nearby(&mut self.graph, pos, f64::from(self.cfg.view_distance)); } @@ -157,7 +163,13 @@ impl Sim { .world .query::<(&EntityId, &Position)>() .iter() - .map(|(_, (&id, &position))| (id, position)) + .map(|(_, (&id, &position))| { + ( + id, + position, + *node_transforms.get(&id).unwrap_or(&na::Matrix4::identity()), + ) + }) .collect(), character_orientations: self .world From da6ba5a93766172a1e908b25d45de358007d1d80 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 16 May 2022 19:37:30 -0700 Subject: [PATCH 07/17] finished the merge --- common/src/collision.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/common/src/collision.rs b/common/src/collision.rs index 19f9e837..dbadd5fa 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -225,16 +225,11 @@ impl ChunkBoundingBox { } // returns the index (single number) of every voxel contained inside -<<<<<<< HEAD /*pub fn every_voxel<'b>(&'b self) -> impl Iterator + 'b { -======= - pub fn every_voxel<'b>(&'b self) -> impl Iterator + 'b { ->>>>>>> 2763c2e639b279dfefe6095d009944dbceab1db2 (self.min_xyz[2]..self.max_xyz[2]).flat_map(move |z| { (self.min_xyz[1]..self.max_xyz[1]).flat_map(move |y| { (self.min_xyz[0]..self.max_xyz[0]).map(move |x| { x + (self.dimension as u32) * y + (self.dimension as u32).pow(2) * z -<<<<<<< HEAD }) }) }) @@ -244,8 +239,6 @@ impl ChunkBoundingBox { (self.min_xyz[1]..self.max_xyz[1]).flat_map(move |y| { (self.min_xyz[0]..self.max_xyz[0]).map(move |x| { z + (self.dimension as u32) * y + (self.dimension as u32).pow(2) * x -======= ->>>>>>> 2763c2e639b279dfefe6095d009944dbceab1db2 }) }) }) From 167d222dd6ed3b33f9186d0fc49e74d3193c1714 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 24 May 2022 12:08:57 -0700 Subject: [PATCH 08/17] Cleanup of force.rs --- client/src/sim.rs | 115 +++++++++--------------- common/src/collision.rs | 16 +--- common/src/force.rs | 78 ++++++++++------ common/src/math.rs | 25 +++--- data.txt | 15 ++++ data.txtLocal | 195 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 319 insertions(+), 125 deletions(-) create mode 100644 data.txt create mode 100644 data.txtLocal diff --git a/client/src/sim.rs b/client/src/sim.rs index 953c6125..8a3918b2 100644 --- a/client/src/sim.rs +++ b/client/src/sim.rs @@ -7,7 +7,7 @@ use tracing::{debug, error, trace}; use crate::{net, prediction::PredictedMotion, Net}; use common::{ collision::BoundingBox, - force, + force::{ForceParams, GravityMethod}, graph::{Graph, NodeId}, math, node::{Chunk, DualGraph, Node}, @@ -194,40 +194,10 @@ impl Sim { Some(&entity) => match self.world.get_mut::(entity) { Ok(mut pos) => { if id_is_character { - let previous_charvel = self.character_velocity; - - // velocity-related stuff. - let m = math::mip(&self.character_velocity, &self.character_velocity); - - let (v_mag, v_norm) = match m <= 0_f32 { - true => (0_f32, new_pos.local * na::Vector4::new(1_f32, 0_f32, 0_f32, 0_f32)), - false => { - let v_mag = m.sqrt(); - let v_norm = self.character_velocity / v_mag; - (v_mag, v_norm) - }, - }; - - let p0 = pos.local - * node_transform.try_inverse().unwrap_or(na::Matrix4::zeros()) - * math::origin(); - + let p0 = node_transform * pos.local * math::origin(); let p1 = new_pos.local * math::origin(); - - assert!((math::mip(&p0, &p0) < 0.0), "Error, p0 is not in the format of a point" ); - assert!((math::mip(&p1, &p1) < 0.0), "Error, p1 is not in the format of a point" ); - assert!((math::mip(&v_norm, &v_norm) >= 0.0), "Error, v_norm is not in the format of a vector"); - - self.character_velocity = math::translate_normal( - p0, - p1, - v_norm, - ) * v_mag; - - for n in self.character_velocity.iter() { - assert!(!n.is_nan(), "Error, character velocity is not a number. character_velocity was {}. v_mag is {}. - p0 is {}. p1 is {}. Their difference is {}.", previous_charvel, v_mag, p0, p1, (p1 - p0)); - } + 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); @@ -297,12 +267,20 @@ impl Sim { fn send_input(&mut self) { let params = self.params.as_ref().unwrap(); + let local_velocity = self + .world + .get_mut::(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 + na::Vector3::new( - self.character_velocity[0], - self.character_velocity[1], - self.character_velocity[2], + local_velocity[0], + local_velocity[1], + local_velocity[2], ) / params.movement_speed, ); @@ -367,6 +345,13 @@ impl Sim { } fn handle_forces(&mut self, time: Duration) { + let force_system = ForceParams { + gravity_intensity: 0.5_f64, + air_drag_factor: 0.90_f64, + gravity_type: GravityMethod::PlanarConstant, + 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::(entity) { @@ -385,51 +370,40 @@ impl Sim { character_position.local * math::origin(), ); - let mut down_temp = math::lorentz_normalize(&math::orthogonalize( + let down_temp = math::lorentz_normalize(&math::orthogonalize( down_info.surface().normal(), &character_v4f64, )); for n in down_temp.iter() { assert!(!n.is_nan(), "error, down direction is not a nunber"); } - - let down_local = self.orientation.conjugate() * (character_position.local.try_inverse().unwrap() * na::convert::<_, na::Vector4>(down_temp)).xyz(); - self.orientation *= na::UnitQuaternion::new(na::Vector3::y().cross(&down_local) * 0.01); - - // the meaning of the surface plane varies based on which side of it you are - let down = match down_info.is_sky() { - true => down_temp, - false => -down_temp, - }; - + let down_local = self.orientation.conjugate() + * (character_position.local.try_inverse().unwrap() + * na::convert::<_, na::Vector4>(down_temp)) + .xyz(); + self.orientation *= na::UnitQuaternion::new( + na::Vector3::z() * down_local.x * -3.0 * time.as_secs_f32(), + ); - // let is_colliding = !down_info.is_sky(); + // the meaning of the surface plane varies based on which side of it you are + let down = -down_temp; let height = down_info.surface().distance_to(&character_v4f64); - // let is_colliding = height < 0.04_f64; - - let acceleration_total = force::gravity(&down, height) - // currently not doing drag or bouyant as added forces. - // + force::normal_buoyant(&down, height, is_colliding) - /*+ force::normal_drag( - is_colliding, - na::convert::<_, na::Vector4>(self.character_velocity), - )*/; - - if is_colliding { - self.character_velocity = na::convert::<_, na::Vector4>(down * -0.05_f64); - } else { - self.character_velocity *= 0.90_f32.powf(time.as_secs_f32()); - self.character_velocity += - na::convert::<_, na::Vector4>(acceleration_total) * time.as_secs_f32(); - } + let new_velocity = force_system.apply_forces( + &na::convert::<_, na::Vector4>(self.character_velocity), + &down, + height, + is_colliding, + time.as_secs_f64(), + ); - + self.character_velocity = math::normalize_vector( + character_position.local, + na::convert::<_, na::Vector4>(new_velocity), + ); - self.character_velocity = math::normalize_vector(character_position.local, self.character_velocity); - for n in self.character_velocity.iter() { assert!(!n.is_nan(), "Error, character velocity is not a number"); } @@ -475,7 +449,7 @@ impl Sim { Material::Void => false, _ => true, } { - return true; // I think this is correct + return true; } } false @@ -484,7 +458,6 @@ impl Sim { return true; } } - // println!("No collsion."); false } } diff --git a/common/src/collision.rs b/common/src/collision.rs index dbadd5fa..a6a4657b 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -224,16 +224,6 @@ impl ChunkBoundingBox { } } - // returns the index (single number) of every voxel contained inside - /*pub fn every_voxel<'b>(&'b self) -> impl Iterator + 'b { - (self.min_xyz[2]..self.max_xyz[2]).flat_map(move |z| { - (self.min_xyz[1]..self.max_xyz[1]).flat_map(move |y| { - (self.min_xyz[0]..self.max_xyz[0]).map(move |x| { - x + (self.dimension as u32) * y + (self.dimension as u32).pow(2) * z - }) - }) - }) - }*/ pub fn every_voxel<'b>(&'b self) -> impl Iterator + 'b { (self.min_xyz[2]..self.max_xyz[2]).flat_map(move |z| { (self.min_xyz[1]..self.max_xyz[1]).flat_map(move |y| { @@ -442,14 +432,10 @@ mod tests { (chunk_coords[2] as f64) / CHUNK_SIZE_F, 1.0, ); - - assert_eq!( central_chunk, chunk_from_location(position).unwrap()); - + assert_eq!(central_chunk, chunk_from_location(position).unwrap()); } - - // places a bounding box at a certain [x,y,z], and verifies that the voxel at [x, y, z] is contained within the bounding box. #[test] fn internal_coordinates() { diff --git a/common/src/force.rs b/common/src/force.rs index be448257..4b914c04 100644 --- a/common/src/force.rs +++ b/common/src/force.rs @@ -1,38 +1,62 @@ -// use crate::Plane; +/* GravityMethod encodes different ways of creating gravity in Hyperbolic space -// this module covers forces like gravity and the normal force. +PlanarConstant has gravity oriented in the "down" direction, and the force of gravity is +constant with repect to height. -// 1/2 absolute units per second squared of acceleration; it will take two seconds for something at rest to fall the length of one chunk. -const GRAVITY_INTENSITY: f64 = 0.50_f64; +PlanarConstant has gravity oriented in the "down" direction, and the force of gravity respects +the divergence theorem, which makes it proportial to the ratio of the area of the ground-plane +to the area of the surface which is equidistant to the ground-plane at the indicated height. -const BUOYANT_INTENSITY: f64 = GRAVITY_INTENSITY * 2.5_f64; - -// 1000 inverse absolute units of drag acceleration; The equilibruim speed of something under all three forces will be 0.1 absolute units a second. -const GROUND_DRAG_INTENSITY: f64 = 10000_f64; -const AIR_DRAG_INTENSITY: f64 = GROUND_DRAG_INTENSITY / 15_f64; +Zero disables gravity. +*/ +pub enum GravityMethod { + PlanarConstant, + PlanarDivergent, + Zero, +} -// gravity pulls downward -pub fn gravity(down: &na::Vector4, height: f64) -> na::Vector4 { - // return down * GRAVITY_INTENSITY; - // versoin of gravity that conforms to divergence theorem - return down * GRAVITY_INTENSITY / height.cosh().powi(2); +pub struct ForceParams { + pub gravity_intensity: f64, + pub air_drag_factor: f64, // lower number means more drag + pub gravity_type: GravityMethod, + pub float_speed: f64, } -// currently hard collions aren't possible, so you will just "float" to the top of the ground. -pub fn normal_buoyant(down: &na::Vector4, height: f64, is_collsion: bool) -> na::Vector4 { - if is_collsion { - return down * -BUOYANT_INTENSITY / height.cosh().powi(2); +impl ForceParams { + pub fn apply_forces( + &self, + inital_velocity: &na::Vector4, + down: &na::Vector4, + height: f64, + is_colliding: bool, + time: f64, + ) -> na::Vector4 { + let mut velocity = inital_velocity.clone(); + + // with current collsion detection tech, we want the player to slowly rise if they are in the ground + if is_colliding { + return down * -self.float_speed; + } + + // otherwise we apply air resistance and gravity + velocity += self.gravity(down, height, time); + velocity = self.air_drag(&velocity, time); + return velocity; } - return na::Vector4::zeros(); -} -// when sinking in to the ground, you will expereince drag which acts agaist whichever way you are moving -// and which gains strength proportional to the speed cubed. -pub fn normal_drag(is_collsion: bool, current_velocity: na::Vector4) -> na::Vector4 { - let speed_squared = current_velocity.norm().powi(2); + /// returns the change in velocity that occurs by pulled by gravity at "height" for "time" + fn gravity(&self, down: &na::Vector4, height: f64, time: f64) -> na::Vector4 { + return match self.gravity_type { + GravityMethod::PlanarConstant => down * (self.gravity_intensity * time), + GravityMethod::PlanarDivergent => { + down * (self.gravity_intensity * time / height.cosh().powi(2)) + } + GravityMethod::Zero => na::zero(), + }; + } - if is_collsion { - return -(GROUND_DRAG_INTENSITY * speed_squared).min(1_f64) * current_velocity; + /// rewrites velocity to account for time spent under drag. + fn air_drag(&self, inital_velocity: &na::Vector4, time: f64) -> na::Vector4 { + return inital_velocity * self.air_drag_factor.powf(time); } - return -(AIR_DRAG_INTENSITY * speed_squared).min(1_f64) * current_velocity; } diff --git a/common/src/math.rs b/common/src/math.rs index c623655e..074fec0b 100644 --- a/common/src/math.rs +++ b/common/src/math.rs @@ -112,25 +112,26 @@ pub fn translate_normal( v0: na::Vector4, ) -> na::Vector4 { let d = distance(&p0, &p1); - if !(d >= na::zero()) {return v0;} + if !(d >= na::zero()) { + return v0; + } - let v1 = (v0 * d.cosh() + p1 * d.sinh()); + let v1 = v0 * d.cosh() + p1 * d.sinh(); // need to do a little processing to increase numerical stability // return v1 + p1 * mip(&p1, &v1); return v1; } /// normalizes vector v with repect to translation matrix t -pub fn normalize_vector( - t: na::Matrix4, - v: na::Vector4, -) -> na::Vector4 { +pub fn normalize_vector(t: na::Matrix4, v: na::Vector4) -> na::Vector4 { let p = t * origin(); let m = mip(&v, &v); - if (m <= na::zero()) {return t * na::Vector4::::new(na::one(), na::zero(), na::zero(), na::zero());} - + if m <= na::zero() { + return t * na::Vector4::::new(na::one(), na::zero(), na::zero(), na::zero()); + } + let v_mag = m.sqrt(); - let v_norm = v/v_mag; + let v_norm = v / v_mag; return (v_norm + p * mip(&p, &v_norm)) * v_mag; } @@ -298,7 +299,7 @@ mod tests { assert_abs_diff_eq!(mip(&vec2, &orth), 0.0, epsilon = 1e-5); } -/* + /* #[test] fn translate_normal_identity() { let p = na::Vector4::new(-0.03635, 0.95129, 0.0, 1.38068); @@ -311,7 +312,7 @@ mod tests { let p0 = origin(); let norm = na::Vector4::new(1.0, 0.0, 0.0, 0.0); let trans = translate_normal(p0, p1, norm); - assert_abs_diff_eq!( mip(&trans, &trans), 1.0, epsilon = 1e-5); - assert_abs_diff_eq!( mip(&trans, &p1), 0.0, epsilon = 1e-5); + assert_abs_diff_eq!(mip(&trans, &trans), 1.0, epsilon = 1e-5); + assert_abs_diff_eq!(mip(&trans, &p1), 0.0, epsilon = 1e-5); } } diff --git a/data.txt b/data.txt new file mode 100644 index 00000000..f7bc31c2 --- /dev/null +++ b/data.txt @@ -0,0 +1,15 @@ +May 24 12:03:43.347  INFO found config at /home/david2/.config/hypermine/client.toml +May 24 12:03:43.351  INFO server: listening address=[::1]:48486 +May 24 12:03:43.403 ERROR /usr/lib/i386-linux-gnu/libvulkan_lvp.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 5628e282e340 +May 24 12:03:43.404 ERROR /usr/lib/i386-linux-gnu/libvulkan_intel.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 5628e282e340 +May 24 12:03:43.432 ERROR /usr/lib/i386-linux-gnu/libvulkan_radeon.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 5628e282e340 +May 24 12:03:43.468  INFO server: connection established id=1v1 address=[::1]:32934 +May 24 12:03:43.469  INFO server:client{id=1v1}: spawning character id=694891ee6e1da842 name=david2,,, +May 24 12:03:43.477  INFO selected device name="NVIDIA GeForce GTX 1660" +May 24 12:06:21.633  INFO exiting due to closed window +May 24 12:06:21.651  INFO metric key=frame.gpu.draw percentile_25=1.222655ms percentile_50=2.040831ms percentile_75=3.076095ms max=5.496831ms +May 24 12:06:21.651  INFO metric key=frame.gpu.after_draw percentile_25=28.655µs percentile_50=29.487µs percentile_75=30.831µs max=2.461695ms +May 24 12:06:21.651  INFO metric key=frame.cpu.voxels.draw percentile_25=45.791µs percentile_50=69.503µs percentile_75=88.895µs max=228.479µs +May 24 12:06:21.651  INFO metric key=frame.cpu.voxels.node_scan percentile_25=939.007µs percentile_50=1.179647ms percentile_75=1.274879ms max=2.375679ms +May 24 12:06:21.651  INFO metric key=frame.cpu percentile_25=3.559423ms percentile_50=4.694015ms percentile_75=4.927487ms max=7.417855ms +May 24 12:06:21.651  INFO metric key=frame.cpu.voxels.graph_traversal percentile_25=748.543µs percentile_50=1.231871ms percentile_75=1.282047ms max=2.390015ms diff --git a/data.txtLocal b/data.txtLocal new file mode 100644 index 00000000..530c7e23 --- /dev/null +++ b/data.txtLocal @@ -0,0 +1,195 @@ +May 16 20:21:42.562  INFO found config at /home/david2/.config/hypermine/client.toml +May 16 20:21:42.566  INFO server: listening address=[::1]:42183 +May 16 20:21:42.625 ERROR /usr/lib/i386-linux-gnu/libvulkan_lvp.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 5645d427e230 +May 16 20:21:42.625 ERROR /usr/lib/i386-linux-gnu/libvulkan_intel.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 5645d427e230 +May 16 20:21:42.639 ERROR /usr/lib/i386-linux-gnu/libvulkan_radeon.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 5645d427e230 +May 16 20:21:42.671  INFO server: connection established id=1v1 address=[::1]:59770 +May 16 20:21:42.671  INFO server:client{id=1v1}: spawning character id=513d7adeca10ae51 name=david2,,, +May 16 20:21:42.674  INFO selected device name="NVIDIA GeForce GTX 1660" +Local velocity is + ┌ ┐ + │ 0.0351257 │ + │ 0.035583485 │ + │ 0 │ + │ -0.0000000037252903 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.0351257 │ + │ 0.035583496 │ + │ 0 │ + │ -0.000000011175871 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.035125703 │ + │ 0.0355835 │ + │ 0 │ + │ -0.000000007450581 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.035125706 │ + │ 0.035583492 │ + │ 0 │ + │ 0.0000000037252903 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.035125695 │ + │ 0.035583496 │ + │ 0 │ + │ 0.0000000037252903 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.035125695 │ + │ 0.035583496 │ + │ 0 │ + │ -0.000000007450581 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.0351257 │ + │ 0.035583504 │ + │ 0 │ + │ 0 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.035125695 │ + │ 0.0355835 │ + │ 0 │ + │ -0.000000014901161 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.0351257 │ + │ 0.035583504 │ + │ 0 │ + │ 0.0000000037252903 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.0351257 │ + │ 0.035583496 │ + │ 0 │ + │ 0.0000000037252903 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.0351257 │ + │ 0.0355835 │ + │ 0 │ + │ -0.000000014901161 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.035125688 │ + │ 0.03558349 │ + │ 0 │ + │ -0.000000011175871 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.03512569 │ + │ 0.03558349 │ + │ 0 │ + │ -0.000000011175871 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.035125695 │ + │ 0.035583504 │ + │ 0 │ + │ -0.0000000037252903 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.035125695 │ + │ 0.035583507 │ + │ 0 │ + │ -0.000000007450581 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.035125695 │ + │ 0.03558351 │ + │ 0 │ + │ 0.000000007450581 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.035125695 │ + │ 0.035583496 │ + │ 0 │ + │ 0 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.0351257 │ + │ 0.035583504 │ + │ 0 │ + │ -0.000000007450581 │ + └ ┘ + +. +Local velocity is + ┌ ┐ + │ 0.03512569 │ + │ 0.035583492 │ + │ 0 │ + │ 0.000000007450581 │ + └ ┘ + +. +May 16 20:21:44.805  INFO exiting due to closed window +Local velocity is + ┌ ┐ + │ 0.035125695 │ + │ 0.035583496 │ + │ 0 │ + │ -0.000000007450581 │ + └ ┘ + +. +May 16 20:21:44.822  INFO metric key=frame.cpu.voxels.node_scan percentile_25=953.343µs percentile_50=1.057791ms percentile_75=1.134591ms max=3.618815ms +May 16 20:21:44.822  INFO metric key=frame.cpu.voxels.graph_traversal percentile_25=1.070079ms percentile_50=1.210367ms percentile_75=1.235967ms max=1.925119ms +May 16 20:21:44.822  INFO metric key=frame.gpu.draw percentile_25=183.167µs percentile_50=189.183µs percentile_75=198.399µs max=260.095µs +May 16 20:21:44.823  INFO metric key=frame.gpu.after_draw percentile_25=10.983µs percentile_50=11.623µs percentile_75=12.167µs max=338.175µs +May 16 20:21:44.823  INFO metric key=frame.cpu percentile_25=4.259839ms percentile_50=4.403199ms percentile_75=4.542463ms max=6.955007ms +May 16 20:21:44.823  INFO metric key=frame.cpu.voxels.draw percentile_25=27.855µs percentile_50=29.791µs percentile_75=33.247µs max=65.791µs From b76e66d5dcc8f093d9928114f401026180b4adf9 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 24 May 2022 13:09:30 -0700 Subject: [PATCH 09/17] gravity/velocity parameters are configurable --- client/src/sim.rs | 15 ++++++++++++--- common/src/force.rs | 2 ++ common/src/proto.rs | 5 ++++- common/src/sim_config.rs | 12 ++++++++++++ data.txt | 31 ++++++++++++++++--------------- server/src/lib.rs | 3 +++ 6 files changed, 49 insertions(+), 19 deletions(-) diff --git a/client/src/sim.rs b/client/src/sim.rs index 8a3918b2..94ad4769 100644 --- a/client/src/sim.rs +++ b/client/src/sim.rs @@ -145,6 +145,10 @@ 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); @@ -345,10 +349,12 @@ impl Sim { } fn handle_forces(&mut self, time: Duration) { + let params = self.params.as_ref().unwrap(); + let force_system = ForceParams { - gravity_intensity: 0.5_f64, - air_drag_factor: 0.90_f64, - gravity_type: GravityMethod::PlanarConstant, + gravity_intensity: params.gravity_intensity, + air_drag_factor: params.drag_factor, + gravity_type: params.gravity_method, float_speed: 0.05_f64, }; @@ -470,6 +476,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) { diff --git a/common/src/force.rs b/common/src/force.rs index 4b914c04..d31ed604 100644 --- a/common/src/force.rs +++ b/common/src/force.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; /* GravityMethod encodes different ways of creating gravity in Hyperbolic space PlanarConstant has gravity oriented in the "down" direction, and the force of gravity is @@ -9,6 +10,7 @@ to the area of the surface which is equidistant to the ground-plane at the indic Zero disables gravity. */ +#[derive(Debug, Deserialize, Serialize, Copy, Clone)] pub enum GravityMethod { PlanarConstant, PlanarDivergent, diff --git a/common/src/proto.rs b/common/src/proto.rs index af2a8606..6cb47378 100644 --- a/common/src/proto.rs +++ b/common/src/proto.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{dodeca, graph::NodeId, EntityId, Step}; +use crate::{dodeca, graph::NodeId, EntityId, Step, force::GravityMethod}; #[derive(Debug, Serialize, Deserialize)] pub struct ClientHello { @@ -17,6 +17,9 @@ pub struct ServerHello { pub movement_speed: f32, /// Unit conversion factor pub meters_to_absolute: f32, + pub gravity_intensity: f64, + pub drag_factor: f64, + pub gravity_method: GravityMethod, } #[derive(Debug, Serialize, Deserialize, Copy, Clone)] diff --git a/common/src/sim_config.rs b/common/src/sim_config.rs index 0c245577..8f088ae9 100644 --- a/common/src/sim_config.rs +++ b/common/src/sim_config.rs @@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize}; use crate::{dodeca, math}; +use crate::force::GravityMethod; + /// Manually specified simulation config parameters #[derive(Serialize, Deserialize, Default)] #[serde(deny_unknown_fields)] @@ -24,6 +26,9 @@ pub struct SimConfigRaw { pub voxel_size: Option, /// Character movement speed in m/s pub movement_speed: Option, + pub gravity_intensity : Option, + pub drag_factor: Option, + pub gravity_method: Option, } /// Complete simulation config parameters @@ -34,6 +39,9 @@ pub struct SimConfig { pub input_queue_size: Duration, pub chunk_size: u8, pub movement_speed: f32, + pub gravity_intensity: f64, + pub drag_factor: f64, + pub gravity_method: GravityMethod, /// Scaling factor converting meters to absolute units pub meters_to_absolute: f32, } @@ -49,6 +57,10 @@ impl SimConfig { input_queue_size: Duration::from_millis(x.input_queue_size_ms.unwrap_or(50).into()), chunk_size, movement_speed: x.movement_speed.unwrap_or(12.0) * meters_to_absolute, + gravity_intensity: x.gravity_intensity.unwrap_or(0.5), + drag_factor: x.drag_factor.unwrap_or(0.90), + gravity_method: x.gravity_method.unwrap_or(GravityMethod::PlanarConstant), + meters_to_absolute, } } diff --git a/data.txt b/data.txt index f7bc31c2..69c1949d 100644 --- a/data.txt +++ b/data.txt @@ -1,15 +1,16 @@ -May 24 12:03:43.347  INFO found config at /home/david2/.config/hypermine/client.toml -May 24 12:03:43.351  INFO server: listening address=[::1]:48486 -May 24 12:03:43.403 ERROR /usr/lib/i386-linux-gnu/libvulkan_lvp.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 5628e282e340 -May 24 12:03:43.404 ERROR /usr/lib/i386-linux-gnu/libvulkan_intel.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 5628e282e340 -May 24 12:03:43.432 ERROR /usr/lib/i386-linux-gnu/libvulkan_radeon.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 5628e282e340 -May 24 12:03:43.468  INFO server: connection established id=1v1 address=[::1]:32934 -May 24 12:03:43.469  INFO server:client{id=1v1}: spawning character id=694891ee6e1da842 name=david2,,, -May 24 12:03:43.477  INFO selected device name="NVIDIA GeForce GTX 1660" -May 24 12:06:21.633  INFO exiting due to closed window -May 24 12:06:21.651  INFO metric key=frame.gpu.draw percentile_25=1.222655ms percentile_50=2.040831ms percentile_75=3.076095ms max=5.496831ms -May 24 12:06:21.651  INFO metric key=frame.gpu.after_draw percentile_25=28.655µs percentile_50=29.487µs percentile_75=30.831µs max=2.461695ms -May 24 12:06:21.651  INFO metric key=frame.cpu.voxels.draw percentile_25=45.791µs percentile_50=69.503µs percentile_75=88.895µs max=228.479µs -May 24 12:06:21.651  INFO metric key=frame.cpu.voxels.node_scan percentile_25=939.007µs percentile_50=1.179647ms percentile_75=1.274879ms max=2.375679ms -May 24 12:06:21.651  INFO metric key=frame.cpu percentile_25=3.559423ms percentile_50=4.694015ms percentile_75=4.927487ms max=7.417855ms -May 24 12:06:21.651  INFO metric key=frame.cpu.voxels.graph_traversal percentile_25=748.543µs percentile_50=1.231871ms percentile_75=1.282047ms max=2.390015ms +May 24 13:07:17.110  INFO found config at /home/david2/.config/hypermine/client.toml +May 24 13:07:17.110 ERROR failed to parse config: invalid TOML value, did you mean to use a quoted string? at line 7 column 18 +May 24 13:07:17.114  INFO server: listening address=[::1]:52821 +May 24 13:07:17.182 ERROR /usr/lib/i386-linux-gnu/libvulkan_lvp.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 55648ef3cfd0 +May 24 13:07:17.182 ERROR /usr/lib/i386-linux-gnu/libvulkan_intel.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 55648ef3cfd0 +May 24 13:07:17.206 ERROR /usr/lib/i386-linux-gnu/libvulkan_radeon.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 55648ef3cfd0 +May 24 13:07:17.253  INFO server: connection established id=1v1 address=[::1]:35213 +May 24 13:07:17.253  INFO server:client{id=1v1}: spawning character id=f50bcc7a11883177 name=david2,,, +May 24 13:07:17.261  INFO selected device name="NVIDIA GeForce GTX 1660" +May 24 13:08:00.232  INFO exiting due to closed window +May 24 13:08:00.253  INFO metric key=frame.cpu.voxels.graph_traversal percentile_25=1.855487ms percentile_50=2.000895ms percentile_75=3.082239ms max=5.328895ms +May 24 13:08:00.253  INFO metric key=frame.cpu.voxels.node_scan percentile_25=1.512447ms percentile_50=1.799167ms percentile_75=2.590719ms max=7.847935ms +May 24 13:08:00.253  INFO metric key=frame.cpu.voxels.draw percentile_25=35.263µs percentile_50=44.767µs percentile_75=66.751µs max=221.183µs +May 24 13:08:00.253  INFO metric key=frame.gpu.after_draw percentile_25=22.927µs percentile_50=27.631µs percentile_75=28.687µs max=4.110335ms +May 24 13:08:00.253  INFO metric key=frame.gpu.draw percentile_25=1.839103ms percentile_50=2.451455ms percentile_75=3.237887ms max=7.901183ms +May 24 13:08:00.253  INFO metric key=frame.cpu percentile_25=6.828031ms percentile_50=7.598079ms percentile_75=10.608639ms max=15.785983ms diff --git a/server/src/lib.rs b/server/src/lib.rs index 6135603b..75138b06 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -145,6 +145,9 @@ impl Server { chunk_size: self.cfg.chunk_size, meters_to_absolute: self.cfg.meters_to_absolute, movement_speed: self.cfg.movement_speed, + gravity_intensity: self.cfg.gravity_intensity, + drag_factor: self.cfg.drag_factor, + gravity_method: self.cfg.gravity_method, }; tokio::spawn(async move { // Errors will be handled by recv task From 36153bc2a1cb4dc4ebf641c27caf8e19ff7e243d Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 25 Jun 2022 11:23:58 -0700 Subject: [PATCH 10/17] more fixes to bounding box accuraccy --- client/src/sim.rs | 1 - common/src/collision.rs | 140 +++++++++++++++++++++++++++++++++++---- common/src/dodeca.rs | 8 +++ common/src/proto.rs | 2 +- common/src/sim_config.rs | 2 +- data.txt | 31 +++++---- 6 files changed, 153 insertions(+), 31 deletions(-) diff --git a/client/src/sim.rs b/client/src/sim.rs index 94ad4769..67f799eb 100644 --- a/client/src/sim.rs +++ b/client/src/sim.rs @@ -148,7 +148,6 @@ impl Sim { 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); diff --git a/common/src/collision.rs b/common/src/collision.rs index a6a4657b..d92ac1ca 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -1,5 +1,9 @@ use crate::node::DualGraph; -use crate::{dodeca::Vertex, graph::NodeId}; +use crate::{ + dodeca::{Side, Vertex, SIDE_COUNT, VERTEX_COUNT}, + graph::NodeId, +}; +use lazy_static::lazy_static; #[allow(dead_code)] /* @@ -101,12 +105,23 @@ impl BoundingBox { for side in sides.iter() { let node = graph.neighbor(opposite_node, *side); let translated_position = side.reflection() * opposite_position; - + // the chunk next to the origin chunk Self::add_sub_bb( &mut bounding_boxes, ChunkBoundingBox::get_chunk_bounding_box( node.unwrap(), - Vertex::from_sides(sides[0], sides[1], sides[2]).unwrap(), // pretty sure this is pointless + start_chunk, + translated_position, + radius, + dimension, + ), + ); + // the chunk on the opposite side of the edge + Self::add_sub_bb( + &mut bounding_boxes, + ChunkBoundingBox::get_chunk_bounding_box( + node.unwrap(), + PERPENDICULAR_VERTEX[*side as usize][start_chunk as usize].unwrap(), translated_position, radius, dimension, @@ -185,8 +200,11 @@ impl ChunkBoundingBox { radius: f64, dimension: u8, ) -> Option { - let euclidean_position = - (chunk.chunk_to_node().try_inverse().unwrap() * translated_position).xyz(); + let euclidean_position = { + let temp = chunk.chunk_to_node().try_inverse().unwrap() * translated_position; + temp.xyz() / temp[3] + }; + let mut min_xyz = na::Vector3::::new(0_u32, 0_u32, 0_u32); let mut max_xyz = na::Vector3::::new(0_u32, 0_u32, 0_u32); @@ -225,16 +243,55 @@ impl ChunkBoundingBox { } pub fn every_voxel<'b>(&'b self) -> impl Iterator + 'b { + let lwm = (self.dimension as u32) + 2; (self.min_xyz[2]..self.max_xyz[2]).flat_map(move |z| { (self.min_xyz[1]..self.max_xyz[1]).flat_map(move |y| { - (self.min_xyz[0]..self.max_xyz[0]).map(move |x| { - z + (self.dimension as u32) * y + (self.dimension as u32).pow(2) * x - }) + (self.min_xyz[0]..self.max_xyz[0]) + .map(move |x| (x + 1) + lwm * (y + 1) + lwm.pow(2) * (z + 1)) }) }) } } +lazy_static! { + /// given a side s and a vertex v, returns a vertex that is adjacent to v but not + /// incident on s, if such a vertex exists + // I can't decide if this should be here or dodeca.rs + static ref PERPENDICULAR_VERTEX: [[Option; VERTEX_COUNT]; SIDE_COUNT] = { + let mut result = [[None; VERTEX_COUNT]; SIDE_COUNT]; + + for s in 0..SIDE_COUNT { + let side = Side::from_index(s); + let incident_vertices = side.vertices(); + for v in 0..5 { + let vertex = incident_vertices[v]; + let mut vertex_counts = [0; VERTEX_COUNT]; + + // count the number of times that vertices appear in all incident sides + let sides_to_tally = vertex.canonical_sides(); + for i in 0..3 { + // five verticies per side + let vertices_to_count = sides_to_tally[i].vertices(); + for j in 0..5 { + vertex_counts[vertices_to_count[j] as usize] += 1; + } + } + + // inceident corners as not perpendicular + for i in side.vertices().iter() {vertex_counts[*i as usize] = -1} + + for i in 0..VERTEX_COUNT { + if vertex_counts[i] == 2 { + result[s][vertex as usize] = Some(Vertex::from_index(i)); + break; + } + } + } + } + result + }; +} + #[cfg(test)] mod tests { use super::*; @@ -243,7 +300,7 @@ mod tests { use crate::{graph::Graph, proto::Position, traversal::ensure_nearby}; use approx::*; - const CHUNK_SIZE: u8 = 12; // might want to test with multiple values in the future. + const CHUNK_SIZE: u8 = 12_u8; // might want to test with multiple values in the future. static CHUNK_SIZE_F: f64 = CHUNK_SIZE as f64; // place a small bounding box near the center of the node. There should be exactly 20 chunks in contact. @@ -463,10 +520,45 @@ mod tests { bb.display_summary(); - let expected_index = chunk_coords[0] - + chunk_coords[1] * (CHUNK_SIZE as u32) - + chunk_coords[2] * (CHUNK_SIZE as u32).pow(2); + let lwm = (CHUNK_SIZE + 2_u8) as u32; + + let expected_index = chunk_coords[0] + 1 + (chunk_coords[1] + 1) * lwm + (chunk_coords[2] + 1)*lwm.pow(2); + + for address in bb.every_voxel_address() { + if expected_index == address.index { + return; + } + } + panic!(); + } + + #[test] + fn internal_coordinates_tiny() { + let mut graph = Graph::new(); + ensure_nearby(&mut graph, &Position::origin(), 4.0); + + let central_chunk = Vertex::B; // arbitrary vertex + let chunk_coords = na::Vector3::new(1_u32, 1_u32, 1_u32); + + let tiny_chunk_size = 1_u8; + + let position = central_chunk.chunk_to_node() + * na::Vector4::new( + (chunk_coords[0] as f64) / 1.0_f64, + (chunk_coords[1] as f64) / 1.0_f64, + (chunk_coords[2] as f64) / 1.0_f64, + 1.0, + ); + + let bb = BoundingBox::create_aabb(NodeId::ROOT, position, 0.3, &graph, 1); + bb.display_summary(); + + let lwm = (tiny_chunk_size + 2_u8) as u32; + + //let expected_index = chunk_coords[0] + 1 + (chunk_coords[1] + 1) * lwm + (chunk_coords[2] + 1)*lwm.pow(2); + let expected_index = chunk_coords[0] + chunk_coords[1] * lwm + chunk_coords[2]*lwm.pow(2); + for address in bb.every_voxel_address() { if expected_index == address.index { return; @@ -474,4 +566,28 @@ mod tests { } panic!(); } + + #[test] + fn perpendicular_vertex_is_complete() { + //print!("{:?}", PERPENDICULAR_VERTEX); + let mut error_count = 0_i32; + + for s in 0..SIDE_COUNT { + let side = Side::from_index(s); + let incident_vertices = side.vertices(); + for v in 0..5 { + let vertex = incident_vertices[v]; + println!("side of {:?} and vertex of {:?}", side, vertex); // not helpful, but I don't want to mess with the formatter. + let result = PERPENDICULAR_VERTEX[s][vertex as usize]; + if result.is_some() { + println!("\tresults in {:?}", result.unwrap()); + } else { + println!("\tIs not thought to exist."); + error_count += 1; + } + } + } + + assert!(error_count == 0_i32); + } } diff --git a/common/src/dodeca.rs b/common/src/dodeca.rs index 53cb3b6f..89260e2a 100644 --- a/common/src/dodeca.rs +++ b/common/src/dodeca.rs @@ -94,6 +94,14 @@ impl Vertex { .cloned() } + #[inline] + pub fn from_index(x: usize) -> Self { + use Vertex::*; + const VALUES: [Vertex; VERTEX_COUNT] = + [A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T]; + VALUES[x] + } + /// Vertex shared by three sides, if any #[inline] pub fn from_sides(a: Side, b: Side, c: Side) -> Option { diff --git a/common/src/proto.rs b/common/src/proto.rs index 6cb47378..a875f240 100644 --- a/common/src/proto.rs +++ b/common/src/proto.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{dodeca, graph::NodeId, EntityId, Step, force::GravityMethod}; +use crate::{dodeca, force::GravityMethod, graph::NodeId, EntityId, Step}; #[derive(Debug, Serialize, Deserialize)] pub struct ClientHello { diff --git a/common/src/sim_config.rs b/common/src/sim_config.rs index 8f088ae9..05e0112c 100644 --- a/common/src/sim_config.rs +++ b/common/src/sim_config.rs @@ -26,7 +26,7 @@ pub struct SimConfigRaw { pub voxel_size: Option, /// Character movement speed in m/s pub movement_speed: Option, - pub gravity_intensity : Option, + pub gravity_intensity: Option, pub drag_factor: Option, pub gravity_method: Option, } diff --git a/data.txt b/data.txt index 69c1949d..88aa91a3 100644 --- a/data.txt +++ b/data.txt @@ -1,16 +1,15 @@ -May 24 13:07:17.110  INFO found config at /home/david2/.config/hypermine/client.toml -May 24 13:07:17.110 ERROR failed to parse config: invalid TOML value, did you mean to use a quoted string? at line 7 column 18 -May 24 13:07:17.114  INFO server: listening address=[::1]:52821 -May 24 13:07:17.182 ERROR /usr/lib/i386-linux-gnu/libvulkan_lvp.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 55648ef3cfd0 -May 24 13:07:17.182 ERROR /usr/lib/i386-linux-gnu/libvulkan_intel.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 55648ef3cfd0 -May 24 13:07:17.206 ERROR /usr/lib/i386-linux-gnu/libvulkan_radeon.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 55648ef3cfd0 -May 24 13:07:17.253  INFO server: connection established id=1v1 address=[::1]:35213 -May 24 13:07:17.253  INFO server:client{id=1v1}: spawning character id=f50bcc7a11883177 name=david2,,, -May 24 13:07:17.261  INFO selected device name="NVIDIA GeForce GTX 1660" -May 24 13:08:00.232  INFO exiting due to closed window -May 24 13:08:00.253  INFO metric key=frame.cpu.voxels.graph_traversal percentile_25=1.855487ms percentile_50=2.000895ms percentile_75=3.082239ms max=5.328895ms -May 24 13:08:00.253  INFO metric key=frame.cpu.voxels.node_scan percentile_25=1.512447ms percentile_50=1.799167ms percentile_75=2.590719ms max=7.847935ms -May 24 13:08:00.253  INFO metric key=frame.cpu.voxels.draw percentile_25=35.263µs percentile_50=44.767µs percentile_75=66.751µs max=221.183µs -May 24 13:08:00.253  INFO metric key=frame.gpu.after_draw percentile_25=22.927µs percentile_50=27.631µs percentile_75=28.687µs max=4.110335ms -May 24 13:08:00.253  INFO metric key=frame.gpu.draw percentile_25=1.839103ms percentile_50=2.451455ms percentile_75=3.237887ms max=7.901183ms -May 24 13:08:00.253  INFO metric key=frame.cpu percentile_25=6.828031ms percentile_50=7.598079ms percentile_75=10.608639ms max=15.785983ms +Jun 25 11:19:12.172  INFO found config at /home/david2/.config/hypermine/client.toml +Jun 25 11:19:12.176  INFO server: listening address=[::1]:52757 +Jun 25 11:19:12.236 ERROR /usr/lib/i386-linux-gnu/libvulkan_lvp.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 55e512011c70 +Jun 25 11:19:12.236 ERROR /usr/lib/i386-linux-gnu/libvulkan_intel.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 55e512011c70 +Jun 25 11:19:12.246 ERROR /usr/lib/i386-linux-gnu/libvulkan_radeon.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 55e512011c70 +Jun 25 11:19:12.277  INFO server: connection established id=1v1 address=[::1]:33710 +Jun 25 11:19:12.277  INFO server:client{id=1v1}: spawning character id=1214b37f4b0b9d55 name=david2,,, +Jun 25 11:19:12.282  INFO selected device name="NVIDIA GeForce GTX 1660" +Jun 25 11:22:55.974  INFO exiting due to closed window +Jun 25 11:22:55.992  INFO metric key=frame.cpu.voxels.graph_traversal percentile_25=1.689599ms percentile_50=2.016255ms percentile_75=2.416639ms max=5.828607ms +Jun 25 11:22:55.992  INFO metric key=frame.gpu.after_draw percentile_25=22.895µs percentile_50=28.687µs percentile_75=74.047µs max=2.957311ms +Jun 25 11:22:55.992  INFO metric key=frame.cpu.voxels.node_scan percentile_25=1.739775ms percentile_50=2.030591ms percentile_75=2.785279ms max=5.844991ms +Jun 25 11:22:55.992  INFO metric key=frame.cpu percentile_25=7.204863ms percentile_50=8.052735ms percentile_75=10.788863ms max=16.596991ms +Jun 25 11:22:55.993  INFO metric key=frame.gpu.draw percentile_25=1.433599ms percentile_50=2.040831ms percentile_75=2.562047ms max=6.344703ms +Jun 25 11:22:55.993  INFO metric key=frame.cpu.voxels.draw percentile_25=54.143µs percentile_50=96.127µs percentile_75=143.743µs max=758.271µs From 3bae7d5021c559c9c6a3cc671d6fedadec947594 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 28 Jul 2022 15:09:10 -0700 Subject: [PATCH 11/17] General comment cleanup --- client/src/sim.rs | 13 ++----------- common/src/collision.rs | 11 ++++++----- common/src/force.rs | 2 +- common/src/math.rs | 4 +--- data.txt | 15 --------------- 5 files changed, 10 insertions(+), 35 deletions(-) delete mode 100644 data.txt diff --git a/client/src/sim.rs b/client/src/sim.rs index 67f799eb..bc5bebbd 100644 --- a/client/src/sim.rs +++ b/client/src/sim.rs @@ -308,15 +308,8 @@ impl Sim { if let Some(ref params) = self.params { // Apply input that hasn't been sent yet let (direction, speed) = sanitize_motion_input( - // TODO: Incorparate the gravity into the view calculations. + // TODO: Incorparate the persistent velocity into the view calculations. self.average_velocity / CHARACTER_SLOWDOWN_FACTOR, - /*+ self.orientation - * na::Vector3::new( - self.character_velocity[0], - self.character_velocity[1], - self.character_velocity[2], - ) - / params.movement_speed,*/ ); // 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 @@ -361,7 +354,7 @@ impl Sim { match self.local_character { Some(entity) => match self.world.get_mut::(entity) { Ok(character_position) => { - // starting with simplier method for testing purposese + // starting with simpler method for testing purposese let is_colliding = self.check_collision(*character_position); let down_info = &self @@ -433,8 +426,6 @@ impl Sim { params.chunk_size, ); - // bb.display_summary(); - for cbb in bb.bounding_boxes { if match self .graph diff --git a/common/src/collision.rs b/common/src/collision.rs index d92ac1ca..097fd6b6 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -476,7 +476,7 @@ mod tests { } } - // eunsures that chunk_from_location has expected behavior + // ensures that chunk_from_location has expected behavior #[test] fn chunk_from_location_proper_chunk() { let central_chunk = Vertex::F; // arbitrary vertex @@ -522,7 +522,8 @@ mod tests { let lwm = (CHUNK_SIZE + 2_u8) as u32; - let expected_index = chunk_coords[0] + 1 + (chunk_coords[1] + 1) * lwm + (chunk_coords[2] + 1)*lwm.pow(2); + let expected_index = + chunk_coords[0] + 1 + (chunk_coords[1] + 1) * lwm + (chunk_coords[2] + 1) * lwm.pow(2); for address in bb.every_voxel_address() { if expected_index == address.index { @@ -553,12 +554,12 @@ mod tests { let bb = BoundingBox::create_aabb(NodeId::ROOT, position, 0.3, &graph, 1); bb.display_summary(); - + let lwm = (tiny_chunk_size + 2_u8) as u32; //let expected_index = chunk_coords[0] + 1 + (chunk_coords[1] + 1) * lwm + (chunk_coords[2] + 1)*lwm.pow(2); - let expected_index = chunk_coords[0] + chunk_coords[1] * lwm + chunk_coords[2]*lwm.pow(2); - + let expected_index = chunk_coords[0] + chunk_coords[1] * lwm + chunk_coords[2] * lwm.pow(2); + for address in bb.every_voxel_address() { if expected_index == address.index { return; diff --git a/common/src/force.rs b/common/src/force.rs index d31ed604..3c720dc4 100644 --- a/common/src/force.rs +++ b/common/src/force.rs @@ -43,7 +43,7 @@ impl ForceParams { // otherwise we apply air resistance and gravity velocity += self.gravity(down, height, time); velocity = self.air_drag(&velocity, time); - return velocity; + velocity } /// returns the change in velocity that occurs by pulled by gravity at "height" for "time" diff --git a/common/src/math.rs b/common/src/math.rs index 074fec0b..0cbf9422 100644 --- a/common/src/math.rs +++ b/common/src/math.rs @@ -117,8 +117,6 @@ pub fn translate_normal( } let v1 = v0 * d.cosh() + p1 * d.sinh(); - // need to do a little processing to increase numerical stability - // return v1 + p1 * mip(&p1, &v1); return v1; } @@ -136,7 +134,7 @@ pub fn normalize_vector(t: na::Matrix4, v: na::Vector4) -> n return (v_norm + p * mip(&p, &v_norm)) * v_mag; } -// make a orthogonal to b +/// make a orthogonal to b pub fn orthogonalize(a: &na::Vector4, b: &na::Vector4) -> na::Vector4 { return a - b * (mip(a, b) / mip(b, b)); } diff --git a/data.txt b/data.txt deleted file mode 100644 index 88aa91a3..00000000 --- a/data.txt +++ /dev/null @@ -1,15 +0,0 @@ -Jun 25 11:19:12.172  INFO found config at /home/david2/.config/hypermine/client.toml -Jun 25 11:19:12.176  INFO server: listening address=[::1]:52757 -Jun 25 11:19:12.236 ERROR /usr/lib/i386-linux-gnu/libvulkan_lvp.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 55e512011c70 -Jun 25 11:19:12.236 ERROR /usr/lib/i386-linux-gnu/libvulkan_intel.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 55e512011c70 -Jun 25 11:19:12.246 ERROR /usr/lib/i386-linux-gnu/libvulkan_radeon.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 55e512011c70 -Jun 25 11:19:12.277  INFO server: connection established id=1v1 address=[::1]:33710 -Jun 25 11:19:12.277  INFO server:client{id=1v1}: spawning character id=1214b37f4b0b9d55 name=david2,,, -Jun 25 11:19:12.282  INFO selected device name="NVIDIA GeForce GTX 1660" -Jun 25 11:22:55.974  INFO exiting due to closed window -Jun 25 11:22:55.992  INFO metric key=frame.cpu.voxels.graph_traversal percentile_25=1.689599ms percentile_50=2.016255ms percentile_75=2.416639ms max=5.828607ms -Jun 25 11:22:55.992  INFO metric key=frame.gpu.after_draw percentile_25=22.895µs percentile_50=28.687µs percentile_75=74.047µs max=2.957311ms -Jun 25 11:22:55.992  INFO metric key=frame.cpu.voxels.node_scan percentile_25=1.739775ms percentile_50=2.030591ms percentile_75=2.785279ms max=5.844991ms -Jun 25 11:22:55.992  INFO metric key=frame.cpu percentile_25=7.204863ms percentile_50=8.052735ms percentile_75=10.788863ms max=16.596991ms -Jun 25 11:22:55.993  INFO metric key=frame.gpu.draw percentile_25=1.433599ms percentile_50=2.040831ms percentile_75=2.562047ms max=6.344703ms -Jun 25 11:22:55.993  INFO metric key=frame.cpu.voxels.draw percentile_25=54.143µs percentile_50=96.127µs percentile_75=143.743µs max=758.271µs From 8a7aa94e3952d4c1e846c8bbedd12ea661cecd38 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 29 Jul 2022 11:15:26 -0700 Subject: [PATCH 12/17] post-merge refactoring --- client/src/sim.rs | 9 ++++----- common/src/collision.rs | 24 +++++++++++++----------- common/src/force.rs | 8 ++++---- common/src/math.rs | 21 ++++++--------------- common/src/worldgen.rs | 2 +- 5 files changed, 28 insertions(+), 36 deletions(-) diff --git a/client/src/sim.rs b/client/src/sim.rs index bc5bebbd..620b2574 100644 --- a/client/src/sim.rs +++ b/client/src/sim.rs @@ -412,7 +412,9 @@ impl Sim { } } - // helper function for handle_forces + /// 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(); @@ -441,10 +443,7 @@ impl Sim { ref voxels, } => { for voxel_address in cbb.every_voxel() { - if match voxels.get(voxel_address as usize) { - Material::Void => false, - _ => true, - } { + if !matches!(voxels.get(voxel_address as usize), Material::Void) { return true; } } diff --git a/common/src/collision.rs b/common/src/collision.rs index 097fd6b6..77aaef6c 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -152,7 +152,7 @@ impl BoundingBox { } } - pub fn every_voxel_address<'a>(&'a self) -> impl Iterator + 'a { + pub fn every_voxel_address(&self) -> impl Iterator + '_ { self.bounding_boxes.iter().flat_map(|cbb| { cbb.every_voxel().map(move |index| VoxelAddress { node: cbb.node, @@ -242,7 +242,7 @@ impl ChunkBoundingBox { } } - pub fn every_voxel<'b>(&'b self) -> impl Iterator + 'b { + pub fn every_voxel(&self) -> impl Iterator + '_ { let lwm = (self.dimension as u32) + 2; (self.min_xyz[2]..self.max_xyz[2]).flat_map(move |z| { (self.min_xyz[1]..self.max_xyz[1]).flat_map(move |y| { @@ -260,9 +260,11 @@ lazy_static! { static ref PERPENDICULAR_VERTEX: [[Option; VERTEX_COUNT]; SIDE_COUNT] = { let mut result = [[None; VERTEX_COUNT]; SIDE_COUNT]; - for s in 0..SIDE_COUNT { - let side = Side::from_index(s); + for side in Side::iter() { let incident_vertices = side.vertices(); + + // 'v' and 'vertex as usize' will have different values. + #[allow(clippy::needless_range_loop)] for v in 0..5 { let vertex = incident_vertices[v]; let mut vertex_counts = [0; VERTEX_COUNT]; @@ -280,9 +282,9 @@ lazy_static! { // inceident corners as not perpendicular for i in side.vertices().iter() {vertex_counts[*i as usize] = -1} - for i in 0..VERTEX_COUNT { - if vertex_counts[i] == 2 { - result[s][vertex as usize] = Some(Vertex::from_index(i)); + for i in Vertex::iter() { + if vertex_counts[i as usize] == 2 { + result[side as usize][vertex as usize] = Some(i); break; } } @@ -295,10 +297,7 @@ lazy_static! { #[cfg(test)] mod tests { use super::*; - use crate::node::{DualGraph, Node}; - use crate::Chunks; use crate::{graph::Graph, proto::Position, traversal::ensure_nearby}; - use approx::*; const CHUNK_SIZE: u8 = 12_u8; // might want to test with multiple values in the future. static CHUNK_SIZE_F: f64 = CHUNK_SIZE as f64; @@ -460,7 +459,7 @@ mod tests { BoundingBox::create_aabb(NodeId::ROOT, position, radius, &graph, CHUNK_SIZE); let mut actual_voxels = 0; - for address in bb.every_voxel_address() { + for _address in bb.every_voxel_address() { actual_voxels += 1; } @@ -576,6 +575,9 @@ mod tests { for s in 0..SIDE_COUNT { let side = Side::from_index(s); let incident_vertices = side.vertices(); + + // 'v' and 'vertex as usize' will have different values. + #[allow(clippy::needless_range_loop)] for v in 0..5 { let vertex = incident_vertices[v]; println!("side of {:?} and vertex of {:?}", side, vertex); // not helpful, but I don't want to mess with the formatter. diff --git a/common/src/force.rs b/common/src/force.rs index 3c720dc4..bc1cf8aa 100644 --- a/common/src/force.rs +++ b/common/src/force.rs @@ -33,7 +33,7 @@ impl ForceParams { is_colliding: bool, time: f64, ) -> na::Vector4 { - let mut velocity = inital_velocity.clone(); + let mut velocity = *inital_velocity; // with current collsion detection tech, we want the player to slowly rise if they are in the ground if is_colliding { @@ -48,17 +48,17 @@ impl ForceParams { /// returns the change in velocity that occurs by pulled by gravity at "height" for "time" fn gravity(&self, down: &na::Vector4, height: f64, time: f64) -> na::Vector4 { - return match self.gravity_type { + match self.gravity_type { GravityMethod::PlanarConstant => down * (self.gravity_intensity * time), GravityMethod::PlanarDivergent => { down * (self.gravity_intensity * time / height.cosh().powi(2)) } GravityMethod::Zero => na::zero(), - }; + } } /// rewrites velocity to account for time spent under drag. fn air_drag(&self, inital_velocity: &na::Vector4, time: f64) -> na::Vector4 { - return inital_velocity * self.air_drag_factor.powf(time); + inital_velocity * self.air_drag_factor.powf(time) } } diff --git a/common/src/math.rs b/common/src/math.rs index ba0702fc..543439a4 100644 --- a/common/src/math.rs +++ b/common/src/math.rs @@ -112,12 +112,12 @@ pub fn translate_normal( v0: na::Vector4, ) -> na::Vector4 { let d = distance(&p0, &p1); - if !(d >= na::zero()) { + // TODO: verfiy this is equvilent to !(d >= na::zero()) + if na::zero::() > d { return v0; } - let v1 = v0 * d.cosh() + p1 * d.sinh(); - return v1; + v0 * d.cosh() + p1 * d.sinh() } /// normalizes vector v with repect to translation matrix t @@ -131,12 +131,12 @@ pub fn normalize_vector(t: na::Matrix4, v: na::Vector4) -> n let v_mag = m.sqrt(); let v_norm = v / v_mag; - return (v_norm + p * mip(&p, &v_norm)) * v_mag; + (v_norm + p * mip(&p, &v_norm)) * v_mag } /// make a orthogonal to b pub fn orthogonalize(a: &na::Vector4, b: &na::Vector4) -> na::Vector4 { - return a - b * (mip(a, b) / mip(b, b)); + a - b * (mip(a, b) / mip(b, b)) } #[rustfmt::skip] @@ -297,20 +297,11 @@ mod tests { assert_abs_diff_eq!(mip(&vec2, &orth), 0.0, epsilon = 1e-5); } - /* + #[test] fn translate_normal_identity() { let p = na::Vector4::new(-0.03635, 0.95129, 0.0, 1.38068); let norm = na::Vector4::new(1.0, 0.0, 0.0, 0.0); assert_abs_diff_eq!(translate_normal(p, p, norm), norm, epsilon = 1e-5); - }*/ - - fn translate_normal_still_normal() { - let p1 = na::Vector4::new(-0.03635, 0.95129, 0.0, 1.38068); - let p0 = origin(); - let norm = na::Vector4::new(1.0, 0.0, 0.0, 0.0); - let trans = translate_normal(p0, p1, norm); - assert_abs_diff_eq!(mip(&trans, &trans), 1.0, epsilon = 1e-5); - assert_abs_diff_eq!(mip(&trans, &p1), 0.0, epsilon = 1e-5); } } diff --git a/common/src/worldgen.rs b/common/src/worldgen.rs index ff0bd262..ac233c16 100644 --- a/common/src/worldgen.rs +++ b/common/src/worldgen.rs @@ -130,7 +130,7 @@ impl NodeState { } pub fn surface(&self) -> Plane { - return self.surface; + self.surface } // if the node lies above the ground-plane or not From d315a4d74082d82521e353b381d1be0727a4ba74 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 2 Aug 2022 13:12:58 -0700 Subject: [PATCH 13/17] Implemented Debug display for Bounding Boxes --- common/src/collision.rs | 56 +++++------- data.txtLocal | 195 ---------------------------------------- 2 files changed, 24 insertions(+), 227 deletions(-) delete mode 100644 data.txtLocal diff --git a/common/src/collision.rs b/common/src/collision.rs index 77aaef6c..801f86a9 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -4,12 +4,13 @@ use crate::{ graph::NodeId, }; use lazy_static::lazy_static; +use std::fmt; #[allow(dead_code)] /* The set of voxels that a collision body covers within a chunk */ -#[derive(PartialEq, Clone, Debug, Copy)] +#[derive(PartialEq, Clone, Copy)] pub struct ChunkBoundingBox { pub node: NodeId, pub chunk: Vertex, @@ -161,33 +162,6 @@ impl BoundingBox { }) }) } - - pub fn display_summary(&self) { - let number_of_chunk_bouding_boxes = { - let mut n = 0_i32; - for _cbb in self.bounding_boxes.iter() { - n += 1; - } - n - }; - println!( - "Bounding box spanning {} chunks:", - number_of_chunk_bouding_boxes - ); - for cbb in self.bounding_boxes.iter() { - println!("\tA chunk"); - //println!("\t\twith node id {}", cbb.node); // can't easily display node id - println!( - "\t\twith bounding box stretching from ({}, {}, {}) to ({}, {}, {})", - cbb.min_xyz[0], - cbb.min_xyz[1], - cbb.min_xyz[2], - cbb.max_xyz[0], - cbb.max_xyz[1], - cbb.max_xyz[2] - ); - } - } } // translated_position should be the object position in the node coordinates of the chunk. @@ -294,6 +268,24 @@ lazy_static! { }; } +impl fmt::Debug for BoundingBox { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Bounding Box") + .field("Chunk components", &self.bounding_boxes) + .finish() + } +} + +impl fmt::Debug for ChunkBoundingBox { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Chunk Bounding Box") + .field("chunk", &self.chunk) + .field("spanning from", &self.min_xyz.data) + .field("spanning to", &self.max_xyz.data) + .finish() + } +} + #[cfg(test)] mod tests { use super::*; @@ -396,7 +388,7 @@ mod tests { &graph, CHUNK_SIZE, ); - bb.display_summary(); + println!("{:?}", bb); assert_eq!(bb.bounding_boxes.len(), 10); } @@ -467,7 +459,7 @@ mod tests { "actual_voxels for reasonable_voxel_count: {} vs {}(min), {}(expected), {}(max)", actual_voxels, minimum_expected_voxel_count, expected_voxel_count, maximum_expected_voxel_count ); - bb.display_summary(); + println!("{:?}", bb); println!("x_f64 is {} radius is {}", x_f64, radius); assert!(actual_voxels >= minimum_expected_voxel_count); assert!(actual_voxels <= maximum_expected_voxel_count); @@ -517,7 +509,7 @@ mod tests { CHUNK_SIZE, ); - bb.display_summary(); + println!("{:?}", bb); let lwm = (CHUNK_SIZE + 2_u8) as u32; @@ -552,7 +544,7 @@ mod tests { let bb = BoundingBox::create_aabb(NodeId::ROOT, position, 0.3, &graph, 1); - bb.display_summary(); + println!("{:?}", bb); let lwm = (tiny_chunk_size + 2_u8) as u32; diff --git a/data.txtLocal b/data.txtLocal deleted file mode 100644 index 530c7e23..00000000 --- a/data.txtLocal +++ /dev/null @@ -1,195 +0,0 @@ -May 16 20:21:42.562  INFO found config at /home/david2/.config/hypermine/client.toml -May 16 20:21:42.566  INFO server: listening address=[::1]:42183 -May 16 20:21:42.625 ERROR /usr/lib/i386-linux-gnu/libvulkan_lvp.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 5645d427e230 -May 16 20:21:42.625 ERROR /usr/lib/i386-linux-gnu/libvulkan_intel.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 5645d427e230 -May 16 20:21:42.639 ERROR /usr/lib/i386-linux-gnu/libvulkan_radeon.so: wrong ELF class: ELFCLASS32 id=Loader Message number=0 queue_labels= cmd_labels= objects=INSTANCE 5645d427e230 -May 16 20:21:42.671  INFO server: connection established id=1v1 address=[::1]:59770 -May 16 20:21:42.671  INFO server:client{id=1v1}: spawning character id=513d7adeca10ae51 name=david2,,, -May 16 20:21:42.674  INFO selected device name="NVIDIA GeForce GTX 1660" -Local velocity is - ┌ ┐ - │ 0.0351257 │ - │ 0.035583485 │ - │ 0 │ - │ -0.0000000037252903 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.0351257 │ - │ 0.035583496 │ - │ 0 │ - │ -0.000000011175871 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.035125703 │ - │ 0.0355835 │ - │ 0 │ - │ -0.000000007450581 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.035125706 │ - │ 0.035583492 │ - │ 0 │ - │ 0.0000000037252903 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.035125695 │ - │ 0.035583496 │ - │ 0 │ - │ 0.0000000037252903 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.035125695 │ - │ 0.035583496 │ - │ 0 │ - │ -0.000000007450581 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.0351257 │ - │ 0.035583504 │ - │ 0 │ - │ 0 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.035125695 │ - │ 0.0355835 │ - │ 0 │ - │ -0.000000014901161 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.0351257 │ - │ 0.035583504 │ - │ 0 │ - │ 0.0000000037252903 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.0351257 │ - │ 0.035583496 │ - │ 0 │ - │ 0.0000000037252903 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.0351257 │ - │ 0.0355835 │ - │ 0 │ - │ -0.000000014901161 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.035125688 │ - │ 0.03558349 │ - │ 0 │ - │ -0.000000011175871 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.03512569 │ - │ 0.03558349 │ - │ 0 │ - │ -0.000000011175871 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.035125695 │ - │ 0.035583504 │ - │ 0 │ - │ -0.0000000037252903 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.035125695 │ - │ 0.035583507 │ - │ 0 │ - │ -0.000000007450581 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.035125695 │ - │ 0.03558351 │ - │ 0 │ - │ 0.000000007450581 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.035125695 │ - │ 0.035583496 │ - │ 0 │ - │ 0 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.0351257 │ - │ 0.035583504 │ - │ 0 │ - │ -0.000000007450581 │ - └ ┘ - -. -Local velocity is - ┌ ┐ - │ 0.03512569 │ - │ 0.035583492 │ - │ 0 │ - │ 0.000000007450581 │ - └ ┘ - -. -May 16 20:21:44.805  INFO exiting due to closed window -Local velocity is - ┌ ┐ - │ 0.035125695 │ - │ 0.035583496 │ - │ 0 │ - │ -0.000000007450581 │ - └ ┘ - -. -May 16 20:21:44.822  INFO metric key=frame.cpu.voxels.node_scan percentile_25=953.343µs percentile_50=1.057791ms percentile_75=1.134591ms max=3.618815ms -May 16 20:21:44.822  INFO metric key=frame.cpu.voxels.graph_traversal percentile_25=1.070079ms percentile_50=1.210367ms percentile_75=1.235967ms max=1.925119ms -May 16 20:21:44.822  INFO metric key=frame.gpu.draw percentile_25=183.167µs percentile_50=189.183µs percentile_75=198.399µs max=260.095µs -May 16 20:21:44.823  INFO metric key=frame.gpu.after_draw percentile_25=10.983µs percentile_50=11.623µs percentile_75=12.167µs max=338.175µs -May 16 20:21:44.823  INFO metric key=frame.cpu percentile_25=4.259839ms percentile_50=4.403199ms percentile_75=4.542463ms max=6.955007ms -May 16 20:21:44.823  INFO metric key=frame.cpu.voxels.draw percentile_25=27.855µs percentile_50=29.791µs percentile_75=33.247µs max=65.791µs From d68fcfa65418a03c9c5b93e8782d760f9222c11d Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 7 Aug 2022 12:16:37 -0700 Subject: [PATCH 14/17] trivial suggested changes --- client/src/sim.rs | 12 +-- common/src/collision.rs | 169 +++++++++++++++++----------------------- common/src/dodeca.rs | 17 ++-- common/src/force.rs | 25 +++--- common/src/worldgen.rs | 13 +--- 5 files changed, 97 insertions(+), 139 deletions(-) diff --git a/client/src/sim.rs b/client/src/sim.rs index 620b2574..26e72d5b 100644 --- a/client/src/sim.rs +++ b/client/src/sim.rs @@ -61,7 +61,7 @@ impl Sim { world: hecs::World::new(), params: None, local_character: None, - character_velocity: na::Vector4::new(0_f32, 0_f32, 0_f32, 0_f32), + character_velocity: na::Vector4::zeros(), orientation: na::one(), step: None, @@ -368,25 +368,19 @@ impl Sim { character_position.local * math::origin(), ); - let down_temp = math::lorentz_normalize(&math::orthogonalize( + let down = -math::lorentz_normalize(&math::orthogonalize( down_info.surface().normal(), &character_v4f64, )); - for n in down_temp.iter() { - assert!(!n.is_nan(), "error, down direction is not a nunber"); - } let down_local = self.orientation.conjugate() * (character_position.local.try_inverse().unwrap() - * na::convert::<_, na::Vector4>(down_temp)) + * na::convert::<_, na::Vector4>(-down)) .xyz(); self.orientation *= na::UnitQuaternion::new( na::Vector3::z() * down_local.x * -3.0 * time.as_secs_f32(), ); - // the meaning of the surface plane varies based on which side of it you are - let down = -down_temp; - let height = down_info.surface().distance_to(&character_v4f64); let new_velocity = force_system.apply_forces( diff --git a/common/src/collision.rs b/common/src/collision.rs index 801f86a9..2ea65a13 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -6,7 +6,6 @@ use crate::{ use lazy_static::lazy_static; use std::fmt; -#[allow(dead_code)] /* The set of voxels that a collision body covers within a chunk */ @@ -32,10 +31,10 @@ pub struct BoundingBox { pub bounding_boxes: Vec, } -// from a node coordinate in an arbitrary node, returns the which chunk the point would reside in +// from a node coordinate in an arbitrary node, returns the chunk the point would reside in pub fn chunk_from_location(location: na::Vector4) -> Option { for v in Vertex::iter() { - let pos = (v.chunk_to_node().try_inverse().unwrap() * location).xyz(); + let pos = (v.node_to_chunk() * location).xyz(); if (pos.x >= 0_f64) && (pos.x <= 1_f64) && (pos.y >= 0_f64) @@ -147,7 +146,7 @@ impl BoundingBox { } // adds a sub-bounding box to a list if it exists - pub fn add_sub_bb(list: &mut Vec, result: Option) { + fn add_sub_bb(list: &mut Vec, result: Option) { if let Some(x) = result { list.push(x); } @@ -175,7 +174,7 @@ impl ChunkBoundingBox { dimension: u8, ) -> Option { let euclidean_position = { - let temp = chunk.chunk_to_node().try_inverse().unwrap() * translated_position; + let temp = chunk.node_to_chunk() * translated_position; temp.xyz() / temp[3] }; @@ -183,7 +182,7 @@ impl ChunkBoundingBox { let mut max_xyz = na::Vector3::::new(0_u32, 0_u32, 0_u32); // It's important to note that euclidean_position is measured as chunk lengths, and radius is measured in absolute units. - // By coicidence, an absolute unit is aproximately a chunk's diameter, and only because of that there is no unit conversion here. + // By coincidence, an absolute unit is approximately a chunk's diameter, and only because of that there is no unit conversion here. // verify at least one box corner is within the chunk if euclidean_position @@ -239,22 +238,22 @@ lazy_static! { // 'v' and 'vertex as usize' will have different values. #[allow(clippy::needless_range_loop)] - for v in 0..5 { - let vertex = incident_vertices[v]; + for vertex in incident_vertices { + // let vertex = incident_vertices[v]; let mut vertex_counts = [0; VERTEX_COUNT]; // count the number of times that vertices appear in all incident sides let sides_to_tally = vertex.canonical_sides(); - for i in 0..3 { - // five verticies per side - let vertices_to_count = sides_to_tally[i].vertices(); - for j in 0..5 { - vertex_counts[vertices_to_count[j] as usize] += 1; + for side_to_tally in sides_to_tally { + // five vertices per side + let vertices_to_count = side_to_tally.vertices(); + for vertex_to_count in vertices_to_count { + vertex_counts[vertex_to_count as usize] += 1; } } - // inceident corners as not perpendicular - for i in side.vertices().iter() {vertex_counts[*i as usize] = -1} + // incident corners are not perpendicular + for &c in side.vertices().iter() {vertex_counts[c as usize] = -1} for i in Vertex::iter() { if vertex_counts[i as usize] == 2 { @@ -294,20 +293,11 @@ mod tests { const CHUNK_SIZE: u8 = 12_u8; // might want to test with multiple values in the future. static CHUNK_SIZE_F: f64 = CHUNK_SIZE as f64; - // place a small bounding box near the center of the node. There should be exactly 20 chunks in contact. - #[test] - fn proper_chunks_20() { + fn proper_chunks_generic(x: f64, y: f64, z: f64, expected_chunks: usize) { let mut graph = Graph::new(); ensure_nearby(&mut graph, &Position::origin(), 4.0); - - let central_chunk = Vertex::A; // arbitrary vertex - let position = central_chunk.chunk_to_node() - * na::Vector4::new( - 0.4 / CHUNK_SIZE_F, - 0.4 / CHUNK_SIZE_F, - 0.4 / CHUNK_SIZE_F, - 1.0, - ); + let central_chunk = Vertex::B; // arbitrary vertex + let position = central_chunk.chunk_to_node() * na::Vector4::new(x, y, z, 1.0); let bb = BoundingBox::create_aabb( NodeId::ROOT, @@ -317,36 +307,44 @@ mod tests { CHUNK_SIZE, ); - assert_eq!(bb.bounding_boxes.len(), 20); + assert_eq!(bb.bounding_boxes.len(), expected_chunks); } - // place a small bounding box in the center of a chunk. There should be exactly 1 chunk in contact. + // place a small bounding box near the center of the node. There should be exactly 20 chunks in contact. #[test] - fn proper_chunks_1() { - let mut graph = Graph::new(); - ensure_nearby(&mut graph, &Position::origin(), 4.0); - - let central_chunk = Vertex::A; // arbitrary vertex - let position = central_chunk.chunk_to_node() * na::Vector4::new(0.5, 0.5, 0.5, 1.0); - - let bb = BoundingBox::create_aabb( - NodeId::ROOT, - position, - 1.0 / CHUNK_SIZE_F, - &graph, - CHUNK_SIZE, + fn proper_chunks_20() { + proper_chunks_generic( + 0.4 / CHUNK_SIZE_F, + 0.4 / CHUNK_SIZE_F, + 0.4 / CHUNK_SIZE_F, + 20, ); + } - assert_eq!(bb.bounding_boxes.len(), 1); + // place a small bounding box in the center of a chunk. There should be exactly 1 chunk in contact. + #[test] + fn proper_chunks_1() { + proper_chunks_generic(0.5, 0.5, 0.5, 1) } // place a small bounding box next to a dodecaherdral vertex. There should be exactly 8 chunks in contact. #[test] fn proper_chunks_8() { + proper_chunks_generic( + 1.0 - 0.4 / CHUNK_SIZE_F, + 1.0 - 0.4 / CHUNK_SIZE_F, + 1.0 - 0.4 / CHUNK_SIZE_F, + 8, + ); + } + + // place a small bounding box next to a dodecaherdral vertex. All chunks in contact should be of the same index + #[test] + fn proper_vertex_ids_corner() { let mut graph = Graph::new(); ensure_nearby(&mut graph, &Position::origin(), 4.0); - let central_chunk = Vertex::K; // arbitrary vertex + let central_chunk = Vertex::C; // arbitrary vertex let position = central_chunk.chunk_to_node() * na::Vector4::new( 1.0 - 0.4 / CHUNK_SIZE_F, @@ -363,55 +361,26 @@ mod tests { CHUNK_SIZE, ); - assert_eq!(bb.bounding_boxes.len(), 8); + for cbb in bb.bounding_boxes { + assert!(cbb.chunk == central_chunk); + } } // place a small bounding box on the center of a dodecaherdral face. There should be exactly 10 chunks in contact. #[test] fn proper_chunks_10() { - let mut graph = Graph::new(); - ensure_nearby(&mut graph, &Position::origin(), 4.0); - - let central_chunk = Vertex::D; // arbitrary vertex - let position = central_chunk.chunk_to_node() - * na::Vector4::new( - 1.0 - 0.5 / CHUNK_SIZE_F, - 0.25 / CHUNK_SIZE_F, - 0.25 / CHUNK_SIZE_F, - 1.0, - ); - - let bb = BoundingBox::create_aabb( - NodeId::ROOT, - position, - 2.0 / CHUNK_SIZE_F, - &graph, - CHUNK_SIZE, + proper_chunks_generic( + 1.0 - 0.5 / CHUNK_SIZE_F, + 0.25 / CHUNK_SIZE_F, + 0.25 / CHUNK_SIZE_F, + 10, ); - println!("{:?}", bb); - - assert_eq!(bb.bounding_boxes.len(), 10); } // place a small bounding box right between the center of a dodecaherdral face and the node center. There should be exactly 5 chunks in contact. #[test] fn proper_chunks_5() { - let mut graph = Graph::new(); - ensure_nearby(&mut graph, &Position::origin(), 4.0); - - let central_chunk = Vertex::E; // arbitrary vertex - let position = central_chunk.chunk_to_node() - * na::Vector4::new(0.4 / CHUNK_SIZE_F, 0.4 / CHUNK_SIZE_F, 0.5, 1.0); - - let bb = BoundingBox::create_aabb( - NodeId::ROOT, - position, - 2.0 / CHUNK_SIZE_F, - &graph, - CHUNK_SIZE, - ); - - assert_eq!(bb.bounding_boxes.len(), 5); + proper_chunks_generic(0.4 / CHUNK_SIZE_F, 0.4 / CHUNK_SIZE_F, 0.5, 5); } // place bounding boxes in a variety of places with a variety of sizes and make sure the amount of voxels contained within are roughly what you would @@ -450,17 +419,18 @@ mod tests { let bb = BoundingBox::create_aabb(NodeId::ROOT, position, radius, &graph, CHUNK_SIZE); - let mut actual_voxels = 0; - for _address in bb.every_voxel_address() { - actual_voxels += 1; - } + let actual_voxels = bb.every_voxel_address().count() as i32; println!( "actual_voxels for reasonable_voxel_count: {} vs {}(min), {}(expected), {}(max)", actual_voxels, minimum_expected_voxel_count, expected_voxel_count, maximum_expected_voxel_count ); - println!("{:?}", bb); - println!("x_f64 is {} radius is {}", x_f64, radius); + println!( + "\tSpans {} chunks; {} voxels per chunk", + bb.bounding_boxes.len(), + actual_voxels / (bb.bounding_boxes.len() as i32) + ); + println!("\tx_f64 is {} radius is {}", x_f64, radius); assert!(actual_voxels >= minimum_expected_voxel_count); assert!(actual_voxels <= maximum_expected_voxel_count); } @@ -470,18 +440,19 @@ mod tests { // ensures that chunk_from_location has expected behavior #[test] fn chunk_from_location_proper_chunk() { - let central_chunk = Vertex::F; // arbitrary vertex - let chunk_coords = na::Vector3::new(1_u32, 1_u32, 1_u32); - - let position = central_chunk.chunk_to_node() - * na::Vector4::new( - (chunk_coords[0] as f64) / CHUNK_SIZE_F, - (chunk_coords[1] as f64) / CHUNK_SIZE_F, - (chunk_coords[2] as f64) / CHUNK_SIZE_F, - 1.0, - ); + for central_chunk in Vertex::iter() { + let chunk_coords = na::Vector3::new(1_u32, 1_u32, 1_u32); + + let position = central_chunk.chunk_to_node() + * na::Vector4::new( + (chunk_coords[0] as f64) / CHUNK_SIZE_F, + (chunk_coords[1] as f64) / CHUNK_SIZE_F, + (chunk_coords[2] as f64) / CHUNK_SIZE_F, + 1.0, + ); - assert_eq!(central_chunk, chunk_from_location(position).unwrap()); + assert_eq!(central_chunk, chunk_from_location(position).unwrap()); + } } // places a bounding box at a certain [x,y,z], and verifies that the voxel at [x, y, z] is contained within the bounding box. diff --git a/common/src/dodeca.rs b/common/src/dodeca.rs index 25c237f8..27b6d026 100644 --- a/common/src/dodeca.rs +++ b/common/src/dodeca.rs @@ -43,6 +43,8 @@ impl Side { ADJACENT[self as usize][other as usize] } + /// All verticies incident on self + #[inline] pub fn vertices(self) -> [Vertex; 5] { SIDE_VERTICES[self as usize] } @@ -94,14 +96,6 @@ impl Vertex { .cloned() } - #[inline] - pub fn from_index(x: usize) -> Self { - use Vertex::*; - const VALUES: [Vertex; VERTEX_COUNT] = - [A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T]; - VALUES[x] - } - /// Vertex shared by three sides, if any #[inline] pub fn from_sides(a: Side, b: Side, c: Side) -> Option { @@ -149,6 +143,11 @@ impl Vertex { ]) * na::Matrix4::new_scaling(0.5) } + /// Transform from hyperbolic node space to euclidean chunk coordinates + pub fn node_to_chunk(self) -> na::Matrix4 { + self.chunk_to_node().try_inverse().unwrap() + } + /// Convenience method for `self.chunk_to_node().determinant() < 0`. pub fn parity(self) -> bool { CHUNK_TO_NODE_PARITY[self as usize] @@ -264,7 +263,7 @@ lazy_static! { for a in 0..5 { for b in 0..SIDE_COUNT { - out[b][a] = result_list[b][a]; // flipped some things + out[b][a] = result_list[b][a]; } } diff --git a/common/src/force.rs b/common/src/force.rs index bc1cf8aa..9b46c053 100644 --- a/common/src/force.rs +++ b/common/src/force.rs @@ -1,15 +1,17 @@ use serde::{Deserialize, Serialize}; -/* GravityMethod encodes different ways of creating gravity in Hyperbolic space +/** + * GravityMethod encodes different ways of creating gravity in Hyperbolic space + * + * PlanarConstant has gravity oriented in the "down" direction, and the force of gravity is + * constant with respect to height. + * + * PlanarDivergent has gravity oriented in the "down" direction, and the force of gravity respects + * the divergence theorem, which makes it proportial to the ratio of the area of the ground-plane + * to the area of the surface which is equidistant to the ground-plane at the indicated height. + * + * Zero disables gravity. + */ -PlanarConstant has gravity oriented in the "down" direction, and the force of gravity is -constant with repect to height. - -PlanarConstant has gravity oriented in the "down" direction, and the force of gravity respects -the divergence theorem, which makes it proportial to the ratio of the area of the ground-plane -to the area of the surface which is equidistant to the ground-plane at the indicated height. - -Zero disables gravity. -*/ #[derive(Debug, Deserialize, Serialize, Copy, Clone)] pub enum GravityMethod { PlanarConstant, @@ -25,6 +27,7 @@ pub struct ForceParams { } impl ForceParams { + /// returns the result of applying appropriate forces to "inital_velocity" over "time" pub fn apply_forces( &self, inital_velocity: &na::Vector4, @@ -35,7 +38,7 @@ impl ForceParams { ) -> na::Vector4 { let mut velocity = *inital_velocity; - // with current collsion detection tech, we want the player to slowly rise if they are in the ground + // with current collision detection tech, we want the player to slowly rise if they are in the ground if is_colliding { return down * -self.float_speed; } diff --git a/common/src/worldgen.rs b/common/src/worldgen.rs index ac233c16..8261acf4 100644 --- a/common/src/worldgen.rs +++ b/common/src/worldgen.rs @@ -75,7 +75,7 @@ impl NodeState { road_state: NodeStateRoad::ROOT, spice: 0, enviro: EnviroFactors { - max_elevation: -0.0, + max_elevation: 0.0, temperature: 0.0, rainfall: 0.0, blockiness: 0.0, @@ -129,19 +129,10 @@ impl NodeState { } } + /// Returns a Plane representing the ground relative to this Node. pub fn surface(&self) -> Plane { self.surface } - - // if the node lies above the ground-plane or not - pub fn is_sky(&self) -> bool { - match self.kind { - Land => false, - DeepLand => false, - Sky => true, - DeepSky => true, - } - } } struct VoxelCoords { From 8ddee5a12d6429e5801454f23d2890f7865b8867 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 10 Aug 2022 14:30:58 -0700 Subject: [PATCH 15/17] code review refactoring --- common/src/collision.rs | 115 +++++++--------------------------------- common/src/dodeca.rs | 70 ++++++++++++++++++++++++ common/src/graph.rs | 19 +++---- common/src/math.rs | 22 -------- 4 files changed, 96 insertions(+), 130 deletions(-) diff --git a/common/src/collision.rs b/common/src/collision.rs index 2ea65a13..82b29982 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -1,9 +1,5 @@ use crate::node::DualGraph; -use crate::{ - dodeca::{Side, Vertex, SIDE_COUNT, VERTEX_COUNT}, - graph::NodeId, -}; -use lazy_static::lazy_static; +use crate::{dodeca::Vertex, graph::NodeId}; use std::fmt; /* @@ -31,24 +27,20 @@ pub struct BoundingBox { pub bounding_boxes: Vec, } -// from a node coordinate in an arbitrary node, returns the chunk the point would reside in -pub fn chunk_from_location(location: na::Vector4) -> Option { +/// from a node coordinate in an arbitrary node, returns the chunk the point would reside in. +// Does not make any attempt to verify the location is within the node in question. +// guaranteed to return something if this precondition is met. +fn chunk_from_location(location: na::Vector4) -> Vertex { + let epsilon = 0.001; for v in Vertex::iter() { let pos = (v.node_to_chunk() * location).xyz(); - if (pos.x >= 0_f64) - && (pos.x <= 1_f64) - && (pos.y >= 0_f64) - && (pos.y <= 1_f64) - && (pos.z >= 0_f64) - && (pos.z <= 1_f64) - { - return Some(v); + if (pos.x >= -epsilon) && (pos.y >= -epsilon) && (pos.z >= -epsilon) { + return v; } } - None + panic!(); } -// Does not figure out which chunk it is in automatically // The pattern I use to unwrap the results of neighbor is kind of awkward. impl BoundingBox { pub fn create_aabb( @@ -63,8 +55,7 @@ impl BoundingBox { "Error: the radius of a bounding box may not exceed 1 absolute unit." ); - let start_chunk = - chunk_from_location(position).expect("Error: cannot find chunk for given position."); + let start_chunk = chunk_from_location(position); let mut bounding_boxes = Vec::::new(); let sides = start_chunk.canonical_sides(); @@ -79,8 +70,8 @@ impl BoundingBox { } // get BBs for foreign chunks sharing a dodeca side - for side in sides.iter() { - if let Some(node) = graph.neighbor(start_node, *side) { + for &side in sides.iter() { + if let Some(node) = graph.neighbor(start_node, side) { let translated_position = side.reflection() * position; for v in side.vertices().iter() { Self::add_sub_bb( @@ -97,13 +88,13 @@ impl BoundingBox { } } - if let Some(opposite_node) = graph.opposing_node(start_node, sides) { + if let Some(opposite_node) = graph.opposing_node(start_node, start_chunk) { let opposite_position = sides[0].reflection() * sides[1].reflection() * sides[2].reflection() * position; // get BBs for the chunks sharing an edge - for side in sides.iter() { - let node = graph.neighbor(opposite_node, *side); + for &side in sides.iter() { + let node = graph.neighbor(opposite_node, side); let translated_position = side.reflection() * opposite_position; // the chunk next to the origin chunk Self::add_sub_bb( @@ -121,7 +112,7 @@ impl BoundingBox { &mut bounding_boxes, ChunkBoundingBox::get_chunk_bounding_box( node.unwrap(), - PERPENDICULAR_VERTEX[*side as usize][start_chunk as usize].unwrap(), + side.perpendicular_vertex(start_chunk).unwrap(), translated_position, radius, dimension, @@ -129,7 +120,7 @@ impl BoundingBox { ); } - // get BB for chunk sharing only a vertex. + // get BB for the chunk sharing only a vertex. Self::add_sub_bb( &mut bounding_boxes, ChunkBoundingBox::get_chunk_bounding_box( @@ -226,47 +217,6 @@ impl ChunkBoundingBox { } } -lazy_static! { - /// given a side s and a vertex v, returns a vertex that is adjacent to v but not - /// incident on s, if such a vertex exists - // I can't decide if this should be here or dodeca.rs - static ref PERPENDICULAR_VERTEX: [[Option; VERTEX_COUNT]; SIDE_COUNT] = { - let mut result = [[None; VERTEX_COUNT]; SIDE_COUNT]; - - for side in Side::iter() { - let incident_vertices = side.vertices(); - - // 'v' and 'vertex as usize' will have different values. - #[allow(clippy::needless_range_loop)] - for vertex in incident_vertices { - // let vertex = incident_vertices[v]; - let mut vertex_counts = [0; VERTEX_COUNT]; - - // count the number of times that vertices appear in all incident sides - let sides_to_tally = vertex.canonical_sides(); - for side_to_tally in sides_to_tally { - // five vertices per side - let vertices_to_count = side_to_tally.vertices(); - for vertex_to_count in vertices_to_count { - vertex_counts[vertex_to_count as usize] += 1; - } - } - - // incident corners are not perpendicular - for &c in side.vertices().iter() {vertex_counts[c as usize] = -1} - - for i in Vertex::iter() { - if vertex_counts[i as usize] == 2 { - result[side as usize][vertex as usize] = Some(i); - break; - } - } - } - } - result - }; -} - impl fmt::Debug for BoundingBox { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Bounding Box") @@ -302,7 +252,7 @@ mod tests { let bb = BoundingBox::create_aabb( NodeId::ROOT, position, - 2.0 / CHUNK_SIZE_F, + 1.6 / CHUNK_SIZE_F, &graph, CHUNK_SIZE, ); @@ -451,7 +401,7 @@ mod tests { 1.0, ); - assert_eq!(central_chunk, chunk_from_location(position).unwrap()); + assert_eq!(central_chunk, chunk_from_location(position)); } } @@ -529,31 +479,4 @@ mod tests { } panic!(); } - - #[test] - fn perpendicular_vertex_is_complete() { - //print!("{:?}", PERPENDICULAR_VERTEX); - let mut error_count = 0_i32; - - for s in 0..SIDE_COUNT { - let side = Side::from_index(s); - let incident_vertices = side.vertices(); - - // 'v' and 'vertex as usize' will have different values. - #[allow(clippy::needless_range_loop)] - for v in 0..5 { - let vertex = incident_vertices[v]; - println!("side of {:?} and vertex of {:?}", side, vertex); // not helpful, but I don't want to mess with the formatter. - let result = PERPENDICULAR_VERTEX[s][vertex as usize]; - if result.is_some() { - println!("\tresults in {:?}", result.unwrap()); - } else { - println!("\tIs not thought to exist."); - error_count += 1; - } - } - } - - assert!(error_count == 0_i32); - } } diff --git a/common/src/dodeca.rs b/common/src/dodeca.rs index 27b6d026..f0160ec8 100644 --- a/common/src/dodeca.rs +++ b/common/src/dodeca.rs @@ -61,6 +61,12 @@ impl Side { let r = na::convert::<_, na::RowVector4>(self.reflection().row(3).clone_owned()); (r * p).x < p.w } + + /// given a vertex v, returns a vertex that is adjacent to v but not incident on self, if such a vertex exists + #[inline] + pub fn perpendicular_vertex(self, v: Vertex) -> Option { + PERPENDICULAR_VERTEX[self as usize][v as usize] + } } /// Vertices of a right dodecahedron @@ -280,6 +286,45 @@ lazy_static! { result }; + + /// given a side s and a vertex v, returns a vertex that is adjacent to v but not + /// incident on s, if such a vertex exists + static ref PERPENDICULAR_VERTEX: [[Option; VERTEX_COUNT]; SIDE_COUNT] = { + let mut result = [[None; VERTEX_COUNT]; SIDE_COUNT]; + + for side in Side::iter() { + let incident_vertices = side.vertices(); + + // 'v' and 'vertex as usize' will have different values. + #[allow(clippy::needless_range_loop)] + for vertex in incident_vertices { + // let vertex = incident_vertices[v]; + let mut vertex_counts = [0; VERTEX_COUNT]; + + // count the number of times that vertices appear in all incident sides + let sides_to_tally = vertex.canonical_sides(); + for side_to_tally in sides_to_tally { + // five vertices per side + let vertices_to_count = side_to_tally.vertices(); + for vertex_to_count in vertices_to_count { + vertex_counts[vertex_to_count as usize] += 1; + } + } + + // incident corners are not perpendicular + for &c in side.vertices().iter() {vertex_counts[c as usize] = -1} + + for i in Vertex::iter() { + if vertex_counts[i as usize] == 2 { + result[side as usize][vertex as usize] = Some(i); + break; + } + } + } + } + result + }; + } #[cfg(test)] @@ -338,4 +383,29 @@ mod tests { epsilon = 1e-10 ); } + + #[test] + fn perpendicular_vertex_is_complete() { + let mut error_count = 0_i32; + + for s in 0..SIDE_COUNT { + let side = Side::from_index(s); + let incident_vertices = side.vertices(); + + #[allow(clippy::needless_range_loop)] + for v in 0..5 { + let vertex = incident_vertices[v]; + println!("side of {:?} and vertex of {:?}", side, vertex); + let result = side.perpendicular_vertex(vertex); + if result.is_some() { + println!("\tresults in {:?}", result.unwrap()); + } else { + println!("\tIs not thought to exist."); + error_count += 1; + } + } + } + + assert!(error_count == 0_i32); + } } diff --git a/common/src/graph.rs b/common/src/graph.rs index 89a6b0df..c2f0177b 100644 --- a/common/src/graph.rs +++ b/common/src/graph.rs @@ -199,18 +199,13 @@ impl Graph { id } - /// find the node opposite "node" along "sides". - // This can be improved to return None in less situations - pub fn opposing_node(&self, node: NodeId, sides: [Side; 3]) -> Option { - if let Some(n1) = self.neighbor(node, sides[0]) { - if let Some(n2) = self.neighbor(n1, sides[1]) { - self.neighbor(n2, sides[2]) - } else { - None - } - } else { - None - } + /// find the node opposite "node" along "vertex" + pub fn opposing_node(&self, node: NodeId, vertex: Vertex) -> Option { + let sides = vertex.canonical_sides(); + self.neighbor( + self.neighbor(self.neighbor(node, sides[0])?, sides[1])?, + sides[2], + ) } /// Ensure all shorter neighbors of a not-yet-created child node exist and return them, excluding the given parent node diff --git a/common/src/math.rs b/common/src/math.rs index 543439a4..3690b9a0 100644 --- a/common/src/math.rs +++ b/common/src/math.rs @@ -105,21 +105,6 @@ pub fn renormalize_isometry(m: &na::Matrix4) -> na::Matrix4 translate_along(&direction, boost_length) * rotation.to_homogeneous() } -/// returns the result of translating normal vector v0 from p0 to p1 -pub fn translate_normal( - p0: na::Vector4, - p1: na::Vector4, - v0: na::Vector4, -) -> na::Vector4 { - let d = distance(&p0, &p1); - // TODO: verfiy this is equvilent to !(d >= na::zero()) - if na::zero::() > d { - return v0; - } - - v0 * d.cosh() + p1 * d.sinh() -} - /// normalizes vector v with repect to translation matrix t pub fn normalize_vector(t: na::Matrix4, v: na::Vector4) -> na::Vector4 { let p = t * origin(); @@ -297,11 +282,4 @@ mod tests { assert_abs_diff_eq!(mip(&vec2, &orth), 0.0, epsilon = 1e-5); } - - #[test] - fn translate_normal_identity() { - let p = na::Vector4::new(-0.03635, 0.95129, 0.0, 1.38068); - let norm = na::Vector4::new(1.0, 0.0, 0.0, 0.0); - assert_abs_diff_eq!(translate_normal(p, p, norm), norm, epsilon = 1e-5); - } } From ec9abf5a2e1f34f71afdda957f64611ce29c5f87 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 15 Aug 2022 11:15:53 -0700 Subject: [PATCH 16/17] Recalibrate reasonable voxel count test --- common/src/collision.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/collision.rs b/common/src/collision.rs index 82b29982..da88548c 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -337,7 +337,7 @@ mod tests { // expect in a euclidean bounding box of the same radius. #[test] fn reasonable_voxel_count() { - let margin_of_error = 3.0; // three times more voxels than what would be expected. + let margin_of_error = 1.5; // 1.5 times more voxels than what would be expected. let radi_to_test = CHUNK_SIZE; // higher number means more test precision. Try to keep it a divisor of CHUNK_SIZE_F. let mut graph = Graph::new(); @@ -352,10 +352,10 @@ mod tests { // Getting the correct estimation for the number of voxels can be tricky let expected_voxel_count = (radius * 2.0 * CHUNK_SIZE_F).powf(3.0); // value to display let minimum_expected_voxel_count = - (((radius * 2.0 * CHUNK_SIZE_F) - 1_f64).powf(3.0) / margin_of_error).floor() + ((((radius * CHUNK_SIZE_F) - 1_f64).powf(3.0) / margin_of_error).floor() * 8_f64 ) as i32; let maximum_expected_voxel_count = - (((radius * 2.0 * CHUNK_SIZE_F) + 1_f64).powf(3.0) * margin_of_error).ceil() + ((((radius * CHUNK_SIZE_F) + 1_f64).powf(3.0) * margin_of_error).ceil() * 20_f64) as i32; let position = central_chunk.chunk_to_node() From 4405b9cb17473d691120780e38579a288d2a9e7b Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 26 Aug 2022 14:29:26 -0700 Subject: [PATCH 17/17] more trival corrections --- common/src/collision.rs | 16 ++++++++-------- common/src/dodeca.rs | 10 +++------- common/src/math.rs | 2 +- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/common/src/collision.rs b/common/src/collision.rs index da88548c..c23a084b 100644 --- a/common/src/collision.rs +++ b/common/src/collision.rs @@ -5,7 +5,7 @@ use std::fmt; /* The set of voxels that a collision body covers within a chunk */ -#[derive(PartialEq, Clone, Copy)] +#[derive(Clone, Copy)] pub struct ChunkBoundingBox { pub node: NodeId, pub chunk: Vertex, @@ -28,8 +28,8 @@ pub struct BoundingBox { } /// from a node coordinate in an arbitrary node, returns the chunk the point would reside in. -// Does not make any attempt to verify the location is within the node in question. -// guaranteed to return something if this precondition is met. +/// Does not make any attempt to verify the location is within the node in question. +/// guaranteed to return something if this precondition is met. fn chunk_from_location(location: na::Vector4) -> Vertex { let epsilon = 0.001; for v in Vertex::iter() { @@ -125,7 +125,7 @@ impl BoundingBox { &mut bounding_boxes, ChunkBoundingBox::get_chunk_bounding_box( opposite_node, - Vertex::from_sides(sides[0], sides[1], sides[2]).unwrap(), + start_chunk, opposite_position, radius, dimension, @@ -352,11 +352,11 @@ mod tests { // Getting the correct estimation for the number of voxels can be tricky let expected_voxel_count = (radius * 2.0 * CHUNK_SIZE_F).powf(3.0); // value to display let minimum_expected_voxel_count = - ((((radius * CHUNK_SIZE_F) - 1_f64).powf(3.0) / margin_of_error).floor() * 8_f64 ) - as i32; + ((((radius * CHUNK_SIZE_F) - 1_f64).powf(3.0) / margin_of_error).floor() + * 8_f64) as i32; let maximum_expected_voxel_count = - ((((radius * CHUNK_SIZE_F) + 1_f64).powf(3.0) * margin_of_error).ceil() * 20_f64) - as i32; + ((((radius * CHUNK_SIZE_F) + 1_f64).powf(3.0) * margin_of_error).ceil() + * 20_f64) as i32; let position = central_chunk.chunk_to_node() * na::Vector4::new( diff --git a/common/src/dodeca.rs b/common/src/dodeca.rs index f0160ec8..ebcae784 100644 --- a/common/src/dodeca.rs +++ b/common/src/dodeca.rs @@ -43,7 +43,7 @@ impl Side { ADJACENT[self as usize][other as usize] } - /// All verticies incident on self + /// All vertices incident on self #[inline] pub fn vertices(self) -> [Vertex; 5] { SIDE_VERTICES[self as usize] @@ -255,7 +255,7 @@ lazy_static! { result }; - /// The 5 vertices incedent on a side + /// The 5 vertices incident on a side static ref SIDE_VERTICES: [[Vertex; 5]; SIDE_COUNT] = { let mut result_list = vec![Vec::new(); SIDE_COUNT]; @@ -295,8 +295,6 @@ lazy_static! { for side in Side::iter() { let incident_vertices = side.vertices(); - // 'v' and 'vertex as usize' will have different values. - #[allow(clippy::needless_range_loop)] for vertex in incident_vertices { // let vertex = incident_vertices[v]; let mut vertex_counts = [0; VERTEX_COUNT]; @@ -392,9 +390,7 @@ mod tests { let side = Side::from_index(s); let incident_vertices = side.vertices(); - #[allow(clippy::needless_range_loop)] - for v in 0..5 { - let vertex = incident_vertices[v]; + for vertex in incident_vertices { println!("side of {:?} and vertex of {:?}", side, vertex); let result = side.perpendicular_vertex(vertex); if result.is_some() { diff --git a/common/src/math.rs b/common/src/math.rs index 3690b9a0..7dcd80b9 100644 --- a/common/src/math.rs +++ b/common/src/math.rs @@ -105,7 +105,7 @@ pub fn renormalize_isometry(m: &na::Matrix4) -> na::Matrix4 translate_along(&direction, boost_length) * rotation.to_homogeneous() } -/// normalizes vector v with repect to translation matrix t +/// normalizes vector v with respect to translation matrix t pub fn normalize_vector(t: na::Matrix4, v: na::Vector4) -> na::Vector4 { let p = t * origin(); let m = mip(&v, &v);