Start implementing convex collisions

This commit is contained in:
sergeypdev 2025-01-17 00:24:17 +04:00
parent 2b3739937a
commit 43f8c66ec1
13 changed files with 824 additions and 98 deletions

View File

@ -1,81 +1,2 @@
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)) {
}

View File

@ -1 +1,135 @@
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)
}
}
}

View 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}))
}

View File

@ -1,5 +1,7 @@
package halfedge
import lg "core:math/linalg"
Vec3 :: [3]f32
Vertex :: struct {
@ -9,6 +11,7 @@ Vertex :: struct {
Face :: struct {
edge: Edge_Index,
normal: Vec3,
}
Vertex_Index :: distinct u16
@ -24,6 +27,7 @@ Half_Edge :: struct {
}
Half_Edge_Mesh :: struct {
center: Vec3,
vertices: []Vertex,
faces: []Face,
edges: []Half_Edge,
@ -35,6 +39,8 @@ mesh_from_vertex_index_list :: proc(
vertices_per_face: int = 3,
allocator := context.allocator,
) -> Half_Edge_Mesh {
assert(vertices_per_face >= 3)
num_faces := len(indices) / vertices_per_face
mesh: Half_Edge_Mesh
verts := make([]Vertex, len(vertices), allocator)
@ -45,18 +51,37 @@ mesh_from_vertex_index_list :: proc(
mesh.faces = faces
mesh.edges = edges
mesh_center_avg_factor := 1.0 / f32(len(vertices))
for pos, i in vertices {
verts[i].pos = pos
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)
for f in 0 ..< num_faces {
base_index := f * vertices_per_face
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 := f * vertices_per_face + i
e := base_index + i
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 {
faces[f].edge = Edge_Index(e)
}
@ -87,3 +112,63 @@ mesh_from_vertex_index_list :: proc(
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
View 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"

View 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
}

View File

@ -0,0 +1 @@
package collision

160
game/physics/shapes.odin Normal file
View 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)
// }
// }

View 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)
// }

View File

@ -70,17 +70,6 @@ GLOBAL_PLANE :: collision.Plane {
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) {
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 {
local_hit_point: rl.Vector3
min_distance := f32(math.F32_MAX)
for corner in BOX_CORNERS_NORM {
for corner in collision.BOX_CORNERS_NORM {
local_pos := extent * corner
dist := collision.signed_distance_plane(local_pos, local_plane)
if dist < min_distance {

30
game/scratch/scratch.odin Normal file
View 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]
}
}

BIN
spanpool Executable file

Binary file not shown.

3
test.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
odin test game -collection:common=./common -collection:game=./game -collection:libs=./libs -strict-style -vet