443 lines
11 KiB
Odin
443 lines
11 KiB
Odin
package collision
|
|
|
|
import "core:log"
|
|
import "core:math"
|
|
import lg "core:math/linalg"
|
|
import "game:halfedge"
|
|
import rl "vendor:raylib"
|
|
import "vendor:raylib/rlgl"
|
|
|
|
_ :: math
|
|
_ :: rl
|
|
_ :: rlgl
|
|
_ :: log
|
|
|
|
Convex :: halfedge.Half_Edge_Mesh
|
|
|
|
BOX_CORNERS_NORM :: [8]Vec3 {
|
|
{-1, 1, 1},
|
|
{-1, -1, 1},
|
|
{-1, 1, -1},
|
|
{-1, -1, -1},
|
|
{1, 1, 1},
|
|
{1, -1, 1},
|
|
{1, 1, -1},
|
|
{1, -1, -1},
|
|
}
|
|
|
|
box_indices := [6 * 4]u16{0, 4, 6, 2, 3, 2, 6, 7, 7, 6, 4, 5, 5, 1, 3, 7, 1, 0, 2, 3, 5, 4, 0, 1}
|
|
|
|
box_to_convex :: proc(box: Box, allocator := context.allocator) -> (convex: Convex) {
|
|
vertices := make([]Vec3, 8, context.temp_allocator)
|
|
|
|
for corner, i in BOX_CORNERS_NORM {
|
|
vertices[i] = box.pos + corner * box.rad
|
|
}
|
|
|
|
convex = Convex(halfedge.mesh_from_vertex_index_list(vertices, box_indices[:], 4, allocator))
|
|
|
|
return
|
|
}
|
|
|
|
Contact_Manifold :: struct {
|
|
points: []Vec3,
|
|
normal: Vec3,
|
|
}
|
|
|
|
convex_vs_convex_sat :: proc(a, b: Convex) -> (manifold: Contact_Manifold, collision: bool) {
|
|
face_query_a := query_separation_face_directions(a, b)
|
|
if face_query_a.separation > 0 {
|
|
return
|
|
}
|
|
face_query_b := query_separation_face_directions(b, a)
|
|
if face_query_b.separation > 0 {
|
|
return
|
|
}
|
|
|
|
edge_separation, edge_a, edge_b := query_separation_edges(a, b)
|
|
_, _ = edge_a, edge_b
|
|
if edge_separation > 0 {
|
|
return
|
|
}
|
|
face_query_a.separation += 0.1
|
|
edge_separation -= 0.1
|
|
|
|
is_face_a_contact := face_query_a.separation >= edge_separation
|
|
is_face_b_contact := face_query_b.separation >= edge_separation
|
|
|
|
log.infof(
|
|
"face_a_sep: %v, face_b_sep: %v, edge_sep: %v",
|
|
face_query_a.separation,
|
|
face_query_b.separation,
|
|
edge_separation,
|
|
)
|
|
|
|
collision = true
|
|
if is_face_a_contact && is_face_b_contact {
|
|
manifold = create_face_contact_manifold(face_query_a, a, face_query_b, b)
|
|
} else {
|
|
manifold = create_edge_contact_manifold(a, b, edge_separation, edge_a, edge_b)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
Face_Query :: struct {
|
|
separation: f32,
|
|
face: halfedge.Face_Index,
|
|
}
|
|
|
|
query_separation_face_directions :: proc(a, b: Convex) -> (result: Face_Query) {
|
|
result.separation = min(f32)
|
|
for face, f in a.faces {
|
|
index := a.edges[face.edge].origin
|
|
|
|
pos := a.vertices[index].pos
|
|
normal := face.normal
|
|
|
|
support_point, _, _ := find_support_point(b, -normal)
|
|
|
|
plane := plane_from_point_normal(pos, normal)
|
|
|
|
distance := signed_distance_plane(support_point.pos, plane)
|
|
if distance > result.separation {
|
|
result.face = halfedge.Face_Index(f)
|
|
result.separation = distance
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
find_support_point_from_slice :: proc(points: []Vec3, normal: Vec3) -> Vec3 {
|
|
p: Vec3
|
|
max_proj := min(f32)
|
|
for vert in points {
|
|
proj := lg.dot(vert, normal)
|
|
if proj > max_proj {
|
|
max_proj = proj
|
|
p = vert
|
|
}
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
find_support_point :: proc(
|
|
convex: Convex,
|
|
normal: Vec3,
|
|
) -> (
|
|
vert: halfedge.Vertex,
|
|
idx: halfedge.Vertex_Index,
|
|
ok: bool,
|
|
) {
|
|
max_proj := min(f32)
|
|
for v, i in convex.vertices {
|
|
proj := lg.dot(v.pos, normal)
|
|
if proj > max_proj {
|
|
max_proj = proj
|
|
vert = v
|
|
idx = halfedge.Vertex_Index(i)
|
|
ok = true
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
query_separation_edges :: proc(
|
|
a, b: Convex,
|
|
) -> (
|
|
separation: f32,
|
|
a_edge: halfedge.Edge_Index,
|
|
b_edge: halfedge.Edge_Index,
|
|
) {
|
|
separation = min(f32)
|
|
a_edge = -1
|
|
b_edge = -1
|
|
|
|
step := 0
|
|
|
|
separating_plane: Plane
|
|
separating_plane_p: Vec3
|
|
success_step: int
|
|
|
|
for edge_a, edge_a_idx in a.edges {
|
|
for edge_b in b.edges {
|
|
edge_a_dir := halfedge.get_edge_direction_normalized(a, edge_a)
|
|
edge_b_dir := halfedge.get_edge_direction_normalized(b, edge_b)
|
|
|
|
axis := lg.normalize0(lg.cross(edge_a_dir, edge_b_dir))
|
|
|
|
if axis == 0 {
|
|
continue
|
|
}
|
|
|
|
|
|
edge_a_origin, _ := halfedge.get_edge_points(a, edge_a)
|
|
if lg.dot(axis, edge_a_origin - a.center) < 0 {
|
|
axis = -axis
|
|
}
|
|
plane_a := plane_from_point_normal(edge_a_origin, axis)
|
|
vert_a, _, _ := find_support_point(a, plane_a.normal)
|
|
vert_b, vert_b_idx, _ := find_support_point(b, -plane_a.normal)
|
|
|
|
// We found the support vert on mesh b, but now we need to find the
|
|
// best edge that includes that point
|
|
vert_b_edge: halfedge.Half_Edge
|
|
vert_b_edge_idx: halfedge.Edge_Index = -1
|
|
{
|
|
min_b2_distance := max(f32)
|
|
it := halfedge.iterator_vertex_edges(b, vert_b_idx)
|
|
for edge, edge_idx in halfedge.iterate_next_vertex_edge(&it) {
|
|
_, vert_b2 := halfedge.get_edge_points(b, edge)
|
|
|
|
distance_b2 := signed_distance_plane(vert_b2, plane_a)
|
|
if distance_b2 < min_b2_distance {
|
|
min_b2_distance = distance_b2
|
|
vert_b_edge = edge
|
|
vert_b_edge_idx = edge_idx
|
|
}
|
|
}
|
|
|
|
assert(vert_b_edge_idx >= 0, "couldn't find the edge on convex B")
|
|
}
|
|
|
|
distance_a := signed_distance_plane(vert_a.pos, plane_a)
|
|
if distance_a > 0 {
|
|
continue
|
|
}
|
|
distance_b := signed_distance_plane(vert_b.pos, plane_a)
|
|
vert_b_projected := vert_b.pos + plane_a.normal * -distance_b
|
|
|
|
if step == -1 {
|
|
// a1, a2 := halfedge.get_edge_points(a, edge_a)
|
|
// edge_a_center := (a1 + a2) * 0.5
|
|
a1, a2 := halfedge.get_edge_points(halfedge.Half_Edge_Mesh(a), edge_a)
|
|
b1, b2 := halfedge.get_edge_points(halfedge.Half_Edge_Mesh(b), vert_b_edge)
|
|
|
|
rl.DrawLine3D(edge_a_origin, edge_a_origin + plane_a.normal, rl.BLUE)
|
|
rl.DrawLine3D(a1 + 0.1, a2 + 0.1, rl.ORANGE)
|
|
rl.DrawLine3D(b1 + 0.1, b2 + 0.1, rl.PURPLE)
|
|
|
|
rl.DrawSphereWires(edge_a_origin, 0.1, 4, 4, rl.ORANGE)
|
|
rl.DrawSphereWires(vert_b.pos, 0.05, 4, 4, rl.BLUE)
|
|
rl.DrawSphereWires(vert_b_projected, 0.05, 4, 4, rl.BLUE)
|
|
rl.DrawLine3D(vert_b.pos, vert_b_projected, rl.VIOLET)
|
|
log.debugf("dist: %v", distance_b)
|
|
|
|
{
|
|
// rl.BeginBlendMode(.ALPHA)
|
|
// defer rl.EndBlendMode()
|
|
debug_draw_plane(edge_a_origin, plane_a, rl.Color{0, 228, 48, 100})
|
|
}
|
|
}
|
|
if distance_b > separation {
|
|
separation = distance_b
|
|
a_edge = halfedge.Edge_Index(edge_a_idx)
|
|
b_edge = vert_b_edge_idx
|
|
separating_plane = plane_a
|
|
separating_plane_p = edge_a_origin
|
|
success_step = step
|
|
}
|
|
|
|
step += 1
|
|
}
|
|
}
|
|
// log.debugf("step: %v", success_step)
|
|
// debug_draw_plane(separating_plane_p, separating_plane, rl.Color{228, 0, 48, 100})
|
|
|
|
return
|
|
}
|
|
|
|
create_face_contact_manifold :: proc(
|
|
face_query_a: Face_Query,
|
|
a: Convex,
|
|
face_query_b: Face_Query,
|
|
b: Convex,
|
|
) -> (
|
|
manifold: Contact_Manifold,
|
|
) {
|
|
is_ref_a := face_query_a.separation > face_query_b.separation
|
|
ref_face_query := is_ref_a ? face_query_a : face_query_b
|
|
ref_convex := is_ref_a ? a : b
|
|
inc_convex := is_ref_a ? b : a
|
|
|
|
ref_face := ref_convex.faces[ref_face_query.face]
|
|
|
|
// incident face
|
|
inc_face: halfedge.Face
|
|
inc_face_idx: halfedge.Face_Index
|
|
// Find the most anti parallel face
|
|
{
|
|
min_dot := f32(1.0)
|
|
for face, face_idx in inc_convex.faces {
|
|
dot := lg.dot(ref_face.normal, face.normal)
|
|
if dot < min_dot {
|
|
min_dot = dot
|
|
inc_face = face
|
|
inc_face_idx = halfedge.Face_Index(face_idx)
|
|
}
|
|
}
|
|
}
|
|
|
|
inc_polygon: []Vec3
|
|
original_vert_count := 0
|
|
|
|
// Get incident face vertices
|
|
{
|
|
it := halfedge.iterator_face_edges(halfedge.Half_Edge_Mesh(inc_convex), inc_face_idx)
|
|
|
|
for _ in halfedge.iterate_next_edge(&it) {
|
|
original_vert_count += 1
|
|
}
|
|
|
|
inc_polygon = make([]Vec3, original_vert_count * 2, context.temp_allocator)
|
|
|
|
halfedge.iterator_reset_edges(&it)
|
|
|
|
i := 0
|
|
for edge in halfedge.iterate_next_edge(&it) {
|
|
inc_polygon[i] = inc_convex.vertices[edge.origin].pos
|
|
i += 1
|
|
}
|
|
}
|
|
|
|
assert(len(inc_polygon) > 0)
|
|
|
|
// Set up ping pong buffers
|
|
inc_polygon2 := make([]Vec3, len(inc_polygon), context.temp_allocator)
|
|
|
|
// Buffer 0 is the original incident polygon, start with index 1
|
|
clip_buf_idx := 1
|
|
clip_bufs := [2][]Vec3{inc_polygon, inc_polygon2}
|
|
|
|
get_other_clip_buf :: proc(idx: int) -> int {
|
|
return (idx + 1) % 2
|
|
}
|
|
|
|
step := 0
|
|
vert_count := original_vert_count
|
|
|
|
{
|
|
it := halfedge.iterator_face_edges(
|
|
halfedge.Half_Edge_Mesh(ref_convex),
|
|
ref_face_query.face,
|
|
)
|
|
|
|
for edge in halfedge.iterate_next_edge(&it) {
|
|
src_polygon := clip_bufs[get_other_clip_buf(clip_buf_idx)]
|
|
clipped_polygon := clip_bufs[clip_buf_idx]
|
|
|
|
clipping_face, clipping_face_idx, _ := halfedge.get_adjacent_face(
|
|
halfedge.Half_Edge_Mesh(ref_convex),
|
|
edge,
|
|
)
|
|
clipping_face_vert :=
|
|
ref_convex.vertices[ref_convex.edges[clipping_face.edge].origin].pos
|
|
|
|
clipping_plane_center: Vec3
|
|
{
|
|
clipping_plane_it := halfedge.iterator_face_edges(
|
|
halfedge.Half_Edge_Mesh(ref_convex),
|
|
clipping_face_idx,
|
|
)
|
|
|
|
num := 0
|
|
for clip_edge in halfedge.iterate_next_edge(&clipping_plane_it) {
|
|
vert := ref_convex.vertices[clip_edge.origin].pos
|
|
clipping_plane_center += vert
|
|
num += 1
|
|
}
|
|
|
|
clipping_plane_center /= f32(num)
|
|
}
|
|
|
|
clipping_plane := plane_from_point_normal(clipping_face_vert, clipping_face.normal)
|
|
|
|
// Actual clipping
|
|
{
|
|
j := 0
|
|
for i in 0 ..< vert_count {
|
|
k := (i + 1) % vert_count
|
|
d1 := signed_distance_plane(src_polygon[i], clipping_plane)
|
|
d2 := signed_distance_plane(src_polygon[k], clipping_plane)
|
|
|
|
if d1 < 0 && d2 < 0 {
|
|
// Both points inside
|
|
clipped_polygon[j] = src_polygon[k]
|
|
j += 1
|
|
} else if d1 >= 0 && d2 < 0 {
|
|
// First point is outside
|
|
_, clipped_polygon[j], _ = intersect_segment_plane(
|
|
{src_polygon[i], src_polygon[k]},
|
|
clipping_plane,
|
|
)
|
|
j += 1
|
|
clipped_polygon[j] = src_polygon[k]
|
|
j += 1
|
|
} else if d1 < 0 && d2 >= 0 {
|
|
// Second point outside
|
|
_, clipped_polygon[j], _ = intersect_segment_plane(
|
|
{src_polygon[i], src_polygon[k]},
|
|
clipping_plane,
|
|
)
|
|
j += 1
|
|
}
|
|
}
|
|
vert_count = j
|
|
}
|
|
|
|
clip_buf_idx = get_other_clip_buf(clip_buf_idx)
|
|
step += 1
|
|
}
|
|
}
|
|
|
|
// Final clipping, remove verts above ref face
|
|
{
|
|
src_polygon := clip_bufs[get_other_clip_buf(clip_buf_idx)]
|
|
clipped_polygon := clip_bufs[clip_buf_idx]
|
|
|
|
ref_face_vert := ref_convex.vertices[ref_convex.edges[ref_face.edge].origin].pos
|
|
ref_plane := plane_from_point_normal(ref_face_vert, ref_face.normal)
|
|
|
|
j := 0
|
|
for i in 0 ..< vert_count {
|
|
d := signed_distance_plane(src_polygon[i], ref_plane)
|
|
|
|
if d <= 0 {
|
|
clipped_polygon[j] = src_polygon[i]
|
|
j += 1
|
|
}
|
|
}
|
|
vert_count = j
|
|
clip_buf_idx = get_other_clip_buf(clip_buf_idx)
|
|
}
|
|
|
|
manifold.normal = ref_face.normal
|
|
manifold.points = clip_bufs[get_other_clip_buf(clip_buf_idx)][:vert_count]
|
|
|
|
return
|
|
}
|
|
|
|
create_edge_contact_manifold :: proc(
|
|
a, b: Convex,
|
|
separation: f32,
|
|
edge_a, edge_b: halfedge.Edge_Index,
|
|
) -> (
|
|
manifold: Contact_Manifold,
|
|
) {
|
|
a1, a2 := halfedge.get_edge_points(a, a.edges[edge_a])
|
|
b1, b2 := halfedge.get_edge_points(b, b.edges[edge_b])
|
|
|
|
rl.DrawLine3D(a1 + 0.1, a2 + 0.1, rl.ORANGE)
|
|
rl.DrawLine3D(b1 + 0.1, b2 + 0.1, rl.BLUE)
|
|
|
|
_, ps := closest_point_between_segments(a1, a2, b1, b2)
|
|
|
|
manifold.points = make([]Vec3, 1, context.temp_allocator)
|
|
manifold.points[0] = (ps[0] + ps[1]) * 0.5
|
|
|
|
return
|
|
}
|