diff --git a/client/src/graphics/draw.rs b/client/src/graphics/draw.rs index 3dd45155..2039794b 100644 --- a/client/src/graphics/draw.rs +++ b/client/src/graphics/draw.rs @@ -9,7 +9,7 @@ use metrics::histogram; use super::{fog, voxels, Base, Fog, Frustum, GltfScene, Meshes, Voxels}; use crate::{Asset, Config, Loader, Sim}; use common::proto::{Character, Position}; -use common::{math, SimConfig}; +use common::SimConfig; /// Manages rendering, independent of what is being rendered to pub struct Draw { @@ -289,7 +289,7 @@ impl Draw { let draw_started = Instant::now(); let view = sim.as_ref().map_or_else(Position::origin, |sim| sim.view()); let projection = frustum.projection(1.0e-4); - let view_projection = projection.matrix() * math::mtranspose(&view.local); + let view_projection = projection.matrix() * na::Matrix4::from(view.local.mtranspose()); self.loader.drive(); let device = &*self.gfx.device; @@ -482,8 +482,7 @@ impl Draw { .expect("positionless entity in graph"); if let Some(character_model) = self.loader.get(self.character_model) { if let Ok(ch) = sim.world.get::<&Character>(entity) { - let transform = transform - * pos.local + let transform = na::Matrix4::from(transform * pos.local) * na::Matrix4::new_scaling(sim.cfg().meters_to_absolute) * ch.state.orientation.to_homogeneous(); for mesh in &character_model.0 { diff --git a/client/src/graphics/frustum.rs b/client/src/graphics/frustum.rs index f5f8a175..0ae3a11c 100644 --- a/client/src/graphics/frustum.rs +++ b/client/src/graphics/frustum.rs @@ -1,3 +1,4 @@ +use common::math::MVector; use common::Plane; #[derive(Debug, Copy, Clone)] @@ -79,7 +80,7 @@ pub struct FrustumPlanes { } impl FrustumPlanes { - pub fn contain(&self, point: &na::Vector4, radius: f32) -> bool { + pub fn contain(&self, point: &MVector, radius: f32) -> bool { for &plane in &[&self.left, &self.right, &self.down, &self.up] { if plane.distance_to(point) < -radius { return false; @@ -92,20 +93,38 @@ impl FrustumPlanes { #[cfg(test)] mod tests { use super::*; - use common::math::{origin, translate_along}; + use common::math::translate_along; use std::f32; #[test] fn planes_sanity() { // 90 degree square let planes = Frustum::from_vfov(f32::consts::FRAC_PI_4, 1.0).planes(); - assert!(planes.contain(&origin(), 0.1)); - assert!(planes.contain(&(translate_along(&-na::Vector3::z()) * origin()), 0.0)); - assert!(!planes.contain(&(translate_along(&na::Vector3::z()) * origin()), 0.0)); + assert!(planes.contain(&MVector::origin(), 0.1)); + assert!(planes.contain( + &(translate_along(&-na::Vector3::z()) * MVector::origin()), + 0.0 + )); + assert!(!planes.contain( + &(translate_along(&na::Vector3::z()) * MVector::origin()), + 0.0 + )); - assert!(!planes.contain(&(translate_along(&na::Vector3::x()) * origin()), 0.0)); - assert!(!planes.contain(&(translate_along(&na::Vector3::y()) * origin()), 0.0)); - assert!(!planes.contain(&(translate_along(&-na::Vector3::x()) * origin()), 0.0)); - assert!(!planes.contain(&(translate_along(&-na::Vector3::y()) * origin()), 0.0)); + assert!(!planes.contain( + &(translate_along(&na::Vector3::x()) * MVector::origin()), + 0.0 + )); + assert!(!planes.contain( + &(translate_along(&na::Vector3::y()) * MVector::origin()), + 0.0 + )); + assert!(!planes.contain( + &(translate_along(&-na::Vector3::x()) * MVector::origin()), + 0.0 + )); + assert!(!planes.contain( + &(translate_along(&-na::Vector3::y()) * MVector::origin()), + 0.0 + )); } } diff --git a/client/src/graphics/voxels/mod.rs b/client/src/graphics/voxels/mod.rs index 747e8e60..dfff5274 100644 --- a/client/src/graphics/voxels/mod.rs +++ b/client/src/graphics/voxels/mod.rs @@ -20,7 +20,7 @@ use common::{ dodeca::Vertex, graph::NodeId, lru_slab::SlotId, - math, + math::{MIsometry, MVector}, node::{Chunk, ChunkId, VoxelData}, LruSlab, }; @@ -88,7 +88,7 @@ impl Voxels { device: &Device, frame: &mut Frame, sim: &mut Sim, - nearby_nodes: &[(NodeId, na::Matrix4)], + nearby_nodes: &[(NodeId, MIsometry)], cmd: vk::CommandBuffer, frustum: &Frustum, ) { @@ -122,12 +122,12 @@ impl Voxels { } let node_scan_started = Instant::now(); let frustum_planes = frustum.planes(); - let local_to_view = math::mtranspose(&view.local); + let local_to_view = view.local.mtranspose(); let mut extractions = Vec::new(); let mut workqueue_is_full = false; for &(node, ref node_transform) in nearby_nodes { - let node_to_view = local_to_view * node_transform; - let origin = node_to_view * math::origin(); + let node_to_view = local_to_view * *node_transform; + let origin = node_to_view * MVector::origin(); if !frustum_planes.contain(&origin, dodeca::BOUNDING_SPHERE_RADIUS) { // Don't bother generating or drawing chunks from nodes that are wholly outside the // frustum. @@ -174,7 +174,7 @@ impl Voxels { frame.drawn.push(slot); // Transfer transform frame.surface.transforms_mut()[slot.0 as usize] = - node_transform * vertex.chunk_to_node(); + na::Matrix4::from(*node_transform) * vertex.chunk_to_node(); } if let (None, &VoxelData::Dense(ref data)) = (&surface, voxels) { // Extract a surface so it can be drawn in future frames diff --git a/client/src/local_character_controller.rs b/client/src/local_character_controller.rs index a4197d7a..7929e9dd 100644 --- a/client/src/local_character_controller.rs +++ b/client/src/local_character_controller.rs @@ -1,4 +1,4 @@ -use common::{math, proto::Position}; +use common::{math, math::MIsometry, proto::Position}; pub struct LocalCharacterController { /// The last extrapolated inter-frame view position, used for rendering and gravity-specific @@ -25,7 +25,8 @@ impl LocalCharacterController { pub fn oriented_position(&self) -> Position { Position { node: self.position.node, - local: self.position.local * self.orientation.to_homogeneous(), + local: self.position.local + * MIsometry::unit_quaternion_to_homogeneous(self.orientation), } } diff --git a/client/src/prediction.rs b/client/src/prediction.rs index 5d9c5ab7..29383442 100644 --- a/client/src/prediction.rs +++ b/client/src/prediction.rs @@ -101,12 +101,13 @@ impl PredictedMotion { #[cfg(test)] mod tests { use super::*; + use common::math::MIsometry; /// An arbitrary position fn pos() -> Position { Position { node: common::graph::NodeId::ROOT, - local: na::one(), + local: MIsometry::identity(), } } diff --git a/client/src/sim.rs b/client/src/sim.rs index 983e818e..3a0bcd26 100644 --- a/client/src/sim.rs +++ b/client/src/sim.rs @@ -12,7 +12,8 @@ use common::{ character_controller, collision_math::Ray, graph::{Graph, NodeId}, - graph_ray_casting, + graph_ray_casting, math, + math::{MIsometry, MVector}, node::{populate_fresh_nodes, ChunkId, VoxelData}, proto::{ self, BlockUpdate, Character, CharacterInput, CharacterState, Command, Component, @@ -109,7 +110,7 @@ impl Sim { selected_material: Material::WoodPlanks, prediction: PredictedMotion::new(proto::Position { node: NodeId::ROOT, - local: na::one(), + local: MIsometry::identity(), }), local_character_controller: LocalCharacterController::new(), } @@ -510,9 +511,8 @@ impl Sim { pub fn view(&self) -> Position { let mut pos = self.local_character_controller.oriented_position(); let up = self.graph.get_relative_up(&pos).unwrap(); - pos.local *= common::math::translate_along( - &(up.as_ref() * (self.cfg.character.character_radius - 1e-3)), - ); + pos.local *= + math::translate_along(&(up.as_ref() * (self.cfg.character.character_radius - 1e-3))); pos } @@ -550,7 +550,7 @@ impl Sim { let ray_casing_result = graph_ray_casting::ray_cast( &self.graph, &view_position, - &Ray::new(na::Vector4::w(), -na::Vector4::z()), + &Ray::new(MVector::w(), -MVector::z()), self.cfg.character.block_reach, ); diff --git a/common/src/character_controller/collision.rs b/common/src/character_controller/collision.rs index 5d0eec0b..56ed53c1 100644 --- a/common/src/character_controller/collision.rs +++ b/common/src/character_controller/collision.rs @@ -2,7 +2,13 @@ use tracing::error; -use crate::{collision_math::Ray, graph::Graph, graph_collision, math, proto::Position}; +use crate::{ + collision_math::Ray, + graph::Graph, + graph_collision, math, + math::{MIsometry, MVector}, + proto::Position, +}; /// Checks for collisions when a character moves with a character-relative displacement vector of `relative_displacement`. pub fn check_collision( @@ -22,7 +28,10 @@ pub fn check_collision( let displacement_norm = displacement_sqr.sqrt(); let displacement_normalized = relative_displacement / displacement_norm; - let ray = Ray::new(math::origin(), displacement_normalized); + let ray = Ray::new( + MVector::origin(), + MVector::::from(displacement_normalized), + ); let tanh_distance = displacement_norm.tanh(); let cast_hit = graph_collision::sphere_cast( @@ -58,7 +67,7 @@ pub fn check_collision( // This normal now represents a contact point at the origin, so we omit the w-coordinate // to ensure that it's orthogonal to the origin. normal: na::UnitVector3::new_normalize( - (math::mtranspose(&displacement_transform) * hit.normal).xyz(), + (displacement_transform.mtranspose() * hit.normal).xyz(), ), }), } @@ -77,7 +86,7 @@ pub struct CollisionCheckingResult { /// Multiplying the character's position by this matrix will move the character as far as it can up to its intended /// displacement until it hits the wall. - pub displacement_transform: na::Matrix4, + pub displacement_transform: MIsometry, pub collision: Option, } @@ -88,7 +97,7 @@ impl CollisionCheckingResult { pub fn stationary() -> CollisionCheckingResult { CollisionCheckingResult { displacement_vector: na::Vector3::zeros(), - displacement_transform: na::Matrix4::identity(), + displacement_transform: MIsometry::identity(), collision: None, } } diff --git a/common/src/character_controller/mod.rs b/common/src/character_controller/mod.rs index 9b56098f..12ac56d0 100644 --- a/common/src/character_controller/mod.rs +++ b/common/src/character_controller/mod.rs @@ -47,7 +47,7 @@ pub fn run_character_step( } // Renormalize - position.local = math::renormalize_isometry(&position.local); + position.local = position.local.renormalize_isometry(); let (next_node, transition_xf) = graph.normalize_transform(position.node, &position.local); if next_node != position.node { position.node = next_node; diff --git a/common/src/chunk_collision.rs b/common/src/chunk_collision.rs index 3a7f3a0c..548f263c 100644 --- a/common/src/chunk_collision.rs +++ b/common/src/chunk_collision.rs @@ -1,6 +1,7 @@ use crate::{ collision_math::Ray, math, + math::MVector, node::{ChunkLayout, VoxelAABB, VoxelData}, voxel_math::Coords, world::Material, @@ -12,7 +13,7 @@ pub struct ChunkCastHit { /// Represents the normal vector of the hit surface in the dual coordinate system of the chunk. /// To get the actual normal vector, project it so that it is orthogonal to the endpoint in Lorentz space. - pub normal: na::Vector4, + pub normal: MVector, } /// Performs sphere casting (swept collision query) against the voxels in the chunk with the given `voxel_data` @@ -90,10 +91,8 @@ fn find_face_collision( for t in bounding_box.grid_planes(t_axis) { // Find a normal to the grid plane. Note that (t, 0, 0, x) is a normal of the plane whose closest point // to the origin is (x, 0, 0, t), and we use that fact here. - let normal = math::lorentz_normalize(&math::tuv_to_xyz( - t_axis, - na::Vector4::new(1.0, 0.0, 0.0, layout.grid_to_dual(t)), - )); + let normal = math::tuv_to_xyz(t_axis, MVector::new(1.0, 0.0, 0.0, layout.grid_to_dual(t))) + .lorentz_normalize(); let Some(new_tanh_distance) = ray.solve_sphere_plane_intersection(&normal, collider_radius.sinh()) @@ -109,7 +108,7 @@ fn find_face_collision( // Which side we approach the plane from affects which voxel we want to use for hit detection. // If exiting a chunk via a chunk boundary, hit detection is handled by a different chunk. // We also want to adjust the normal vector to always face outward from the hit block - let (normal, voxel_t) = if math::mip(&ray.direction, &normal) < 0.0 { + let (normal, voxel_t) = if ray.direction.mip(&normal) < 0.0 { if t == 0 { continue; } @@ -122,7 +121,7 @@ fn find_face_collision( }; let ray_endpoint = ray.ray_point(new_tanh_distance); - let contact_point = ray_endpoint - normal * math::mip(&ray_endpoint, &normal); + let contact_point = ray_endpoint - normal * ray_endpoint.mip(&normal); // Compute the u and v-coordinates of the voxels at the contact point let Some(voxel_u) = layout.dual_to_voxel(contact_point[u_axis] / contact_point.w) else { @@ -169,18 +168,14 @@ fn find_edge_collision( // Loop through all grid lines overlapping the bounding box for (u, v) in bounding_box.grid_lines(u_axis, v_axis) { // Compute vectors Lorentz-orthogonal to the edge and to each other - let edge_normal0 = math::lorentz_normalize(&math::tuv_to_xyz( - t_axis, - na::Vector4::new(0.0, 1.0, 0.0, layout.grid_to_dual(u)), - )); + let edge_normal0 = + math::tuv_to_xyz(t_axis, MVector::new(0.0, 1.0, 0.0, layout.grid_to_dual(u))) + .lorentz_normalize(); - let edge_normal1 = math::tuv_to_xyz( - t_axis, - na::Vector4::new(0.0, 0.0, 1.0, layout.grid_to_dual(v)), - ); - let edge_normal1 = math::lorentz_normalize( - &(edge_normal1 - edge_normal0 * math::mip(&edge_normal0, &edge_normal1)), - ); + let edge_normal1 = + math::tuv_to_xyz(t_axis, MVector::new(0.0, 0.0, 1.0, layout.grid_to_dual(v))); + let edge_normal1 = + (edge_normal1 - edge_normal0 * edge_normal0.mip(&edge_normal1)).lorentz_normalize(); let Some(new_tanh_distance) = ray.solve_sphere_line_intersection( &edge_normal0, @@ -197,8 +192,8 @@ fn find_edge_collision( let ray_endpoint = ray.ray_point(new_tanh_distance); let contact_point = ray_endpoint - - edge_normal0 * math::mip(&ray_endpoint, &edge_normal0) - - edge_normal1 * math::mip(&ray_endpoint, &edge_normal1); + - edge_normal0 * ray_endpoint.mip(&edge_normal0) + - edge_normal1 * ray_endpoint.mip(&edge_normal1); // Compute the t-coordinate of the voxels at the contact point let Some(voxel_t) = layout.dual_to_voxel(contact_point[t_axis] / contact_point.w) else { @@ -254,19 +249,18 @@ fn find_vertex_collision( // Compute vectors Lorentz-orthogonal to the vertex and to each other let vertex_normal0 = - math::lorentz_normalize(&na::Vector4::new(1.0, 0.0, 0.0, layout.grid_to_dual(x))); + MVector::new(1.0, 0.0, 0.0, layout.grid_to_dual(x)).lorentz_normalize(); - let vertex_normal1 = na::Vector4::new(0.0, 1.0, 0.0, layout.grid_to_dual(y)); - let vertex_normal1 = math::lorentz_normalize( - &(vertex_normal1 - vertex_normal0 * math::mip(&vertex_normal0, &vertex_normal1)), - ); + let vertex_normal1 = MVector::new(0.0, 1.0, 0.0, layout.grid_to_dual(y)); + let vertex_normal1 = (vertex_normal1 + - vertex_normal0 * vertex_normal0.mip(&vertex_normal1)) + .lorentz_normalize(); - let vertex_normal2 = na::Vector4::new(0.0, 0.0, 1.0, layout.grid_to_dual(z)); - let vertex_normal2 = math::lorentz_normalize( - &(vertex_normal2 - - vertex_normal0 * math::mip(&vertex_normal0, &vertex_normal2) - - vertex_normal1 * math::mip(&vertex_normal1, &vertex_normal2)), - ); + let vertex_normal2 = MVector::new(0.0, 0.0, 1.0, layout.grid_to_dual(z)); + let vertex_normal2 = (vertex_normal2 + - vertex_normal0 * vertex_normal0.mip(&vertex_normal2) + - vertex_normal1 * vertex_normal1.mip(&vertex_normal2)) + .lorentz_normalize(); let Some(new_tanh_distance) = ray.solve_sphere_point_intersection( &vertex_normal0, @@ -283,12 +277,13 @@ fn find_vertex_collision( } // Determine the cube-centric coordinates of the vertex - let vertex_position = math::lorentz_normalize(&na::Vector4::new( + let vertex_position = MVector::new( layout.grid_to_dual(x), layout.grid_to_dual(y), layout.grid_to_dual(z), 1.0, - )); + ) + .lorentz_normalize(); // A collision was found. Update the hit. let ray_endpoint = ray.ray_point(new_tanh_distance); @@ -356,29 +351,29 @@ mod tests { ray_end_grid_coords: [f32; 3], wrapped_fn: impl FnOnce(&Ray, f32), ) { - let ray_start = math::lorentz_normalize(&na::Vector4::new( + let ray_start = MVector::new( ray_start_grid_coords[0] / ctx.layout.dual_to_grid_factor(), ray_start_grid_coords[1] / ctx.layout.dual_to_grid_factor(), ray_start_grid_coords[2] / ctx.layout.dual_to_grid_factor(), 1.0, - )); + ) + .lorentz_normalize(); - let ray_end = math::lorentz_normalize(&na::Vector4::new( + let ray_end = MVector::new( ray_end_grid_coords[0] / ctx.layout.dual_to_grid_factor(), ray_end_grid_coords[1] / ctx.layout.dual_to_grid_factor(), ray_end_grid_coords[2] / ctx.layout.dual_to_grid_factor(), 1.0, - )); + ) + .lorentz_normalize(); let ray = Ray::new( ray_start, - math::lorentz_normalize( - &((ray_end - ray_start) - + ray_start * math::mip(&ray_start, &(ray_end - ray_start))), - ), + ((ray_end - ray_start) + ray_start * ray_start.mip(&(ray_end - ray_start))) + .lorentz_normalize(), ); - let tanh_distance = (-math::mip(&ray_start, &ray_end)).acosh(); + let tanh_distance = (-ray_start.mip(&ray_end)).acosh(); wrapped_fn(&ray, tanh_distance) } @@ -518,16 +513,15 @@ mod tests { // The ray we care about is after its start point has moved to the contact point. let ray = math::translate( &ray.position, - &math::lorentz_normalize(&ray.ray_point(hit.tanh_distance)), + &ray.ray_point(hit.tanh_distance).lorentz_normalize(), ) * ray; // Project normal to be perpendicular to the ray's position - let corrected_normal = math::lorentz_normalize( - &(hit.normal + ray.position * math::mip(&hit.normal, &ray.position)), - ); + let corrected_normal = + (hit.normal + ray.position * hit.normal.mip(&ray.position)).lorentz_normalize(); // Check that the normal and ray are pointing opposite directions - assert!(math::mip(&corrected_normal, &ray.direction) < 0.0); + assert!(corrected_normal.mip(&ray.direction) < 0.0); } /// Tests that a suitable collision is found when approaching a single voxel from various angles and that diff --git a/common/src/chunk_ray_casting.rs b/common/src/chunk_ray_casting.rs index b764ed25..b539339f 100644 --- a/common/src/chunk_ray_casting.rs +++ b/common/src/chunk_ray_casting.rs @@ -1,6 +1,7 @@ use crate::{ collision_math::Ray, math, + math::MVector, node::{ChunkLayout, VoxelAABB, VoxelData}, voxel_math::{CoordAxis, CoordSign, Coords}, world::Material, @@ -68,10 +69,8 @@ fn find_face_collision( for t in bounding_box.grid_planes(t_axis) { // Find a normal to the grid plane. Note that (t, 0, 0, x) is a normal of the plane whose closest point // to the origin is (x, 0, 0, t), and we use that fact here. - let normal = math::lorentz_normalize(&math::tuv_to_xyz( - t_axis, - na::Vector4::new(1.0, 0.0, 0.0, layout.grid_to_dual(t)), - )); + let normal = math::tuv_to_xyz(t_axis, MVector::new(1.0, 0.0, 0.0, layout.grid_to_dual(t))) + .lorentz_normalize(); let Some(new_tanh_distance) = ray.solve_point_plane_intersection(&normal) else { continue; @@ -85,7 +84,7 @@ fn find_face_collision( // Which side we approach the plane from affects which voxel we want to use for hit detection. // If exiting a chunk via a chunk boundary, hit detection is handled by a different chunk. // We also want to retain this face_direction for reporting the hit result later. - let (face_sign, voxel_t) = if math::mip(&ray.direction, &normal) < 0.0 { + let (face_sign, voxel_t) = if ray.direction.mip(&normal) < 0.0 { if t == 0 { continue; } @@ -98,7 +97,7 @@ fn find_face_collision( }; let ray_endpoint = ray.ray_point(new_tanh_distance); - let contact_point = ray_endpoint - normal * math::mip(&ray_endpoint, &normal); + let contact_point = ray_endpoint - normal * ray_endpoint.mip(&normal); // Compute the u and v-coordinates of the voxels at the contact point let Some(voxel_u) = layout.dual_to_voxel(contact_point[u_axis] / contact_point.w) else { @@ -125,7 +124,6 @@ fn find_face_collision( face_sign, }); } - hit } @@ -182,29 +180,29 @@ mod tests { ray_end_grid_coords: [f32; 3], wrapped_fn: impl FnOnce(&Ray, f32), ) { - let ray_start = math::lorentz_normalize(&na::Vector4::new( + let ray_start = MVector::new( ray_start_grid_coords[0] / ctx.layout.dual_to_grid_factor(), ray_start_grid_coords[1] / ctx.layout.dual_to_grid_factor(), ray_start_grid_coords[2] / ctx.layout.dual_to_grid_factor(), 1.0, - )); + ) + .lorentz_normalize(); - let ray_end = math::lorentz_normalize(&na::Vector4::new( + let ray_end = MVector::new( ray_end_grid_coords[0] / ctx.layout.dual_to_grid_factor(), ray_end_grid_coords[1] / ctx.layout.dual_to_grid_factor(), ray_end_grid_coords[2] / ctx.layout.dual_to_grid_factor(), 1.0, - )); + ) + .lorentz_normalize(); let ray = Ray::new( ray_start, - math::lorentz_normalize( - &((ray_end - ray_start) - + ray_start * math::mip(&ray_start, &(ray_end - ray_start))), - ), + ((ray_end - ray_start) + ray_start * ray_start.mip(&(ray_end - ray_start))) + .lorentz_normalize(), ); - let tanh_distance = (-math::mip(&ray_start, &ray_end)).acosh(); + let tanh_distance = (-(ray_start.mip(&ray_end))).acosh(); wrapped_fn(&ray, tanh_distance) } diff --git a/common/src/collision_math.rs b/common/src/collision_math.rs index b64f2eed..bbee7986 100644 --- a/common/src/collision_math.rs +++ b/common/src/collision_math.rs @@ -1,15 +1,15 @@ -use crate::math; +use crate::math::{MIsometry, MVector}; /// A ray in hyperbolic space. The fields must be lorentz normalized, with `mip(position, position) == -1`, /// `mip(direction, direction) == 1`, and `mip(position, direction) == 0`. #[derive(Debug)] pub struct Ray { - pub position: na::Vector4, - pub direction: na::Vector4, + pub position: MVector, + pub direction: MVector, } impl Ray { - pub fn new(position: na::Vector4, direction: na::Vector4) -> Ray { + pub fn new(position: MVector, direction: MVector) -> Ray { Ray { position, direction, @@ -18,7 +18,7 @@ impl Ray { /// Returns a point along this ray `atanh(tanh_distance)` units away from the origin. This point /// is _not_ lorentz normalized. - pub fn ray_point(&self, tanh_distance: f32) -> na::Vector4 { + pub fn ray_point(&self, tanh_distance: f32) -> MVector { self.position + self.direction * tanh_distance } @@ -26,11 +26,11 @@ impl Ray { /// intersects the given plane. pub fn solve_sphere_plane_intersection( &self, - plane_normal: &na::Vector4, + plane_normal: &MVector, sinh_radius: f32, ) -> Option { - let mip_pos_a = math::mip(&self.position, plane_normal); - let mip_dir_a = math::mip(&self.direction, plane_normal); + let mip_pos_a = self.position.mip(plane_normal); + let mip_dir_a = self.direction.mip(plane_normal); solve_quadratic( mip_pos_a.powi(2) - sinh_radius.powi(2), @@ -43,14 +43,14 @@ impl Ray { /// intersects the given line. pub fn solve_sphere_line_intersection( &self, - line_normal0: &na::Vector4, - line_normal1: &na::Vector4, + line_normal1: &MVector, + line_normal0: &MVector, sinh_radius: f32, ) -> Option { - let mip_pos_a = math::mip(&self.position, line_normal0); - let mip_dir_a = math::mip(&self.direction, line_normal0); - let mip_pos_b = math::mip(&self.position, line_normal1); - let mip_dir_b = math::mip(&self.direction, line_normal1); + let mip_pos_a = self.position.mip(line_normal0); + let mip_dir_a = self.direction.mip(line_normal0); + let mip_pos_b = self.position.mip(line_normal1); + let mip_dir_b = self.direction.mip(line_normal1); solve_quadratic( mip_pos_a.powi(2) + mip_pos_b.powi(2) - sinh_radius.powi(2), @@ -63,17 +63,17 @@ impl Ray { /// intersects the given point. pub fn solve_sphere_point_intersection( &self, - point_normal0: &na::Vector4, - point_normal1: &na::Vector4, - point_normal2: &na::Vector4, + point_normal0: &MVector, + point_normal1: &MVector, + point_normal2: &MVector, sinh_radius: f32, ) -> Option { - let mip_pos_a = math::mip(&self.position, point_normal0); - let mip_dir_a = math::mip(&self.direction, point_normal0); - let mip_pos_b = math::mip(&self.position, point_normal1); - let mip_dir_b = math::mip(&self.direction, point_normal1); - let mip_pos_c = math::mip(&self.position, point_normal2); - let mip_dir_c = math::mip(&self.direction, point_normal2); + let mip_pos_a = self.position.mip(point_normal0); + let mip_dir_a = self.direction.mip(point_normal0); + let mip_pos_b = self.position.mip(point_normal1); + let mip_dir_b = self.direction.mip(point_normal1); + let mip_pos_c = self.position.mip(point_normal2); + let mip_dir_c = self.direction.mip(point_normal2); solve_quadratic( mip_pos_a.powi(2) + mip_pos_b.powi(2) + mip_pos_c.powi(2) - sinh_radius.powi(2), @@ -84,9 +84,9 @@ impl Ray { /// Finds the tanh of the distance a point will have to travel along a ray before it /// intersects the given plane. - pub fn solve_point_plane_intersection(&self, plane_normal: &na::Vector4) -> Option { - let mip_pos_a = math::mip(&self.position, plane_normal); - let mip_dir_a = math::mip(&self.direction, plane_normal); + pub fn solve_point_plane_intersection(&self, plane_normal: &MVector) -> Option { + let mip_pos_a = self.position.mip(plane_normal); + let mip_dir_a = self.direction.mip(plane_normal); let result = -mip_pos_a / mip_dir_a; if result.is_finite() && result > 0.0 { @@ -97,7 +97,7 @@ impl Ray { } } -impl std::ops::Mul<&Ray> for na::Matrix4 { +impl std::ops::Mul<&Ray> for MIsometry { type Output = Ray; #[inline] @@ -152,32 +152,30 @@ mod tests { use approx::assert_abs_diff_eq; use super::*; + use crate::math; #[test] fn solve_sphere_plane_intersection_example() { // Hit the z=0 plane with a radius of 0.2 let ray = math::translate_along(&na::Vector3::new(0.0, 0.0, -0.5)) - * &Ray::new(math::origin(), na::Vector4::new(0.8, 0.0, 0.6, 0.0)); - let normal = -na::Vector4::z(); - let hit_point = math::lorentz_normalize( - &ray.ray_point( + * &Ray::new(MVector::origin(), MVector::new(0.8, 0.0, 0.6, 0.0)); + let normal = -MVector::z(); + let hit_point = ray + .ray_point( ray.solve_sphere_plane_intersection(&normal, 0.2_f32.sinh()) .unwrap(), - ), - ); - assert_abs_diff_eq!( - math::mip(&hit_point, &normal), - 0.2_f32.sinh(), - epsilon = 1e-4 - ); + ) + .lorentz_normalize(); + + assert_abs_diff_eq!(hit_point.mip(&normal), 0.2_f32.sinh(), epsilon = 1e-4); } #[test] fn solve_sphere_plane_intersection_direct_hit() { // Directly hit the z=0 plane with a ray 0.5 units away and a radius of 0.2. let ray = math::translate_along(&na::Vector3::new(0.0, 0.0, -0.5)) - * &Ray::new(math::origin(), na::Vector4::z()); - let normal = -na::Vector4::z(); + * &Ray::new(MVector::origin(), MVector::z()); + let normal = -MVector::z(); assert_abs_diff_eq!( ray.solve_sphere_plane_intersection(&normal, 0.2_f32.sinh()) .unwrap(), @@ -190,8 +188,8 @@ mod tests { fn solve_sphere_plane_intersection_miss() { // No collision with the plane anywhere along the ray's line let ray = math::translate_along(&na::Vector3::new(0.0, 0.0, -0.5)) - * &Ray::new(math::origin(), na::Vector4::x()); - let normal = -na::Vector4::z(); + * &Ray::new(MVector::origin(), MVector::x()); + let normal = -MVector::z(); assert!(ray .solve_sphere_plane_intersection(&normal, 0.2_f32.sinh()) .is_none()); @@ -201,8 +199,8 @@ mod tests { fn solve_sphere_plane_intersection_margin() { // Sphere is already contacting the plane, with some error let ray = math::translate_along(&na::Vector3::new(0.0, 0.0, -0.2)) - * &Ray::new(math::origin(), na::Vector4::z()); - let normal = -na::Vector4::z(); + * &Ray::new(MVector::origin(), MVector::z()); + let normal = -MVector::z(); assert_eq!( ray.solve_sphere_plane_intersection(&normal, 0.2001_f32.sinh()) .unwrap(), @@ -215,22 +213,20 @@ mod tests { // Hit the x=z=0 line with a radius of 0.2 let ray = math::translate_along(&na::Vector3::new(0.0, 0.0, -0.5)) * &Ray::new( - math::origin(), - na::Vector4::new(1.0, 2.0, 3.0, 0.0).normalize(), + MVector::origin(), + MVector::new(1.0, 2.0, 3.0, 0.0).normalize(), ); - let line_normal0 = na::Vector4::x(); - let line_normal1 = na::Vector4::z(); - let hit_point = math::lorentz_normalize( - &ray.ray_point( + let line_normal0 = MVector::x(); + let line_normal1 = MVector::z(); + let hit_point = ray + .ray_point( ray.solve_sphere_line_intersection(&line_normal0, &line_normal1, 0.2_f32.sinh()) .unwrap(), - ), - ); + ) + .lorentz_normalize(); // Measue the distance from hit_point to the line and ensure it's equal to the radius assert_abs_diff_eq!( - (math::mip(&hit_point, &line_normal0).powi(2) - + math::mip(&hit_point, &line_normal1).powi(2)) - .sqrt(), + (hit_point.mip(&line_normal0).powi(2) + hit_point.mip(&line_normal1).powi(2)).sqrt(), 0.2_f32.sinh(), epsilon = 1e-4 ); @@ -243,9 +239,9 @@ mod tests { // Ensure the ray is slightly off-center so that the distance math is shown to be correct let ray = math::translate_along(&na::Vector3::new(0.0, 0.7, 0.0)) * math::translate_along(&na::Vector3::new(0.0, 0.0, -0.5)) - * &Ray::new(math::origin(), na::Vector4::z()); - let line_normal0 = na::Vector4::x(); - let line_normal1 = na::Vector4::z(); + * &Ray::new(MVector::origin(), MVector::z()); + let line_normal0 = MVector::x(); + let line_normal1 = MVector::z(); assert_abs_diff_eq!( ray.solve_sphere_line_intersection(&line_normal0, &line_normal1, 0.2_f32.sinh()) .unwrap(), @@ -258,9 +254,9 @@ mod tests { fn solve_sphere_line_intersection_miss() { // No collision with the line anywhere along the ray's line let ray = math::translate_along(&na::Vector3::new(0.0, 0.0, -0.5)) - * &Ray::new(math::origin(), na::Vector4::x()); - let line_normal0 = na::Vector4::x(); - let line_normal1 = na::Vector4::z(); + * &Ray::new(MVector::origin(), MVector::x()); + let line_normal0 = MVector::x(); + let line_normal1 = MVector::z(); assert!(ray .solve_sphere_line_intersection(&line_normal0, &line_normal1, 0.2_f32.sinh()) .is_none()); @@ -270,9 +266,9 @@ mod tests { fn solve_sphere_line_intersection_margin() { // Sphere is already contacting the line, with some error let ray = math::translate_along(&na::Vector3::new(0.0, 0.0, -0.2)) - * &Ray::new(math::origin(), na::Vector4::z()); - let line_normal0 = na::Vector4::x(); - let line_normal1 = na::Vector4::z(); + * &Ray::new(MVector::origin(), MVector::z()); + let line_normal0 = MVector::x(); + let line_normal1 = MVector::z(); assert_eq!( ray.solve_sphere_line_intersection(&line_normal0, &line_normal1, 0.2001_f32.sinh()) .unwrap(), @@ -288,11 +284,11 @@ mod tests { // Similar reasoning can also apply to `solve_sphere_point_intersection` even though it is // not tested explicitly in the same way. let ray = Ray::new( - na::Vector4::new(-0.019093871, -0.0014823675, 0.059645057, 1.0019588), - na::Vector4::new(-0.02954007, 0.9965602, 0.07752046, 0.003702946), + MVector::new(-0.019093871, -0.0014823675, 0.059645057, 1.0019588), + MVector::new(-0.02954007, 0.9965602, 0.07752046, 0.003702946), ); - let line_normal0 = na::Vector4::::x(); - let line_normal1 = na::Vector4::::y(); + let line_normal0 = MVector::::x(); + let line_normal1 = MVector::::y(); let radius = 0.019090926_f32; // The following returns wrong results in the other implementation, so we test this case // to make sure there are no regressions. @@ -306,15 +302,15 @@ mod tests { // Hit the origin with a radius of 0.2 let ray = math::translate_along(&na::Vector3::new(0.0, 0.0, -0.5)) * &Ray::new( - math::origin(), - na::Vector4::new(1.0, 2.0, 6.0, 0.0).normalize(), + MVector::origin(), + MVector::new(1.0, 2.0, 6.0, 0.0).normalize(), ); - let point_position = math::origin(); - let point_normal0 = na::Vector4::x(); - let point_normal1 = na::Vector4::y(); - let point_normal2 = na::Vector4::z(); - let hit_point = math::lorentz_normalize( - &ray.ray_point( + let point_position = MVector::origin(); + let point_normal0 = MVector::x(); + let point_normal1 = MVector::y(); + let point_normal2 = MVector::z(); + let hit_point = ray + .ray_point( ray.solve_sphere_point_intersection( &point_normal0, &point_normal1, @@ -322,10 +318,10 @@ mod tests { 0.2_f32.sinh(), ) .unwrap(), - ), - ); + ) + .lorentz_normalize(); assert_abs_diff_eq!( - -math::mip(&hit_point, &point_position), + -hit_point.mip(&point_position), 0.2_f32.cosh(), epsilon = 1e-4 ); @@ -335,10 +331,10 @@ mod tests { fn solve_sphere_point_intersection_direct_hit() { // Directly hit the origin with a ray 0.5 units away and a radius of 0.2. let ray = math::translate_along(&na::Vector3::new(0.0, 0.0, -0.5)) - * &Ray::new(math::origin(), na::Vector4::z()); - let point_normal0 = na::Vector4::x(); - let point_normal1 = na::Vector4::y(); - let point_normal2 = na::Vector4::z(); + * &Ray::new(MVector::origin(), MVector::z()); + let point_normal0 = MVector::x(); + let point_normal1 = MVector::y(); + let point_normal2 = MVector::z(); assert_abs_diff_eq!( ray.solve_sphere_point_intersection( &point_normal0, @@ -356,10 +352,10 @@ mod tests { fn solve_sphere_point_intersection_miss() { // No collision with the point anywhere along the ray's line let ray = math::translate_along(&na::Vector3::new(0.0, 0.0, -0.5)) - * &Ray::new(math::origin(), na::Vector4::x()); - let point_normal0 = na::Vector4::x(); - let point_normal1 = na::Vector4::y(); - let point_normal2 = na::Vector4::z(); + * &Ray::new(MVector::origin(), MVector::x()); + let point_normal0 = MVector::x(); + let point_normal1 = MVector::y(); + let point_normal2 = MVector::z(); assert!(ray .solve_sphere_point_intersection( &point_normal0, @@ -374,10 +370,10 @@ mod tests { fn solve_sphere_point_intersection_margin() { // Sphere is already contacting the point, with some error let ray = math::translate_along(&na::Vector3::new(0.0, 0.0, -0.2)) - * &Ray::new(math::origin(), na::Vector4::z()); - let point_normal0 = na::Vector4::x(); - let point_normal1 = na::Vector4::y(); - let point_normal2 = na::Vector4::z(); + * &Ray::new(MVector::origin(), MVector::z()); + let point_normal0 = MVector::x(); + let point_normal1 = MVector::y(); + let point_normal2 = MVector::z(); assert_eq!( ray.solve_sphere_point_intersection( &point_normal0, @@ -394,12 +390,12 @@ mod tests { fn foo() { // Hit the z=0 plane let ray = math::translate_along(&na::Vector3::new(0.0, 0.0, -0.5)) - * &Ray::new(math::origin(), na::Vector4::new(0.8, 0.0, 0.6, 0.0)); - let normal = -na::Vector4::z(); - let hit_point = math::lorentz_normalize( - &ray.ray_point(ray.solve_point_plane_intersection(&normal).unwrap()), - ); - assert_abs_diff_eq!(math::mip(&hit_point, &normal), 0.0, epsilon = 1e-4); + * &Ray::new(MVector::origin(), MVector::new(0.8, 0.0, 0.6, 0.0)); + let normal = -MVector::z(); + let hit_point = ray + .ray_point(ray.solve_point_plane_intersection(&normal).unwrap()) + .lorentz_normalize(); + assert_abs_diff_eq!(hit_point.mip(&normal), 0.0, epsilon = 1e-4); } #[test] diff --git a/common/src/dodeca.rs b/common/src/dodeca.rs index d4f9208a..23a89937 100644 --- a/common/src/dodeca.rs +++ b/common/src/dodeca.rs @@ -3,6 +3,7 @@ use data::*; use serde::{Deserialize, Serialize}; +use crate::math::{MIsometry, MVector}; use crate::voxel_math::ChunkAxisPermutation; /// Sides of a right dodecahedron @@ -45,33 +46,33 @@ impl Side { /// Outward normal vector of this side #[inline] - pub fn normal(self) -> &'static na::Vector4 { + pub fn normal(self) -> &'static MVector { &side_normals_f32()[self as usize] } /// Outward normal vector of this side #[inline] - pub fn normal_f64(self) -> &'static na::Vector4 { + pub fn normal_f64(self) -> &'static MVector { &side_normals_f64()[self as usize] } /// Reflection across this side #[inline] - pub fn reflection(self) -> &'static na::Matrix4 { + pub fn reflection(self) -> &'static MIsometry { &reflections_f32()[self as usize] } /// Reflection across this side #[inline] - pub fn reflection_f64(self) -> &'static na::Matrix4 { + pub fn reflection_f64(self) -> &'static MIsometry { &reflections_f64()[self as usize] } /// Whether `p` is opposite the dodecahedron across the plane containing `self` #[inline] - pub fn is_facing(self, p: &na::Vector4) -> bool { - let r = na::convert::<_, na::RowVector4>(self.reflection_f64().row(3).clone_owned()); - (r * p).x < p.w + pub fn is_facing(self, p: &MVector) -> bool { + let r = na::convert::<_, na::RowVector4>(self.reflection().row(3).clone_owned()); + (r * na::Vector4::from(*p)).x < p.w } } @@ -178,41 +179,45 @@ impl Vertex { /// Transform from euclidean chunk coordinates to hyperbolic node space pub fn chunk_to_node(self) -> na::Matrix4 { - self.dual_to_node() * na::Matrix4::new_scaling(1.0 / Self::dual_to_chunk_factor()) + na::Matrix4::from(*self.dual_to_node()) + * na::Matrix4::new_scaling(1.0 / Self::dual_to_chunk_factor()) } /// Transform from euclidean chunk coordinates to hyperbolic node space pub fn chunk_to_node_f64(self) -> na::Matrix4 { - self.dual_to_node_f64() * na::Matrix4::new_scaling(1.0 / Self::dual_to_chunk_factor_f64()) + na::Matrix4::from(*self.dual_to_node_f64()) + * na::Matrix4::new_scaling(1.0 / Self::dual_to_chunk_factor_f64()) } /// Transform from hyperbolic node space to euclidean chunk coordinates pub fn node_to_chunk(self) -> na::Matrix4 { - na::Matrix4::new_scaling(Self::dual_to_chunk_factor()) * self.node_to_dual() + na::Matrix4::new_scaling(Self::dual_to_chunk_factor()) + * na::Matrix4::from(*self.node_to_dual()) } /// Transform from hyperbolic node space to euclidean chunk coordinates pub fn node_to_chunk_f64(self) -> na::Matrix4 { - na::Matrix4::new_scaling(Self::dual_to_chunk_factor_f64()) * self.node_to_dual_f64() + na::Matrix4::new_scaling(Self::dual_to_chunk_factor_f64()) + * na::Matrix4::from(*self.node_to_dual_f64()) } /// Transform from cube-centric coordinates to dodeca-centric coordinates - pub fn dual_to_node(self) -> &'static na::Matrix4 { + pub fn dual_to_node(self) -> &'static MIsometry { &dual_to_node_f32()[self as usize] } /// Transform from cube-centric coordinates to dodeca-centric coordinates - pub fn dual_to_node_f64(self) -> &'static na::Matrix4 { + pub fn dual_to_node_f64(self) -> &'static MIsometry { &dual_to_node_f64()[self as usize] } /// Transform from dodeca-centric coordinates to cube-centric coordinates - pub fn node_to_dual(self) -> &'static na::Matrix4 { + pub fn node_to_dual(self) -> &'static MIsometry { &node_to_dual_f32()[self as usize] } /// Transform from dodeca-centric coordinates to cube-centric coordinates - pub fn node_to_dual_f64(self) -> &'static na::Matrix4 { + pub fn node_to_dual_f64(self) -> &'static MIsometry { &node_to_dual_f64()[self as usize] } @@ -260,7 +265,7 @@ mod data { use std::sync::OnceLock; use crate::dodeca::{Side, Vertex, SIDE_COUNT, VERTEX_COUNT}; - use crate::math; + use crate::math::{MIsometry, MVector}; use crate::voxel_math::ChunkAxisPermutation; /// Whether two sides share an edge @@ -281,13 +286,13 @@ mod data { } /// Vector corresponding to the outer normal of each side - pub fn side_normals_f64() -> &'static [na::Vector4; SIDE_COUNT] { - static LOCK: OnceLock<[na::Vector4; SIDE_COUNT]> = OnceLock::new(); + pub fn side_normals_f64() -> &'static [MVector; SIDE_COUNT] { + static LOCK: OnceLock<[MVector; SIDE_COUNT]> = OnceLock::new(); LOCK.get_or_init(|| { let phi = libm::sqrt(1.25) + 0.5; // golden ratio - let f = math::lorentz_normalize(&na::Vector4::new(1.0, phi, 0.0, libm::sqrt(phi))); + let f = MVector::new(1.0, phi, 0.0, libm::sqrt(phi)).lorentz_normalize(); - let mut result: [na::Vector4; SIDE_COUNT] = [na::zero(); SIDE_COUNT]; + let mut result: [MVector; SIDE_COUNT] = [MVector::zero(); SIDE_COUNT]; let mut i = 0; for (x, y, z, w) in [ (f.x, f.y, f.z, f.w), @@ -296,7 +301,7 @@ mod data { (-f.x, -f.y, f.z, f.w), ] { for (x, y, z, w) in [(x, y, z, w), (y, z, x, w), (z, x, y, w)] { - result[i] = na::Vector4::new(x, y, z, w); + result[i] = MVector::new(x, y, z, w); i += 1; } } @@ -305,9 +310,9 @@ mod data { } /// Transform that moves from a neighbor to a reference node, for each side - pub fn reflections_f64() -> &'static [na::Matrix4; SIDE_COUNT] { - static LOCK: OnceLock<[na::Matrix4; SIDE_COUNT]> = OnceLock::new(); - LOCK.get_or_init(|| side_normals_f64().map(|r| math::reflect(&r))) + pub fn reflections_f64() -> &'static [MIsometry; SIDE_COUNT] { + static LOCK: OnceLock<[MIsometry; SIDE_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| side_normals_f64().map(|r| r.reflect())) } /// Sides incident to a vertex, in canonical order @@ -399,21 +404,20 @@ mod data { } /// Transform that converts from cube-centric coordinates to dodeca-centric coordinates - pub fn dual_to_node_f64() -> &'static [na::Matrix4; VERTEX_COUNT] { - static LOCK: OnceLock<[na::Matrix4; VERTEX_COUNT]> = OnceLock::new(); + pub fn dual_to_node_f64() -> &'static [MIsometry; VERTEX_COUNT] { + static LOCK: OnceLock<[MIsometry; VERTEX_COUNT]> = OnceLock::new(); LOCK.get_or_init(|| { - let mip_origin_normal = math::mip(&math::origin(), &side_normals_f64()[0]); // This value is the same for every side - let mut result = [na::zero(); VERTEX_COUNT]; + let mip_origin_normal = MVector::origin().mip(&side_normals_f64()[0]); // This value is the same for every side + let mut result = [MIsometry::identity(); VERTEX_COUNT]; for (i, map) in result.iter_mut().enumerate() { let [a, b, c] = vertex_sides()[i]; - let vertex_position = math::lorentz_normalize( - &(math::origin() - - (a.normal_f64() + b.normal_f64() + c.normal_f64()) * mip_origin_normal), - ); - *map = na::Matrix4::from_columns(&[ - -a.normal_f64(), - -b.normal_f64(), - -c.normal_f64(), + let vertex_position = (MVector::origin() + - (*a.normal_f64() + *b.normal_f64() + *c.normal_f64()) * mip_origin_normal) + .lorentz_normalize(); + *map = MIsometry::from_columns_unchecked(&[ + -*a.normal_f64(), + -*b.normal_f64(), + -*c.normal_f64(), vertex_position, ]); } @@ -422,9 +426,9 @@ mod data { } /// Transform that converts from dodeca-centric coordinates to cube-centric coordinates - pub fn node_to_dual_f64() -> &'static [na::Matrix4; VERTEX_COUNT] { - static LOCK: OnceLock<[na::Matrix4; VERTEX_COUNT]> = OnceLock::new(); - LOCK.get_or_init(|| dual_to_node_f64().map(|m| math::mtranspose(&m))) + pub fn node_to_dual_f64() -> &'static [MIsometry; VERTEX_COUNT] { + static LOCK: OnceLock<[MIsometry; VERTEX_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| dual_to_node_f64().map(|m| m.mtranspose())) } pub fn dual_to_chunk_factor_f64() -> f64 { @@ -476,31 +480,30 @@ mod data { let mut result = [false; VERTEX_COUNT]; for v in Vertex::iter() { - result[v as usize] = math::parity(&v.chunk_to_node_f64()); + result[v as usize] = v.dual_to_node().parity(); } - result }) } - pub fn side_normals_f32() -> &'static [na::Vector4; SIDE_COUNT] { - static LOCK: OnceLock<[na::Vector4; SIDE_COUNT]> = OnceLock::new(); - LOCK.get_or_init(|| side_normals_f64().map(|n| n.cast())) + pub fn side_normals_f32() -> &'static [MVector; SIDE_COUNT] { + static LOCK: OnceLock<[MVector; SIDE_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| side_normals_f64().map(|n| n.to_f32())) } - pub fn reflections_f32() -> &'static [na::Matrix4; SIDE_COUNT] { - static LOCK: OnceLock<[na::Matrix4; SIDE_COUNT]> = OnceLock::new(); - LOCK.get_or_init(|| reflections_f64().map(|n| n.cast())) + pub fn reflections_f32() -> &'static [MIsometry; SIDE_COUNT] { + static LOCK: OnceLock<[MIsometry; SIDE_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| reflections_f64().map(|n| n.to_f32())) } - pub fn dual_to_node_f32() -> &'static [na::Matrix4; VERTEX_COUNT] { - static LOCK: OnceLock<[na::Matrix4; VERTEX_COUNT]> = OnceLock::new(); - LOCK.get_or_init(|| dual_to_node_f64().map(|n| n.cast())) + pub fn dual_to_node_f32() -> &'static [MIsometry; VERTEX_COUNT] { + static LOCK: OnceLock<[MIsometry; VERTEX_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| dual_to_node_f64().map(|n| n.to_f32())) } - pub fn node_to_dual_f32() -> &'static [na::Matrix4; VERTEX_COUNT] { - static LOCK: OnceLock<[na::Matrix4; VERTEX_COUNT]> = OnceLock::new(); - LOCK.get_or_init(|| node_to_dual_f64().map(|n| n.cast())) + pub fn node_to_dual_f32() -> &'static [MIsometry; VERTEX_COUNT] { + static LOCK: OnceLock<[MIsometry; VERTEX_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| node_to_dual_f64().map(|n| n.to_f32())) } pub fn dual_to_chunk_factor_f32() -> f32 { @@ -602,17 +605,17 @@ mod tests { #[test] fn side_is_facing() { for side in Side::iter() { - assert!(!side.is_facing::(&math::origin())); - assert!(side.is_facing(&(side.reflection() * math::origin()))); + assert!(!side.is_facing(&MVector::origin())); + assert!(side.is_facing(&(*side.reflection() * MVector::origin()))); } } #[test] fn radius() { - let corner = Vertex::A.chunk_to_node_f64() * math::origin(); + let corner = *Vertex::A.dual_to_node_f64() * MVector::origin(); assert_abs_diff_eq!( BOUNDING_SPHERE_RADIUS_F64, - math::distance(&corner, &math::origin()), + math::distance(&corner, &MVector::origin()), epsilon = 1e-10 ); let phi = (1.0 + 5.0f64.sqrt()) / 2.0; // Golden ratio diff --git a/common/src/graph.rs b/common/src/graph.rs index 7fcc628d..7aab13f9 100644 --- a/common/src/graph.rs +++ b/common/src/graph.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::{ dodeca::{Side, SIDE_COUNT}, - math, + math::{MIsometry, MVector}, node::{ChunkId, ChunkLayout, Node}, }; @@ -120,13 +120,13 @@ impl Graph { /// Given a `transform` relative to a `reference` node, computes the node /// that it's closest to and the transform that moves it there - pub fn normalize_transform( + pub fn normalize_transform( &self, mut reference: NodeId, - original: &na::Matrix4, - ) -> (NodeId, na::Matrix4) { - let mut transform = na::Matrix4::identity(); - let mut location = original * math::origin(); + original: &MIsometry, + ) -> (NodeId, MIsometry) { + let mut transform = MIsometry::identity(); + let mut location = *original * MVector::origin(); 'outer: loop { for side in Side::iter() { if !side.is_facing(&location) { @@ -136,7 +136,7 @@ impl Graph { None => continue, Some(x) => x, }; - let mat = na::convert::<_, na::Matrix4>(*side.reflection_f64()); + let mat = *side.reflection(); location = mat * location; transform = mat * transform; continue 'outer; @@ -401,10 +401,9 @@ mod tests { let mut graph = Graph::new(1); let a = graph.ensure_neighbor(NodeId::ROOT, Side::A); { - let (node, xf) = - graph.normalize_transform::(NodeId::ROOT, &na::Matrix4::identity()); + let (node, xf) = graph.normalize_transform(NodeId::ROOT, &MIsometry::identity()); assert_eq!(node, NodeId::ROOT); - assert_abs_diff_eq!(xf, na::Matrix4::identity(), epsilon = 1e-5); + assert_abs_diff_eq!(xf, MIsometry::identity(), epsilon = 1e-5); } { let (node, xf) = graph.normalize_transform(NodeId::ROOT, Side::A.reflection()); diff --git a/common/src/graph_collision.rs b/common/src/graph_collision.rs index 08949222..4ff72863 100644 --- a/common/src/graph_collision.rs +++ b/common/src/graph_collision.rs @@ -2,7 +2,7 @@ use crate::{ chunk_collision::chunk_sphere_cast, collision_math::Ray, graph::Graph, - math, + math::MVector, node::{Chunk, ChunkId}, proto::Position, traversal::RayTraverser, @@ -56,7 +56,7 @@ pub fn sphere_cast( Some(GraphCastHit { tanh_distance: hit.tanh_distance, chunk, - normal: math::mtranspose(&transform) * hit.normal, + normal: transform.mtranspose() * hit.normal, }) }); } @@ -79,7 +79,7 @@ pub struct GraphCastHit { /// Represents the normal vector of the hit surface in the original coordinate system /// of the sphere casting. To get the actual normal vector, project it so that it is orthogonal /// to the endpoint in Lorentz space. - pub normal: na::Vector4, + pub normal: MVector, } #[cfg(test)] @@ -88,6 +88,7 @@ mod tests { collision_math::Ray, dodeca::{self, Side, Vertex}, graph::{Graph, NodeId}, + math::MIsometry, node::{populate_fresh_nodes, VoxelData}, proto::Position, traversal::{ensure_nearby, nearby_nodes}, @@ -169,40 +170,43 @@ mod tests { } // Find the transform of the chosen chunk - let chosen_chunk_transform: na::Matrix4 = - self.chosen_voxel.node_path.iter().fold( - na::Matrix4::identity(), - |transform: na::Matrix4, side| transform * side.reflection(), - ) * self.chosen_voxel.vertex.dual_to_node(); + let chosen_chunk_transform: MIsometry = self + .chosen_voxel + .node_path + .iter() + .fold(MIsometry::identity(), |transform: MIsometry, side| { + transform * *side.reflection() + }) + * *self.chosen_voxel.vertex.dual_to_node(); let dual_to_grid_factor = graph.layout().dual_to_grid_factor(); let ray_target = chosen_chunk_transform - * math::lorentz_normalize(&na::Vector4::new( + * MVector::new( self.chosen_chunk_relative_grid_ray_end[0] / dual_to_grid_factor, self.chosen_chunk_relative_grid_ray_end[1] / dual_to_grid_factor, self.chosen_chunk_relative_grid_ray_end[2] / dual_to_grid_factor, 1.0, - )); + ) + .lorentz_normalize(); - let ray_position = Vertex::A.dual_to_node() - * math::lorentz_normalize(&na::Vector4::new( + let ray_position = *Vertex::A.dual_to_node() + * MVector::new( self.start_chunk_relative_grid_ray_start[0] / dual_to_grid_factor, self.start_chunk_relative_grid_ray_start[1] / dual_to_grid_factor, self.start_chunk_relative_grid_ray_start[2] / dual_to_grid_factor, 1.0, - )); + ) + .lorentz_normalize(); let ray_direction = ray_target - ray_position; let ray = Ray::new( ray_position, - math::lorentz_normalize( - &(ray_direction + ray_position * math::mip(&ray_position, &ray_direction)), - ), + (ray_direction + ray_position * ray_position.mip(&ray_direction)) + .lorentz_normalize(), ); - let tanh_distance = ((-math::mip(&ray_position, &ray_target)).acosh() - + self.ray_length_modifier) - .tanh(); + let tanh_distance = + ((-ray_position.mip(&ray_target)).acosh() + self.ray_length_modifier).tanh(); let hit = sphere_cast( self.collider_radius, @@ -221,7 +225,7 @@ mod tests { "collision occurred in wrong chunk" ); assert!( - math::mip(&hit.as_ref().unwrap().normal, &ray.direction) < 0.0, + hit.as_ref().unwrap().normal.mip(&ray.direction) < 0.0, "normal is facing the wrong way" ); } else { @@ -434,13 +438,13 @@ mod tests { } // The node coordinates of the corner of the missing node - let vertex_pos = Vertex::A.dual_to_node() * math::origin(); + let vertex_pos = *Vertex::A.dual_to_node() * MVector::origin(); // Use a ray starting from the origin. The direction vector is vertex_pos with the w coordinate // set to 0 and normalized let ray = Ray::new( - math::origin(), - (vertex_pos - na::Vector4::w() * vertex_pos.w).normalize(), + MVector::origin(), + (vertex_pos - MVector::w() * vertex_pos.w).normalize(), ); let sphere_radius = 0.1; diff --git a/common/src/math.rs b/common/src/math.rs index 1c6c3d6b..69cc3bd3 100644 --- a/common/src/math.rs +++ b/common/src/math.rs @@ -3,62 +3,352 @@ //! Vector4 values are assumed to be homogeneous Klein model coordinates unless otherwise //! stated. Note that Minkowski model coordinates are valid Klein coordinates, but not vis versa. +// all the inline functions are basically just wrappers around corresponding nalgebra functions + use na::{RealField, Scalar}; use serde::{Deserialize, Serialize}; +use std::ops::*; + +#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)] +#[repr(C)] +pub struct MVector(na::Vector4); -/// A point on the surface of the 3D hyperboloid in Minkowski coordinates with an implicit w -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)] #[repr(C)] -pub struct HPoint(na::Vector3); +pub struct MIsometry(na::Matrix4); -impl HPoint { - pub fn new(x: N, y: N, z: N) -> Self { - Self(na::Vector3::new(x, y, z)) +impl From>> for MVector { + fn from(value: na::Unit>) -> Self { + Self(value.into_inner().push(na::zero())) } +} - /// Construct from Minkowski coordinates - pub fn from_homogeneous(v: &na::Vector4) -> Self { - Self(v.xyz()) +impl From> for MVector { + fn from(value: na::Vector4) -> Self { + Self(value) } } -impl HPoint { - pub fn origin() -> Self { - Self::new(na::zero(), na::zero(), na::zero()) +impl Deref for MVector { + type Target = na::coordinates::XYZW; + #[inline] + fn deref(&self) -> &Self::Target { + self.0.deref() } +} + +impl DerefMut for MVector { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.deref_mut() + } +} - /// Convert to Minkowski coordinates - pub fn to_homogeneous(self) -> na::Vector4 { +impl Deref for MIsometry { + type Target = na::coordinates::M4x4; + #[inline] + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +impl From> for na::Matrix4 { + fn from(value: MIsometry) -> na::Matrix4 { + value.0 + } +} + +impl From> for na::Vector4 { + fn from(value: MVector) -> na::Vector4 { + value.0 + } +} + +impl MIsometry { + pub fn to_f64(self) -> MIsometry { + MIsometry(self.0.cast::()) + } +} + +impl MIsometry { + pub fn to_f32(self) -> MIsometry { + MIsometry(self.0.cast::()) + } +} + +impl MVector { + pub fn to_f64(self) -> MVector { + MVector(self.0.cast::()) + } +} + +impl MVector { + pub fn to_f32(self) -> MVector { + MVector(self.0.cast::()) + } +} + +impl MIsometry { + #[inline] + pub fn as_ref(self) -> [[N; 4]; 4] { + *self.0.as_ref() + } + #[inline] + pub fn unit_quaternion_to_homogeneous(rotation: na::UnitQuaternion) -> Self { + MIsometry(rotation.to_homogeneous()) + } + #[inline] + pub fn rotation_to_homogeneous(rotation: na::Rotation3) -> Self { + MIsometry(rotation.to_homogeneous()) + } + #[inline] + pub fn row(self, i: usize) -> na::RowVector4 { + self.0.row(i).into() + } + #[inline] + pub fn identity() -> Self { + Self(na::Matrix4::identity()) + } + #[inline] + pub fn map N2>(&self, f: F) -> MIsometry { + MIsometry(self.0.map(f)) + } + #[inline] + pub fn from_columns_unchecked(columns: &[MVector; 4]) -> Self { + Self(na::Matrix4::from_columns(&(*columns).map(|x| x.0))) + } + /// Minkowski transpose. Inverse for hyperbolic isometries + #[rustfmt::skip] + pub fn mtranspose(self) -> Self { + MIsometry( + na::Matrix4::new( + self.0.m11, self.0.m21, self.0.m31, -self.0.m41, + self.0.m12, self.0.m22, self.0.m32, -self.0.m42, + self.0.m13, self.0.m23, self.0.m33, -self.0.m43, + -self.0.m14, -self.0.m24, -self.0.m34, self.0.m44, + ) + ) + } + /// Whether an isometry reverses winding with respect to the norm + pub fn parity(self) -> bool { + self.0.fixed_view::<3, 3>(0, 0).determinant() < na::zero::() + } + pub fn renormalize_isometry(self) -> MIsometry { + let boost = translate( + &MVector::origin(), + &MVector(self.0.index((.., 3)).into()).lorentz_normalize(), + ); + let inverse_boost = boost.mtranspose(); + let rotation = renormalize_rotation_reflection( + &((inverse_boost * self).0) + .fixed_view::<3, 3>(0, 0) + .clone_owned(), + ); + MIsometry(boost.0 * rotation.to_homogeneous()) + } +} + +impl MVector { + pub fn lorentz_normalize(self: &MVector) -> Self { + let sf2 = self.mip(self); + if sf2 == na::zero() { + return MVector::origin(); + } + let sf = sf2.abs().sqrt(); + *self / sf + } + /// Point or plane reflection around point or normal `p` + pub fn reflect(self) -> MIsometry { + MIsometry( + na::Matrix4::::identity() + - self.minkowski_outer_product(&self) * na::convert::<_, N>(2.0) / self.mip(&self), + ) + } + /// Minkowski inner product, aka _h + pub fn mip(self, other: &Self) -> N { + self.x * other.x + self.y * other.y + self.z * other.z - self.w * other.w + } + pub fn minkowski_outer_product(self, other: &Self) -> na::Matrix4 { + ((self).0) * na::RowVector4::new(other.x, other.y, other.z, -other.w) + } + pub fn from_gans(gans: &na::Vector3) -> Self { // x^2 + y^2 + z^2 - w^2 = -1 // sqrt(x^2 + y^2 + z^2 + 1) = w - let w = (sqr(self.0.x) + sqr(self.0.y) + sqr(self.0.z) + na::one()).sqrt(); - na::Vector4::new(self.0.x, self.0.y, self.0.z, w) + let w = (sqr(gans.x) + sqr(gans.y) + sqr(gans.z) + na::one()).sqrt(); + MVector(na::Vector4::new(gans.x, gans.y, gans.z, w)) + } + #[inline] + pub fn zero() -> Self { + Self(na::zero()) + } + #[inline] + pub fn origin() -> Self { + Self::w() + } + #[inline] + pub fn normalize(self) -> Self { + Self(self.0.normalize()) + } + #[inline] + pub fn x() -> Self { + Self(na::Vector4::x()) + } + #[inline] + pub fn y() -> Self { + Self(na::Vector4::y()) + } + #[inline] + pub fn z() -> Self { + Self(na::Vector4::z()) + } + #[inline] + pub fn w() -> Self { + Self(na::Vector4::w()) + } + #[inline] + pub fn new(x: N, y: N, z: N, w: N) -> Self { + MVector(na::Vector4::new(x, y, z, w)) + } + #[inline] + pub fn xyz(self) -> na::Vector3 { + self.0.xyz() + } +} + +impl Mul> for MIsometry { + type Output = MIsometry; + #[inline] + fn mul(self, rhs: MIsometry) -> Self::Output { + MIsometry(self.0 * rhs.0) + } +} + +impl Mul for MVector { + type Output = MVector; + #[inline] + fn mul(self, rhs: N) -> Self::Output { + MVector(self.0 * rhs) } } -/// Point or plane reflection around point or normal `p` -pub fn reflect(p: &na::Vector4) -> na::Matrix4 { - na::Matrix4::::identity() - - minkowski_outer_product(p, p) * na::convert::<_, N>(2.0) / mip(p, p) +impl Div for MVector { + type Output = MVector; + #[inline] + fn div(self, rhs: N) -> Self::Output { + MVector(self.0 / rhs) + } +} + +impl Add for MVector { + type Output = Self; + #[inline] + fn add(self, other: Self) -> Self { + Self(self.0 + other.0) + } +} + +impl Sub for MVector { + type Output = Self; + #[inline] + fn sub(self, other: Self) -> Self { + Self(self.0 - other.0) + } +} + +impl Neg for MVector { + type Output = Self; + #[inline] + fn neg(self) -> Self { + Self(-self.0) + } +} + +impl Mul> for MIsometry { + type Output = MVector; + #[inline] + fn mul(self, rhs: MVector) -> Self::Output { + MVector(self.0 * rhs.0) + } +} + +impl Mul for MIsometry { + type Output = MIsometry; + #[inline] + fn mul(self, rhs: N) -> Self::Output { + MIsometry(self.0 * rhs) + } +} + +impl std::ops::AddAssign for MVector { + #[inline] + fn add_assign(&mut self, other: Self) { + self.0 += other.0; + } +} + +impl MulAssign for MVector { + #[inline] + fn mul_assign(&mut self, rhs: N) { + self.0 *= rhs; + } +} + +impl MulAssign for MIsometry { + #[inline] + fn mul_assign(&mut self, rhs: N) { + self.0 *= rhs; + } +} + +impl MulAssign> for MIsometry { + #[inline] + fn mul_assign(&mut self, rhs: MIsometry) { + self.0 *= rhs.0; + } +} + +impl Index for MVector { + type Output = N; + #[inline] + fn index(&self, i: usize) -> &Self::Output { + &self.0[i] + } +} + +impl IndexMut for MVector { + #[inline] + fn index_mut(&mut self, i: usize) -> &mut N { + &mut self.0[i] + } +} + +impl Index<(usize, usize)> for MIsometry { + type Output = N; + #[inline] + fn index(&self, ij: (usize, usize)) -> &Self::Output { + &self.0[ij] + } } /// Transform that translates `a` to `b` given that `a` and `b` are Lorentz normalized pointlike vectors -pub fn translate(a: &na::Vector4, b: &na::Vector4) -> na::Matrix4 { - let a_plus_b = a + b; - na::Matrix4::::identity() - minkowski_outer_product(b, a) * na::convert::<_, N>(2.0) - + minkowski_outer_product(&a_plus_b, &a_plus_b) / (N::one() - mip(a, b)) +pub fn translate(a: &MVector, b: &MVector) -> MIsometry { + let a_plus_b = *a + *b; + MIsometry( + (na::Matrix4::::identity()) - (b.minkowski_outer_product(a) * na::convert::<_, N>(2.0)) + + ((a_plus_b.minkowski_outer_product(&a_plus_b)) / (N::one() - a.mip(b))), + ) } /// Transform that translates the origin in the direction of the given vector with distance equal to its magnitude -pub fn translate_along(v: &na::Vector3) -> na::Matrix4 { +pub fn translate_along(v: &na::Vector3) -> MIsometry { let norm = v.norm(); if norm == na::zero() { - return na::Matrix4::identity(); + return MIsometry::identity(); } // g = Lorentz gamma factor let g = norm.cosh(); let bgc = norm.sinhc(); - translate(&origin(), &(v * bgc).insert_row(3, g)) + translate(&MVector::origin(), &MVector((v * bgc).insert_row(3, g))) } /// 4D reflection around a normal vector; length is not significant (so long as it's nonzero) @@ -66,34 +356,12 @@ pub fn euclidean_reflect(v: &na::Vector4) -> na::Matrix4 na::Matrix4::identity() - v * v.transpose() * (na::convert::<_, N>(2.0) / v.norm_squared()) } -pub fn midpoint(a: &na::Vector4, b: &na::Vector4) -> na::Vector4 { - a * (mip(b, b) * mip(a, b)).sqrt() + b * (mip(a, a) * mip(a, b)).sqrt() -} - -pub fn distance(a: &na::Vector4, b: &na::Vector4) -> N { - (sqr(mip(a, b)) / (mip(a, a) * mip(b, b))).sqrt().acosh() +pub fn midpoint(a: &MVector, b: &MVector) -> MVector { + *a * (b.mip(b) * a.mip(b)).sqrt() + *b * (a.mip(a) * a.mip(b)).sqrt() } -pub fn origin() -> na::Vector4 { - na::Vector4::new(na::zero(), na::zero(), na::zero(), na::one()) -} - -pub fn lorentz_normalize(v: &na::Vector4) -> na::Vector4 { - let sf2 = mip(v, v); - if sf2 == na::zero() { - return origin(); - } - let sf = sf2.abs().sqrt(); - v / sf -} - -pub fn renormalize_isometry(m: &na::Matrix4) -> na::Matrix4 { - let boost = translate(&origin(), &lorentz_normalize(&m.index((.., 3)).into())); - let inverse_boost = mtranspose(&boost); - let rotation = renormalize_rotation_reflection( - &(inverse_boost * m).fixed_view::<3, 3>(0, 0).clone_owned(), - ); - boost * rotation.to_homogeneous() +pub fn distance(a: &MVector, b: &MVector) -> N { + (sqr(a.mip(b)) / (a.mip(a) * b.mip(b))).sqrt().acosh() } #[rustfmt::skip] @@ -110,32 +378,11 @@ fn renormalize_rotation_reflection(m: &na::Matrix3) -> n ) } -/// Whether an isometry reverses winding with respect to the norm -pub fn parity(m: &na::Matrix4) -> bool { - m.fixed_view::<3, 3>(0, 0).determinant() < na::zero::() -} - -/// Minkowski inner product, aka _h -pub fn mip(a: &na::Vector4, b: &na::Vector4) -> N { - a.x * b.x + a.y * b.y + a.z * b.z - a.w * b.w -} - #[inline] pub fn sqr(x: N) -> N { x * x } -/// Minkowski transpose. Inverse for hyperbolic isometries -#[rustfmt::skip] -pub fn mtranspose(m: &na::Matrix4) -> na::Matrix4 { - na::Matrix4::new( - m.m11, m.m21, m.m31, -m.m41, - m.m12, m.m22, m.m32, -m.m42, - m.m13, m.m23, m.m33, -m.m43, - -m.m14, -m.m24, -m.m34, m.m44, - ) -} - /// Updates `subject` by moving it along the line determined by `projection_direction` so that /// its dot product with `normal` is `distance`. This effectively projects vectors onto the plane /// `distance` units away from the origin with normal `normal`. The projection is non-orthogonal in @@ -179,11 +426,30 @@ pub fn tuv_to_xyz, N: Copy>(t_axis: usi result } -fn minkowski_outer_product( - a: &na::Vector4, - b: &na::Vector4, -) -> na::Matrix4 { - *a * na::RowVector4::new(b.x, b.y, b.z, -b.w) +#[cfg(test)] +impl approx::AbsDiffEq> for MIsometry { + type Epsilon = N; + #[inline] + fn default_epsilon() -> Self::Epsilon { + na::Matrix4::::default_epsilon() + } + #[inline] + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + self.0.abs_diff_eq(&other.0, epsilon) + } +} + +#[cfg(test)] +impl approx::AbsDiffEq> for MVector { + type Epsilon = N; + #[inline] + fn default_epsilon() -> Self::Epsilon { + na::Vector4::::default_epsilon() + } + #[inline] + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + self.0.abs_diff_eq(&other.0, epsilon) + } } #[cfg(test)] @@ -195,12 +461,14 @@ mod tests { #[rustfmt::skip] fn reflect_example() { assert_abs_diff_eq!( - reflect(&lorentz_normalize(&na::Vector4::new(0.5, 0.0, 0.0, 1.0))), - na::Matrix4::new( - 1.666, 0.0, 0.0, -1.333, - 0.0 , 1.0, 0.0, 0.0, - 0.0 , 0.0, 1.0, 0.0, - 1.333, 0.0, 0.0, -1.666 + MVector::new(0.5, 0.0, 0.0, 1.0).lorentz_normalize().reflect(), + MIsometry( + na::Matrix4::new( + 1.666, 0.0, 0.0, -1.333, + 0.0 , 1.0, 0.0, 0.0, + 0.0 , 0.0, 1.0, 0.0, + 1.333, 0.0, 0.0, -1.666 + ) ), epsilon = 1e-3 ); @@ -211,14 +479,16 @@ mod tests { fn translate_example() { assert_abs_diff_eq!( translate( - &lorentz_normalize(&na::Vector4::new(-0.5, -0.5, 0.0, 1.0)), - &lorentz_normalize(&na::Vector4::new(0.3, -0.7, 0.0, 1.0)) + &MVector::new(-0.5, -0.5, 0.0, 1.0).lorentz_normalize(), + &MVector::new(0.3, -0.7, 0.0, 1.0).lorentz_normalize() ), - na::Matrix4::new( - 1.676, 0.814, 0.0, 1.572, - -1.369, 0.636, 0.0, -1.130, - 0.0, 0.0, 1.0, 0.0, - 1.919, 0.257, 0.0, 2.179, + MIsometry( + na::Matrix4::new( + 1.676, 0.814, 0.0, 1.572, + -1.369, 0.636, 0.0, -1.130, + 0.0, 0.0, 1.0, 0.0, + 1.919, 0.257, 0.0, 2.179, + ) ), epsilon = 1e-3 ); @@ -226,9 +496,9 @@ mod tests { #[test] fn translate_identity() { - let a = lorentz_normalize(&na::Vector4::new(-0.5, -0.5, 0.0, 1.0)); - let b = lorentz_normalize(&na::Vector4::new(0.3, -0.7, 0.0, 1.0)); - let o = na::Vector4::new(0.0, 0.0, 0.0, 1.0); + let a = MVector::new(-0.5, -0.5, 0.0, 1.0).lorentz_normalize(); + let b = MVector::new(0.3, -0.7, 0.0, 1.0).lorentz_normalize(); + let o = MVector::new(0.0, 0.0, 0.0, 1.0); assert_abs_diff_eq!( translate(&a, &b), translate(&o, &a) * translate(&o, &(translate(&a, &o) * b)) * translate(&a, &o), @@ -238,9 +508,9 @@ mod tests { #[test] fn translate_equivalence() { - let a = lorentz_normalize(&na::Vector4::new(-0.5, -0.5, 0.0, 1.0)); - let o = na::Vector4::new(0.0, 0.0, 0.0, 1.0); - let direction = a.xyz().normalize(); + let a = MVector::new(-0.5, -0.5, 0.0, 1.0).lorentz_normalize(); + let o = MVector::new(0.0, 0.0, 0.0, 1.0); + let direction = a.0.xyz().normalize(); let distance = dbg!(distance(&o, &a)); assert_abs_diff_eq!( translate(&o, &a), @@ -253,28 +523,28 @@ mod tests { fn translate_distance() { let dx = 2.3; let xf = translate_along(&(na::Vector3::x() * dx)); - assert_abs_diff_eq!(dx, distance(&origin(), &(xf * origin()))); + assert_abs_diff_eq!(dx, distance(&MVector::origin(), &(xf * MVector::origin()))); } #[test] fn distance_example() { - let a = na::Vector4::new(0.2, 0.0, 0.0, 1.0); - let b = na::Vector4::new(-0.5, -0.5, 0.0, 1.0); + let a = MVector::new(0.2, 0.0, 0.0, 1.0); + let b = MVector::new(-0.5, -0.5, 0.0, 1.0); // Paper doubles distances for reasons unknown assert_abs_diff_eq!(distance(&a, &b), 2.074 / 2.0, epsilon = 1e-3); } #[test] fn distance_commutative() { - let p = HPoint::new(-1.0, -1.0, 0.0).to_homogeneous(); - let q = HPoint::new(1.0, -1.0, 0.0).to_homogeneous(); + let p = MVector::from_gans(&na::Vector3::new(-1.0, -1.0, 0.0)); + let q = MVector::from_gans(&na::Vector3::new(1.0, -1.0, 0.0)); assert_abs_diff_eq!(distance(&p, &q), distance(&q, &p)); } #[test] fn midpoint_distance() { - let p = HPoint::new(-1.0, -1.0, 0.0).to_homogeneous(); - let q = HPoint::new(1.0, -1.0, 0.0).to_homogeneous(); + let p = MVector::from_gans(&na::Vector3::new(-1.0, -1.0, 0.0)); + let q = MVector::from_gans(&na::Vector3::new(1.0, -1.0, 0.0)); let m = midpoint(&p, &q); assert_abs_diff_eq!(distance(&p, &m), distance(&m, &q), epsilon = 1e-5); assert_abs_diff_eq!(distance(&p, &m) * 2.0, distance(&p, &q), epsilon = 1e-5); @@ -283,44 +553,44 @@ mod tests { #[test] fn renormalize_translation() { let mat = translate( - &lorentz_normalize(&na::Vector4::new(-0.5, -0.5, 0.0, 1.0)), - &lorentz_normalize(&na::Vector4::new(0.3, -0.7, 0.0, 1.0)), + &MVector::new(-0.5, -0.5, 0.0, 1.0).lorentz_normalize(), + &MVector::new(0.3, -0.7, 0.0, 1.0).lorentz_normalize(), ); - assert_abs_diff_eq!(renormalize_isometry(&mat), mat, epsilon = 1e-5); + assert_abs_diff_eq!(mat.renormalize_isometry(), mat, epsilon = 1e-5); } #[test] #[rustfmt::skip] fn renormalize_reflection() { - let mat = na::Matrix4::new(-1.0, 0.0, 0.0, 0.0, + let mat = MIsometry(na::Matrix4::new(-1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0); - assert_abs_diff_eq!(renormalize_isometry(&mat), mat, epsilon = 1e-5); + 0.0, 0.0, 0.0, 1.0)); + assert_abs_diff_eq!(mat.renormalize_isometry(), mat, epsilon = 1e-5); } #[test] #[rustfmt::skip] fn renormalize_normalizes_matrix() { // Matrix chosen with random entries between -1 and 1 - let error = na::Matrix4::new( + let error = MIsometry(na::Matrix4::new( -0.77, -0.21, 0.57, -0.59, 0.49, -0.68, 0.36, 0.68, -0.75, -0.54, -0.13, -0.59, - -0.57, -0.80, 0.00, -0.53); + -0.57, -0.80, 0.00, -0.53)); // translation with some error - let mat = translate( - &lorentz_normalize(&na::Vector4::new(-0.5, -0.5, 0.0, 1.0)), - &lorentz_normalize(&na::Vector4::new(0.3, -0.7, 0.0, 1.0)), - ) + error * 0.05; + let mat = MIsometry(translate( + &MVector::new(-0.5, -0.5, 0.0, 1.0).lorentz_normalize(), + &MVector::new(0.3, -0.7, 0.0, 1.0).lorentz_normalize(), + ).0 + error.0 * 0.05); - let normalized_mat = renormalize_isometry(&mat); + let normalized_mat = mat.renormalize_isometry(); // Check that the matrix is actually normalized assert_abs_diff_eq!( - mtranspose(&normalized_mat) * normalized_mat, - na::Matrix4::identity(), + normalized_mat.mtranspose() * normalized_mat, + MIsometry::identity(), epsilon = 1e-5 ); } diff --git a/common/src/node.rs b/common/src/node.rs index 711378a7..ac7e02cb 100644 --- a/common/src/node.rs +++ b/common/src/node.rs @@ -12,7 +12,7 @@ use crate::proto::{BlockUpdate, Position, SerializedVoxelData}; use crate::voxel_math::{ChunkDirection, CoordAxis, CoordSign, Coords}; use crate::world::Material; use crate::worldgen::NodeState; -use crate::{margins, math, Chunks}; +use crate::{margins, Chunks}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ChunkId { @@ -40,7 +40,7 @@ impl Graph { pub fn get_relative_up(&self, position: &Position) -> Option> { let node = self.get(position.node).as_ref()?; Some(na::UnitVector3::new_normalize( - (math::mtranspose(&position.local) * node.state.up_direction()).xyz(), + (position.local.mtranspose() * node.state.up_direction()).xyz(), )) } @@ -373,9 +373,9 @@ impl VoxelAABB { radius: f32, ) -> Option { // Convert the ray to grid coordinates - let grid_start = - na::Point3::from_homogeneous(ray.position).unwrap() * layout.dual_to_grid_factor(); - let grid_end = na::Point3::from_homogeneous(ray.ray_point(tanh_distance)).unwrap() + let grid_start = na::Point3::from_homogeneous(ray.position.into()).unwrap() + * layout.dual_to_grid_factor(); + let grid_end = na::Point3::from_homogeneous(ray.ray_point(tanh_distance).into()).unwrap() * layout.dual_to_grid_factor(); // Convert the radius to grid coordinates using a crude conservative estimate let max_grid_radius = radius * layout.dual_to_grid_factor(); @@ -433,6 +433,7 @@ mod tests { use std::collections::HashSet; use crate::math; + use crate::math::{MIsometry, MVector}; use super::*; @@ -445,9 +446,11 @@ mod tests { let layout = ChunkLayout::new(dimension); // Pick an arbitrary ray by transforming the positive-x-axis ray. - let ray = na::Rotation3::from_axis_angle(&na::Vector3::z_axis(), 0.4).to_homogeneous() - * math::translate_along(&na::Vector3::new(0.2, 0.3, 0.1)) - * &Ray::new(na::Vector4::w(), na::Vector4::x()); + let ray = MIsometry::rotation_to_homogeneous(na::Rotation3::from_axis_angle( + &na::Vector3::z_axis(), + 0.4, + )) * math::translate_along(&na::Vector3::new(0.2, 0.3, 0.1)) + * &Ray::new(MVector::w(), MVector::x()); let tanh_distance = 0.2; let radius = 0.1; @@ -458,9 +461,8 @@ mod tests { let num_ray_test_points = 20; let ray_test_points: Vec<_> = (0..num_ray_test_points) .map(|i| { - math::lorentz_normalize( - &ray.ray_point(tanh_distance * (i as f32 / (num_ray_test_points - 1) as f32)), - ) + ray.ray_point(tanh_distance * (i as f32 / (num_ray_test_points - 1) as f32)) + .lorentz_normalize() }) .collect(); @@ -480,14 +482,14 @@ mod tests { continue; } - let mut plane_normal = na::Vector4::zeros(); + let mut plane_normal = MVector::zero(); plane_normal[t_axis] = 1.0; plane_normal[3] = layout.grid_to_dual(t); - let plane_normal = math::lorentz_normalize(&plane_normal); + let plane_normal = plane_normal.lorentz_normalize(); for test_point in &ray_test_points { assert!( - math::mip(test_point, &plane_normal).abs() > radius.sinh(), + test_point.mip(&plane_normal).abs() > radius.sinh(), "Plane not covered: t_axis={t_axis}, t={t}, test_point={test_point:?}", ); } @@ -502,7 +504,7 @@ mod tests { // For a given axis, all lines have the same direction, so set up the appropriate vector // in advance. - let mut line_direction = na::Vector4::zeros(); + let mut line_direction = MVector::zero(); line_direction[t_axis] = 1.0; let line_direction = line_direction; @@ -513,16 +515,16 @@ mod tests { continue; } - let mut line_position = na::Vector4::zeros(); + let mut line_position = MVector::zero(); line_position[u_axis] = layout.grid_to_dual(u); line_position[v_axis] = layout.grid_to_dual(v); line_position[3] = 1.0; - let line_position = math::lorentz_normalize(&line_position); + let line_position = line_position.lorentz_normalize(); for test_point in &ray_test_points { assert!( - (math::mip(test_point, &line_position).powi(2) - - math::mip(test_point, &line_direction).powi(2)) + (test_point.mip(&line_position).powi(2) + - test_point.mip(&line_direction).powi(2)) .sqrt() > radius.cosh(), "Line not covered: t_axis={t_axis}, u={u}, v={v}, test_point={test_point:?}", @@ -543,16 +545,17 @@ mod tests { continue; } - let point_position = math::lorentz_normalize(&na::Vector4::new( + let point_position = MVector::new( layout.grid_to_dual(x), layout.grid_to_dual(y), layout.grid_to_dual(z), 1.0, - )); + ) + .lorentz_normalize(); for test_point in &ray_test_points { assert!( - -math::mip(test_point, &point_position) > radius.cosh(), + -test_point.mip(&point_position) > radius.cosh(), "Point not covered: x={x}, y={y}, z={z}, test_point={test_point:?}", ); } diff --git a/common/src/plane.rs b/common/src/plane.rs index 556d9a44..72888021 100644 --- a/common/src/plane.rs +++ b/common/src/plane.rs @@ -2,13 +2,13 @@ use std::ops::{Mul, Neg}; use crate::{ dodeca::{Side, Vertex}, - math::{lorentz_normalize, mip}, + math::{MIsometry, MVector}, }; /// A hyperbolic plane #[derive(Debug, Copy, Clone)] pub struct Plane { - normal: na::Vector4, + normal: MVector, } impl From for Plane { @@ -24,7 +24,7 @@ impl From>> for Plane { /// A plane passing through the origin fn from(x: na::Unit>) -> Self { Self { - normal: x.into_inner().push(na::zero()), + normal: MVector::from(x), } } } @@ -46,24 +46,24 @@ impl Mul> for Side { } } -impl<'a, N: na::RealField + Copy> Mul> for &'a na::Matrix4 { +impl<'a, N: na::RealField + Copy> Mul> for &'a MIsometry { type Output = Plane; fn mul(self, rhs: Plane) -> Plane { Plane { - normal: lorentz_normalize(&(self * rhs.normal)), + normal: (*self * rhs.normal).lorentz_normalize(), } } } impl Plane { /// Hyperbolic normal vector identifying the plane - pub fn normal(&self) -> &na::Vector4 { + pub fn normal(&self) -> &MVector { &self.normal } /// Shortest distance between the plane and a point - pub fn distance_to(&self, point: &na::Vector4) -> N { - let mip_value = mip(&self.normal, point); + pub fn distance_to(&self, point: &MVector) -> N { + let mip_value = self.normal.mip(point); // Workaround for bug fixed in rust PR #72486 mip_value.abs().asinh() * mip_value.signum() } @@ -72,7 +72,7 @@ impl Plane { impl Plane { /// Like `distance_to`, but using chunk coordinates for a chunk in the same node space pub fn distance_to_chunk(&self, chunk: Vertex, coord: &na::Vector3) -> f64 { - let pos = lorentz_normalize(&(chunk.chunk_to_node_f64() * coord.push(1.0))); + let pos = (MVector::from(chunk.chunk_to_node_f64() * coord.push(1.0))).lorentz_normalize(); self.distance_to(&pos) } } @@ -80,7 +80,7 @@ impl Plane { #[cfg(test)] mod tests { use super::*; - use crate::math::{origin, translate_along}; + use crate::math::translate_along; use approx::*; #[test] @@ -94,7 +94,7 @@ mod tests { let plane = Plane::from(axis); assert_abs_diff_eq!( plane.distance_to( - &(translate_along(&(axis.into_inner() * distance)) * origin()) + &(translate_along(&(axis.into_inner() * distance)) * MVector::origin()) ), distance ); diff --git a/common/src/proto.rs b/common/src/proto.rs index b8db0c82..5895f55c 100644 --- a/common/src/proto.rs +++ b/common/src/proto.rs @@ -1,8 +1,8 @@ use serde::{Deserialize, Serialize}; use crate::{ - dodeca, graph::NodeId, node::ChunkId, voxel_math::Coords, world::Material, EntityId, SimConfig, - Step, + dodeca, graph::NodeId, math::MIsometry, node::ChunkId, voxel_math::Coords, world::Material, + EntityId, SimConfig, Step, }; #[derive(Debug, Serialize, Deserialize)] @@ -19,14 +19,14 @@ pub struct ServerHello { #[derive(Debug, Serialize, Deserialize, Copy, Clone)] pub struct Position { pub node: NodeId, - pub local: na::Matrix4, + pub local: MIsometry, } impl Position { pub fn origin() -> Self { Self { node: NodeId::ROOT, - local: na::Matrix4::identity(), + local: MIsometry::identity(), } } } diff --git a/common/src/sim_config.rs b/common/src/sim_config.rs index cb8c0d83..124cb1d0 100644 --- a/common/src/sim_config.rs +++ b/common/src/sim_config.rs @@ -2,7 +2,7 @@ use std::time::Duration; use serde::{Deserialize, Serialize}; -use crate::{dodeca, math}; +use crate::{dodeca, math, math::MVector}; /// Manually specified simulation config parameters #[derive(Serialize, Deserialize, Default)] @@ -69,8 +69,8 @@ impl SimConfig { /// Compute the scaling factor from meters to absolute units, given the number of voxels in a chunk /// and the approximate size of a voxel in meters. fn meters_to_absolute(chunk_size: u8, voxel_size: f32) -> f32 { - let a = dodeca::Vertex::A.chunk_to_node() * na::Vector4::new(1.0, 0.5, 0.5, 1.0); - let b = dodeca::Vertex::A.chunk_to_node() * na::Vector4::new(0.0, 0.5, 0.5, 1.0); + let a = MVector::from(dodeca::Vertex::A.chunk_to_node() * na::Vector4::new(1.0, 0.5, 0.5, 1.0)); + let b = MVector::from(dodeca::Vertex::A.chunk_to_node() * na::Vector4::new(0.0, 0.5, 0.5, 1.0)); let minimum_chunk_face_separation = math::distance(&a, &b); let absolute_voxel_size = minimum_chunk_face_separation / f32::from(chunk_size); absolute_voxel_size / voxel_size diff --git a/common/src/traversal.rs b/common/src/traversal.rs index bd4d0fd1..82695158 100644 --- a/common/src/traversal.rs +++ b/common/src/traversal.rs @@ -6,7 +6,7 @@ use crate::{ collision_math::Ray, dodeca::{self, Side, Vertex}, graph::{Graph, NodeId}, - math, + math::{MIsometry, MVector}, node::ChunkId, proto::Position, }; @@ -16,12 +16,12 @@ pub fn ensure_nearby(graph: &mut Graph, start: &Position, distance: f32) { // We do a breadth-first instead of a depth-first traversal here to ensure that we take the // minimal path to each node. This greatly helps prevent error from accumulating due to // hundreds of transformations being composed. - let mut pending = VecDeque::<(NodeId, na::Matrix4)>::new(); + let mut pending = VecDeque::<(NodeId, MIsometry)>::new(); let mut visited = FxHashSet::::default(); - pending.push_back((start.node, na::Matrix4::identity())); + pending.push_back((start.node, MIsometry::identity())); visited.insert(start.node); - let start_p = start.local * math::origin(); + let start_p = start.local * MVector::origin(); while let Some((node, current_transform)) = pending.pop_front() { for side in Side::iter() { @@ -30,9 +30,9 @@ pub fn ensure_nearby(graph: &mut Graph, start: &Position, distance: f32) { continue; } visited.insert(neighbor); - let neighbor_transform = current_transform * side.reflection(); - let neighbor_p = neighbor_transform * math::origin(); - if -math::mip(&start_p, &neighbor_p) > distance.cosh() { + let neighbor_transform = current_transform * *side.reflection(); + let neighbor_p = neighbor_transform * MVector::origin(); + if -start_p.mip(&neighbor_p) > distance.cosh() { continue; } pending.push_back((neighbor, neighbor_transform)); @@ -46,10 +46,10 @@ pub fn nearby_nodes( graph: &Graph, start: &Position, distance: f32, -) -> Vec<(NodeId, na::Matrix4)> { +) -> Vec<(NodeId, MIsometry)> { struct PendingNode { id: NodeId, - transform: na::Matrix4, + transform: MIsometry, } let mut result = Vec::new(); @@ -59,20 +59,20 @@ pub fn nearby_nodes( // hundreds of transformations being composed. let mut pending = VecDeque::::new(); let mut visited = FxHashSet::::default(); - let start_p = start.local * math::origin(); + let start_p = start.local * MVector::origin(); pending.push_back(PendingNode { id: start.node, - transform: na::Matrix4::identity(), + transform: MIsometry::identity(), }); visited.insert(start.node); while let Some(current) = pending.pop_front() { - let current_p = current.transform * math::origin(); - if -math::mip(&start_p, ¤t_p) > distance.cosh() { + let current_p = current.transform * MVector::origin(); + if -start_p.mip(¤t_p) > distance.cosh() { continue; } - result.push((current.id, na::convert(current.transform))); + result.push((current.id, current.transform)); for side in Side::iter() { let neighbor = match graph.neighbor(current.id, side) { @@ -84,7 +84,7 @@ pub fn nearby_nodes( } pending.push_back(PendingNode { id: neighbor, - transform: current.transform * side.reflection(), + transform: current.transform * *side.reflection(), }); visited.insert(neighbor); } @@ -100,9 +100,9 @@ pub struct RayTraverser<'a> { /// Chunks that have already been added to `iterator_queue` and shouldn't be added again visited_chunks: FxHashSet, /// Chunks that should be returned by `next` in the future - iterator_queue: VecDeque<(Option, Vertex, na::Matrix4)>, + iterator_queue: VecDeque<(Option, Vertex, MIsometry)>, /// Chunks whose neighbors should be queried in the future - search_queue: VecDeque<(Option, Vertex, na::Matrix4)>, + search_queue: VecDeque<(Option, Vertex, MIsometry)>, klein_lower_boundary: f32, klein_upper_boundary: f32, } @@ -113,7 +113,8 @@ impl<'a> RayTraverser<'a> { let mut closest_vertex = Vertex::A; let mut closest_vertex_cosh_distance = f32::INFINITY; for vertex in Vertex::iter() { - let vertex_cosh_distance = (vertex.node_to_dual() * position.local * math::origin()).w; + let vertex_cosh_distance = + (*vertex.node_to_dual() * position.local * MVector::origin()).w; if vertex_cosh_distance < closest_vertex_cosh_distance { closest_vertex = vertex; closest_vertex_cosh_distance = vertex_cosh_distance; @@ -143,7 +144,7 @@ impl<'a> RayTraverser<'a> { } } - pub fn next(&mut self, tanh_distance: f32) -> Option<(Option, na::Matrix4)> { + pub fn next(&mut self, tanh_distance: f32) -> Option<(Option, MIsometry)> { loop { // Return the next entry that's queued up if let Some(entry @ (node, vertex, node_transform)) = self.iterator_queue.pop_front() { @@ -151,7 +152,7 @@ impl<'a> RayTraverser<'a> { // Combine node and vertex, and convert node transform to chunk transform return Some(( node.map(|node| ChunkId::new(node, vertex)), - vertex.node_to_dual() * node_transform, + *vertex.node_to_dual() * node_transform, )); } @@ -162,14 +163,14 @@ impl<'a> RayTraverser<'a> { continue; }; - let local_ray = vertex.node_to_dual() * node_transform * self.ray; + let local_ray = *vertex.node_to_dual() * node_transform * self.ray; // Compute the Klein-Beltrami coordinates of the ray segment's endpoints. To check whether neighboring chunks // are needed, we need to check whether the endpoints of the line segments lie outside the boundaries of the square // bounded by `klein_lower_boundary` and `klein_upper_boundary`. - let klein_ray_start = na::Point3::from_homogeneous(local_ray.position).unwrap(); + let klein_ray_start = na::Point3::from_homogeneous(local_ray.position.into()).unwrap(); let klein_ray_end = - na::Point3::from_homogeneous(local_ray.ray_point(tanh_distance)).unwrap(); + na::Point3::from_homogeneous(local_ray.ray_point(tanh_distance).into()).unwrap(); // Add neighboring chunks as necessary based on a conservative AABB check, using one coordinate at a time. for axis in 0..3 { @@ -178,7 +179,7 @@ impl<'a> RayTraverser<'a> { || klein_ray_end[axis] <= self.klein_lower_boundary { let side = vertex.canonical_sides()[axis]; - let next_node_transform = side.reflection() * node_transform; + let next_node_transform = *side.reflection() * node_transform; // Crude check to ensure that the neighboring chunk's node can be in the path of the ray. For simplicity, this // check treats each node as a sphere and assumes the ray is pointed directly towards its center. The check is // needed because chunk generation uses this approximation, and this check is not guaranteed to pass near corners diff --git a/common/src/worldgen.rs b/common/src/worldgen.rs index 57f14e2a..d0b88d0b 100644 --- a/common/src/worldgen.rs +++ b/common/src/worldgen.rs @@ -5,6 +5,7 @@ use crate::{ dodeca::{Side, Vertex}, graph::{Graph, NodeId}, margins, math, + math::MVector, node::{ChunkId, VoxelData}, terraingen::VoronoiInfo, world::Material, @@ -119,8 +120,8 @@ impl NodeState { } } - pub fn up_direction(&self) -> na::Vector4 { - self.surface.normal().cast() + pub fn up_direction(&self) -> MVector { + self.surface.normal().to_f32() } } diff --git a/server/src/sim.rs b/server/src/sim.rs index dac719b4..e5b761ad 100644 --- a/server/src/sim.rs +++ b/server/src/sim.rs @@ -146,7 +146,7 @@ impl Sim { if let Some(pos) = entity.get::<&Position>() { components.push(( ComponentType::Position as u64, - postcard::to_stdvec(pos.local.as_ref()).unwrap(), + postcard::to_stdvec(&pos.local.as_ref()).unwrap(), )); } if let Some(ch) = entity.get::<&Character>() {