diff --git a/game/container/spanpool/doc.odin b/game/container/spanpool/doc.odin index 0151e5d..b321ace 100644 --- a/game/container/spanpool/doc.odin +++ b/game/container/spanpool/doc.odin @@ -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: 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.. []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) + } + } +} diff --git a/game/container/spanpool/spanpool_test.odin b/game/container/spanpool/spanpool_test.odin new file mode 100644 index 0000000..2068c0f --- /dev/null +++ b/game/container/spanpool/spanpool_test.odin @@ -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})) +} diff --git a/game/halfedge/halfedge.odin b/game/halfedge/halfedge.odin index d5b97c3..96b3bdd 100644 --- a/game/halfedge/halfedge.odin +++ b/game/halfedge/halfedge.odin @@ -1,5 +1,7 @@ package halfedge +import lg "core:math/linalg" + Vec3 :: [3]f32 Vertex :: struct { @@ -8,7 +10,8 @@ Vertex :: struct { } Face :: struct { - edge: Edge_Index, + 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 { - for i in 0.. 2 { + point_dist := lg.dot(normal, vertices[index]) + assert(abs(dist - point_dist) < 0.00001) + } + if i == 0 { faces[f].edge = Edge_Index(e) } @@ -66,10 +91,10 @@ mesh_from_vertex_index_list :: proc( edges[e] = { origin = Vertex_Index(index), - twin = -1, - face = Face_Index(f), - next = Edge_Index(next_edge), - prev = Edge_Index(prev_edge), + twin = -1, + face = Face_Index(f), + next = Edge_Index(next_edge), + prev = Edge_Index(prev_edge), } next_index := indices[next_edge] @@ -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 +} diff --git a/game/imports_test.odin b/game/imports_test.odin new file mode 100644 index 0000000..a792462 --- /dev/null +++ b/game/imports_test.odin @@ -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" diff --git a/game/physics/collision/convex.odin b/game/physics/collision/convex.odin new file mode 100644 index 0000000..36be322 --- /dev/null +++ b/game/physics/collision/convex.odin @@ -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 +} diff --git a/game/physics/collision/convex_test.odin b/game/physics/collision/convex_test.odin new file mode 100644 index 0000000..dbf5d42 --- /dev/null +++ b/game/physics/collision/convex_test.odin @@ -0,0 +1 @@ +package collision diff --git a/game/physics/shapes.odin b/game/physics/shapes.odin new file mode 100644 index 0000000..af38a8a --- /dev/null +++ b/game/physics/shapes.odin @@ -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) +// } +// } diff --git a/game/physics/shapes_test.odin b/game/physics/shapes_test.odin new file mode 100644 index 0000000..2a932e3 --- /dev/null +++ b/game/physics/shapes_test.odin @@ -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) +// } diff --git a/game/physics/simulation.odin b/game/physics/simulation.odin index 314bee4..ccad7f6 100644 --- a/game/physics/simulation.odin +++ b/game/physics/simulation.odin @@ -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 { diff --git a/game/scratch/scratch.odin b/game/scratch/scratch.odin new file mode 100644 index 0000000..bf22ba2 --- /dev/null +++ b/game/scratch/scratch.odin @@ -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] + } +} diff --git a/spanpool b/spanpool new file mode 100755 index 0000000..11b965a Binary files /dev/null and b/spanpool differ diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..281d443 --- /dev/null +++ b/test.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +odin test game -collection:common=./common -collection:game=./game -collection:libs=./libs -strict-style -vet