Start implementing convex collisions
This commit is contained in:
parent
2b3739937a
commit
43f8c66ec1
@ -1,81 +1,2 @@
|
|||||||
package spanpool
|
package spanpool
|
||||||
|
|
||||||
import "core:slice"
|
|
||||||
|
|
||||||
Element :: struct($E: typeid) {
|
|
||||||
value: $T,
|
|
||||||
gen: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
Span :: struct {
|
|
||||||
first, len: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
Span_Pool :: struct($E: typeid) {
|
|
||||||
elems: [dynamic]Element(E),
|
|
||||||
free_spans: [dynamic]Span,
|
|
||||||
}
|
|
||||||
|
|
||||||
Handle :: struct {
|
|
||||||
first: i32,
|
|
||||||
len: i32,
|
|
||||||
gen: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
allocate :: proc(s: ^$T/Span_Pool($E), elems: []T) -> (handle: Handle) {
|
|
||||||
handle = _allocate(s, len(elems))
|
|
||||||
|
|
||||||
for i in 0..<handle.len {
|
|
||||||
s.elems[i + handle.first].value = elems[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_allocate :: proc(s: ^$T/Span_Pool($E), count: i32) -> (handle: Handle) {
|
|
||||||
handle.len = count
|
|
||||||
|
|
||||||
maybe_existing_span: Maybe(i32)
|
|
||||||
for span, i in s.free_spans {
|
|
||||||
if span.len >= count {
|
|
||||||
maybe_existing_span = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
existing_span, ok := maybe_existing_span.?
|
|
||||||
if ok {
|
|
||||||
span := s.free_spans[existing_span]
|
|
||||||
handle.first = span.first
|
|
||||||
new_len := span.len - count
|
|
||||||
|
|
||||||
if new_len == 0 {
|
|
||||||
ordered_remove(&s.free_spans, existing_span)
|
|
||||||
} else {
|
|
||||||
s.free_spans[existing_span].first += count
|
|
||||||
s.free_spans[existing_span].len = new_len
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
handle.first = len(s.elems)
|
|
||||||
resize(&s.elems, len(s.elems) + count)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now figure out the generation index
|
|
||||||
max_gen := 0
|
|
||||||
for i in handle.first..<handle.first + handle.len {
|
|
||||||
gen = max(s.elems[i].gen)
|
|
||||||
}
|
|
||||||
|
|
||||||
handle.gen = max_gen + 1
|
|
||||||
for i in handle.first..<handle.first + handle.len {
|
|
||||||
s.elems[i].gen = handle.gen
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
free :: proc(s: ^$T/Span_Pool($E), handle: Handle) {
|
|
||||||
append(&s.free_spans, Span{first = handle.first, len = handle.len})
|
|
||||||
}
|
|
||||||
|
|
||||||
reconcile :: proc(s: ^$T/Span_Pool($E)) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1 +1,135 @@
|
|||||||
package spanpool
|
package spanpool
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:slice"
|
||||||
|
|
||||||
|
Span :: struct {
|
||||||
|
first, len: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
Generation_Index :: i32
|
||||||
|
|
||||||
|
Span_Pool :: struct($E: typeid) {
|
||||||
|
elems: [dynamic]E,
|
||||||
|
generations: [dynamic]Generation_Index,
|
||||||
|
free_spans: [dynamic]Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle :: struct {
|
||||||
|
first: i32,
|
||||||
|
len: i32,
|
||||||
|
gen: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_spanpool :: proc(s: ^$T/Span_Pool($E)) {
|
||||||
|
delete(s.elems)
|
||||||
|
delete(s.generations)
|
||||||
|
delete(s.free_spans)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_slice :: proc(s: ^$T/Span_Pool($E), handle: Handle) -> []E {
|
||||||
|
assert(int(handle.first + handle.len) <= len(s.elems))
|
||||||
|
assert(s.generations[handle.first] == handle.gen)
|
||||||
|
|
||||||
|
return s.elems[handle.first:handle.first + handle.len]
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_single :: proc(s: ^$T/Span_Pool($E), handle: Handle) -> ^E {
|
||||||
|
assert(int(handle.first + handle.len) <= len(s.elems))
|
||||||
|
assert(handle.len == 1)
|
||||||
|
assert(s.generations[handle.first] == handle.gen)
|
||||||
|
|
||||||
|
return &s.elems[handle.first]
|
||||||
|
}
|
||||||
|
|
||||||
|
allocate_elems :: proc(s: ^$T/Span_Pool($E), elems: ..E) -> (handle: Handle) {
|
||||||
|
handle = allocate_num(s, i32(len(elems)))
|
||||||
|
|
||||||
|
for i in 0 ..< handle.len {
|
||||||
|
s.elems[i + handle.first] = elems[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
allocate_num :: proc(s: ^$T/Span_Pool($E), count: i32) -> (handle: Handle) {
|
||||||
|
handle.len = count
|
||||||
|
|
||||||
|
maybe_existing_span: Maybe(i32)
|
||||||
|
for span, i in s.free_spans {
|
||||||
|
if span.len >= count {
|
||||||
|
maybe_existing_span = i32(i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
existing_span, ok := maybe_existing_span.?
|
||||||
|
if ok {
|
||||||
|
span := s.free_spans[existing_span]
|
||||||
|
handle.first = span.first
|
||||||
|
new_len := span.len - count
|
||||||
|
|
||||||
|
if new_len == 0 {
|
||||||
|
ordered_remove(&s.free_spans, existing_span)
|
||||||
|
} else {
|
||||||
|
s.free_spans[existing_span].first += count
|
||||||
|
s.free_spans[existing_span].len = new_len
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handle.first = i32(len(s.elems))
|
||||||
|
new_len := len(s.elems) + int(count)
|
||||||
|
resize(&s.elems, new_len)
|
||||||
|
resize(&s.generations, new_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now figure out the generation index
|
||||||
|
max_gen := i32(0)
|
||||||
|
for i in handle.first ..< handle.first + handle.len {
|
||||||
|
max_gen = max(max_gen, s.generations[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.gen = max_gen + 1
|
||||||
|
for i in handle.first ..< handle.first + handle.len {
|
||||||
|
s.generations[i] = handle.gen
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
free :: proc(s: ^$T/Span_Pool($E), handle: Handle, loc := #caller_location) {
|
||||||
|
fmt.assertf(
|
||||||
|
int(handle.first + handle.len) <= len(s.elems),
|
||||||
|
"invalid handle %v",
|
||||||
|
handle,
|
||||||
|
loc = loc,
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in 0 ..< handle.len {
|
||||||
|
fmt.assertf(
|
||||||
|
s.generations[handle.first + i] == handle.gen,
|
||||||
|
"wrong generation number %v at index %v for handle %v",
|
||||||
|
s.generations[handle.first + i],
|
||||||
|
i,
|
||||||
|
handle,
|
||||||
|
loc = loc,
|
||||||
|
)
|
||||||
|
s.generations[handle.first + i] += 1
|
||||||
|
}
|
||||||
|
append(&s.free_spans, Span{first = handle.first, len = handle.len})
|
||||||
|
}
|
||||||
|
|
||||||
|
reconcile :: proc(s: ^$T/Span_Pool($E)) {
|
||||||
|
slice.sort_by_key(s.free_spans[:], proc(span: Span) -> i32 {
|
||||||
|
return span.first
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := len(s.free_spans) - 2; i >= 0; i -= 1 {
|
||||||
|
cur_span, next_span := &s.free_spans[i], &s.free_spans[i + 1]
|
||||||
|
|
||||||
|
if (cur_span.first + cur_span.len == next_span.first) {
|
||||||
|
// Merge
|
||||||
|
cur_span.len += next_span.len
|
||||||
|
unordered_remove(&s.free_spans, i + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
169
game/container/spanpool/spanpool_test.odin
Normal file
169
game/container/spanpool/spanpool_test.odin
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package spanpool
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:c/libc"
|
||||||
|
import "core:log"
|
||||||
|
import "core:slice"
|
||||||
|
import "core:testing"
|
||||||
|
|
||||||
|
expect_assert :: proc(
|
||||||
|
t: ^testing.T,
|
||||||
|
data: $T,
|
||||||
|
client_proc: proc(_: ^testing.T, data: T),
|
||||||
|
expr := #caller_expression,
|
||||||
|
loc := #caller_location,
|
||||||
|
) {
|
||||||
|
@(thread_local)
|
||||||
|
jmp: libc.jmp_buf
|
||||||
|
|
||||||
|
context.assertion_failure_proc =
|
||||||
|
proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! {
|
||||||
|
log.infof("expected assertion %s: %s ", prefix, message, location = loc)
|
||||||
|
libc.longjmp(&jmp, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
jmp_res := libc.setjmp(&jmp)
|
||||||
|
|
||||||
|
if jmp_res == 0 {
|
||||||
|
client_proc(t, data)
|
||||||
|
libc.longjmp(&jmp, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jmp_res != 1 {
|
||||||
|
log.errorf("%v DID NOT assert", expr, location = loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_basic_alloc :: proc(t: ^testing.T) {
|
||||||
|
pool := Span_Pool(u32){}
|
||||||
|
defer destroy_spanpool(&pool)
|
||||||
|
|
||||||
|
handle := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
|
||||||
|
testing.expect_value(t, handle.first, 0)
|
||||||
|
testing.expect_value(t, handle.len, 4)
|
||||||
|
testing.expect_value(t, handle.gen, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_basic_free :: proc(t: ^testing.T) {
|
||||||
|
pool := Span_Pool(u32){}
|
||||||
|
defer destroy_spanpool(&pool)
|
||||||
|
|
||||||
|
handle := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
|
||||||
|
free(&pool, handle)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(pool.free_spans), 1)
|
||||||
|
testing.expect_value(t, pool.free_spans[0], Span{first = 0, len = 4})
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_double_free :: proc(t: ^testing.T) {
|
||||||
|
pool := Span_Pool(u32){}
|
||||||
|
defer destroy_spanpool(&pool)
|
||||||
|
|
||||||
|
expect_assert(t, &pool, proc(t: ^testing.T, pool: ^Span_Pool(u32)) {
|
||||||
|
handle := allocate_elems(pool, 1, 2, 3, 4)
|
||||||
|
|
||||||
|
free(pool, handle)
|
||||||
|
free(pool, handle)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_resolve :: proc(t: ^testing.T) {
|
||||||
|
pool := Span_Pool(u32){}
|
||||||
|
defer destroy_spanpool(&pool)
|
||||||
|
|
||||||
|
handle := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
handle2 := allocate_elems(&pool, 4, 3, 2, 1)
|
||||||
|
|
||||||
|
testing.expect(t, slice.equal(resolve_slice(&pool, handle), []u32{1, 2, 3, 4}))
|
||||||
|
testing.expect(t, slice.equal(resolve_slice(&pool, handle2), []u32{4, 3, 2, 1}))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_multiple_spans :: proc(t: ^testing.T) {
|
||||||
|
pool := Span_Pool(u32){}
|
||||||
|
defer destroy_spanpool(&pool)
|
||||||
|
|
||||||
|
handle := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
handle2 := allocate_elems(&pool, 4, 3, 2, 1)
|
||||||
|
|
||||||
|
free(&pool, handle)
|
||||||
|
free(&pool, handle2)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(pool.free_spans), 2)
|
||||||
|
testing.expect_value(t, pool.free_spans[0], Span{first = 0, len = 4})
|
||||||
|
testing.expect_value(t, pool.free_spans[1], Span{first = 4, len = 4})
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_reconcile :: proc(t: ^testing.T) {
|
||||||
|
pool := Span_Pool(u32){}
|
||||||
|
defer destroy_spanpool(&pool)
|
||||||
|
|
||||||
|
handle := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
handle2 := allocate_elems(&pool, 4, 3, 2, 1)
|
||||||
|
|
||||||
|
free(&pool, handle)
|
||||||
|
free(&pool, handle2)
|
||||||
|
|
||||||
|
reconcile(&pool)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(pool.free_spans), 1)
|
||||||
|
testing.expect_value(t, pool.free_spans[0], Span{first = 0, len = 8})
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_reconcile_with_gaps :: proc(t: ^testing.T) {
|
||||||
|
pool := Span_Pool(u32){}
|
||||||
|
defer destroy_spanpool(&pool)
|
||||||
|
|
||||||
|
handle := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
handle2 := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
handle3 := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
handle4 := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
handle5 := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
handle6 := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
handle7 := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
|
||||||
|
free(&pool, handle)
|
||||||
|
free(&pool, handle3)
|
||||||
|
free(&pool, handle4)
|
||||||
|
free(&pool, handle6)
|
||||||
|
free(&pool, handle7)
|
||||||
|
|
||||||
|
_, _ = handle2, handle5
|
||||||
|
|
||||||
|
reconcile(&pool)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(pool.free_spans), 3)
|
||||||
|
testing.expect_value(t, pool.free_spans[0], Span{first = 0, len = 4})
|
||||||
|
testing.expect_value(t, pool.free_spans[1], Span{first = 8, len = 8})
|
||||||
|
testing.expect_value(t, pool.free_spans[2], Span{first = 20, len = 8})
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_free_span_reuse :: proc(t: ^testing.T) {
|
||||||
|
pool := Span_Pool(u32){}
|
||||||
|
defer destroy_spanpool(&pool)
|
||||||
|
|
||||||
|
handle := allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
_ = allocate_elems(&pool, 1, 2, 3, 4)
|
||||||
|
|
||||||
|
free(&pool, handle)
|
||||||
|
|
||||||
|
handle3 := allocate_elems(&pool, 5, 6)
|
||||||
|
handle4 := allocate_elems(&pool, 7, 8)
|
||||||
|
handle5 := allocate_elems(&pool, 9, 10)
|
||||||
|
|
||||||
|
testing.expect_value(t, handle3, Handle{first = 0, len = 2, gen = 3})
|
||||||
|
testing.expect_value(t, handle4, Handle{first = 2, len = 2, gen = 3})
|
||||||
|
testing.expect_value(t, handle5, Handle{first = 8, len = 2, gen = 1})
|
||||||
|
testing.expect(t, slice.equal(resolve_slice(&pool, handle3), []u32{5, 6}))
|
||||||
|
testing.expect(t, slice.equal(resolve_slice(&pool, handle4), []u32{7, 8}))
|
||||||
|
testing.expect(t, slice.equal(resolve_slice(&pool, handle5), []u32{9, 10}))
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
package halfedge
|
package halfedge
|
||||||
|
|
||||||
|
import lg "core:math/linalg"
|
||||||
|
|
||||||
Vec3 :: [3]f32
|
Vec3 :: [3]f32
|
||||||
|
|
||||||
Vertex :: struct {
|
Vertex :: struct {
|
||||||
@ -9,6 +11,7 @@ Vertex :: struct {
|
|||||||
|
|
||||||
Face :: struct {
|
Face :: struct {
|
||||||
edge: Edge_Index,
|
edge: Edge_Index,
|
||||||
|
normal: Vec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
Vertex_Index :: distinct u16
|
Vertex_Index :: distinct u16
|
||||||
@ -24,6 +27,7 @@ Half_Edge :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Half_Edge_Mesh :: struct {
|
Half_Edge_Mesh :: struct {
|
||||||
|
center: Vec3,
|
||||||
vertices: []Vertex,
|
vertices: []Vertex,
|
||||||
faces: []Face,
|
faces: []Face,
|
||||||
edges: []Half_Edge,
|
edges: []Half_Edge,
|
||||||
@ -35,6 +39,8 @@ mesh_from_vertex_index_list :: proc(
|
|||||||
vertices_per_face: int = 3,
|
vertices_per_face: int = 3,
|
||||||
allocator := context.allocator,
|
allocator := context.allocator,
|
||||||
) -> Half_Edge_Mesh {
|
) -> Half_Edge_Mesh {
|
||||||
|
assert(vertices_per_face >= 3)
|
||||||
|
|
||||||
num_faces := len(indices) / vertices_per_face
|
num_faces := len(indices) / vertices_per_face
|
||||||
mesh: Half_Edge_Mesh
|
mesh: Half_Edge_Mesh
|
||||||
verts := make([]Vertex, len(vertices), allocator)
|
verts := make([]Vertex, len(vertices), allocator)
|
||||||
@ -45,18 +51,37 @@ mesh_from_vertex_index_list :: proc(
|
|||||||
mesh.faces = faces
|
mesh.faces = faces
|
||||||
mesh.edges = edges
|
mesh.edges = edges
|
||||||
|
|
||||||
|
mesh_center_avg_factor := 1.0 / f32(len(vertices))
|
||||||
|
|
||||||
for pos, i in vertices {
|
for pos, i in vertices {
|
||||||
verts[i].pos = pos
|
verts[i].pos = pos
|
||||||
verts[i].edge = -1
|
verts[i].edge = -1
|
||||||
|
mesh.center += pos * mesh_center_avg_factor
|
||||||
}
|
}
|
||||||
|
|
||||||
temp_edges: map[[2]u16]Edge_Index = make_map(map[[2]u16]Edge_Index, context.temp_allocator)
|
temp_edges: map[[2]u16]Edge_Index = make_map(map[[2]u16]Edge_Index, context.temp_allocator)
|
||||||
|
|
||||||
for f in 0 ..< num_faces {
|
for f in 0 ..< num_faces {
|
||||||
for i in 0..<vertices_per_face {
|
base_index := f * vertices_per_face
|
||||||
e := f * vertices_per_face + i
|
i1, i2, i3 := base_index + 0, base_index + 1, base_index + 2
|
||||||
|
v1, v2, v3 := vertices[i1], vertices[i2], vertices[i3]
|
||||||
|
|
||||||
|
// Assuming ccw winding
|
||||||
|
normal := lg.normalize0(lg.cross(v2 - v1, v3 - v1))
|
||||||
|
dist := lg.dot(v1, normal)
|
||||||
|
|
||||||
|
faces[f].normal = normal
|
||||||
|
|
||||||
|
for i in 0 ..< vertices_per_face {
|
||||||
|
e := base_index + i
|
||||||
index := indices[e]
|
index := indices[e]
|
||||||
|
|
||||||
|
// check that point lies on the same plane
|
||||||
|
if i > 2 {
|
||||||
|
point_dist := lg.dot(normal, vertices[index])
|
||||||
|
assert(abs(dist - point_dist) < 0.00001)
|
||||||
|
}
|
||||||
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
faces[f].edge = Edge_Index(e)
|
faces[f].edge = Edge_Index(e)
|
||||||
}
|
}
|
||||||
@ -87,3 +112,63 @@ mesh_from_vertex_index_list :: proc(
|
|||||||
|
|
||||||
return mesh
|
return mesh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_edge_points :: proc(mesh: Half_Edge_Mesh, edge: Half_Edge) -> (a: Vec3, b: Vec3) {
|
||||||
|
a = mesh.vertices[edge.origin].pos
|
||||||
|
b = mesh.vertices[mesh.edges[edge.next].origin].pos
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
get_edge_direction_normalized :: proc(mesh: Half_Edge_Mesh, edge: Half_Edge) -> (dir: Vec3) {
|
||||||
|
a, b := get_edge_points(mesh, edge)
|
||||||
|
return lg.normalize0(b - a)
|
||||||
|
}
|
||||||
|
|
||||||
|
get_adjacent_face :: proc(
|
||||||
|
mesh: Half_Edge_Mesh,
|
||||||
|
edge: Half_Edge,
|
||||||
|
) -> (
|
||||||
|
face: Face,
|
||||||
|
ok: bool,
|
||||||
|
) #optional_ok {
|
||||||
|
if edge.twin < 0 {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
twin := mesh.edges[edge.twin]
|
||||||
|
face = mesh.faces[twin.face]
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Edge_Iterator :: struct {
|
||||||
|
mesh: Half_Edge_Mesh,
|
||||||
|
first_edge: Edge_Index,
|
||||||
|
current_edge: Edge_Index,
|
||||||
|
past_first: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator_face_edges :: proc(mesh: Half_Edge_Mesh, face: Face_Index) -> (it: Edge_Iterator) {
|
||||||
|
it.mesh = mesh
|
||||||
|
it.first_edge = mesh.faces[face].edge
|
||||||
|
it.current_edge = it.first_edge
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
iterate_next_edge :: proc(it: ^Edge_Iterator) -> (edge: Half_Edge, ok: bool) {
|
||||||
|
if it.current_edge == it.first_edge {
|
||||||
|
if !it.past_first {
|
||||||
|
it.past_first = true
|
||||||
|
} else {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
edge = it.mesh.edges[it.current_edge]
|
||||||
|
ok = true
|
||||||
|
it.current_edge = edge.next
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
10
game/imports_test.odin
Normal file
10
game/imports_test.odin
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
@(require) import "assets"
|
||||||
|
@(require) import "container/spanpool"
|
||||||
|
@(require) import "debug"
|
||||||
|
@(require) import "halfedge"
|
||||||
|
@(require) import "physics"
|
||||||
|
@(require) import "physics/bvh"
|
||||||
|
@(require) import "physics/collision"
|
||||||
|
@(require) import "scratch"
|
204
game/physics/collision/convex.odin
Normal file
204
game/physics/collision/convex.odin
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package collision
|
||||||
|
|
||||||
|
import "core:math"
|
||||||
|
import lg "core:math/linalg"
|
||||||
|
import "game:halfedge"
|
||||||
|
|
||||||
|
Convex :: distinct halfedge.Half_Edge_Mesh
|
||||||
|
|
||||||
|
BOX_CORNERS_NORM :: [8]Vec3 {
|
||||||
|
Vec3{-1, -1, -1},
|
||||||
|
Vec3{-1, -1, 1},
|
||||||
|
Vec3{-1, 1, -1},
|
||||||
|
Vec3{-1, 1, 1},
|
||||||
|
Vec3{1, -1, -1},
|
||||||
|
Vec3{1, -1, 1},
|
||||||
|
Vec3{1, 1, -1},
|
||||||
|
Vec3{1, 1, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
box_indices := [6 * 4]u16 {
|
||||||
|
// near
|
||||||
|
0,
|
||||||
|
4,
|
||||||
|
6,
|
||||||
|
2,
|
||||||
|
// right
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
7,
|
||||||
|
6,
|
||||||
|
// far
|
||||||
|
5,
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
7,
|
||||||
|
// left
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
// top
|
||||||
|
2,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
3,
|
||||||
|
// bottom
|
||||||
|
0,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
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] = 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)
|
||||||
|
if edge_separation > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
is_face_a_contact := face_query_a.separation > edge_separation
|
||||||
|
is_face_b_contact := 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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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, plane)
|
||||||
|
if distance > result.separation {
|
||||||
|
result.face = halfedge.Face_Index(f)
|
||||||
|
result.separation = distance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
find_support_point :: proc(convex: Convex, normal: Vec3) -> Vec3 {
|
||||||
|
p: Vec3
|
||||||
|
max_proj := min(f32)
|
||||||
|
for vert in convex.vertices {
|
||||||
|
proj := lg.dot(vert.pos, normal)
|
||||||
|
if proj > max_proj {
|
||||||
|
max_proj = proj
|
||||||
|
p = vert.pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
for edge_a, edge_a_idx in a.edges {
|
||||||
|
for edge_b, edge_b_idx in b.edges {
|
||||||
|
edge_a_dir := halfedge.get_edge_direction_normalized(
|
||||||
|
halfedge.Half_Edge_Mesh(a),
|
||||||
|
edge_a,
|
||||||
|
)
|
||||||
|
edge_b_dir := halfedge.get_edge_direction_normalized(
|
||||||
|
halfedge.Half_Edge_Mesh(b),
|
||||||
|
edge_b,
|
||||||
|
)
|
||||||
|
|
||||||
|
axis := lg.cross(edge_a_dir, edge_b_dir)
|
||||||
|
|
||||||
|
edge_a_origin := a.vertices[edge_a.origin].pos
|
||||||
|
if lg.dot(axis, edge_a_origin - a.center) < 0 {
|
||||||
|
axis = -axis
|
||||||
|
}
|
||||||
|
|
||||||
|
plane_a := plane_from_point_normal(edge_a_origin, axis)
|
||||||
|
vert_b := find_support_point(b, -plane_a.normal)
|
||||||
|
distance := signed_distance_plane(vert_b, plane_a)
|
||||||
|
if distance > separation {
|
||||||
|
separation = distance
|
||||||
|
a_edge = halfedge.Edge_Index(edge_a_idx)
|
||||||
|
b_edge = halfedge.Edge_Index(edge_b_idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_face_query := is_ref_a ? face_query_b : face_query_a
|
||||||
|
inc_convex := is_ref_a ? b : a
|
||||||
|
|
||||||
|
it := halfedge.iterator_face_edges(halfedge.Half_Edge_Mesh(ref_convex), ref_face_query.face)
|
||||||
|
|
||||||
|
for edge in halfedge.iterate_next_edge(&it) {
|
||||||
|
clipping_face := halfedge.get_adjacent_face(halfedge.Half_Edge_Mesh(ref_convex), edge)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
1
game/physics/collision/convex_test.odin
Normal file
1
game/physics/collision/convex_test.odin
Normal file
@ -0,0 +1 @@
|
|||||||
|
package collision
|
160
game/physics/shapes.odin
Normal file
160
game/physics/shapes.odin
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
package physics
|
||||||
|
//
|
||||||
|
// import "core:container/intrusive/list"
|
||||||
|
// import "game:container/spanpool"
|
||||||
|
// import "game:halfedge"
|
||||||
|
// import rl "vendor:raylib"
|
||||||
|
//
|
||||||
|
// Shape_Container :: struct {
|
||||||
|
// shapes: spanpool.Span_Pool(Shape_Instance),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// destroy_shape_container :: proc(container: ^Shape_Container) {
|
||||||
|
// spanpool.destroy_spanpool(&container.shapes)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Shape_Handle :: distinct spanpool.Handle
|
||||||
|
//
|
||||||
|
// Shape_Sphere :: struct {
|
||||||
|
// radius: f32,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Shape_Convex :: struct {
|
||||||
|
// using mesh: halfedge.Half_Edge_Mesh,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// box_to_convex :: proc(box: Shape_Box, allocator := context.allocator) -> (convex: Shape_Convex) {
|
||||||
|
// vertices := make([]rl.Vector3, 8, context.temp_allocator)
|
||||||
|
//
|
||||||
|
// for corner, i in BOX_CORNERS_NORM {
|
||||||
|
// vertices[i] = corner * box.size
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// convex.mesh = halfedge.mesh_from_vertex_index_list(vertices, box_indices[:], 4, allocator)
|
||||||
|
//
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Shape_Box :: struct {
|
||||||
|
// size: rl.Vector3,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Shape_Union :: struct {
|
||||||
|
// children: Shape_Handle,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Shape_Variant :: union {
|
||||||
|
// Shape_Sphere,
|
||||||
|
// Shape_Box,
|
||||||
|
// Shape_Union,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Shape_Instance :: struct {
|
||||||
|
// pos: rl.Vector3,
|
||||||
|
// rot: rl.Quaternion,
|
||||||
|
// shape: Shape_Variant,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Shape_Desc_Union :: struct {
|
||||||
|
// children: list.List,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Shape_Desc_Variant :: union {
|
||||||
|
// Shape_Desc_Union,
|
||||||
|
// Shape_Sphere,
|
||||||
|
// Shape_Box,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Shape_Desc :: struct {
|
||||||
|
// pos: rl.Vector3,
|
||||||
|
// rot: rl.Quaternion,
|
||||||
|
// shape: Shape_Desc_Variant,
|
||||||
|
// using link: list.Node,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// shape_desc_add_child :: proc(
|
||||||
|
// desc: ^Shape_Desc,
|
||||||
|
// child: Shape_Desc,
|
||||||
|
// allocator := context.temp_allocator,
|
||||||
|
// ) {
|
||||||
|
// union_shape := desc.shape.(Shape_Desc_Union)
|
||||||
|
//
|
||||||
|
// allocated_child := new(Shape_Desc, allocator)
|
||||||
|
// allocated_child^ = child
|
||||||
|
//
|
||||||
|
// list.push_back(&union_shape.children, &allocated_child.link)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// shape_container_add :: proc(
|
||||||
|
// c: ^Shape_Container,
|
||||||
|
// desc: Shape_Desc,
|
||||||
|
// loc := #caller_location,
|
||||||
|
// ) -> Shape_Handle {
|
||||||
|
// root_handle := spanpool.allocate_num(&c.shapes, 1)
|
||||||
|
// shape_container_add_internal(c, Shape_Handle(root_handle), 0, desc, loc = loc)
|
||||||
|
// return Shape_Handle(root_handle)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// shape_container_free :: proc(c: ^Shape_Container, handle: Shape_Handle, loc := #caller_location) {
|
||||||
|
// assert(handle.len == 1)
|
||||||
|
//
|
||||||
|
// shape_container_free_internal(c, handle, 0, loc = loc)
|
||||||
|
// spanpool.free(&c.shapes, spanpool.Handle(handle), loc = loc)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// shape_container_add_internal :: proc(
|
||||||
|
// c: ^Shape_Container,
|
||||||
|
// handle: Shape_Handle,
|
||||||
|
// idx: i32,
|
||||||
|
// desc: Shape_Desc,
|
||||||
|
// loc := #caller_location,
|
||||||
|
// ) {
|
||||||
|
// shape_ptr := &spanpool.resolve_slice(&c.shapes, spanpool.Handle(handle))[idx]
|
||||||
|
// switch s in desc.shape {
|
||||||
|
// case Shape_Sphere:
|
||||||
|
// shape_ptr.shape = s
|
||||||
|
// case Shape_Box:
|
||||||
|
// shape_ptr.shape = s
|
||||||
|
// case Shape_Desc_Union:
|
||||||
|
// child_len: i32
|
||||||
|
// {
|
||||||
|
// it := list.iterator_head(s.children, Shape_Desc, "link")
|
||||||
|
// for child in list.iterate_next(&it) {
|
||||||
|
// child_len += 1
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// {
|
||||||
|
// children_handle := spanpool.allocate_num(&c.shapes, child_len)
|
||||||
|
// shape_ptr = &spanpool.resolve_slice(&c.shapes, spanpool.Handle(handle))[idx] // Re-resolve handle because spanpool might have reallocated
|
||||||
|
//
|
||||||
|
// shape_ptr.shape = Shape_Union {
|
||||||
|
// children = Shape_Handle(children_handle),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// it := list.iterator_head(s.children, Shape_Desc, "link")
|
||||||
|
// i: i32
|
||||||
|
// for child in list.iterate_next(&it) {
|
||||||
|
// shape_container_add_internal(c, Shape_Handle(children_handle), i, child^)
|
||||||
|
// i += 1
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// shape_container_free_internal :: proc(
|
||||||
|
// c: ^Shape_Container,
|
||||||
|
// handle: Shape_Handle,
|
||||||
|
// idx: i32,
|
||||||
|
// loc := #caller_location,
|
||||||
|
// ) {
|
||||||
|
// shape := spanpool.resolve_slice(&c.shapes, spanpool.Handle(handle))[idx]
|
||||||
|
// switch s in shape.shape {
|
||||||
|
// case Shape_Box, Shape_Sphere:
|
||||||
|
// // Nothing to do
|
||||||
|
// case Shape_Union:
|
||||||
|
// for i in 0 ..< s.children.len {
|
||||||
|
// shape_container_free_internal(c, s.children, i)
|
||||||
|
// }
|
||||||
|
// spanpool.free(&c.shapes, spanpool.Handle(s.children), loc = loc)
|
||||||
|
// }
|
||||||
|
// }
|
20
game/physics/shapes_test.odin
Normal file
20
game/physics/shapes_test.odin
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package physics
|
||||||
|
//
|
||||||
|
// @(test)
|
||||||
|
// test_code :: proc() {
|
||||||
|
// cont: Shape_Container
|
||||||
|
// root := Shape_Desc {
|
||||||
|
// shape = Shape_Desc_Union{},
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// shape_desc_add_child(
|
||||||
|
// &root,
|
||||||
|
// Shape_Desc{pos = rl.Vector3{5, 0, 0}, shape = Shape_Sphere{radius = 10}},
|
||||||
|
// )
|
||||||
|
// shape_desc_add_child(
|
||||||
|
// &root,
|
||||||
|
// Shape_Desc{pos = rl.Vector3{-5, 0, 0}, shape = Shape_Sphere{radius = 10}},
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// handle := shape_container_add(&cont, root)
|
||||||
|
// }
|
@ -70,17 +70,6 @@ GLOBAL_PLANE :: collision.Plane {
|
|||||||
dist = 0,
|
dist = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
BOX_CORNERS_NORM :: [8]rl.Vector3 {
|
|
||||||
rl.Vector3{-1, -1, -1},
|
|
||||||
rl.Vector3{-1, -1, 1},
|
|
||||||
rl.Vector3{-1, 1, -1},
|
|
||||||
rl.Vector3{-1, 1, 1},
|
|
||||||
rl.Vector3{1, -1, -1},
|
|
||||||
rl.Vector3{1, -1, 1},
|
|
||||||
rl.Vector3{1, 1, -1},
|
|
||||||
rl.Vector3{1, 1, 1},
|
|
||||||
}
|
|
||||||
|
|
||||||
simulate_step :: proc(scene: ^Scene, config: Solver_Config) {
|
simulate_step :: proc(scene: ^Scene, config: Solver_Config) {
|
||||||
body_states := make([]Body_Sim_State, len(scene.bodies), context.temp_allocator)
|
body_states := make([]Body_Sim_State, len(scene.bodies), context.temp_allocator)
|
||||||
|
|
||||||
@ -139,7 +128,7 @@ simulate_step :: proc(scene: ^Scene, config: Solver_Config) {
|
|||||||
if hit {
|
if hit {
|
||||||
local_hit_point: rl.Vector3
|
local_hit_point: rl.Vector3
|
||||||
min_distance := f32(math.F32_MAX)
|
min_distance := f32(math.F32_MAX)
|
||||||
for corner in BOX_CORNERS_NORM {
|
for corner in collision.BOX_CORNERS_NORM {
|
||||||
local_pos := extent * corner
|
local_pos := extent * corner
|
||||||
dist := collision.signed_distance_plane(local_pos, local_plane)
|
dist := collision.signed_distance_plane(local_pos, local_plane)
|
||||||
if dist < min_distance {
|
if dist < min_distance {
|
||||||
|
30
game/scratch/scratch.odin
Normal file
30
game/scratch/scratch.odin
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package scratch
|
||||||
|
|
||||||
|
import "core:mem"
|
||||||
|
import "core:mem/virtual"
|
||||||
|
|
||||||
|
@(thread_local)
|
||||||
|
scratch_init_done: bool
|
||||||
|
@(thread_local)
|
||||||
|
arenas: [2]virtual.Arena
|
||||||
|
@(thread_local)
|
||||||
|
allocators: [2]mem.Allocator
|
||||||
|
|
||||||
|
_init_scratch :: #force_inline proc() {
|
||||||
|
if !scratch_init_done {
|
||||||
|
for i in 0 ..< 2 {
|
||||||
|
_ = virtual.arena_init_static(&arenas[i])
|
||||||
|
allocators[i] = virtual.arena_allocator(&arenas[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_scratch :: proc(conflict := context.temp_allocator) -> mem.Allocator {
|
||||||
|
_init_scratch()
|
||||||
|
|
||||||
|
if conflict.data == allocators[1].data {
|
||||||
|
return allocators[0]
|
||||||
|
} else {
|
||||||
|
return allocators[1]
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user