#include "collision.h" #include "../lib/ds.h" #include "../lib/geometry.h" #include void phy_collisions_broadphase(phy_world *world, phy_body_pair *pair_list, u32 max_pairs, u32 *num_pairs) { // Update AABBs for(u32 i = 0; i < world->bodies_count; i++) { phy_body *body = world->bodies + i; body->aabb = phy_aabb_from_body(body); } u32 pairs_count = 0; for(u32 a = 0; a < world->bodies_count; a++) { phy_body *body_a = world->bodies + a; for(u32 b = a + 1; b < world->bodies_count; b++) { phy_body *body_b = world->bodies + b; if(overlaps(body_a->aabb, body_b->aabb)) { // Add to list of possible collisions. if(pairs_count < max_pairs) { pair_list[pairs_count] = phy_body_pair{ .body_a = body_a, .body_b = body_b }; } pairs_count++; } } } if(num_pairs) *num_pairs = pairs_count; } // @Cleanup: put this in the right place // @Performance: this is probably not a good way of doing this void project_box_on_axis(phy_body *body, v3 axis, f32 *min, f32 *max) { v3 points[8]; build_cube_vertices(points); m4 transform = translation_v3(body->position) * rotation_v3(body->rotation) * scale_v3(body->box.dimensions); *min = *max = project_point_on_vector(axis, body->position); for(int i = 0; i < 8; i++) { f32 projected = project_point_on_vector(axis, (transform * V4(points[i], 1)).xyz); *min = minimum(*min, projected); *max = maximum(*max, projected); } } f32 axis_range_distance(f32 a_min, f32 a_max, f32 b_min, f32 b_max) { f32 a_range = a_max - a_min; f32 a_center = a_min + a_range / 2; f32 b_range = b_max - b_min; f32 b_center = b_min + b_range / 2; return fabs(a_center - b_center) - (a_range + b_range) / 2; } bool sat_box_face_point_collision_test(phy_body *body_a, phy_body *body_b, v3 axis, f32 a_size_on_current_axis, f32 *depth, v3 *furthest_a, v3 *furthest_b) { f32 a_middle = project_point_on_vector(axis, body_a->position); f32 b_middle = project_point_on_vector(axis, body_b->position); f32 direction = (b_middle - a_middle < 0) ? -1 : 1; f32 boundary = a_middle + direction * a_size_on_current_axis / 2; f32 dist = +INFINITY; v3 point; v3 points[8]; build_cube_vertices(points); m4 transform = translation_v3(body_b->position) * rotation_v3(body_b->rotation) * scale_v3(body_b->box.dimensions); for(int i = 0; i < 8; i++) { v3 p = (transform * V4(points[i], 1)).xyz; f32 projected = project_point_on_vector(axis, p); f32 curr_dist = direction * (projected - boundary); if(curr_dist < dist) { dist = curr_dist; point = p; } } if(dist < 0) // Possible collision { v3 furthest_point_b = point; v3 furthest_point_a = point + -dist * -direction * axis; *depth = -dist * direction; *furthest_a = furthest_point_a; *furthest_b = furthest_point_b; return true; } // else Separated on this axis. No collision return false; } bool sat_box_collision_test(phy_body *body_a, phy_body *body_b, phy_collision *res_collision) { // Separating axis test // 3 + 3 = 6 face normals // 3 * 3 = 9 right angle to pair of edges v3 axes[15]; m4 a_rotation = rotation_v3(body_a->rotation); axes[0] = extract_column(a_rotation, 0).xyz; axes[1] = extract_column(a_rotation, 1).xyz; axes[2] = extract_column(a_rotation, 2).xyz; m4 b_rotation = rotation_v3(body_b->rotation); axes[3] = -extract_column(b_rotation, 0).xyz; axes[4] = -extract_column(b_rotation, 1).xyz; axes[5] = -extract_column(b_rotation, 2).xyz; for(int a = 0; a < 3; a++) for(int b = 0; b < 3; b++) { (axes+6)[3*a + b] = cross(axes[a], axes[b + 3]); } phy_collision collision; collision.body_a = body_a; collision.body_b = body_b; collision.depth = +INFINITY; // Face axes: body a for(int i = 0; i < 3; i++) { v3 axis = axes[i]; f32 depth; v3 furthest_point_a, furthest_point_b; bool collides = sat_box_face_point_collision_test(body_a, body_b, axis, body_a->box.dimensions.E[i], &depth, &furthest_point_a, &furthest_point_b); if(!collides) return false; if(abs(depth) < abs(collision.depth)) { collision.depth = depth; collision.furthest_point_a = furthest_point_a; collision.furthest_point_b = furthest_point_b; collision.best_separation_direction = axis; } } // Face axes: body b for(int i = 0; i < 3; i++) { v3 axis = axes[3 + i]; f32 depth; v3 furthest_point_a, furthest_point_b; bool collides = sat_box_face_point_collision_test(body_b, body_a, axis, body_b->box.dimensions.E[i], &depth, &furthest_point_b, &furthest_point_a); if(!collides) return false; if(abs(depth) < abs(collision.depth)) { collision.depth = depth; collision.furthest_point_a = furthest_point_a; collision.furthest_point_b = furthest_point_b; collision.best_separation_direction = -axis; } } // @TODO: edge-edge *res_collision = collision; return true; } void phy_collisions_detection(phy_world *world, phy_body_pair *pair_list, u32 num_pairs, phy_collision *collision_list, u32 max_collisions, u32 *num_collisions) { u32 n_collisions = 0; for(u32 i = 0; i < num_pairs; i++) { phy_body *body_a = pair_list[i].body_a; phy_body *body_b = pair_list[i].body_b; if(body_b->shape < body_a->shape) swap(body_b, body_a); switch(body_a->shape) { case PHY_SHAPE_SPHERE: { switch(body_b->shape) { case PHY_SHAPE_SPHERE: { f32 dist = distance(body_a->position, body_b->position); f32 radius_sum = body_a->sphere.radius + body_b->sphere.radius; if(dist <= radius_sum) { phy_collision collision; collision.body_a = body_a; collision.body_b = body_b; collision.depth = radius_sum - dist; v3 direction = normalize(body_b->position - body_a->position); collision.furthest_point_a = body_a->position + (body_a->sphere.radius - collision.depth) * direction; collision.furthest_point_b = body_b->position + (body_b->sphere.radius - collision.depth) * -direction; collision.best_separation_direction = direction; if(n_collisions < max_collisions) collision_list[n_collisions] = collision; n_collisions++; } } break; case PHY_SHAPE_BOX: { // Use box's cordinate space m4 box_b_coord_space_transform = rotation_v3(-body_b->rotation) * translation_v3(-body_b->position); v4 sphere_center4 = box_b_coord_space_transform * V4(body_a->position, 1.0); v3 sphere_center = sphere_center4.xyz / sphere_center4.w; Box box = { .min = -0.5*body_b->box.dimensions, .max = 0.5*body_b->box.dimensions }; v3 closest_point; f32 dist = box_SDF(box, sphere_center, &closest_point); if(dist - body_a->sphere.radius <= 0) { phy_collision collision; collision.body_a = body_a; collision.body_b = body_b; collision.depth = dist - body_a->sphere.radius; v3 direction = normalize(closest_point - body_a ->position); collision.furthest_point_a = body_a->position + (body_a->sphere.radius - collision.depth) * direction; collision.furthest_point_b = body_a->position + body_a->sphere.radius * direction; collision.best_separation_direction = direction; if(n_collisions < max_collisions) collision_list[n_collisions] = collision; n_collisions++; } } break; case PHY_SHAPE_MESH: { assert(false && "Unmanaged PHY_SHAPE pair"); } break; default: { assert(false && "Unmanaged PHY_SHAPE pair"); } break; } } break; case PHY_SHAPE_BOX: { switch(body_b->shape) { // case PHY_SHAPE_SPHERE: // Already managed case PHY_SHAPE_BOX: { phy_collision collision; bool collides = sat_box_collision_test(body_a, body_b, &collision); if(collides) { if(n_collisions < max_collisions) collision_list[n_collisions] = collision; n_collisions++; } } break; case PHY_SHAPE_MESH: { assert(false && "Unmanaged PHY_SHAPE pair"); } break; default: { assert(false && "Unmanaged PHY_SHAPE pair"); } break; } } break; case PHY_SHAPE_MESH: { switch(body_b->shape) { // case PHY_SHAPE_SPHERE: // Already managed // case PHY_SHAPE_BOX: // Already managed case PHY_SHAPE_MESH: { assert(false && "Unmanaged PHY_SHAPE pair"); } break; default: { assert(false && "Unmanaged PHY_SHAPE pair"); } break; } } break; default: { assert(false && "Unmanaged PHY_SHAPE"); } break; } } *num_collisions = n_collisions; } void phy_collisions_resolution(phy_world *world, phy_collision *collision_list, u32 num_collisions) { for(u32 i = 0; i < num_collisions; i++) { phy_body *body_a = collision_list[i].body_a; phy_body *body_b = collision_list[i].body_b; v3 separation_direction = collision_list[i].best_separation_direction; f32 depth = collision_list[i].depth; if(body_a->mass != 0) // Not a static body { body_a->position += -separation_direction * depth;//-normalize(body_a->velocity) * dot(normalize(body_a->velocity), separation_direction * depth); body_a->velocity = {0,0,0};//length(body_a->velocity) * separation_direction * body_a->bounciness; } if(body_b->mass != 0) // Not a static body { body_b->position += separation_direction * depth;//-normalize(body_b->velocity) * dot(normalize(body_b->velocity), separation_direction * depth); body_b->velocity = {0,0,0};//length(body_a->velocity) * -separation_direction * body_b->bounciness; } } }