From 54f77c87be1929d2cd23037ae32cbcfad097a55d Mon Sep 17 00:00:00 2001 From: jacob Date: Sun, 4 Jan 2026 04:11:02 -0600 Subject: [PATCH] import old collision detection code --- src/collider/collider.h | 6 +- src/gpu/gpu_dx12/gpu_dx12_core.c | 2 +- src/pp/pp_sim/pp_sim_core.c | 713 +++++++++++++++++++++++++++++-- src/pp/pp_sim/pp_sim_core.h | 58 ++- src/pp/pp_vis/pp_vis_core.c | 55 ++- 5 files changed, 767 insertions(+), 67 deletions(-) diff --git a/src/collider/collider.h b/src/collider/collider.h index d18d5c5c..6a715592 100644 --- a/src/collider/collider.h +++ b/src/collider/collider.h @@ -108,9 +108,9 @@ Struct(CLD_GjkData) CLD_MenkowskiSimplex simplex; Vec2 final_dir; - // If 1, simplex represents triangle inside of CLD_Menkowski difference + // If 1, simplex represents triangle inside of menkowski difference // encapsulating the origin. If 0, simplex represents the closest - // feature on CLD_Menkowski difference to the origin. + // feature on menkowski difference to the origin. b32 overlapping; #if COLLIDER_DEBUG @@ -124,7 +124,7 @@ Struct(CLD_GjkData) Struct(CLD_EpaData) { Vec2 normal; - CLD_MenkowskiFeature closest_feature; // Represents closest feature (edge or point) to origin on CLD_Menkowski difference + CLD_MenkowskiFeature closest_feature; // Represents closest feature (edge or point) to origin on menkowski difference #if COLLIDER_DEBUG CLD_Prototype prototype; diff --git a/src/gpu/gpu_dx12/gpu_dx12_core.c b/src/gpu/gpu_dx12/gpu_dx12_core.c index d28d3468..bc205ac2 100644 --- a/src/gpu/gpu_dx12/gpu_dx12_core.c +++ b/src/gpu/gpu_dx12/gpu_dx12_core.c @@ -947,7 +947,7 @@ G_ResourceHandle G_PushResource(G_ArenaHandle arena_handle, G_CommandListHandle d3d_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; d3d_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; d3d_desc.Format = DXGI_FORMAT_UNKNOWN; - d3d_desc.Width = AlignU64(MaxU64(desc.buffer.size, min_buffer_size), 4); + d3d_desc.Width = AlignU64ToNextPow2(MaxU64(desc.buffer.size, min_buffer_size)); d3d_desc.Height = 1; d3d_desc.DepthOrArraySize = 1; d3d_desc.MipLevels = 1; diff --git a/src/pp/pp_sim/pp_sim_core.c b/src/pp/pp_sim/pp_sim_core.c index 61988934..ee23ba4e 100644 --- a/src/pp/pp_sim/pp_sim_core.c +++ b/src/pp/pp_sim/pp_sim_core.c @@ -153,32 +153,12 @@ S_Shape S_MulXformShape(Xform xf, S_Shape shape) return result; } -Vec2 S_SupportPointFromShape(S_Shape shape, Vec2 dir) -{ - // FIXME: Properly handle rounded polygons - Vec2 result = Zi; - Vec2 dir_norm = NormVec2(dir); - f32 max_dot = -Inf; - for (i32 i = 0; i < shape.points_count; ++i) - { - Vec2 p = shape.points[i]; - f32 dot = DotVec2(p, dir_norm); - if (dot > max_dot) - { - max_dot = dot; - result = p; - } - } - result = AddVec2(result, MulVec2(dir_norm, shape.radius)); - return result; -} - Rng2 S_BoundingBoxFromShape(S_Shape shape) { - Vec2 left = S_SupportPointFromShape(shape, VEC2(-1, 0)); - Vec2 top = S_SupportPointFromShape(shape, VEC2(0, -1)); - Vec2 right = S_SupportPointFromShape(shape, VEC2(1, 0)); - Vec2 bottom = S_SupportPointFromShape(shape, VEC2(0, 1)); + Vec2 left = S_SupportPointFromShape(shape, VEC2(-1, 0)).p; + Vec2 top = S_SupportPointFromShape(shape, VEC2(0, -1)).p; + Vec2 right = S_SupportPointFromShape(shape, VEC2(1, 0)).p; + Vec2 bottom = S_SupportPointFromShape(shape, VEC2(0, 1)).p; Rng2 result = Zi; result.p0 = VEC2(left.x, top.y); @@ -186,6 +166,673 @@ Rng2 S_BoundingBoxFromShape(S_Shape shape) return result; } + +//////////////////////////////////////////////////////////// +//~ Collision + +// NOTE: Everything here is pretty much copied directly from the old physics +// prototype. It's slow and does more than what we need. For example we should +// probably just switch from GJK to SAT for shape collision testing. + +S_SupportPoint S_SupportPointFromShapeEx(S_Shape shape, Vec2 dir, i32 ignore_idx) +{ + S_SupportPoint result = Zi; + Vec2 dir_norm = NormVec2(dir); + f32 max_dot = -Inf; + if (shape.points_count == 1) + { + // Don't ignore for single-point colliders + ignore_idx = -1; + } + for (i32 point_idx = 0; point_idx < shape.points_count; ++point_idx) + { + if (point_idx != ignore_idx) + { + Vec2 p = shape.points[point_idx]; + f32 dot = DotVec2(p, dir_norm); + if (dot > max_dot) + { + max_dot = dot; + result.p = p; + result.id = point_idx; + } + } + } + result.p = AddVec2(result.p, MulVec2(dir_norm, shape.radius)); + return result; +} + +S_SupportPoint S_SupportPointFromShape(S_Shape shape, Vec2 dir) +{ + return S_SupportPointFromShapeEx(shape, dir, -1); +} + +S_MenkowskiPoint S_MenkowskiPointFromShapes(S_Shape shape0, S_Shape shape1, Vec2 dir) +{ + S_MenkowskiPoint result = Zi; + result.s0 = S_SupportPointFromShape(shape0, dir); + result.s1 = S_SupportPointFromShape(shape1, NegVec2(dir)); + result.p = SubVec2(result.s0.p, result.s1.p); + return result; +} + +S_ClippedLine S_ClipLineToLine(Vec2 a0, Vec2 b0, Vec2 a1, Vec2 b1, Vec2 normal) +{ + Vec2 vab0 = SubVec2(b0, a0); + Vec2 vab1 = SubVec2(b1, a1); + Vec2 va0a1 = SubVec2(a1, a0); + Vec2 vb0b1 = SubVec2(b1, b0); + f32 vab0_w = WedgeVec2(vab0, normal); + f32 vab1_w = WedgeVec2(vab1, normal); + f32 va0a1_w = WedgeVec2(va0a1, normal); + f32 vb0b1_w = WedgeVec2(vb0b1, normal); + + // FIXME: Handle 0 denominator + f32 a0t; + f32 b0t; + { + f32 w = 1 / vab0_w; + a0t = ClampF32(va0a1_w * w, 0, 1); + b0t = ClampF32(vb0b1_w * -w, 0, 1); + } + f32 a1t; + f32 b1t; + { + f32 w = 1 / vab1_w; + a1t = ClampF32(-va0a1_w * w, 0, 1); + b1t = ClampF32(-vb0b1_w * -w, 0, 1); + } + + S_ClippedLine result = Zi; + result.a0_clipped = AddVec2(a0, MulVec2(vab0, a0t)); + result.a1_clipped = AddVec2(a1, MulVec2(vab1, a1t)); + result.b0_clipped = AddVec2(b0, MulVec2(vab0, -b0t)); + result.b1_clipped = AddVec2(b1, MulVec2(vab1, -b1t)); + return result; +} + +Vec2 S_ClipPointToLine(Vec2 a, Vec2 b, Vec2 p, Vec2 normal) +{ + Vec2 vab = SubVec2(b, a); + Vec2 vap = SubVec2(p, a); + + f32 vab_w = WedgeVec2(vab, normal); + f32 vap_w = WedgeVec2(vap, normal); + + f32 t; + { + f32 w = 1 / vab_w; + t = ClampF32(vap_w * w, 0, 1); + } + + Vec2 result = AddVec2(a, MulVec2(vab, t)); + return result; +} + +S_CollisionData S_CollisionDataFromShapes(S_Shape shape0, S_Shape shape1) +{ + S_CollisionData result = Zi; + TempArena scratch = BeginScratchNoConflict(); + + f32 tolerance = 0.005f; // How close can non-overlapping shapes be before collision is considered + f32 min_unique_pt_dist_sq = (0.001f * 0.001f); // NOTE: Should always be less than tolerance, since colliding = 1 if origin is within this distance. + u32 max_iterations = 64; // To prevent extremely large prototypes when origin is in exact center of rounded feature + + ////////////////////////////// + //- GJK + + S_MenkowskiSimplex simplex = Zi; + Vec2 non_overlapping_dir = Zi; + b32 is_overlapping = 0; + { + S_MenkowskiPoint m = Zi; + + // First point is support point in shape's general directions to eachother + Vec2 dir = SubVec2(shape1.centroid, shape0.centroid); + if (IsVec2Zero(dir)) dir = VEC2(1, 0); + simplex.a = S_MenkowskiPointFromShapes(shape0, shape1, dir); + simplex.count = 1; + + Vec2 removed_a = Zi; + Vec2 removed_b = Zi; + u32 num_removed = 0; + for (;;) + { + ////////////////////////////// + //- Find initial points in simplex + + if (simplex.count == 1) + { + // Second point is support point towards origin + dir = NegVec2(simplex.a.p); + + m = S_MenkowskiPointFromShapes(shape0, shape1, dir); + // Check that new point is far enough away from existing point + if (Vec2LenSq(SubVec2(m.p, simplex.a.p)) < min_unique_pt_dist_sq) + { + is_overlapping = 0; + break; + } + simplex.b = simplex.a; + simplex.a = m; + simplex.count = 2; + + // Third point is support point in direction of line normal towards origin + dir = PerpVec2TowardsDir(SubVec2(simplex.b.p, simplex.a.p), NegVec2(simplex.a.p)); + } + + ////////////////////////////// + //- Find third piont in simplex + + { + m = S_MenkowskiPointFromShapes(shape0, shape1, dir); + // Check that new point is far enough away from existing points + if ( + Vec2LenSq(SubVec2(m.p, simplex.a.p)) < min_unique_pt_dist_sq || + Vec2LenSq(SubVec2(m.p, simplex.b.p)) < min_unique_pt_dist_sq || ( + (num_removed >= 1) && ( + (Vec2LenSq(SubVec2(m.p, removed_a)) < min_unique_pt_dist_sq) || + (num_removed >= 2 && Vec2LenSq(SubVec2(m.p, removed_b)) < min_unique_pt_dist_sq) + ) + ) || + AbsF32(WedgeVec2(SubVec2(simplex.b.p, simplex.a.p), SubVec2(m.p, simplex.a.p))) < min_unique_pt_dist_sq + ) + { + is_overlapping = 0; + break; + } + simplex.c = simplex.b; + simplex.b = simplex.a; + simplex.a = m; + simplex.count = 3; + + if ( + (AbsF32(WedgeVec2(SubVec2(simplex.b.p, simplex.a.p), NegVec2(simplex.a.p))) <= min_unique_pt_dist_sq) || + (AbsF32(WedgeVec2(SubVec2(simplex.c.p, simplex.b.p), NegVec2(simplex.b.p))) <= min_unique_pt_dist_sq) || + (AbsF32(WedgeVec2(SubVec2(simplex.c.p, simplex.a.p), NegVec2(simplex.a.p))) <= min_unique_pt_dist_sq) + ) + { + // Simplex lies on origin + is_overlapping = 1; + break; + } + } + + ////////////////////////////// + //- Determine origin region + + Vec2 vab = SubVec2(simplex.b.p, simplex.a.p); + Vec2 vac = SubVec2(simplex.c.p, simplex.a.p); + Vec2 vbc = SubVec2(simplex.c.p, simplex.b.p); + + Vec2 rab_dir = PerpVec2TowardsDir(vab, NegVec2(vac)); + Vec2 rac_dir = PerpVec2TowardsDir(vac, NegVec2(vab)); + Vec2 rbc_dir = PerpVec2TowardsDir(vbc, vab); + + f32 rab_dot = DotVec2(rab_dir, NegVec2(simplex.a.p)); + f32 rac_dot = DotVec2(rac_dir, NegVec2(simplex.a.p)); + f32 rbc_dot = DotVec2(rbc_dir, NegVec2(simplex.b.p)); + + f32 vab_dot = DotVec2(vab, NegVec2(simplex.a.p)) / Vec2LenSq(vab); + f32 vac_dot = DotVec2(vac, NegVec2(simplex.a.p)) / Vec2LenSq(vac); + f32 vbc_dot = DotVec2(vbc, NegVec2(simplex.b.p)) / Vec2LenSq(vbc); + + if (rab_dot >= 0 && vab_dot >= 0 && vab_dot <= 1) + { + // Region ab, remove c + num_removed = 1; + removed_a = simplex.c.p; + simplex.count = 2; + dir = rab_dir; // Next third point is in direction of region ab + } + else if (rac_dot >= 0 && vac_dot >= 0 && vac_dot <= 1) + { + // Region ac, remove b + num_removed = 1; + removed_a = simplex.b.p; + simplex.count = 2; + simplex.b = simplex.c; + dir = rac_dir; // Next third point is in direction of region ac + } + else if (rbc_dot >= 0 && vbc_dot >= 0 && vbc_dot <= 1) + { + // Region bc, remove a + num_removed = 1; + removed_a = simplex.a.p; + simplex.count = 2; + simplex.a = simplex.b; + simplex.b = simplex.c; + dir = rbc_dir; // Next third point is in direction of region bc + } + else if (vab_dot <= 0 && vac_dot <= 0) + { + // Region a, remove bc + num_removed = 2; + removed_a = simplex.b.p; + removed_b = simplex.c.p; + simplex.count = 1; + } + else if (vab_dot >= 1 && vbc_dot <= 0) + { + // Region b, remove ac + num_removed = 2; + removed_a = simplex.a.p; + removed_b = simplex.c.p; + simplex.count = 1; + simplex.a = simplex.b; + } + else if (vac_dot >= 1 && vbc_dot >= 1) + { + // Region c, remove ab + num_removed = 2; + removed_a = simplex.a.p; + removed_b = simplex.b.p; + simplex.count = 1; + simplex.a = simplex.c; + } + else + { + // No region, must be in simplex + is_overlapping = 1; + break; + } + } + if (!is_overlapping) + { + non_overlapping_dir = dir; + } + } + + ////////////////////////////// + //- EPA + + Vec2 normal = Zi; + S_MenkowskiSimplex closest_feature = Zi; + { + S_MenkowskiPoint *proto = 0; + u32 proto_count = 0; + if (is_overlapping) + { + proto = ArenaNext(scratch.arena, S_MenkowskiPoint); + { + Assert(simplex.count == 3); + S_MenkowskiPoint *tmp = PushStructsNoZero(scratch.arena, S_MenkowskiPoint, 3); + tmp[0] = simplex.a; + tmp[1] = simplex.b; + tmp[2] = simplex.c; + proto_count = 3; + } + + i32 winding = WindingFromVec2(SubVec2(simplex.c.p, simplex.a.p), SubVec2(simplex.b.p, simplex.a.p)); + + u32 epa_iterations = 0; + for (;;) + { + ++epa_iterations; + + // Find dir from origin to closest edge + // FIXME: Winding order of ps & pe index + f32 closest_len_sq = Inf; + S_MenkowskiPoint closest_a = Zi; + S_MenkowskiPoint closest_b = Zi; + u32 closest_b_index = 0; + for (u32 i = 0; i < proto_count; ++i) + { + u32 a_index = i; + u32 b_index = (i < proto_count - 1) ? (i + 1) : 0; + S_MenkowskiPoint a = proto[a_index]; + S_MenkowskiPoint b = proto[b_index]; + + Vec2 vab = SubVec2(b.p, a.p); + Vec2 vao = NegVec2(a.p); + + f32 proj_ratio = ClampF32(DotVec2(vao, vab) / Vec2LenSq(vab), 0, 1); + Vec2 proj = AddVec2(a.p, MulVec2(vab, proj_ratio)); + + f32 proj_len_sq = Vec2LenSq(proj); + if (proj_len_sq < closest_len_sq - min_unique_pt_dist_sq) + { + closest_a = a; + closest_b = b; + closest_b_index = b_index; + closest_len_sq = proj_len_sq; + } + } + Vec2 vab = SubVec2(closest_b.p, closest_a.p); + + // Find new point in dir + Vec2 dir = MulVec2(PerpVec2(vab), winding); + S_MenkowskiPoint m = S_MenkowskiPointFromShapes(shape0, shape1, dir); + + // if (debug) + // { + // // If debug step count is reached, we still want to inspect the normal at the step + // normal = NormVec2(dir); + // closest_feature.a = closest_a; + // closest_feature.b = closest_b; + // closest_feature.count = 2; + // } + + // Check validity of new point + { + b32 valid = 1; + + { + // NOTE: Changing this value affects how stable normals are for circular colliders + //const f32 validity_epsilon = min_unique_pt_dist_sq; // Arbitrary + //const f32 validity_epsilon = 0.00000000001f; // Arbitrary + const f32 validity_epsilon = min_unique_pt_dist_sq; // Arbitrary + + Vec2 vam = SubVec2(m.p, closest_a.p); + Vec2 vbm = SubVec2(closest_b.p, closest_a.p); + + f32 dot = DotVec2(vab, vam) / Vec2LenSq(vab); + + if (dot >= -validity_epsilon && dot <= 1 - validity_epsilon && (WedgeVec2(vab, vam) * -winding) >= -validity_epsilon) + { + // New point is not between edge + valid = 0; + } + else if (Vec2LenSq(vam) < min_unique_pt_dist_sq || Vec2LenSq(vbm) < min_unique_pt_dist_sq) + { + // New point is too close to existing + valid = 0; + } + } + + if (!valid || epa_iterations >= max_iterations) + { + normal = NormVec2(dir); + closest_feature.a = closest_a; + closest_feature.b = closest_b; + closest_feature.count = 2; + break; + } + } + + // Expand prototype + PushStructNoZero(scratch.arena, S_MenkowskiPoint); + ++proto_count; + + // Shift points in prototype to make room + for (u32 i = proto_count - 1; i > closest_b_index; --i) + { + u32 shift_from = (i > 0) ? i - 1 : proto_count - 1; + u32 shift_to = i; + proto[shift_to] = proto[shift_from]; + } + + // Insert new point into prototype + proto[closest_b_index] = m; + } + } + else + { + normal = NormVec2(non_overlapping_dir); + closest_feature.count = simplex.count; + closest_feature.a = simplex.a; + closest_feature.b = simplex.b; + } + } + + ////////////////////////////// + //- Determine collision + + b32 is_colliding = 0; + { + if (is_overlapping) + { + is_colliding = 1; + } + else + { + // Shapes not overlapping, determine if distance between shapes within tolerance + if (closest_feature.count == 1) + { + Vec2 p = NegVec2(closest_feature.a.p); + if (Vec2LenSq(p) <= (tolerance * tolerance)) + { + is_colliding = 1; + } + } + else + { + // Project origin to determine if distance is within tolerance. + Assert(closest_feature.count == 2); + Vec2 vab = SubVec2(closest_feature.b.p, closest_feature.a.p); + Vec2 vao = NegVec2(closest_feature.a.p); + f32 ratio = ClampF32(DotVec2(vab, vao) / DotVec2(vab, vab), 0, 1); + Vec2 p = AddVec2(closest_feature.a.p, MulVec2(vab, ratio)); + if (Vec2LenSq(p) <= (tolerance * tolerance)) + { + is_colliding = 1; + } + } + } + } + + ////////////////////////////// + //- Compute collision points + + // Clip to determine final points + i32 collision_points_count = 0; + S_CollisionPoint collision_points[2] = Zi; + if (is_colliding) + { + // Max vertices must be < 16 to fit in 4 bit ids + StaticAssert(countof(shape0.points) <= 16); + { + b32 collapse0 = 0; + b32 collapse1 = 0; + + S_SupportPoint a0 = closest_feature.a.s0; + S_SupportPoint a1 = closest_feature.a.s1; + S_SupportPoint b0 = closest_feature.b.s0; + S_SupportPoint b1 = closest_feature.b.s1; + // FIXME: Manually account for shapes w/ 1 & 2 points + if (closest_feature.count == 2) + { + if (a0.id == b0.id) + { + if (shape0.points_count > 1) + { + b0 = S_SupportPointFromShapeEx(shape0, normal, b0.id); + } + else + { + collapse0 = 1; + b0 = a0; + } + } + if (a1.id == b1.id) + { + if (shape1.points_count > 1) + { + b1 = S_SupportPointFromShapeEx(shape1, NegVec2(normal), b1.id); + } + else + { + collapse1 = 1; + b1 = a1; + } + } + } + else + { + collapse0 = 1; + collapse1 = 1; + b0 = a0; + b1 = a1; + } + + Vec2 vab0 = SubVec2(b0.p, a0.p); + Vec2 vab1 = SubVec2(b1.p, a1.p); + Vec2 vab0_norm = NormVec2(vab0); + Vec2 vab1_norm = NormVec2(vab1); + + // Swap points based on normal direction for consistent clipping + if (WedgeVec2(normal, vab0) < 0) + { + S_SupportPoint tmp = a0; + a0 = b0; + b0 = tmp; + vab0 = NegVec2(vab0); + } + if (WedgeVec2(normal, vab1) < 0) + { + S_SupportPoint tmp = a1; + a1 = b1; + b1 = tmp; + vab1 = NegVec2(vab1); + } + + // Collapse lines that are too far in the direction of the normal to be accurately clipped + f32 collapse_epsilon = 0.05f; + collapse0 = collapse0 || AbsF32(WedgeVec2(normal, vab0_norm)) < collapse_epsilon; + collapse1 = collapse1 || AbsF32(WedgeVec2(normal, vab1_norm)) < collapse_epsilon; + + // Collapse lines into deepest point + if (collapse0) + { + if (DotVec2(normal, vab0) > 0) + { + a0 = b0; + } + else + { + // TODO: Remove this (debugging) + b0 = a0; + } + } + if (collapse1) + { + if (DotVec2(normal, vab1) < 0) + { + a1 = b1; + } + else + { + // TODO: Remove this (debugging) + b1 = a1; + } + } + + f32 a_sep = Inf; + f32 b_sep = Inf; + Vec2 a_midpoint = Zi; + Vec2 b_midpoint = Zi; + b32 ignore_a = 1; + b32 ignore_b = 1; + if (!collapse0 && !collapse1) + { + // Clip line to line + S_ClippedLine clip_result = S_ClipLineToLine(a0.p, b0.p, a1.p, b1.p, normal); + Vec2 a0_clipped = clip_result.a0_clipped; + Vec2 a1_clipped = clip_result.a1_clipped; + Vec2 b0_clipped = clip_result.b0_clipped; + Vec2 b1_clipped = clip_result.b1_clipped; + // Calc midpoint between clipped a & b + Vec2 va0a1_clipped = SubVec2(a1_clipped, a0_clipped); + Vec2 vb0b1_clipped = SubVec2(b1_clipped, b0_clipped); + a_sep = DotVec2(va0a1_clipped, normal); + b_sep = DotVec2(vb0b1_clipped, normal); + a_midpoint = AddVec2(a0_clipped, MulVec2(va0a1_clipped, 0.5f)); + b_midpoint = AddVec2(b0_clipped, MulVec2(vb0b1_clipped, 0.5f)); + ignore_a = 0; + ignore_b = 0; + Vec2 vfin = SubVec2(b_midpoint, a_midpoint); + if (Vec2LenSq(vfin) < (0.005 * 0.005)) + { + if (a_sep > b_sep) + { + ignore_a = 1; + } + else + { + ignore_b = 1; + } + } + } + else + { + Vec2 p0 = a0.p; + Vec2 p1 = a1.p; + // TODO: Choose ID based on closest clipped point + if (collapse1 && !collapse0) + { + // Project a1 onto vab0 + p0 = S_ClipPointToLine(a0.p, b0.p, a1.p, normal); + } + if (collapse0 && !collapse1) + { + // Project a0 onto vab1 + p1 = S_ClipPointToLine(a1.p, b1.p, a0.p, normal); + } + // Calc midpoint + Vec2 vsep = SubVec2(p1, p0); + a_midpoint = AddVec2(p0, MulVec2(vsep, 0.5f)); + a_sep = DotVec2(normal, p1) - DotVec2(normal, p0); + ignore_a = 0; + } + + // Insert points + if (!ignore_a && a_sep < tolerance) + { + S_CollisionPoint *point = &collision_points[collision_points_count++]; + point->id = a0.id | (a1.id << 4); + point->separation = a_sep; + point->p = a_midpoint; + } + if (!ignore_b && b_sep < tolerance) + { + S_CollisionPoint *point = &collision_points[collision_points_count++]; + point->id = b0.id | (b1.id << 4); + point->separation = b_sep; + point->p = b_midpoint; + } + } + } + + ////////////////////////////// + //- Compute closest points + + Vec2 closest_p0 = Zi; + Vec2 closest_p1 = Zi; + if (closest_feature.count == 1) + { + closest_p0 = closest_feature.a.s0.p; + closest_p1 = closest_feature.a.s1.p; + } + else + { + Assert(closest_feature.count == 2); + // FIXME: Winding order dependent? + f32 ratio = 0; + { + // Determine ratio between edge a & b that projected origin lies + Vec2 vab = SubVec2(closest_feature.b.p, closest_feature.a.p); + Vec2 vao = NegVec2(closest_feature.a.p); + ratio = ClampF32(DotVec2(vab, vao) / DotVec2(vab, vab), 0, 1); + } + // Shape 0 + closest_p0 = SubVec2(closest_feature.b.s0.p, closest_feature.a.s0.p); + closest_p0 = MulVec2(closest_p0, ratio); + closest_p0 = AddVec2(closest_p0, closest_feature.a.s0.p); + // Shape 1 + closest_p1 = SubVec2(closest_feature.b.s1.p, closest_feature.a.s1.p); + closest_p1 = MulVec2(closest_p1, ratio); + closest_p1 = AddVec2(closest_p1, closest_feature.a.s1.p); + } + + CopyStructs(result.collision_points, collision_points, countof(collision_points)); + result.collision_points_count = collision_points_count; + result.closest_p0 = closest_p0; + result.closest_p1 = closest_p1; + + EndScratch(scratch); + return result; +} + //////////////////////////////////////////////////////////// //~ Lookup helpers @@ -594,7 +1241,7 @@ void S_TickForever(WaveLaneCtx *lane) test_rect.p1 = VEC2(1, 1); constraint->shape1 = S_ShapeFromDesc( - .radius = 0.2, + .radius = 0.5, .count = 4, .points[0] = VEC2(test_rect.p0.x, test_rect.p0.y), .points[1] = VEC2(test_rect.p1.x, test_rect.p0.y), @@ -653,8 +1300,12 @@ void S_TickForever(WaveLaneCtx *lane) Vec2 normal = NormVec2(shape_dir); Vec2 neg_normal = NegVec2(normal); - Vec2 shape0_pt = S_SupportPointFromShape(shape0, shape_dir); - Vec2 shape1_pt = S_SupportPointFromShape(shape1, neg_shape_dir); + // Vec2 shape0_pt = S_SupportPointFromShape(shape0, shape_dir); + // Vec2 shape1_pt = S_SupportPointFromShape(shape1, neg_shape_dir); + + S_CollisionData collision_data = S_CollisionDataFromShapes(shape0, shape1); + Vec2 shape0_pt = collision_data.closest_p0; + Vec2 shape1_pt = collision_data.closest_p1; Vec2 sep = SubVec2(shape1_pt, shape0_pt); f32 sep_along_normal = DotVec2(sep, normal); @@ -663,13 +1314,11 @@ void S_TickForever(WaveLaneCtx *lane) // f32 sep_normal = DotVec2(sep, normal); - S_DebugDrawPoint(shape0_pt, Color_Cyan); S_DebugDrawPoint(shape1_pt, Color_Cyan); - - - + S_DebugDrawPoint(collision_data.collision_points[0].p, Color_Red); + S_DebugDrawPoint(collision_data.collision_points[0].p, Color_Red); if (sep_along_normal < 0) { @@ -732,7 +1381,7 @@ void S_TickForever(WaveLaneCtx *lane) { Vec4 color = VEC4(0.8, 0.8, 0.8, 1); Vec2 p0 = world_shape.centroid; - Vec2 p1 = S_SupportPointFromShape(world_shape, ent->look); + Vec2 p1 = S_SupportPointFromShape(world_shape, ent->look).p; S_DebugDrawLine(p0, p1, color); } } diff --git a/src/pp/pp_sim/pp_sim_core.h b/src/pp/pp_sim/pp_sim_core.h index 2fa1127e..4aafdde2 100644 --- a/src/pp/pp_sim/pp_sim_core.h +++ b/src/pp/pp_sim/pp_sim_core.h @@ -110,6 +110,52 @@ Struct(S_EntList) u64 count; }; +//////////////////////////////////////////////////////////// +//~ Collision types + +Struct(S_SupportPoint) +{ + Vec2 p; + i32 id; // Index of the originating piont in the shape +}; + +Struct(S_CollisionPoint) +{ + Vec2 p; + f32 separation; + u32 id; // Based on polygon edge-to-edge +}; + +Struct(S_MenkowskiPoint) +{ + Vec2 p; // Menkowski difference point + S_SupportPoint s0; // Support point of first shape in dir + S_SupportPoint s1; // Support point of second shape in -dir +}; + +Struct(S_MenkowskiSimplex) +{ + i32 count; + S_MenkowskiPoint a, b, c; +}; + +Struct(S_ClippedLine) +{ + Vec2 a0_clipped, b0_clipped; + Vec2 a1_clipped, b1_clipped; +}; + +Struct(S_CollisionData) +{ + // Contact manifold + i32 collision_points_count; + S_CollisionPoint collision_points[2]; + + // Closest points + Vec2 closest_p0; + Vec2 closest_p1; +}; + //////////////////////////////////////////////////////////// //~ Constraint types @@ -324,10 +370,18 @@ S_Shape S_ShapeFromDescEx(S_ShapeDesc desc); #define S_ShapeFromDesc(...) S_ShapeFromDescEx((S_ShapeDesc) { __VA_ARGS__ }) S_Shape S_MulXformShape(Xform xf, S_Shape shape); - -Vec2 S_SupportPointFromShape(S_Shape shape, Vec2 dir); Rng2 S_BoundingBoxFromShape(S_Shape shape); +//////////////////////////////////////////////////////////// +//~ Collision + +S_SupportPoint S_SupportPointFromShapeEx(S_Shape shape, Vec2 dir, i32 ignore_idx); +S_SupportPoint S_SupportPointFromShape(S_Shape shape, Vec2 dir); +S_MenkowskiPoint S_MenkowskiPointFromShapes(S_Shape shape0, S_Shape shape1, Vec2 dir); +S_ClippedLine S_ClipLineToLine(Vec2 a0, Vec2 b0, Vec2 a1, Vec2 b1, Vec2 normal); +Vec2 S_ClipPointToLine(Vec2 a, Vec2 b, Vec2 p, Vec2 normal); +S_CollisionData S_CollisionDataFromShapes(S_Shape shape0, S_Shape shape1); + //////////////////////////////////////////////////////////// //~ Lookup helpers diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index 8b992c2a..05aeee34 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -191,7 +191,7 @@ void V_DrawShape(S_Shape shape, Vec4 srgb, i32 detail, V_DrawFlag flags) { f32 rad = ((f32)i / (f32)detail) * Tau; Vec2 dir = Vec2FromAngle(rad); - Vec2 sp = S_SupportPointFromShape(shape, dir); + Vec2 sp = S_SupportPointFromShape(shape, dir).p; draw_points.points[i] = sp; } V_DrawPoly(draw_points, srgb, flags); @@ -237,8 +237,7 @@ V_WidgetTheme V_GetWidgetTheme(void) theme.icon_font = UI_BuiltinIconFont(); // theme.font_size = 14; - // theme.font_size = TweakFloat("Font size", 14, 6, 50, .precision = 0); - theme.font_size = TweakFloat("Font size", 14, 6, 50, .precision = 2); + theme.font_size = TweakFloat("Font size", 14, 6, 50, .precision = 0); theme.h1 = 2.00; theme.h2 = 1.50; theme.h3 = 1.25; @@ -247,8 +246,6 @@ V_WidgetTheme V_GetWidgetTheme(void) theme.h6 = 0.75; theme.micro = 0.50; - // theme.rounding = 0; - // theme.rounding = 1; theme.rounding = TweakFloat("Rounding", 1, 0, 1); theme.text_padding_x = 5; @@ -1511,7 +1508,7 @@ void V_TickForever(WaveLaneCtx *lane) UI_Push(BorderColor, window_border_color); // UI_Push(BorderSize, theme.window_bd_sz); UI_Push(BorderSize, 1); - UI_Push(Rounding, UI_RGROW(0.095 * theme.rounding)); + UI_Push(Rounding, UI_RGROW(0.06 * theme.rounding)); UI_Push(Width, UI_FNT(40, 0)); UI_Push(Height, UI_SHRINK(0, 0)); UI_Push(ChildLayoutAxis, Axis_Y); @@ -1707,28 +1704,6 @@ void V_TickForever(WaveLaneCtx *lane) String new_tweak_str = tweak_var.value; b32 is_default = MatchString(new_tweak_str, tweak_var.initial); - // Tweak label - { - UI_BuildSpacer(UI_PIX(spacing, 1), Axis_X); - if (is_default) - { - UI_SetNext(TextColor, theme.col.hint); - UI_SetNext(FontSize, UI_Top(FontSize) * theme.h5); - } - else - { - UI_SetNext(TextColor, Color_White); - } - UI_SetNext(ChildAlignment, UI_Region_Left); - UI_SetNext(Width, UI_SHRINK(0, 1)); - UI_SetNext(Height, UI_SHRINK(0, 1)); - UI_SetNext(Text, new_tweak_str); - UI_SetNext(Flags, UI_BoxFlag_DrawText); - UI_SetNext(BackgroundColor, 0); - UI_SetNext(BorderColor, 0); - UI_BuildBox(); - } - // Reset button if (!is_default) { @@ -1769,6 +1744,28 @@ void V_TickForever(WaveLaneCtx *lane) UI_BuildIconEx(reset_key, theme.icon_font, UI_Icon_Loop2); } + // Tweak label + { + UI_BuildSpacer(UI_PIX(spacing, 1), Axis_X); + if (is_default) + { + UI_SetNext(TextColor, theme.col.hint); + } + else + { + UI_SetNext(TextColor, Color_White); + } + UI_SetNext(FontSize, UI_Top(FontSize) * theme.h5); + UI_SetNext(ChildAlignment, UI_Region_Left); + UI_SetNext(Width, UI_SHRINK(0, 1)); + UI_SetNext(Height, UI_SHRINK(0, 1)); + UI_SetNext(Text, new_tweak_str); + UI_SetNext(Flags, UI_BoxFlag_DrawText | UI_BoxFlag_NoTextTruncation); + UI_SetNext(BackgroundColor, 0); + UI_SetNext(BorderColor, 0); + UI_BuildBox(); + } + UI_BuildSpacer(UI_PIX(spacing, 1), Axis_X); switch (tweak_var.kind) @@ -2299,7 +2296,7 @@ void V_TickForever(WaveLaneCtx *lane) S_Ent *ent = &cmd->ent; *ent = *S_EntFromKey(&V.lookup, V.player_key); ent->key = V.player_key; - ent->move_speed = 0.1; + ent->move_speed = 0.05; ent->local_shape = S_ShapeFromDesc( .mass = 10, .count = 1,