diff --git a/rocketsim/Cargo.toml b/rocketsim/Cargo.toml index 0b2e1441..a9b5af7d 100644 --- a/rocketsim/Cargo.toml +++ b/rocketsim/Cargo.toml @@ -19,7 +19,7 @@ byteorder = "1.5.0" derive_hash_fast = "0.2.3" env_logger = "0.11.8" fastrand = "2.3.0" -glam = "0.32.1" +glam = { version = "0.33.0", features = ["debug-glam-assert"] } log = "0.4.29" rocketsim_derive = { path = "./rocketsim_derive" } zerocopy = { version = "0.8.34", features = ["derive"] } @@ -42,7 +42,7 @@ test-case = "3.3.1" [target.'cfg(not(any(target_arch = "x86", target_arch = "x86_64")))'.dependencies] # use glam with experimental support for portable SIMD when not running on x86 # architectures -glam = { version = "0.32.1", features = ["core-simd"], optional = true } +glam = { version = "0.33.0", features = ["core-simd", "debug-glam-assert"], optional = true } [features] # default = ["vis"] diff --git a/rocketsim/src/bullet/collision/broadphase/broadphase_proxy.rs b/rocketsim/src/bullet/collision/broadphase/broadphase_proxy.rs index f139b7fe..303ead22 100644 --- a/rocketsim/src/bullet/collision/broadphase/broadphase_proxy.rs +++ b/rocketsim/src/bullet/collision/broadphase/broadphase_proxy.rs @@ -1,3 +1,5 @@ +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor}; + use glam::USizeVec3; use crate::shared::Aabb; @@ -8,7 +10,66 @@ pub enum CollisionFilterGroups { HoopsNet = (1 << 2), DropshotTile = (1 << 3), DropshotFloor = (1 << 4), - All = -1, +} + +impl CollisionFilterGroups { + pub const ALL: u8 = Self::Default as u8 + | Self::Static as u8 + | Self::HoopsNet as u8 + | Self::DropshotTile as u8 + | Self::DropshotFloor as u8; +} + +impl BitOrAssign for u8 { + fn bitor_assign(&mut self, rhs: CollisionFilterGroups) { + *self |= rhs as u8; + } +} + +impl BitAndAssign for u8 { + fn bitand_assign(&mut self, rhs: CollisionFilterGroups) { + *self &= rhs as u8; + } +} + +impl BitAnd for u8 { + type Output = u8; + + fn bitand(self, rhs: CollisionFilterGroups) -> Self::Output { + self & (rhs as u8) + } +} + +impl BitOr for u8 { + type Output = u8; + + fn bitor(self, rhs: CollisionFilterGroups) -> Self::Output { + self | (rhs as u8) + } +} + +impl BitXor for u8 { + type Output = u8; + + fn bitxor(self, rhs: CollisionFilterGroups) -> Self::Output { + self ^ (rhs as u8) + } +} + +impl BitXor for CollisionFilterGroups { + type Output = u8; + + fn bitxor(self, rhs: Self) -> Self::Output { + self as u8 ^ rhs as u8 + } +} + +impl BitOr for CollisionFilterGroups { + type Output = u8; + + fn bitor(self, rhs: Self) -> Self::Output { + self as u8 | rhs as u8 + } } pub struct BroadphaseProxy { diff --git a/rocketsim/src/bullet/collision/broadphase/grid_broadphase.rs b/rocketsim/src/bullet/collision/broadphase/grid_broadphase.rs index 9ed03c4e..3267a38a 100644 --- a/rocketsim/src/bullet/collision/broadphase/grid_broadphase.rs +++ b/rocketsim/src/bullet/collision/broadphase/grid_broadphase.rs @@ -204,7 +204,7 @@ impl GridBroadphase { let sbp = &mut self.handles[proxy_idx]; sbp.aabb = aabb; - if sbp.collision_filter_group & CollisionFilterGroups::Static as u8 != 0 { + if (sbp.collision_filter_group & CollisionFilterGroups::Static) != 0 { self.cell_grid.update_cells_static(sbp, col_obj, proxy_idx); } else { let old_idx = sbp.cell_idx; @@ -231,7 +231,7 @@ impl GridBroadphase { ) -> usize { debug_assert!(aabb.min.cmple(aabb.max).all()); - let is_static = collision_filter_group & CollisionFilterGroups::Static as u8 != 0; + let is_static = (collision_filter_group & CollisionFilterGroups::Static) != 0; let new_handle_idx = self.handles.len(); let indices = self.cell_grid.get_cell_indices(aabb.min); let cell_idx = self.cell_grid.cell_indices_to_idx(indices); @@ -274,7 +274,7 @@ impl GridBroadphase { .enumerate() .skip(self.min_dyn_handle_idx) .filter(|(_, proxy)| { - proxy.collision_filter_group & CollisionFilterGroups::Static as u8 == 0 + (proxy.collision_filter_group & CollisionFilterGroups::Static) == 0 }) { let cell = &self.cell_grid.cells[proxy.cell_idx]; diff --git a/rocketsim/src/bullet/collision/dispatch/collision_dispatcher.rs b/rocketsim/src/bullet/collision/dispatch/collision_dispatcher.rs index ca255a1d..c726ea38 100644 --- a/rocketsim/src/bullet/collision/dispatch/collision_dispatcher.rs +++ b/rocketsim/src/bullet/collision/dispatch/collision_dispatcher.rs @@ -6,6 +6,7 @@ use super::{ use crate::bullet::{ collision::{ broadphase::{BroadphaseProxy, GridBroadphase}, + dispatch::convex_convex_collision_alg, narrowphase::persistent_manifold::{ContactAddedCallback, PersistentManifold}, shapes::collision_shape::CollisionShapes, }, @@ -79,6 +80,14 @@ impl CollisionDispatcher { compound, contact_added_callback, ), + CollisionShapes::ConvexHull(_) => convex_convex_collision_alg::process_collision( + &RigidBodyWrapper { + obj: col_obj_a, + world_trans: *col_obj_a.get_world_trans(), + }, + col_obj_b, + contact_added_callback, + ), _ => unreachable!(), }, CollisionShapes::TriangleMesh(mesh) => match col_obj_b.get_collision_shape() { @@ -160,6 +169,14 @@ impl CollisionDispatcher { contact_added_callback, ) } + CollisionShapes::Sphere(_) => convex_convex_collision_alg::process_collision( + &RigidBodyWrapper { + obj: col_obj_a, + world_trans: *col_obj_a.get_world_trans(), + }, + col_obj_b, + contact_added_callback, + ), _ => unreachable!(), }, CollisionShapes::Triangle(_) => unreachable!(), diff --git a/rocketsim/src/bullet/collision/dispatch/collision_world.rs b/rocketsim/src/bullet/collision/dispatch/collision_world.rs index 087168dd..efa6423f 100644 --- a/rocketsim/src/bullet/collision/dispatch/collision_world.rs +++ b/rocketsim/src/bullet/collision/dispatch/collision_world.rs @@ -39,18 +39,16 @@ impl CollisionWorld { filter_group: u8, filter_mask: u8, ) -> usize { - obj.world_array_idx = self.collision_objs.len(); + let idx = self.collision_objs.len(); + obj.world_array_idx = idx; let trans = obj.get_world_trans(); let aabb = obj.get_collision_shape().get_aabb(trans); - let proxy = self .broadphase_pair_cache .create_proxy(aabb, &obj, filter_group, filter_mask); obj.set_broadphase_handle(proxy); - - let idx = self.collision_objs.len(); self.collision_objs.push(obj); idx @@ -91,7 +89,12 @@ impl CollisionWorld { aabb += aabb2; } - debug_assert!(col_obj.is_static_obj() || (aabb.max - aabb.min).length_squared() < 1e12); + debug_assert!( + col_obj.is_static_obj() || (aabb.max - aabb.min).length_squared() < 1e12, + "object #{i} {:?} has invalid aabb: {:?}", + col_obj.user_idx, + aabb + ); self.broadphase_pair_cache .set_aabb(col_obj, col_obj.get_broadphase_handle(), aabb); } diff --git a/rocketsim/src/bullet/collision/dispatch/internal_edge_utility.rs b/rocketsim/src/bullet/collision/dispatch/internal_edge_utility.rs index dc8943ef..4fd1ec0c 100644 --- a/rocketsim/src/bullet/collision/dispatch/internal_edge_utility.rs +++ b/rocketsim/src/bullet/collision/dispatch/internal_edge_utility.rs @@ -115,12 +115,12 @@ impl ProcessTriangle for ConnectivityProcessor<'_> { let mut computed_normal_b = orn * self.shape.normal; if computed_normal_b.dot(tri.normal) < 0.0 { computed_normal_b *= -1.0; - info.flags |= TriInfoFlag::V0V1SwapNormalB as u8; + info.flags |= TriInfoFlag::V0V1SwapNormalB; } info.edge_v0_v1_angle = -corrected_angle; if is_convex { - info.flags |= TriInfoFlag::V0V1Convex as u8; + info.flags |= TriInfoFlag::V0V1Convex; } } 2 => { @@ -129,12 +129,12 @@ impl ProcessTriangle for ConnectivityProcessor<'_> { let mut computed_normal_b = orn * self.shape.normal; if computed_normal_b.dot(tri.normal) < 0.0 { computed_normal_b *= -1.0; - info.flags |= TriInfoFlag::V2V0SwapNormalB as u8; + info.flags |= TriInfoFlag::V2V0SwapNormalB; } info.edge_v2_v0_angle = -corrected_angle; if is_convex { - info.flags |= TriInfoFlag::V2V0Convex as u8; + info.flags |= TriInfoFlag::V2V0Convex; } } 3 => { @@ -143,12 +143,12 @@ impl ProcessTriangle for ConnectivityProcessor<'_> { let mut computed_normal_b = orn * self.shape.normal; if computed_normal_b.dot(tri.normal) < 0.0 { computed_normal_b *= -1.0; - info.flags |= TriInfoFlag::V1V2SwapNormalB as u8; + info.flags |= TriInfoFlag::V1V2SwapNormalB; } info.edge_v1_v2_angle = -corrected_angle; if is_convex { - info.flags |= TriInfoFlag::V1V2Convex as u8; + info.flags |= TriInfoFlag::V1V2Convex; } } _ => unreachable!(), @@ -273,14 +273,14 @@ pub fn adjust_internal_edge_contacts( } if info.edge_v0_v1_angle != 0.0 { - let is_edge_convex = (info.flags & TriInfoFlag::V0V1Convex as u8) != 0; + let is_edge_convex = (info.flags & TriInfoFlag::V0V1Convex) != 0; let swap_factor = f32::from(is_edge_convex) * 2.0 - 1.0; let edge = -tri.edges[0]; let n_a = swap_factor * tri.normal; let orn = Quat::from_axis_angle_simd(edge.normalize(), info.edge_v0_v1_angle); let mut computed_normal_b = orn * tri.normal; - if (info.flags & TriInfoFlag::V0V1SwapNormalB as u8) != 0 { + if (info.flags & TriInfoFlag::V0V1SwapNormalB) != 0 { computed_normal_b *= -1.0; } let n_b = swap_factor * computed_normal_b; @@ -319,14 +319,14 @@ pub fn adjust_internal_edge_contacts( } if info.edge_v1_v2_angle != 0.0 { - let is_edge_convex = (info.flags & TriInfoFlag::V1V2Convex as u8) != 0; + let is_edge_convex = (info.flags & TriInfoFlag::V1V2Convex) != 0; let swap_factor = f32::from(is_edge_convex) * 2.0 - 1.0; let edge = -tri.edges[1]; let n_a = swap_factor * tri.normal; let orn = Quat::from_axis_angle_simd(edge.normalize(), info.edge_v1_v2_angle); let mut computed_normal_b = orn * tri.normal; - if (info.flags & TriInfoFlag::V1V2SwapNormalB as u8) != 0 { + if (info.flags & TriInfoFlag::V1V2SwapNormalB) != 0 { computed_normal_b *= -1.0; } let n_b = swap_factor * computed_normal_b; @@ -365,14 +365,14 @@ pub fn adjust_internal_edge_contacts( } if info.edge_v2_v0_angle != 0.0 { - let is_edge_convex = (info.flags & TriInfoFlag::V2V0Convex as u8) != 0; + let is_edge_convex = (info.flags & TriInfoFlag::V2V0Convex) != 0; let swap_factor = f32::from(is_edge_convex) * 2.0 - 1.0; let edge = -tri.edges[2]; let n_a = swap_factor * tri.normal; let orn = Quat::from_axis_angle_simd(edge.normalize(), info.edge_v2_v0_angle); let mut computed_normal_b = orn * tri.normal; - if (info.flags & TriInfoFlag::V2V0SwapNormalB as u8) != 0 { + if (info.flags & TriInfoFlag::V2V0SwapNormalB) != 0 { computed_normal_b *= -1.0; } let n_b = swap_factor * computed_normal_b; diff --git a/rocketsim/src/bullet/collision/dispatch/ray_packet_callbacks.rs b/rocketsim/src/bullet/collision/dispatch/ray_packet_callbacks.rs index f5d0b1bf..19e09f8d 100644 --- a/rocketsim/src/bullet/collision/dispatch/ray_packet_callbacks.rs +++ b/rocketsim/src/bullet/collision/dispatch/ray_packet_callbacks.rs @@ -31,7 +31,7 @@ impl Default for RayResultCallbackBase { closest_hit_fraction: Vec4::ONE, collision_obj_idx: [None; 4], collision_filter_group: CollisionFilterGroups::Default as u8, - collision_filter_mask: CollisionFilterGroups::All as u8, + collision_filter_mask: CollisionFilterGroups::ALL, ignore_obj_world_idx: None, } } @@ -48,8 +48,8 @@ pub trait RayResultCallback { return false; } - proxy0.collision_filter_group & base.collision_filter_mask != 0 - && base.collision_filter_group & proxy0.collision_filter_mask != 0 + (proxy0.collision_filter_group & base.collision_filter_mask) != 0 + && (base.collision_filter_group & proxy0.collision_filter_mask) != 0 } fn add_single_result(&mut self, ray_result: LocalRayResult, ray_idx: usize); } diff --git a/rocketsim/src/bullet/collision/narrowphase/gjk/gjk_pair_detector.rs b/rocketsim/src/bullet/collision/narrowphase/gjk/gjk_pair_detector.rs index 3cc6f20a..a5ad480b 100644 --- a/rocketsim/src/bullet/collision/narrowphase/gjk/gjk_pair_detector.rs +++ b/rocketsim/src/bullet/collision/narrowphase/gjk/gjk_pair_detector.rs @@ -1,4 +1,4 @@ -use glam::Vec3A; +use glam::{Affine3A, Vec3A}; use super::{ GjkResult, closest_point_input::ClosestPointInput, penetration::calc_pen_depth, @@ -59,7 +59,13 @@ impl GjkPairDetector { check_simplex, mut degenerate_simplex, squared_distance, - } = self.update_separating_axis_and_simplex(input, shape_a, shape_b); + } = self.update_separating_axis_and_simplex( + &local_trans_a, + &local_trans_b, + shape_a, + shape_b, + input.maximum_distance_squared, + ); let mut is_valid = false; let mut distance = 0.0; @@ -111,18 +117,6 @@ impl GjkPairDetector { } if is_valid && distance * distance < input.maximum_distance_squared { - { - // in some degenerate cases (usually when the use uses very small margins) - // the contact normal is pointing the wrong direction - // todo: see if this can be removed :/ - let pos_a = shape_a.get_aabb(&local_trans_a).center(); - let pos_b = shape_b.get_aabb(&local_trans_b).center(); - let diff = pos_a - pos_b; - if diff.dot(diff).is_sign_negative() { - normal_in_b *= -1.0; - } - } - self.separating_axis = normal_in_b; self.separating_distance = distance; @@ -167,35 +161,35 @@ impl GjkPairDetector { fn update_separating_axis_and_simplex( &mut self, - input: &ClosestPointInput<'_>, + transform_a: &Affine3A, + transform_b: &Affine3A, shape_a: &CollisionShapes, shape_b: &CollisionShapes, + maximum_distance_squared: f32, ) -> GjkIterationResult { let mut check_simplex = false; let mut degenerate_simplex = 0; let mut squared_distance = f32::MAX; for _ in 0..MAX_ITERATIONS { - let separating_axis_in_a = input - .transform_a + let separating_axis_in_a = transform_a .matrix3 .mul_transpose_vec3a(-self.separating_axis); - let separating_axis_in_b = input - .transform_b + let separating_axis_in_b = transform_b .matrix3 .mul_transpose_vec3a(self.separating_axis); let p_in_a = shape_a.local_get_support_vertex_without_margin(separating_axis_in_a); - let p_world = input.transform_a.transform_point3a(p_in_a); + let p_world = transform_a.transform_point3a(p_in_a); let q_in_b = shape_b.local_get_support_vertex_without_margin(separating_axis_in_b); - let q_world = input.transform_b.transform_point3a(q_in_b); + let q_world = transform_b.transform_point3a(q_in_b); let w = p_world - q_world; let delta = self.separating_axis.dot(w); // Potential exit: the shapes are separated far enough. - if delta > 0.0 && delta * delta > squared_distance * input.maximum_distance_squared { + if delta > 0.0 && delta * delta > squared_distance * maximum_distance_squared { degenerate_simplex = 10; check_simplex = true; break; diff --git a/rocketsim/src/bullet/collision/narrowphase/gjk/penetration.rs b/rocketsim/src/bullet/collision/narrowphase/gjk/penetration.rs index 62a283d0..f6150ba0 100644 --- a/rocketsim/src/bullet/collision/narrowphase/gjk/penetration.rs +++ b/rocketsim/src/bullet/collision/narrowphase/gjk/penetration.rs @@ -68,12 +68,11 @@ fn distance( let mut w0 = Vec3A::ZERO; let mut w1 = Vec3A::ZERO; - for i in 0..simplex.rank { - let sv = gjk.store[simplex.c[i]]; + for (i, sv_d) in gjk.simplex_d().enumerate() { let weight = simplex.p[i]; if weight != 0.0 { - w0 += shape.support0::(sv.d) * weight; - w1 += shape.support1::(-sv.d) * weight; + w0 += shape.support0::(sv_d) * weight; + w1 += shape.support1::(-sv_d) * weight; } } diff --git a/rocketsim/src/bullet/collision/narrowphase/gjk/solver/epa2.rs b/rocketsim/src/bullet/collision/narrowphase/gjk/solver/epa2.rs index dd85dd96..8e37e35e 100644 --- a/rocketsim/src/bullet/collision/narrowphase/gjk/solver/epa2.rs +++ b/rocketsim/src/bullet/collision/narrowphase/gjk/solver/epa2.rs @@ -384,17 +384,15 @@ impl Epa2 { return EpaStatus::FallBack; } - let simplex = gjk.simplex(); - let mut status = EpaStatus::Valid; let mut sv_indices = [0usize; 4]; - for (i, gjk_index) in simplex.c.into_iter().enumerate() { + for (i, [w, d]) in gjk.simplex_w_d().enumerate() { let sv_index = self.next_sv; self.next_sv += 1; - self.sv_store[sv_index].w = gjk.store[gjk_index].w; - self.sv_store[sv_index].d = gjk.store[gjk_index].d; + self.sv_store[sv_index].w = w; + self.sv_store[sv_index].d = d; self.sv_store[sv_index].support_a = Vec3A::ZERO; self.sv_store[sv_index].support_b = Vec3A::ZERO; diff --git a/rocketsim/src/bullet/collision/narrowphase/gjk/solver/gjk.rs b/rocketsim/src/bullet/collision/narrowphase/gjk/solver/gjk.rs index d40d068c..dfc2ce6a 100644 --- a/rocketsim/src/bullet/collision/narrowphase/gjk/solver/gjk.rs +++ b/rocketsim/src/bullet/collision/narrowphase/gjk/solver/gjk.rs @@ -47,7 +47,7 @@ pub struct Gjk<'a> { ray: Vec3A, distance: f32, simplices: [GjkSimplex; 2], - pub store: [GjkSupport; 4], + store: [GjkSupport; 4], free: ArrayVec, current: usize, simplex: usize, @@ -77,8 +77,21 @@ impl<'a> Gjk<'a> { } } - pub const fn simplex(&self) -> GjkSimplex { - self.simplices[self.simplex] + pub const fn simplex(&self) -> &GjkSimplex { + &self.simplices[self.simplex] + } + + pub fn simplex_d(&self) -> impl Iterator + '_ { + let simplex = &self.simplices[self.simplex]; + simplex.c[..simplex.rank].iter().map(|&i| self.store[i].d) + } + + pub fn simplex_w_d(&self) -> impl Iterator + '_ { + let simplex = &self.simplices[self.simplex]; + simplex.c[..simplex.rank].iter().map(|&i| { + let sv = self.store[i]; + [sv.w, sv.d] + }) } pub fn evaluate(&mut self, guess: Vec3A) -> GjkStatus { diff --git a/rocketsim/src/bullet/collision/narrowphase/gjk/solver/voronoi_simplex.rs b/rocketsim/src/bullet/collision/narrowphase/gjk/solver/voronoi_simplex.rs index b7df0271..97610ae7 100644 --- a/rocketsim/src/bullet/collision/narrowphase/gjk/solver/voronoi_simplex.rs +++ b/rocketsim/src/bullet/collision/narrowphase/gjk/solver/voronoi_simplex.rs @@ -384,17 +384,17 @@ impl VoronoiSimplexSolver { true } - fn point_outside_of_plane(p: Vec3A, a: Vec3A, b: Vec3A, c: Vec3A, d: Vec3A) -> i32 { + fn point_outside_of_plane(p: Vec3A, a: Vec3A, b: Vec3A, c: Vec3A, d: Vec3A) -> Option { let normal = (b - a).cross(c - a); let signp = (p - a).dot(normal); let signd = (d - a).dot(normal); if signd * signd < DEGENERATE_TETRAHEDRON_EPS * DEGENERATE_TETRAHEDRON_EPS { - return -1; + return None; } - i32::from(signp * signd < 0.0) + Some(signp * signd < 0.0) } fn closest_pt_point_tetrahedron( @@ -405,41 +405,41 @@ impl VoronoiSimplexSolver { d: Vec3A, final_result: &mut SubSimplexClosestResult, ) -> bool { - let mut temp_result = SubSimplexClosestResult::new(); - final_result.closest_point_on_simplex = p; - final_result.used_vertices.reset(); final_result.used_vertices.used_vertex_a = true; final_result.used_vertices.used_vertex_b = true; final_result.used_vertices.used_vertex_c = true; final_result.used_vertices.used_vertex_d = true; - final_result.degenerate = false; - let point_outside_abc = Self::point_outside_of_plane(p, a, b, c, d); - let point_outside_acd = Self::point_outside_of_plane(p, a, c, d, b); - let point_outside_adb = Self::point_outside_of_plane(p, a, d, b, c); - let point_outside_bdc = Self::point_outside_of_plane(p, b, d, c, a); + let Some(point_outside_abc) = Self::point_outside_of_plane(p, a, b, c, d) else { + final_result.degenerate = true; + return false; + }; - if point_outside_abc < 0 - || point_outside_acd < 0 - || point_outside_adb < 0 - || point_outside_bdc < 0 - { + let Some(point_outside_acd) = Self::point_outside_of_plane(p, a, c, d, b) else { final_result.degenerate = true; return false; - } + }; + + let Some(point_outside_adb) = Self::point_outside_of_plane(p, a, d, b, c) else { + final_result.degenerate = true; + return false; + }; - if point_outside_abc == 0 - && point_outside_acd == 0 - && point_outside_adb == 0 - && point_outside_bdc == 0 - { + let Some(point_outside_bdc) = Self::point_outside_of_plane(p, b, d, c, a) else { + final_result.degenerate = true; + return false; + }; + + final_result.degenerate = false; + if !point_outside_abc && !point_outside_acd && !point_outside_adb && !point_outside_bdc { return false; } let mut best_sq_dist = f32::MAX; + let mut temp_result = SubSimplexClosestResult::new(); - if point_outside_abc != 0 { + if point_outside_abc { Self::closest_pt_point_triangle(p, a, b, c, &mut temp_result); let q = temp_result.closest_point_on_simplex; let sq_dist = (q - p).length_squared(); @@ -459,7 +459,7 @@ impl VoronoiSimplexSolver { } } - if point_outside_acd != 0 { + if point_outside_acd { Self::closest_pt_point_triangle(p, a, c, d, &mut temp_result); let q = temp_result.closest_point_on_simplex; let sq_dist = (q - p).length_squared(); @@ -479,7 +479,7 @@ impl VoronoiSimplexSolver { } } - if point_outside_adb != 0 { + if point_outside_adb { Self::closest_pt_point_triangle(p, a, d, b, &mut temp_result); let q = temp_result.closest_point_on_simplex; let sq_dist = (q - p).length_squared(); @@ -499,7 +499,7 @@ impl VoronoiSimplexSolver { } } - if point_outside_bdc != 0 { + if point_outside_bdc { Self::closest_pt_point_triangle(p, b, d, c, &mut temp_result); let q = temp_result.closest_point_on_simplex; let sq_dist = (q - p).length_squared(); diff --git a/rocketsim/src/bullet/collision/narrowphase/persistent_manifold.rs b/rocketsim/src/bullet/collision/narrowphase/persistent_manifold.rs index 91793768..ef8864c0 100644 --- a/rocketsim/src/bullet/collision/narrowphase/persistent_manifold.rs +++ b/rocketsim/src/bullet/collision/narrowphase/persistent_manifold.rs @@ -206,8 +206,8 @@ impl PersistentManifold { let insert_idx = self.add_manifold_point(new_pt); - if body0.collision_flags & CollisionFlags::CustomMaterialCallback as u8 != 0 - || body1.collision_flags & CollisionFlags::CustomMaterialCallback as u8 != 0 + if (body0.collision_flags & CollisionFlags::CustomMaterialCallback) != 0 + || (body1.collision_flags & CollisionFlags::CustomMaterialCallback) != 0 { contact_added_callback.callback(&mut self.point_cache[insert_idx], body0, body1, idx_1); } diff --git a/rocketsim/src/bullet/collision/shapes/triangle_info_map.rs b/rocketsim/src/bullet/collision/shapes/triangle_info_map.rs index a500f1ac..63a69feb 100644 --- a/rocketsim/src/bullet/collision/shapes/triangle_info_map.rs +++ b/rocketsim/src/bullet/collision/shapes/triangle_info_map.rs @@ -1,7 +1,7 @@ use std::{ f32::consts::TAU, iter::repeat_n, - ops::{Deref, DerefMut}, + ops::{BitAnd, BitOrAssign, Deref, DerefMut}, }; pub enum TriInfoFlag { @@ -13,6 +13,20 @@ pub enum TriInfoFlag { V2V0SwapNormalB = 1 << 5, } +impl BitOrAssign for u8 { + fn bitor_assign(&mut self, rhs: TriInfoFlag) { + *self |= rhs as u8; + } +} + +impl BitAnd for u8 { + type Output = u8; + + fn bitand(self, rhs: TriInfoFlag) -> Self::Output { + self & (rhs as u8) + } +} + #[derive(Clone, Copy)] pub struct TriangleInfo { pub flags: u8, diff --git a/rocketsim/src/bullet/dynamics/constraint_solver/seq_impulse_constraint_solver.rs b/rocketsim/src/bullet/dynamics/constraint_solver/seq_impulse_constraint_solver.rs index 976adfa4..2dec5ec1 100644 --- a/rocketsim/src/bullet/dynamics/constraint_solver/seq_impulse_constraint_solver.rs +++ b/rocketsim/src/bullet/dynamics/constraint_solver/seq_impulse_constraint_solver.rs @@ -476,7 +476,7 @@ impl SeqImpulseConstraintSolver { solver.ang_vel += solver.delta_ang_vel; if solver.push_vel.length_squared() != 0.0 || solver.turn_vel.length_squared() != 0.0 { - if body.collision_flags & CollisionFlags::NoAngularMotion as u8 != 0 { + if body.collision_flags & CollisionFlags::NoAngularMotion != 0 { integrate_trans_no_rot( &mut solver.world_trans.translation, solver.push_vel, diff --git a/rocketsim/src/bullet/dynamics/discrete_dynamics_world.rs b/rocketsim/src/bullet/dynamics/discrete_dynamics_world.rs index 59f5ffc9..cf320d39 100644 --- a/rocketsim/src/bullet/dynamics/discrete_dynamics_world.rs +++ b/rocketsim/src/bullet/dynamics/discrete_dynamics_world.rs @@ -69,20 +69,19 @@ impl DiscreteDynamicsWorld { } pub fn add_rigid_body_default(&mut self, mut body: RigidBody) -> usize { - if !body.is_static_obj() && body.collision_flags & CollisionFlags::NoWorldGravity as u8 == 0 - { + if !body.is_static_obj() && (body.collision_flags & CollisionFlags::NoWorldGravity) == 0 { body.set_gravity(self.gravity); } let (group, mask) = if body.is_static_obj() { ( CollisionFilterGroups::Static as u8, - CollisionFilterGroups::All as u8 ^ CollisionFilterGroups::Static as u8, + CollisionFilterGroups::ALL ^ CollisionFilterGroups::Static, ) } else { ( CollisionFilterGroups::Default as u8, - CollisionFilterGroups::All as u8, + CollisionFilterGroups::ALL, ) }; @@ -99,8 +98,7 @@ impl DiscreteDynamicsWorld { } pub fn add_rigid_body(&mut self, mut body: RigidBody, group: u8, mask: u8) -> usize { - if !body.is_static_obj() && body.collision_flags & CollisionFlags::NoWorldGravity as u8 == 0 - { + if !body.is_static_obj() && (body.collision_flags & CollisionFlags::NoWorldGravity) == 0 { body.set_gravity(self.gravity); } diff --git a/rocketsim/src/bullet/dynamics/rigid_body.rs b/rocketsim/src/bullet/dynamics/rigid_body.rs index c6df5533..38eacd68 100644 --- a/rocketsim/src/bullet/dynamics/rigid_body.rs +++ b/rocketsim/src/bullet/dynamics/rigid_body.rs @@ -1,3 +1,5 @@ +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}; + use glam::{Affine3A, Mat3A, Quat, Vec3A}; use crate::{ @@ -54,6 +56,64 @@ pub enum CollisionFlags { NoAngularMotion = (1 << 5), } +impl BitOrAssign for u8 { + fn bitor_assign(&mut self, rhs: CollisionFlags) { + *self |= rhs as u8; + } +} + +impl BitAndAssign for u8 { + fn bitand_assign(&mut self, rhs: CollisionFlags) { + *self &= rhs as u8; + } +} + +impl BitAnd for u8 { + type Output = u8; + + fn bitand(self, rhs: CollisionFlags) -> Self::Output { + self & (rhs as u8) + } +} + +impl BitOr for u8 { + type Output = u8; + + fn bitor(self, rhs: CollisionFlags) -> Self::Output { + self | (rhs as u8) + } +} + +impl BitXor for u8 { + type Output = u8; + + fn bitxor(self, rhs: CollisionFlags) -> Self::Output { + self ^ (rhs as u8) + } +} + +impl BitOr for CollisionFlags { + type Output = u8; + + fn bitor(self, rhs: Self) -> Self::Output { + self as u8 | rhs as u8 + } +} + +impl BitXorAssign for u8 { + fn bitxor_assign(&mut self, rhs: CollisionFlags) { + *self ^= rhs as u8; + } +} + +impl Not for CollisionFlags { + type Output = u8; + + fn not(self) -> Self::Output { + !(self as u8) + } +} + pub struct RigidBody { world_trans: Affine3A, world_quat: Quat, @@ -92,10 +152,10 @@ pub struct RigidBody { impl RigidBody { pub fn new(info: RigidBodyConstructionInfo) -> Self { - let inverse_mass = if info.mass == 0.0 { - 0.0 + let (inverse_mass, collision_flags) = if info.mass == 0.0 { + (0.0, CollisionFlags::StaticObject as u8) } else { - 1.0 / info.mass + (1.0 / info.mass, 0) }; let linear_damping = info.linear_damping.clamp(0.0, 1.0); @@ -119,11 +179,7 @@ impl RigidBody { contact_processing_threshold: f32::MAX, broadphase_handle: 0, shape: info.collision_shape, - collision_flags: if info.mass == 0.0 { - CollisionFlags::StaticObject as u8 - } else { - 0 - }, + collision_flags, companion_id: None, world_array_idx: 0, activation: ActivationState::Active, @@ -150,7 +206,7 @@ impl RigidBody { } pub fn set_world_trans(&mut self, world_trans: Affine3A) { - if self.collision_flags & CollisionFlags::NoAngularMotion as u8 == 0 { + if (self.collision_flags & CollisionFlags::NoAngularMotion) == 0 { self.world_quat = Quat::from_mat3a(&world_trans.matrix3); } @@ -169,8 +225,8 @@ impl RigidBody { &self.shape } - pub const fn is_static_obj(&self) -> bool { - self.collision_flags & CollisionFlags::StaticObject as u8 != 0 + pub fn is_static_obj(&self) -> bool { + (self.collision_flags & CollisionFlags::StaticObject) != 0 } pub const fn is_active(&self) -> bool { @@ -180,14 +236,8 @@ impl RigidBody { ) } - pub const fn has_contact_response(&self) -> bool { - self.collision_flags & CollisionFlags::NoContactResponse as u8 == 0 - } - - #[inline] - #[allow(unused)] - pub const fn get_activation_state(&self) -> ActivationState { - self.activation + pub fn has_contact_response(&self) -> bool { + self.collision_flags & CollisionFlags::NoContactResponse == 0 } pub fn set_activation_state(&mut self, new_state: ActivationState) { @@ -281,7 +331,7 @@ impl RigidBody { let mut trans = self.world_trans; let mut quat = self.world_quat; - if self.collision_flags & CollisionFlags::NoAngularMotion as u8 != 0 { + if (self.collision_flags & CollisionFlags::NoAngularMotion) != 0 { integrate_trans_no_rot(&mut trans.translation, self.lin_vel, time_step); } else { integrate_trans(&mut trans, &mut quat, self.lin_vel, self.ang_vel, time_step); @@ -302,7 +352,7 @@ impl RigidBody { } pub fn update_activation_state(&mut self, _time_step: f32) { - if self.collision_flags & CollisionFlags::CanSleep as u8 == 0 { + if self.collision_flags & CollisionFlags::CanSleep == 0 { self.set_activation_state(ActivationState::Active); return; } diff --git a/rocketsim/src/sim/arena/arena_config.rs b/rocketsim/src/sim/arena/arena_config.rs index bd1a6901..6923666e 100644 --- a/rocketsim/src/sim/arena/arena_config.rs +++ b/rocketsim/src/sim/arena/arena_config.rs @@ -19,7 +19,6 @@ pub struct ArenaConfig { pub max_aabb_len: f32, pub no_ball_rot: bool, /// Use a custom list of boost pads (`custom_boost_pads`) instead of the normal one - /// NOTE: This will disable the boost pad grid and will thus worsen performance pub custom_boost_pads: Option>, /// Optional RNG seed for deterministic behavior /// If None, a random seed will be used diff --git a/rocketsim/src/sim/arena/arena_contact_tracker.rs b/rocketsim/src/sim/arena/arena_contact_tracker.rs index de98834c..7b9fb2bb 100644 --- a/rocketsim/src/sim/arena/arena_contact_tracker.rs +++ b/rocketsim/src/sim/arena/arena_contact_tracker.rs @@ -20,8 +20,6 @@ pub(crate) struct ContactRecord { pub is_swap: bool, pub rb_idx_a: usize, pub rb_idx_b: usize, - pub user_idx_a: UserInfoTypes, - pub user_idx_b: UserInfoTypes, pub manifold_point: ManifoldPoint, } @@ -82,12 +80,8 @@ impl ContactAddedCallback for ArenaContactTracker { }; manifold_point.combined_friction = hit_coefs.friction; manifold_point.combined_restitution = hit_coefs.restitution; - } else if user_idx_a == UserInfoTypes::Ball { - if user_idx_b == UserInfoTypes::DropshotTile { - todo!() - } else if user_idx_b == UserInfoTypes::None { - manifold_point.is_special = true; - } + } else if user_idx_a == UserInfoTypes::Ball && user_idx_b == UserInfoTypes::None { + manifold_point.is_special = true; } // NOTE: Push *before* the manifold is mutated by adjust_internal_edge_contacts() @@ -95,8 +89,6 @@ impl ContactAddedCallback for ArenaContactTracker { is_swap: should_swap, rb_idx_a: body_a.world_array_idx, rb_idx_b: body_b.world_array_idx, - user_idx_a, - user_idx_b, manifold_point: *manifold_point, }); diff --git a/rocketsim/src/sim/arena/arena_state.rs b/rocketsim/src/sim/arena/arena_state.rs index 4bec297b..7ddc3434 100644 --- a/rocketsim/src/sim/arena/arena_state.rs +++ b/rocketsim/src/sim/arena/arena_state.rs @@ -3,11 +3,10 @@ use crate::{BallState, BoostPadConfig, BoostPadState, CarInfo, CarState, GameMod #[derive(Debug, Clone)] pub struct ArenaState { pub(crate) game_mode: GameMode, - pub car_infos: Vec, - pub car_states: Vec, - pub ball_state: BallState, - pub boost_pad_configs: Vec, - pub boost_pad_states: Vec, + pub tick_count: u64, + pub cars: Vec<(CarInfo, CarState)>, + pub ball: BallState, + pub boost_pads: Vec<(BoostPadConfig, BoostPadState)>, } impl ArenaState { @@ -20,21 +19,20 @@ impl ArenaState { pub fn new_empty(game_mode: GameMode) -> Self { Self { game_mode, - car_infos: Vec::new(), - car_states: Vec::new(), - ball_state: BallState::default(), - boost_pad_states: Vec::new(), - boost_pad_configs: Vec::new(), + tick_count: 0, + cars: Vec::new(), + ball: BallState::default(), + boost_pads: Vec::new(), } } #[must_use] pub const fn num_cars(&self) -> usize { - self.car_infos.len() + self.cars.len() } #[must_use] pub const fn num_boost_pads(&self) -> usize { - self.boost_pad_configs.len() + self.boost_pads.len() } } diff --git a/rocketsim/src/sim/arena/base.rs b/rocketsim/src/sim/arena/base.rs index 0b9a78c0..7f7a83f8 100644 --- a/rocketsim/src/sim/arena/base.rs +++ b/rocketsim/src/sim/arena/base.rs @@ -12,7 +12,7 @@ use crate::{ ArenaEvent::{BallHitWorld, CarPickupBoost}, ArenaMemWeightMode, ArenaState, BallHitWorldEvent, BoostPadConfig, BoostPadGrid, BoostPadState, Car, CarBodyConfig, CarControls, CarInfo, CarPickupBoostEvent, CarState, GameMode, - MutatorConfig, PhysState, Team, + MutatorConfig, PhysState, Team, TileDamageState, TileStates, bullet::{ collision::{ broadphase::{CollisionFilterGroups, GridBroadphase}, @@ -21,10 +21,11 @@ use crate::{ }, dynamics::{ discrete_dynamics_world::DiscreteDynamicsWorld, - rigid_body::{ActivationState, RigidBody, RigidBodyConstructionInfo}, + rigid_body::{ActivationState, CollisionFlags, RigidBody, RigidBodyConstructionInfo}, }, }, consts::{self, BT_TO_UU, TICK_RATE, TICK_TIME, UU_TO_BT}, + make_tile_shapes, sim::{ ArenaEvent::{self, CarHitBall}, Ball, BallState, BoostPad, CarHitBallEvent, CarHitCarEvent, CarHitWorldEvent, DemoMode, @@ -42,6 +43,7 @@ pub struct Arena { pub(crate) cars: Vec, pub(crate) tick_count: u64, pub(crate) boost_pad_grid: Option, + pub(crate) tile_states: Option, pub(crate) contact_tracker: ArenaContactTracker, pub(crate) events: ArenaEventList, @@ -113,6 +115,12 @@ impl Arena { None }; + let tile_states = if config.game_mode == GameMode::Dropshot { + Some(TileStates::DEFAULT) + } else { + None + }; + let rng = config.rng_seed.map_or_else(Rng::new, Rng::with_seed); Self { @@ -123,6 +131,7 @@ impl Arena { tick_count: 0, cars: Vec::with_capacity(6), bullet_world, + tile_states, contact_tracker: ArenaContactTracker::new(), events: ArenaEventList::new(), @@ -142,7 +151,7 @@ impl Arena { shape: CollisionShapes, pos_bt: Vec3A, group: Option, - ) { + ) -> usize { let mut rb_info = RigidBodyConstructionInfo::new(0.0, shape); rb_info.restitution = consts::arena::BASE_COEFS.restitution; rb_info.friction = consts::arena::BASE_COEFS.friction; @@ -152,11 +161,11 @@ impl Arena { if let Some(group) = group { bullet_world.add_rigid_body( shape_rb, - group | CollisionFilterGroups::Static as u8, - group, - ); + group | CollisionFilterGroups::Static, + group ^ CollisionFilterGroups::Static, + ) } else { - bullet_world.add_rigid_body_default(shape_rb); + bullet_world.add_rigid_body_default(shape_rb) } } @@ -236,12 +245,12 @@ impl Arena { if game_mode != GameMode::Dropshot { // Side walls add_plane( - Vec3A::new(arena_aabb.min.x, 0.0, arena_aabb.center().z), + Vec3A::new(arena_aabb.min.x, 0.0, arena_aabb.max.z / 2.0), Vec3A::X, None, ); add_plane( - Vec3A::new(arena_aabb.max.x, 0.0, arena_aabb.center().z), + Vec3A::new(arena_aabb.max.x, 0.0, arena_aabb.max.z / 2.0), Vec3A::NEG_X, None, ); @@ -251,20 +260,33 @@ impl Arena { GameMode::Hoops => { // Y walls add_plane( - Vec3A::new(0.0, arena_aabb.min.y, arena_aabb.center().z), + Vec3A::new(0.0, arena_aabb.min.y, arena_aabb.max.z / 2.0), Vec3A::Y, None, ); add_plane( - Vec3A::new(0.0, arena_aabb.min.z, arena_aabb.center().z), + Vec3A::new(0.0, arena_aabb.max.y, arena_aabb.max.z / 2.0), Vec3A::NEG_Y, None, ); } GameMode::Dropshot => { - // Add tiles - todo!() + for (i, tile) in make_tile_shapes().enumerate() { + // Shift down so the collision doesn't peek through the floor + let pos = Vec3A::new(0.0, 0.0, -tile.get_margin()); + + let rb_idx = Self::add_static_collision_shape( + bullet_world, + CollisionShapes::ConvexHull(tile), + pos, + Some(CollisionFilterGroups::DropshotTile as u8), + ); + + let rb = &mut bullet_world.bodies_mut()[rb_idx]; + rb.user_idx = UserInfoTypes::DropshotTile; + rb.user_pointer = i; + } } _ => {} } @@ -406,6 +428,9 @@ impl Arena { GameMode::Snowday => { ball_state.phys.vel.z = f32::EPSILON; } + GameMode::Dropshot => { + self.update_tile_states(); + } _ => {} } @@ -414,8 +439,6 @@ impl Arena { if let Some(boost_pad_grid) = self.boost_pad_grid.as_mut() { boost_pad_grid.reset(); } - - // TODO: Reset tile states } /// Creates and adds a car to the arena, returning the index of the car in the cars vector @@ -480,11 +503,15 @@ impl Arena { let contact = *self.contact_tracker.get_record(idx); let bodies = self.bullet_world.bodies(); - let user_pointer_a = bodies[contact.rb_idx_a].user_pointer; - let user_pointer_b = bodies[contact.rb_idx_b].user_pointer; - - if contact.user_idx_a == UserInfoTypes::Car { - match contact.user_idx_b { + let rb_a = &bodies[contact.rb_idx_a]; + let rb_b = &bodies[contact.rb_idx_b]; + let user_idx_a = rb_a.user_idx; + let user_idx_b = rb_b.user_idx; + let user_pointer_a = rb_a.user_pointer; + let user_pointer_b = rb_b.user_pointer; + + match user_idx_a { + UserInfoTypes::Car => match user_idx_b { UserInfoTypes::Ball => { self.on_car_ball_collision( user_pointer_a, @@ -500,9 +527,17 @@ impl Arena { ); } _ => self.on_car_world_collision(user_pointer_a, &contact.manifold_point), - } - } else if contact.user_idx_a == UserInfoTypes::Ball { - self.on_ball_world_collision(&contact.manifold_point, contact.rb_idx_a); + }, + UserInfoTypes::Ball => match user_idx_b { + UserInfoTypes::DropshotTile => { + self.on_ball_tile_collision(user_pointer_b); + } + UserInfoTypes::None => { + self.on_ball_world_collision(&contact.manifold_point, contact.rb_idx_a); + } + _ => {} + }, + _ => {} } } @@ -533,8 +568,10 @@ impl Arena { self.ball .finish_physics_tick(ball_rb, &self.config.mutators); - if self.config.game_mode == GameMode::Dropshot { - todo!("Dropshot tile state sync") + if self.config.game_mode == GameMode::Dropshot + && self.ball.state.ds_info.last_damage_tick == Some(self.tick_count) + { + self.update_tile_states(); } self.tick_count += 1; @@ -633,7 +670,7 @@ impl Arena { let pad = self.boost_pads()[idx]; let cooldown = pad.gave_boost_tick_count.map_or(0.0, |gave_boost_tick| { let max_cooldown = pad.max_cooldown; - let time_since = ((self.tick_count() - gave_boost_tick) as f32) * TICK_TIME; + let time_since = ((self.tick_count() as i64 - gave_boost_tick) as f32) * TICK_TIME; (max_cooldown - time_since).max(0.0) }); @@ -646,8 +683,8 @@ impl Arena { let pad = &mut boost_pad_grid.all_pads[idx]; if state.cooldown > 0.0 { let time_since_pickup = (pad.max_cooldown - state.cooldown).max(0.0); - let ticks_since_pickup = (time_since_pickup * TICK_RATE).round() as u64; - pad.gave_boost_tick_count = Some(tick_count - ticks_since_pickup); + let ticks_since_pickup = (time_since_pickup * TICK_RATE).round() as i64; + pad.gave_boost_tick_count = Some(tick_count as i64 - ticks_since_pickup); } else { boost_pad_grid.all_pads[idx].gave_boost_tick_count = None; } @@ -681,21 +718,52 @@ impl Arena { .collect() } + pub fn get_tile_states(&self) -> &TileStates { + self.tile_states.as_ref().unwrap() + } + + fn update_tile_states(&mut self) { + for (team_idx, team_states) in self.tile_states.as_ref().unwrap().states.iter().enumerate() + { + for (tile_idx, &new_state) in team_states.iter().enumerate() { + let rb_idx = tile_idx + consts::dropshot::NUM_TILES_PER_TEAM * team_idx; + let tile_rb = &mut self.bullet_world.bodies_mut()[rb_idx]; + if new_state == TileDamageState::Broken { + tile_rb.collision_flags |= CollisionFlags::NoContactResponse; + } else { + tile_rb.collision_flags &= !CollisionFlags::NoContactResponse; + } + } + } + } + + pub fn set_tile_states(&mut self, tile_states: TileStates) { + self.tile_states = Some(tile_states); + self.update_tile_states(); + } + #[must_use] pub fn get_arena_state(&self) -> ArenaState { - let car_infos = self.cars.iter().map(|c| c.info).collect::>(); - let car_states = self.cars.iter().map(|c| c.state).collect::>(); - let ball_state = *self.get_ball_state(); - let boost_pad_states = self.get_all_boost_pad_states(); - let boost_pad_configs = self.get_all_boost_pad_configs(); + let cars = self + .cars + .iter() + .map(|c| (c.info, c.state)) + .collect::>(); + let ball = *self.get_ball_state(); + + let boost_pads = match self.config.game_mode { + GameMode::Soccar | GameMode::Hoops | GameMode::Snowday => (0..self.num_boost_pads()) + .map(|i| (*self.get_boost_pad_config(i), self.get_boost_pad_state(i))) + .collect(), + GameMode::Dropshot | GameMode::Heatseeker | GameMode::TheVoid => Vec::new(), + }; ArenaState { game_mode: self.config.game_mode, - car_infos, - car_states, - ball_state, - boost_pad_states, - boost_pad_configs, + tick_count: self.tick_count, + cars, + ball, + boost_pads, } } @@ -734,6 +802,14 @@ impl Arena { } impl Arena { + fn on_ball_tile_collision(&mut self, tile_idx: usize) { + self.ball.on_dropshot_tile_collision( + self.tile_states.as_mut().unwrap(), + tile_idx, + self.tick_count, + ); + } + fn on_ball_world_collision(&mut self, manifold_point: &ManifoldPoint, rb_index: usize) { let contact_point = manifold_point.pos_world_on_b * BT_TO_UU; let contact_normal = manifold_point.normal_world_on_b; diff --git a/rocketsim/src/sim/ball/ball_state.rs b/rocketsim/src/sim/ball/ball_state.rs index b7947f61..8a58a4a4 100644 --- a/rocketsim/src/sim/ball/ball_state.rs +++ b/rocketsim/src/sim/ball/ball_state.rs @@ -33,14 +33,12 @@ pub struct DropshotInfo { /// 1 = damages r=1 -> 1 tile /// 2 = damages r=2 -> 7 tiles /// 3 = damages r=3 -> 19 tiles - pub charge_level: i32, + pub charge_level: u8, /// Resets when a tile is damaged pub accumulated_hit_force: f32, /// Which side of the field the ball can damage (0=none, -1=blue, 1=orange) pub y_target_dir: i8, - pub has_damaged: bool, - /// Only valid if `has_damaged` - pub last_damage_tick: u64, + pub last_damage_tick: Option, } impl Default for DropshotInfo { @@ -54,8 +52,7 @@ impl DropshotInfo { charge_level: 1, accumulated_hit_force: 0., y_target_dir: 0, - has_damaged: false, - last_damage_tick: 0, + last_damage_tick: None, }; } diff --git a/rocketsim/src/sim/ball/base.rs b/rocketsim/src/sim/ball/base.rs index 2aa47da8..31fe713a 100644 --- a/rocketsim/src/sim/ball/base.rs +++ b/rocketsim/src/sim/ball/base.rs @@ -3,7 +3,7 @@ use std::f32::consts::TAU; use glam::{Affine3A, Vec2, Vec3A}; use crate::{ - BallState, Car, GameMode, MutatorConfig, + BallState, Car, GameMode, MutatorConfig, Team, TileDamageState, TileStates, bullet::{ collision::{ broadphase::CollisionFilterGroups, @@ -18,7 +18,8 @@ use crate::{ }, linear_math::angle::Angle, }, - consts::{UU_TO_BT, dropshot, heatseeker, snowday}, + consts::{BT_TO_UU, UU_TO_BT, dropshot, heatseeker, snowday}, + get_neighbor_indices_1, get_neighbor_indices_2, get_tile_pos, sim::{UserInfoTypes, consts}, }; @@ -89,18 +90,17 @@ impl Ball { let mut body = RigidBody::new(info); body.user_idx = UserInfoTypes::Ball; - body.collision_flags |= - CollisionFlags::CustomMaterialCallback as u8 | CollisionFlags::CanSleep as u8; + body.collision_flags |= CollisionFlags::CustomMaterialCallback | CollisionFlags::CanSleep; if no_rot && matches!(body.get_collision_shape(), CollisionShapes::Sphere(_)) { - body.collision_flags |= CollisionFlags::NoAngularMotion as u8; + body.collision_flags |= CollisionFlags::NoAngularMotion; } let rigid_body_idx = bullet_world.add_rigid_body( body, - CollisionFilterGroups::Default as u8 - | CollisionFilterGroups::HoopsNet as u8 - | CollisionFilterGroups::DropshotTile as u8, - CollisionFilterGroups::All as u8, + CollisionFilterGroups::Default + | CollisionFilterGroups::HoopsNet + | CollisionFilterGroups::DropshotTile, + CollisionFilterGroups::ALL, ); Self { @@ -346,7 +346,7 @@ impl Ball { } } - pub fn on_world_hit(&mut self, rb: &mut RigidBody, game_mode: GameMode, normal: Vec3A) { + pub(crate) fn on_world_hit(&mut self, rb: &mut RigidBody, game_mode: GameMode, normal: Vec3A) { match game_mode { GameMode::Heatseeker => { const ARENA_EXTENT: Vec3A = consts::arena::get_aabb(GameMode::Soccar).max; @@ -387,4 +387,62 @@ impl Ball { _ => {} } } + + pub(crate) fn on_dropshot_tile_collision( + &mut self, + tile_states: &mut TileStates, + tile_total_index: usize, + tick_count: u64, + ) { + let team_idx = tile_total_index / dropshot::NUM_TILES_PER_TEAM; + let tile_idx = tile_total_index % dropshot::NUM_TILES_PER_TEAM; + let tile_state = tile_states.states[team_idx][tile_idx]; + let tile_pos = get_tile_pos( + Team::try_from(u8::try_from(team_idx).unwrap()).unwrap(), + tile_idx, + ); + + // This should be possible in rare circumstances where two tiles are hit simultaneously + if tile_state == TileDamageState::Broken { + return; + } + + if let Some(last_damage_tick) = self.state.ds_info.last_damage_tick { + let time_since_damage = (tick_count - last_damage_tick) as f32 * consts::TICK_TIME; + if time_since_damage <= dropshot::MIN_DAMAGE_INTERVAL { + return; // Hasn't been long enough since we last damaged + } + } + + if self.state.phys.vel.z * BT_TO_UU > -dropshot::MIN_DOWNWARD_SPEED_TO_DAMAGE { + return; // Not going fast enough downward + } + + if self.state.ds_info.charge_level > 1 + && self.state.ds_info.y_target_dir != 0 + && tile_pos.y.signum() != f32::from(self.state.ds_info.y_target_dir) + { + return; // Wrong side of the arena + } + + // All checks passed, break the tile(s) + let indices_to_break = match self.state.ds_info.charge_level { + 3 => get_neighbor_indices_2(tile_idx), + 2 => get_neighbor_indices_1(tile_idx), + _ => &[tile_idx], + }; + for &i in indices_to_break { + let prev_state = tile_states.states[team_idx][i]; + tile_states.states[team_idx][i] = match prev_state { + TileDamageState::Full => TileDamageState::Damaged, + TileDamageState::Damaged => TileDamageState::Broken, + TileDamageState::Broken => continue, // Already broken, skip + } + } + + self.state.ds_info.last_damage_tick = Some(tick_count); + self.state.ds_info.accumulated_hit_force = 0.0; + self.state.ds_info.charge_level = 1; + self.state.ds_info.y_target_dir = 0; + } } diff --git a/rocketsim/src/sim/boost_pad/base.rs b/rocketsim/src/sim/boost_pad/base.rs index 952ff226..b0831c31 100644 --- a/rocketsim/src/sim/boost_pad/base.rs +++ b/rocketsim/src/sim/boost_pad/base.rs @@ -10,7 +10,7 @@ pub(crate) struct BoostPad { pub max_cooldown: f32, pub boost_amount: f32, pub aabb: Aabb, - pub gave_boost_tick_count: Option, + pub gave_boost_tick_count: Option, } impl BoostPad { diff --git a/rocketsim/src/sim/boost_pad/boost_pad_config.rs b/rocketsim/src/sim/boost_pad/boost_pad_config.rs index 503b220e..75d8452b 100644 --- a/rocketsim/src/sim/boost_pad/boost_pad_config.rs +++ b/rocketsim/src/sim/boost_pad/boost_pad_config.rs @@ -1,7 +1,6 @@ use glam::Vec3A; #[derive(Clone, Copy, Debug, Default)] - pub struct BoostPadConfig { pub pos: Vec3A, pub is_big: bool, diff --git a/rocketsim/src/sim/boost_pad/boost_pad_grid.rs b/rocketsim/src/sim/boost_pad/boost_pad_grid.rs index d08e777d..712207a0 100644 --- a/rocketsim/src/sim/boost_pad/boost_pad_grid.rs +++ b/rocketsim/src/sim/boost_pad/boost_pad_grid.rs @@ -21,7 +21,8 @@ impl bvh::ProcessNode for BoostPadProcessor<'_> { let pad = &mut self.all_pads[pad_idx]; if let Some(last_give_tick_count) = pad.gave_boost_tick_count - && ((self.tick_count - last_give_tick_count) as f32 * TICK_TIME) < pad.max_cooldown + && ((self.tick_count as i64 - last_give_tick_count) as f32 * TICK_TIME) + < pad.max_cooldown { return; } @@ -37,7 +38,7 @@ impl bvh::ProcessNode for BoostPadProcessor<'_> { // Give boost self.car_state.boost = (self.car_state.boost + pad.boost_amount) .min(self.mutator_config.car_max_boost_amount); - pad.gave_boost_tick_count = Some(self.tick_count); + pad.gave_boost_tick_count = Some(self.tick_count as i64); self.pad_idx = Some(pad_idx); } } diff --git a/rocketsim/src/sim/boost_pad/boost_pad_state.rs b/rocketsim/src/sim/boost_pad/boost_pad_state.rs index 8eaef6ff..91f004a1 100644 --- a/rocketsim/src/sim/boost_pad/boost_pad_state.rs +++ b/rocketsim/src/sim/boost_pad/boost_pad_state.rs @@ -1,5 +1,4 @@ #[derive(Clone, Copy, Debug)] - pub struct BoostPadState { /// The last tick when we gave a car boost pub cooldown: f32, diff --git a/rocketsim/src/sim/car/base.rs b/rocketsim/src/sim/car/base.rs index 6b7b3ea5..61a9b3ca 100644 --- a/rocketsim/src/sim/car/base.rs +++ b/rocketsim/src/sim/car/base.rs @@ -76,12 +76,12 @@ impl Car { let mut body = RigidBody::new(rb_info); body.user_idx = UserInfoTypes::Car; - body.collision_flags |= CollisionFlags::CustomMaterialCallback as u8; + body.collision_flags |= CollisionFlags::CustomMaterialCallback; let rigid_body_idx = bullet_world.add_rigid_body( body, - CollisionFilterGroups::Default as u8 | CollisionFilterGroups::DropshotFloor as u8, - CollisionFilterGroups::All as u8, + CollisionFilterGroups::Default | CollisionFilterGroups::DropshotFloor, + CollisionFilterGroups::ALL, ); let mut wheels = [WheelInfo::DEFAULT; NUM_WHEELS]; diff --git a/rocketsim/src/sim/consts.rs b/rocketsim/src/sim/consts.rs index 7c98b1cc..4c850fba 100644 --- a/rocketsim/src/sim/consts.rs +++ b/rocketsim/src/sim/consts.rs @@ -480,12 +480,12 @@ pub mod dropshot { + TILE_HEXAGON_VERTS_BT[4].to_array()[1]) * BT_TO_UU; pub const TILE_OFFSET_Y: f32 = 2.54736 * BT_TO_UU; - pub const NUM_TILES_PER_TEAM: i32 = 70; + pub const NUM_TILES_PER_TEAM: usize = 70; pub const TEAM_AMOUNT: i32 = 2; /// Number descends each row - pub const TILES_IN_FIRST_ROW: i32 = 13; - pub const TILES_IN_LAST_ROW: i32 = 7; - pub const NUM_TILE_ROWS: i32 = TILES_IN_FIRST_ROW - TILES_IN_LAST_ROW + 1; + pub const TILES_IN_FIRST_ROW: u8 = 13; + pub const TILES_IN_LAST_ROW: u8 = 7; + pub const NUM_TILE_ROWS: u8 = TILES_IN_FIRST_ROW - TILES_IN_LAST_ROW + 1; pub const TILE_HEXAGON_AABB_MAX: Vec3A = Vec3A::new(7.6643, 8.85, 0.); pub const TILE_HEXAGON_VERTS_BT: [Vec3A; 6] = [ Vec3A::new(0.0, -8.85, 0.), diff --git a/rocketsim/src/sim/dropshot_tiles/mod.rs b/rocketsim/src/sim/dropshot_tiles/mod.rs new file mode 100644 index 00000000..c5655cb8 --- /dev/null +++ b/rocketsim/src/sim/dropshot_tiles/mod.rs @@ -0,0 +1,51 @@ +mod state; +mod tiles; + +use glam::Vec3A; +pub use state::{TileDamageState, TileStates}; + +use crate::{ + Team, + bullet::collision::shapes::convex_hull_shape::ConvexHullShape, + consts::{UU_TO_BT, dropshot}, +}; + +#[must_use] +pub(crate) fn get_tile_pos(team: Team, index: usize) -> Vec3A { + tiles::TILE_POSITIONS[index] * f32::from(team as i8 * 2 - 1) +} + +#[must_use] +pub(crate) fn get_neighbor_indices_1(start_idx: usize) -> &'static [usize] { + tiles::TILE_NEIGHBORS_1[start_idx].as_slice() +} + +#[must_use] +pub(crate) fn get_neighbor_indices_2(start_idx: usize) -> &'static [usize] { + tiles::TILE_NEIGHBORS_2[start_idx].as_slice() +} + +pub(crate) fn make_tile_shapes() -> impl Iterator { + Team::ALL.into_iter().flat_map(|team| { + (0..dropshot::NUM_TILES_PER_TEAM).map(move |i| { + let pos = get_tile_pos(team, i); + + let mut points = Vec::with_capacity(6); + for j in 0..6 { + const CLAMP_Y: f32 = dropshot::TILE_OFFSET_Y * UU_TO_BT; + + let mut vert = pos * UU_TO_BT + dropshot::TILE_HEXAGON_VERTS_BT[j]; + + // Clamp vert from crossing middle part at x=0 + vert.y = match team { + Team::Blue => vert.y.min(-CLAMP_Y), + Team::Orange => vert.y.max(CLAMP_Y), + }; + + points.push(vert); + } + + ConvexHullShape::new(points.into_boxed_slice()) + }) + }) +} diff --git a/rocketsim/src/sim/dropshot_tiles/state.rs b/rocketsim/src/sim/dropshot_tiles/state.rs new file mode 100644 index 00000000..96865e9c --- /dev/null +++ b/rocketsim/src/sim/dropshot_tiles/state.rs @@ -0,0 +1,33 @@ +use crate::{Team, consts::dropshot}; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub enum TileDamageState { + #[default] + Full, + Damaged, + Broken, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TileStates { + pub states: [[TileDamageState; dropshot::NUM_TILES_PER_TEAM]; 2], +} + +impl Default for TileStates { + fn default() -> Self { + Self::DEFAULT + } +} + +impl TileStates { + pub const DEFAULT: Self = Self { + states: [[TileDamageState::Full; dropshot::NUM_TILES_PER_TEAM]; 2], + }; + + pub fn get_team_states(&self, team: Team) -> &[TileDamageState; dropshot::NUM_TILES_PER_TEAM] { + match team { + Team::Blue => &self.states[0], + Team::Orange => &self.states[1], + } + } +} diff --git a/rocketsim/src/sim/dropshot_tiles/tiles.rs b/rocketsim/src/sim/dropshot_tiles/tiles.rs new file mode 100644 index 00000000..5946805d --- /dev/null +++ b/rocketsim/src/sim/dropshot_tiles/tiles.rs @@ -0,0 +1,107 @@ +use glam::{Vec3, Vec3A}; + +use crate::{Team, consts::dropshot}; + +pub const TILE_POSITIONS: [Vec3A; dropshot::NUM_TILES_PER_TEAM] = get_tile_positions(); +pub const TILE_NEIGHBORS_1: [Varr<7>; dropshot::NUM_TILES_PER_TEAM] = get_tile_neighbors::<1, _>(); +pub const TILE_NEIGHBORS_2: [Varr<19>; dropshot::NUM_TILES_PER_TEAM] = get_tile_neighbors::<2, _>(); + +const fn get_tile_positions() -> [Vec3A; dropshot::NUM_TILES_PER_TEAM] { + let mut tile_positions = [Vec3A::ZERO; dropshot::NUM_TILES_PER_TEAM]; + + let mut cur_idx = 0; + let mut y = dropshot::TILE_OFFSET_Y; + + let mut i = 0; + while i < dropshot::NUM_TILE_ROWS { + let num_tiles = dropshot::TILES_IN_FIRST_ROW - i; + let row_size_x = dropshot::TILE_WIDTH_X * num_tiles as f32; + let row_start_x = -(row_size_x / 2.0) + (dropshot::TILE_WIDTH_X / 2.0); + + let mut j = 0; + while j < num_tiles { + let x = row_start_x + dropshot::TILE_WIDTH_X * j as f32; + tile_positions[cur_idx] = Vec3A::new(x, y, 0.0); + cur_idx += 1; + j += 1; + } + + y += dropshot::ROW_OFFSET_Y; + i += 1; + } + + assert!( + cur_idx == dropshot::NUM_TILES_PER_TEAM, + "Failed to reach tile amount, make sure tile info is correct" + ); + + tile_positions +} + +#[derive(Clone, Copy)] +pub struct Varr { + arr: [usize; N], + len: usize, +} + +impl Varr { + const fn new() -> Self { + Self { + arr: [0; N], + len: 0, + } + } + + const fn push(&mut self, value: usize) { + if self.len >= N { + panic!("Varr overflow"); + } + + self.arr[self.len] = value; + self.len += 1; + } + + pub fn as_slice(&self) -> &[usize] { + &self.arr[..self.len] + } +} + +const fn get_tile_pos(team: Team, index: usize) -> Vec3 { + let [x, y, z] = TILE_POSITIONS[index].to_array(); + let team = (team as i8 * 2 - 1) as f32; + Vec3::new(x * team, y * team, z * team) +} + +const fn get_tile_neighbors() +-> [Varr; dropshot::NUM_TILES_PER_TEAM] { + let neighbor_max_radius = dropshot::TILE_WIDTH_X * 1.2 * RADIUS as f32; + let neighbor_max_radius_sq = neighbor_max_radius * neighbor_max_radius; + + let mut neighbors = [Varr::new(); dropshot::NUM_TILES_PER_TEAM]; + + let mut i = 0; + while i < dropshot::NUM_TILES_PER_TEAM { + let pos = get_tile_pos(Team::Blue, i); + + let mut j = 0; + while j < dropshot::NUM_TILES_PER_TEAM { + let other_pos = get_tile_pos(Team::Blue, j); + let diff = Vec3 { + x: pos.x - other_pos.x, + y: pos.y - other_pos.y, + z: pos.z - other_pos.z, + }; + + let dist_sq = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z; + if dist_sq < neighbor_max_radius_sq { + neighbors[i].push(j); + } + + j += 1; + } + + i += 1; + } + + neighbors +} diff --git a/rocketsim/src/sim/mod.rs b/rocketsim/src/sim/mod.rs index 821bf4b2..7f27b036 100644 --- a/rocketsim/src/sim/mod.rs +++ b/rocketsim/src/sim/mod.rs @@ -5,6 +5,7 @@ mod car; #[allow(clippy::redundant_pub_crate)] pub(crate) mod collision_mesh_file; pub mod consts; +mod dropshot_tiles; mod game_mode; mod linear_piece_curve; mod mutator_config; @@ -16,6 +17,7 @@ pub use arena::*; pub use ball::*; pub use boost_pad::*; pub use car::*; +pub use dropshot_tiles::*; pub use game_mode::*; pub use mutator_config::*; pub use phys_state::*; diff --git a/rocketsim/src/vis/camera/camera_man.rs b/rocketsim/src/vis/camera/camera_man.rs index 9af27848..a3061d8c 100644 --- a/rocketsim/src/vis/camera/camera_man.rs +++ b/rocketsim/src/vis/camera/camera_man.rs @@ -74,7 +74,7 @@ impl CameraMan { CameraViewMode::BirdsEye => { self.cur_pos = self.config.birds_eye_pos; self.cur_dir = - (arena_state.ball_state.pos - self.config.birds_eye_pos).normalize_or_zero(); + (arena_state.ball.pos - self.config.birds_eye_pos).normalize_or_zero(); } CameraViewMode::CarCam(car_cam) => { car_cam.update_car_cam_pos_dir( diff --git a/rocketsim/src/vis/camera/car_cam.rs b/rocketsim/src/vis/camera/car_cam.rs index 6ca3abf0..883bda87 100644 --- a/rocketsim/src/vis/camera/car_cam.rs +++ b/rocketsim/src/vis/camera/car_cam.rs @@ -45,8 +45,8 @@ impl CarCam { pos: &mut Vec3A, dir: &mut Vec3A, ) { - let car_state = &arena_state.car_states[self.car_idx]; - let ball_state = &arena_state.ball_state; + let car_state = &arena_state.cars[self.car_idx].1; + let ball_state = &arena_state.ball; let car_ball_dir = (ball_state.pos - car_state.pos).normalize_or_zero(); diff --git a/rocketsim/src/vis/vis_inst.rs b/rocketsim/src/vis/vis_inst.rs index b1350c03..b2b9cca6 100644 --- a/rocketsim/src/vis/vis_inst.rs +++ b/rocketsim/src/vis/vis_inst.rs @@ -152,24 +152,20 @@ impl VisInst { "ball", Some("ball"), None, - astate.ball_state.pos, - astate.ball_state.rot_mat, + astate.ball.pos, + astate.ball.rot_mat, ); // Ball trail self.ball_ribbon - .update(true, astate.ball_state.pos, Vec3A::ZERO, dt); + .update(true, astate.ball.pos, Vec3A::ZERO, dt); self.ball_ribbon - .render(&mut new_render_state, astate.ball_state.pos); + .render(&mut new_render_state, astate.ball.pos); // Ball-to-ground line new_render_state.add_line_simple( - astate.ball_state.pos, - Vec3A::new( - astate.ball_state.pos.x, - astate.ball_state.pos.y, - arena_aabb.min.z, - ), + astate.ball.pos, + Vec3A::new(astate.ball.pos.x, astate.ball.pos.y, arena_aabb.min.z), Color::WHITE.with_alpha(0.15), 2.0, ); @@ -177,8 +173,7 @@ impl VisInst { // Render cars for i in 0..astate.num_cars() { - let car_info = &astate.car_infos[i]; - let car_state = &astate.car_states[i]; + let (car_info, car_state) = &astate.cars[i]; if !car_state.is_demoed { let car_texture_name = if car_info.team == Team::Blue { @@ -228,8 +223,7 @@ impl VisInst { // Render boost pads if self.game_mode != GameMode::Heatseeker { for i in 0..astate.num_boost_pads() { - let pad_config = astate.boost_pad_configs[i]; - let pad_state = astate.boost_pad_states[i]; + let (pad_config, pad_state) = astate.boost_pads[i]; let pad_model = if pad_config.is_big { if pad_state.is_active() {