First implementation of track collision (non convex triangle level geometry)

This commit is contained in:
sergeypdev 2025-05-12 01:17:49 +04:00
parent 890ac2494a
commit 735c61fd05
13 changed files with 836 additions and 147 deletions

View File

@ -28,9 +28,17 @@ destroy_spanpool :: proc(s: ^$T/Span_Pool($E)) {
delete(s.free_spans)
}
// TODO: use everywhere
is_handle_valid :: proc(s: ^$T/Span_Pool($E), handle: Handle) -> bool {
return(
handle.gen != 0 &&
int(handle.first + handle.len) <= len(s.elems) &&
s.generations[handle.first] == handle.gen \
)
}
resolve_slice :: proc(s: ^$T/Span_Pool($E), handle: Handle, loc := #caller_location) -> []E {
assert(int(handle.first + handle.len) <= len(s.elems), "invalid spanpool handle", loc)
assert(s.generations[handle.first] == handle.gen, "invalid spanpool handle", loc)
assert(is_handle_valid(s, handle), "invalid spanpool handle", loc)
return s.elems[handle.first:handle.first + handle.len]
}

View File

@ -118,7 +118,7 @@ Car :: struct {
SOLVER_CONFIG :: physics.Solver_Config {
timestep = 1.0 / 60,
gravity = rl.Vector3{0, -9.8, 0},
substreps_minus_one = 8 - 1,
substreps_minus_one = 4 - 1,
}
Game_Memory :: struct {
@ -359,30 +359,34 @@ World_Update_Config :: struct {
}
update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
rl.BeginDrawing()
rl.BeginMode3D(game_camera_3d())
if !world.pause {
car_model := assets.get_model(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb")
car_bounds := rl.GetModelBoundingBox(car_model)
world.car_com = (car_bounds.min + car_bounds.max) / 2
physics.immediate_body(
&world.physics_scene,
#hash("floor", "fnv32a"),
physics.Body_Config {
initial_pos = {0, -0.5, 0},
initial_rot = linalg.QUATERNIONF32_IDENTITY,
shape = physics.Shape_Box{size = {1000, 1, 1000}},
},
)
if true {
physics.immediate_body(
&world.physics_scene,
#hash("floor", "fnv32a"),
physics.Body_Config {
initial_pos = {0, -0.5, 0},
initial_rot = linalg.QUATERNIONF32_IDENTITY,
shape = physics.Shape_Box{size = {100, 1, 100}},
},
)
physics.immediate_body(
&world.physics_scene,
#hash("ramp", "fnv32a"),
physics.Body_Config {
initial_pos = {0, 0, 0},
initial_rot = linalg.quaternion_from_euler_angle_x_f32(-10 * math.RAD_PER_DEG),
shape = physics.Shape_Box{size = {5, 1, 100}},
},
)
physics.immediate_body(
&world.physics_scene,
#hash("ramp", "fnv32a"),
physics.Body_Config {
initial_pos = {0, 0, 0},
initial_rot = linalg.quaternion_from_euler_angle_x_f32(-10 * math.RAD_PER_DEG),
shape = physics.Shape_Box{size = {5, 1, 100}},
},
)
}
car_convex := assets.get_convex(&g_mem.assetman, "assets/car_convex.obj")
@ -614,6 +618,26 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
wheel.turn_angle = TURN_ANGLE * turn_vel_correction * turn_input
}
if len(world.track.points) > 1 {
interpolated_points := calculate_spline_interpolated_points(
world.track.points[:],
context.temp_allocator,
)
track_verts, track_inds := spline_generate_mesh(
interpolated_points,
context.temp_allocator,
)
physics.immediate_level_geom(
&world.physics_scene,
#hash("track", "fnv32a"),
{
rotation = linalg.QUATERNIONF32_IDENTITY,
vertices = track_verts,
indices = track_inds,
},
)
}
physics.simulate(
&world.physics_scene,
SOLVER_CONFIG,
@ -1019,7 +1043,6 @@ draw_world :: proc(world: ^World) {
draw :: proc() {
tracy.Zone()
rl.BeginDrawing()
defer rl.EndDrawing()
rl.ClearBackground(rl.GRAY)
render.clear_stencil()
@ -1038,14 +1061,13 @@ draw :: proc() {
// )
{
rl.BeginMode3D(camera)
defer rl.EndMode3D()
draw_world(world)
{
// Debug draw spline road
{
if false {
rlgl.EnableWireMode()
defer rlgl.DisableWireMode()

View File

@ -102,8 +102,8 @@ mesh_from_vertex_index_list :: proc(
faces[f].edge = Edge_Index(e)
}
next_edge := f * vertices_per_face + ((i + 1) % vertices_per_face)
prev_edge := f * vertices_per_face + ((i - 1) % vertices_per_face)
next_edge := f * vertices_per_face + ((i + 1) %% vertices_per_face)
prev_edge := f * vertices_per_face + ((i - 1) %% vertices_per_face)
edges[e] = {
origin = Vertex_Index(index),

View File

@ -8,8 +8,8 @@ import "core:math"
import lg "core:math/linalg"
import "core:mem"
import "game:debug"
import "libs:tracy"
import rl "libs:raylib"
import "libs:tracy"
_ :: log
_ :: rl
@ -70,6 +70,7 @@ build_bvh_from_mesh :: proc(mesh: Mesh, allocator := context.allocator) -> (mesh
assert(len(indices) % 3 == 0)
bvh := &mesh_bvh.bvh
mesh_bvh.mesh = mesh
num_triangles := len(indices) / 3
@ -93,18 +94,10 @@ build_bvh_from_mesh :: proc(mesh: Mesh, allocator := context.allocator) -> (mesh
i1, i2, i3 := indices[i * 3], indices[i * 3 + 1], indices[i * 3 + 2]
v1, v2, v3 := vertices[i1], vertices[i2], vertices[i3]
centroids[i] = (v1 + v2 + v3) * 0.33333333333
centroids[i] = (v1 + v2 + v3) / 3.0
aabbs[i].min = Vec3 {
min(v1.x, v2.x, v3.x),
min(v1.y, v2.y, v3.y),
min(v1.z, v2.z, v3.z),
}
aabbs[i].max = Vec3 {
max(v1.x, v2.x, v3.x),
max(v1.y, v2.y, v3.y),
max(v1.z, v2.z, v3.z),
}
aabbs[i].min = lg.min_triple(v1, v2, v3)
aabbs[i].max = lg.max_triple(v1, v2, v3)
size := aabbs[i].max - aabbs[i].min
assert(size.x >= 0)
@ -286,8 +279,10 @@ iterator_intersect_leaf_aabb :: proc(
) {
it.bvh = bvh
it.bounds = bounds
queue.init(&it.nodes_to_process, queue.DEFAULT_CAPACITY, context.temp_allocator)
queue.push_back(&it.nodes_to_process, 0)
if len(it.bvh.nodes) > 0 {
queue.init(&it.nodes_to_process, queue.DEFAULT_CAPACITY, context.temp_allocator)
queue.push_back(&it.nodes_to_process, 0)
}
return it
}
@ -302,8 +297,10 @@ iterator_intersect_leaf_ray :: proc(
it.bvh = bvh
it.ray = ray
it.min_t = distance
queue.init(&it.nodes_to_process, queue.DEFAULT_CAPACITY, context.temp_allocator)
queue.push_back(&it.nodes_to_process, 0)
if len(it.bvh.nodes) > 0 {
queue.init(&it.nodes_to_process, queue.DEFAULT_CAPACITY, context.temp_allocator)
queue.push_back(&it.nodes_to_process, 0)
}
return it
}

View File

@ -41,6 +41,26 @@ box_to_convex :: proc(box: Box, allocator := context.allocator) -> (convex: Conv
return
}
double_sided_triangle_indices := [6]u16{0, 1, 2, 0, 2, 1}
double_sided_triangle_to_convex :: proc(
tri: [3]Vec3,
allocator := context.allocator,
) -> (
convex: Convex,
) {
tri := tri
convex = Convex(
halfedge.mesh_from_vertex_index_list(
tri[:],
double_sided_triangle_indices[:],
3,
allocator,
),
)
return
}
Contact_Type_Face :: struct {
face_idx_a: halfedge.Face_Index,
face_idx_b: halfedge.Face_Index,
@ -85,8 +105,8 @@ convex_vs_convex_sat :: proc(a, b: Convex) -> (manifold: Contact_Manifold, colli
return
}
is_face_a_contact := (face_query_a.separation + 0.2) > edge_separation
is_face_b_contact := (face_query_b.separation + 0.2) > edge_separation
is_face_a_contact := (face_query_a.separation + 0.001) > edge_separation
is_face_b_contact := (face_query_b.separation + 0.001) > edge_separation
if is_face_a_contact || is_face_b_contact {
manifold = create_face_contact_manifold(face_query_a, a, face_query_b, b)
@ -287,7 +307,7 @@ create_face_contact_manifold :: proc(
) {
tracy.Zone()
is_ref_a := (face_query_a.separation + 0.1) > face_query_b.separation
is_ref_a := (face_query_a.separation + 0.01) > 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
@ -400,7 +420,18 @@ create_face_contact_manifold :: proc(
clipping_plane_center /= f32(num)
}
clipping_plane := plane_from_point_normal(clipping_face_vert, clipping_face.normal)
clipping_normal := clipping_face.normal
// If adjacent face is facing backwards from us (like a double sided triangle)
// use the edge normal
if lg.dot(ref_face.normal, clipping_face.normal) < -0.99 {
e1, e2 := halfedge.get_edge_points(ref_convex, edge)
clipping_normal = lg.cross(e2 - e1, clipping_face_vert - clipping_plane_center)
clipping_normal = -lg.normalize0(lg.cross(e2 - e1, clipping_normal))
}
clipping_plane := plane_from_point_normal(clipping_face_vert, clipping_normal)
// Actual clipping
{
@ -578,7 +609,7 @@ create_edge_contact_manifold :: proc(
edge_idx_b = edge_b,
}
manifold.normal = separating_plane.normal
manifold.separation = separation
manifold.separation = lg.dot(ps[0] - ps[1], separating_plane.normal)
manifold.points_a[0] = ps[0]
manifold.points_b[0] = ps[1]
manifold.points_len = 1

View File

@ -69,6 +69,21 @@ draw_debug_scene :: proc(scene: ^Scene) {
}
}
if false {
for &level_geom, geom_idx in sim_state.level_geoms {
if level_geom.alive {
vertices, indices := get_level_geom_data(sim_state, level_geom.geometry)
for i in 0 ..< len(indices) / 3 {
i1, i2, i3 := indices[i * 3 + 0], indices[i * 3 + 1], indices[i * 3 + 2]
v1, v2, v3 := vertices[i1], vertices[i2], vertices[i3]
rl.DrawTriangle3D(v1, v2, v3, debug.int_to_color(i32(geom_idx + 1)))
}
}
}
}
for _, i in sim_state.suspension_constraints {
wheel := &sim_state.suspension_constraints_slice[i]
if wheel.alive {
@ -130,7 +145,8 @@ draw_debug_scene :: proc(scene: ^Scene) {
points_a_slice, points_b_slice :=
points_a[:contact.manifold.points_len], points_b[:contact.manifold.points_len]
debug_transform_points_local_to_world(get_body(sim_state, contact.a), points_a_slice)
debug_transform_points_local_to_world(get_body(sim_state, contact.b), points_b_slice)
b_handle := Body_Handle(contact.b) if contact.type == .Body_vs_Body else INVALID_BODY
debug_transform_points_local_to_world(get_body(sim_state, b_handle), points_b_slice)
debug_draw_manifold_points(
contact,
-1,

View File

@ -125,6 +125,21 @@ body_get_convex_shape_world :: proc(
return
}
level_geom_get_convex_shape :: proc(
sim_state: ^Sim_State,
level_geom: Level_Geom_Ptr,
tri_idx: int,
allocator := context.temp_allocator,
) -> (
mesh: collision.Convex,
) {
vertices, indices := get_level_geom_data(sim_state, level_geom.geometry)
return collision.double_sided_triangle_to_convex(
get_transformed_triangle(vertices, indices, tri_idx, level_geom.x, level_geom.q),
allocator,
)
}
shape_get_aabb :: proc(shape: Collision_Shape) -> (aabb: AABB) {
switch s in shape {
case Shape_Box:

View File

@ -170,6 +170,29 @@ immediate_engine :: proc(
return
}
immediate_level_geom :: proc(
scene: ^Scene,
id: u32,
config: Level_Geom_Config,
) -> (
handle: Level_Geom_Handle,
) {
state := &scene.solver_state
sim_state := get_sim_state(scene)
h, ok := immediate_container_find_or_add(
&state.immediate_level_geoms,
state.simulation_frame,
id,
)
if ok {
update_level_geom_from_config(sim_state, get_level_geom(sim_state, h^), config)
} else {
h^ = add_level_geom(sim_state, config)
}
return
}
prune_immediate :: proc(scene: ^Scene) {
tracy.Zone()
@ -181,6 +204,10 @@ prune_immediate :: proc(scene: ^Scene) {
state.simulation_frame,
)
removed_engines := immediate_container_prune(&state.immediate_engines, state.simulation_frame)
removed_level_geoms := immediate_container_prune(
&state.immediate_level_geoms,
state.simulation_frame,
)
for handle in removed_bodies {
remove_body(sim_state, handle)
}
@ -190,4 +217,7 @@ prune_immediate :: proc(scene: ^Scene) {
for handle in removed_engines {
remove_engine(sim_state, handle)
}
for handle in removed_level_geoms {
remove_level_geom(sim_state, handle)
}
}

View File

@ -16,10 +16,28 @@ AABB :: struct {
extent: Vec3,
}
Contact_Pair :: [2]i32
Contact_Pair_Bodies :: struct {
a, b: Body_Handle,
}
Contact_Pair_Body_Level :: struct {
a: Body_Handle,
tri_idx: i32,
}
make_contact_pair :: proc(body_a: i32, body_b: i32) -> Contact_Pair {
return {min(body_a, body_b), max(body_a, body_b)}
Contact_Pair :: union {
Contact_Pair_Bodies,
Contact_Pair_Body_Level,
}
make_contact_pair_bodies :: proc(body_a: i32, body_b: i32) -> Contact_Pair_Bodies {
return {
index_to_body_handle(int(min(body_a, body_b))),
index_to_body_handle(int(max(body_a, body_b))),
}
}
make_contact_pair_body_level :: proc(body: i32, tri_idx: i32) -> Contact_Pair_Body_Level {
return {index_to_body_handle(int(body)), tri_idx}
}
Contact_Container :: struct {
@ -46,9 +64,11 @@ Sim_State :: struct {
bodies: #soa[dynamic]Body,
suspension_constraints: #soa[dynamic]Suspension_Constraint,
engines: [dynamic]Engine,
level_geoms: [dynamic]Level_Geom,
// Number of alive bodies
num_bodies: i32,
num_engines: i32,
num_level_geoms: i32,
// Slices. When you call get_body or get_suspension_constraint you will get a pointer to an element in this slice
bodies_slice: #soa[]Body,
@ -56,6 +76,7 @@ Sim_State :: struct {
first_free_body_plus_one: i32,
first_free_suspension_constraint_plus_one: i32,
first_free_engine_plus_one: i32,
first_free_level_geom_plus_one: i32,
// Persistent stuff for simulation
contact_container: Contact_Container,
@ -65,6 +86,10 @@ Sim_State :: struct {
// Engine array data
rpm_torque_curves_pool: spanpool.Span_Pool([2]f32),
gear_ratios_pool: spanpool.Span_Pool(f32),
// Level geometry
geometry_vertices_pool: spanpool.Span_Pool(Vec3),
geometry_indices_pool: spanpool.Span_Pool(u16),
}
Scene :: struct {
@ -86,6 +111,7 @@ copy_sim_state :: proc(dst: ^Sim_State, src: ^Sim_State) {
resize(&dst.bodies, len(src.bodies))
resize(&dst.suspension_constraints, len(src.suspension_constraints))
resize(&dst.engines, len(src.engines))
resize(&dst.level_geoms, len(src.level_geoms))
dst.bodies_slice = dst.bodies[:]
dst.suspension_constraints_slice = dst.suspension_constraints[:]
@ -97,11 +123,14 @@ copy_sim_state :: proc(dst: ^Sim_State, src: ^Sim_State) {
dst.suspension_constraints[i] = src.suspension_constraints[i]
}
copy(dst.engines[:], src.engines[:])
copy(dst.level_geoms[:], src.level_geoms[:])
contact_container_copy(&dst.contact_container, src.contact_container)
convex_container_copy(&dst.convex_container, src.convex_container)
spanpool.copy(&dst.rpm_torque_curves_pool, src.rpm_torque_curves_pool)
spanpool.copy(&dst.gear_ratios_pool, src.gear_ratios_pool)
spanpool.copy(&dst.geometry_vertices_pool, src.geometry_vertices_pool)
spanpool.copy(&dst.geometry_indices_pool, src.geometry_indices_pool)
}
copy_physics_scene :: proc(dst, src: ^Scene) {
@ -299,14 +328,30 @@ Engine :: struct {
next_plus_one: i32,
}
Geometry_Handle :: struct {
vertices: spanpool.Handle,
indices: spanpool.Handle,
}
Level_Geom :: struct {
alive: bool,
geometry: Geometry_Handle,
x: Vec3,
q: Quat,
next_plus_one: i32,
}
// Index plus one, so handle 0 maps to invalid body
Body_Handle :: distinct i32
Suspension_Constraint_Handle :: distinct i32
Engine_Handle :: distinct i32
// Handle for static geometry
Level_Geom_Handle :: distinct i32
INVALID_BODY :: Body_Handle(0)
INVALID_SUSPENSION_CONSTRAINT :: Suspension_Constraint_Handle(0)
INVALID_ENGINE :: Engine_Handle(0)
INVALID_LEVEL_GEOM :: Level_Geom_Handle(0)
is_body_handle_valid :: proc(handle: Body_Handle) -> bool {
return i32(handle) > 0
@ -317,27 +362,41 @@ is_suspension_constraint_handle_valid :: proc(handle: Suspension_Constraint_Hand
is_engine_handle_valid :: proc(handle: Engine_Handle) -> bool {
return i32(handle) > 0
}
is_level_geom_handle_valid :: proc(handle: Level_Geom_Handle) -> bool {
return i32(handle) > 0
}
is_handle_valid :: proc {
is_body_handle_valid,
is_suspension_constraint_handle_valid,
is_engine_handle_valid,
is_level_geom_handle_valid,
}
index_to_body_handle :: proc(idx: int) -> Body_Handle {
return Body_Handle(idx + 1)
}
index_to_level_geom :: proc(idx: int) -> Level_Geom_Handle {
return Level_Geom_Handle(idx + 1)
}
body_handle_to_index :: proc(handle: Body_Handle) -> int {
return int(handle) - 1
}
level_geom_handle_to_index :: proc(handle: Level_Geom_Handle) -> int {
return int(handle) - 1
}
handle_to_index :: proc {
body_handle_to_index,
level_geom_handle_to_index,
}
Body_Ptr :: #soa^#soa[]Body
Suspension_Constraint_Ptr :: #soa^#soa[]Suspension_Constraint
Engine_Ptr :: ^Engine
Level_Geom_Ptr :: ^Level_Geom
_invalid_body: #soa[1]Body
_invalid_body_slice := _invalid_body[:]
@ -372,6 +431,11 @@ flip_sim_state :: proc(scene: ^Scene) {
get_body :: proc(sim_state: ^Sim_State, handle: Body_Handle) -> Body_Ptr {
index := int(handle) - 1
if index < 0 || index >= len(sim_state.bodies_slice) {
_invalid_body_slice[0] = {
alive = true,
q = lg.QUATERNIONF32_IDENTITY,
prev_q = lg.QUATERNIONF32_IDENTITY,
}
return &_invalid_body_slice[0]
}
@ -438,6 +502,13 @@ Engine_Config :: struct {
axle: Drive_Axle_Config,
}
Level_Geom_Config :: struct {
position: Vec3,
rotation: Quat,
vertices: []Vec3,
indices: []u16,
}
calculate_body_params_from_config :: proc(
config: Body_Config,
) -> (
@ -701,6 +772,79 @@ remove_engine :: proc(sim_state: ^Sim_State, handle: Engine_Handle) {
}
}
invalid_level_geom: Level_Geom
get_level_geom :: proc(sim_state: ^Sim_State, handle: Level_Geom_Handle) -> Level_Geom_Ptr {
index := int(handle) - 1
if index < 0 || index >= len(sim_state.bodies_slice) {
return &invalid_level_geom
}
return &sim_state.level_geoms[index]
}
get_level_geom_data :: proc(
sim_state: ^Sim_State,
handle: Geometry_Handle,
) -> (
vertices: []Vec3,
indices: []u16,
) {
vertices = spanpool.resolve_slice(&sim_state.geometry_vertices_pool, handle.vertices)
indices = spanpool.resolve_slice(&sim_state.geometry_indices_pool, handle.indices)
return
}
update_level_geom_from_config :: proc(
sim_state: ^Sim_State,
level_geom: Level_Geom_Ptr,
config: Level_Geom_Config,
) {
level_geom.x = config.position
level_geom.q = config.rotation
if spanpool.is_handle_valid(&sim_state.geometry_vertices_pool, level_geom.geometry.vertices) {
spanpool.free(&sim_state.geometry_vertices_pool, level_geom.geometry.vertices)
spanpool.free(&sim_state.geometry_indices_pool, level_geom.geometry.indices)
}
level_geom.geometry.vertices = spanpool.allocate_elems(
&sim_state.geometry_vertices_pool,
..config.vertices,
)
level_geom.geometry.indices = spanpool.allocate_elems(
&sim_state.geometry_indices_pool,
..config.indices,
)
}
add_level_geom :: proc(sim_state: ^Sim_State, config: Level_Geom_Config) -> Level_Geom_Handle {
sim_state.num_level_geoms += 1
level_geom: Level_Geom
level_geom.alive = true
update_level_geom_from_config(sim_state, &level_geom, config)
if sim_state.first_free_level_geom_plus_one > 0 {
index := sim_state.first_free_level_geom_plus_one
new_level_geom := get_level_geom(sim_state, Level_Geom_Handle(index))
next_plus_one := new_level_geom.next_plus_one
new_level_geom^ = level_geom
sim_state.first_free_level_geom_plus_one = next_plus_one
return Level_Geom_Handle(index)
}
append(&sim_state.level_geoms, level_geom)
index := len(sim_state.level_geoms)
return Level_Geom_Handle(index)
}
remove_level_geom :: proc(sim_state: ^Sim_State, handle: Level_Geom_Handle) {
level_geom := get_level_geom(sim_state, handle)
spanpool.free(&sim_state.geometry_vertices_pool, level_geom.geometry.vertices)
spanpool.free(&sim_state.geometry_indices_pool, level_geom.geometry.indices)
}
_get_first_free_body :: proc(sim_state: ^Sim_State) -> i32 {
return sim_state.first_free_body_plus_one - 1
@ -709,12 +853,16 @@ _get_first_free_body :: proc(sim_state: ^Sim_State) -> i32 {
destry_sim_state :: proc(sim_state: ^Sim_State) {
delete_soa(sim_state.bodies)
delete_soa(sim_state.suspension_constraints)
delete(sim_state.engines)
delete(sim_state.level_geoms)
delete_soa(sim_state.contact_container.contacts)
delete_map(sim_state.contact_container.lookup)
delete(sim_state.engines)
convex_container_destroy(&sim_state.convex_container)
spanpool.destroy_spanpool(&sim_state.rpm_torque_curves_pool)
spanpool.destroy_spanpool(&sim_state.gear_ratios_pool)
spanpool.destroy_spanpool(&sim_state.geometry_vertices_pool)
spanpool.destroy_spanpool(&sim_state.geometry_indices_pool)
}
destroy_physics_scene :: proc(scene: ^Scene) {

View File

@ -1,5 +1,6 @@
package physics
import rl " libs:raylib"
import "base:runtime"
import "bvh"
import "collision"
@ -38,6 +39,7 @@ Solver_State :: struct {
immediate_bodies: Immediate_Container(Body_Handle),
immediate_suspension_constraints: Immediate_Container(Suspension_Constraint_Handle),
immediate_engines: Immediate_Container(Engine_Handle),
immediate_level_geoms: Immediate_Container(Level_Geom_Handle),
}
copy_solver_state :: proc(dst, src: ^Solver_State) {
@ -50,12 +52,14 @@ copy_solver_state :: proc(dst, src: ^Solver_State) {
&src.immediate_suspension_constraints,
)
immediate_container_copy(&dst.immediate_engines, &src.immediate_engines)
immediate_container_copy(&dst.immediate_level_geoms, &src.immediate_level_geoms)
}
destroy_solver_state :: proc(state: ^Solver_State) {
immediate_container_destroy(&state.immediate_bodies)
immediate_container_destroy(&state.immediate_suspension_constraints)
immediate_container_destroy(&state.immediate_engines)
immediate_container_destroy(&state.immediate_level_geoms)
}
MAX_STEPS :: 10
@ -65,15 +69,85 @@ Step_Mode :: enum {
Single,
}
Static_TLAS :: struct {
bvh_tree: bvh.Mesh_BVH,
tri_to_level_geom: []Level_Geom_Handle,
}
// Top Level Acceleration Structure
TLAS :: struct {
Dynamic_TLAS :: struct {
bvh_tree: bvh.BVH,
body_aabbs: []bvh.AABB,
}
build_static_tlas :: proc(sim_state: ^Sim_State) -> Static_TLAS {
tracy.Zone()
num_vertices, num_indices: int
for i in 0 ..< len(sim_state.level_geoms) {
level_geom := &sim_state.level_geoms[i]
if level_geom.alive {
num_vertices += int(level_geom.geometry.vertices.len)
num_indices += int(level_geom.geometry.indices.len)
}
}
if num_vertices == 0 {
return Static_TLAS{}
}
vertices := make([]bvh.Vec3, num_vertices, context.temp_allocator)
indices := make([]u16, num_indices, context.temp_allocator)
tri_to_level_geom := make([]Level_Geom_Handle, num_indices / 3, context.temp_allocator)
num_vertices, num_indices = 0, 0
for i in 0 ..< len(sim_state.level_geoms) {
level_geom := &sim_state.level_geoms[i]
if level_geom.alive {
geom_verts, geom_inds := get_level_geom_data(sim_state, level_geom.geometry)
base_vertex := num_vertices
copy_slice(vertices[base_vertex:], geom_verts)
num_vertices += len(geom_verts)
for j in 0 ..< len(geom_inds) {
if num_indices %% 3 == 0 {
tri := num_indices / 3
tri_to_level_geom[tri] = index_to_level_geom(i)
}
ind := geom_inds[j]
indices[num_indices] = u16(base_vertex + int(ind))
num_indices += 1
}
}
}
assert(num_vertices == len(vertices))
assert(num_indices == len(indices))
// for i in 0 ..< len(indices) / 3 {
// tri := get_triangle(vertices, indices, i)
// rl.DrawTriangle3D(tri[0], tri[1], tri[2], rl.RED)
// }
result := Static_TLAS {
bvh_tree = bvh.build_bvh_from_mesh(
bvh.Mesh{vertices = vertices, indices = indices},
context.temp_allocator,
),
tri_to_level_geom = tri_to_level_geom,
}
return result
}
// TODO: free intermediate temp allocs
// Creates TLAS using temp allocator
build_tlas :: proc(sim_state: ^Sim_State, config: Solver_Config) -> TLAS {
build_dynamic_tlas :: proc(sim_state: ^Sim_State, config: Solver_Config) -> Dynamic_TLAS {
tracy.Zone()
body_aabbs := make([]bvh.AABB, sim_state.num_bodies, context.temp_allocator)
@ -92,7 +166,7 @@ build_tlas :: proc(sim_state: ^Sim_State, config: Solver_Config) -> TLAS {
phys_aabb := body_get_aabb(body)
EXPAND_K :: 2
expand := lg.abs(EXPAND_K * config.timestep * body.v)
expand := lg.abs(EXPAND_K * config.timestep * body.v) + 0.1
phys_aabb.extent += expand * 0.5
aabb.min = phys_aabb.center - phys_aabb.extent
@ -103,12 +177,12 @@ build_tlas :: proc(sim_state: ^Sim_State, config: Solver_Config) -> TLAS {
sim_state_bvh := bvh.build_bvh_from_aabbs(body_aabbs, body_indices, context.temp_allocator)
return TLAS{bvh_tree = sim_state_bvh, body_aabbs = body_aabbs}
return Dynamic_TLAS{bvh_tree = sim_state_bvh, body_aabbs = body_aabbs}
}
raycast_bodies :: proc(
sim_state: ^Sim_State,
tlas: ^TLAS,
tlas: ^Dynamic_TLAS,
origin, dir: Vec3,
distance := max(f32),
) -> (
@ -152,8 +226,115 @@ raycast_bodies :: proc(
return
}
get_triangle :: proc(vertices: []Vec3, indices: []u16, tri_idx: int) -> (tri: [3]Vec3) {
i1, i2, i3 := indices[tri_idx * 3 + 0], indices[tri_idx * 3 + 1], indices[tri_idx * 3 + 2]
tri[0], tri[1], tri[2] = vertices[i1], vertices[i2], vertices[i3]
return
}
get_transformed_triangle :: proc(
vertices: []Vec3,
indices: []u16,
tri_idx: int,
position: Vec3,
rotation: Quat,
) -> (
tri: [3]Vec3,
) {
tri = get_triangle(vertices, indices, tri_idx)
tri[0] = lg.quaternion_mul_vector3(rotation, tri[0]) + position
tri[1] = lg.quaternion_mul_vector3(rotation, tri[1]) + position
tri[2] = lg.quaternion_mul_vector3(rotation, tri[2]) + position
return
}
get_triangle_aabb :: proc(tri: [3]Vec3) -> (aabb: bvh.AABB) {
aabb.min = lg.min(tri[0], lg.min(tri[1], tri[2]))
aabb.max = lg.max(tri[0], lg.max(tri[1], tri[2]))
return
}
remove_invalid_contacts :: proc(
sim_state: ^Sim_State,
static_tlas: Static_TLAS,
dyn_tlas: Dynamic_TLAS,
) {
tracy.Zone()
i := 0
for i < len(sim_state.contact_container.contacts) {
contact := sim_state.contact_container.contacts[i]
should_remove := false
if contact.type == .Body_vs_Body {
should_remove |= !get_body(sim_state, contact.a).alive
should_remove |= !get_body(sim_state, Body_Handle(contact.b)).alive
if !should_remove {
aabb_a := dyn_tlas.body_aabbs[int(contact.a) - 1]
aabb_b := dyn_tlas.body_aabbs[int(contact.b) - 1]
should_remove |= !bvh.test_aabb_vs_aabb(aabb_a, aabb_b)
}
} else {
// a is a body, b is a triangle index
should_remove |= !get_body(sim_state, contact.a).alive
if !should_remove {
should_remove |= int(contact.tri_idx * 3) > len(static_tlas.bvh_tree.mesh.indices)
if !should_remove {
aabb_a := dyn_tlas.body_aabbs[int(contact.a) - 1]
tri := get_triangle(
static_tlas.bvh_tree.mesh.vertices,
static_tlas.bvh_tree.mesh.indices,
int(contact.tri_idx),
)
aabb_b := get_triangle_aabb(tri)
should_remove |= !bvh.test_aabb_vs_aabb(aabb_a, aabb_b)
}
}
}
pair_from_contact :: proc(contact: Contact) -> (pair: Contact_Pair) {
if contact.type == .Body_vs_Body {
pair = make_contact_pair_bodies(i32(contact.a) - 1, i32(contact.b) - 1)
} else {
pair = make_contact_pair_body_level(
i32(body_handle_to_index(contact.a)),
contact.tri_idx,
)
}
return
}
if should_remove {
removed_pair := pair_from_contact(contact)
delete_key(&sim_state.contact_container.lookup, removed_pair)
unordered_remove_soa(&sim_state.contact_container.contacts, i)
if i < len(sim_state.contact_container.contacts) {
moved_contact := &sim_state.contact_container.contacts[i]
moved_pair := pair_from_contact(moved_contact^)
sim_state.contact_container.lookup[moved_pair] = i32(i)
}
} else {
i += 1
}
}
}
// TODO: free intermediate temp allocs
find_new_contacts :: proc(sim_state: ^Sim_State, tlas: ^TLAS) {
find_new_contacts :: proc(
sim_state: ^Sim_State,
static_tlas: ^Static_TLAS,
dyn_tlas: ^Dynamic_TLAS,
) {
tracy.Zone()
for i in 0 ..< len(sim_state.bodies_slice) {
@ -162,29 +343,68 @@ find_new_contacts :: proc(sim_state: ^Sim_State, tlas: ^TLAS) {
body := &sim_state.bodies_slice[i]
if body.alive {
body_aabb := tlas.body_aabbs[i]
it := bvh.iterator_intersect_leaf_aabb(&tlas.bvh_tree, body_aabb)
body_aabb := dyn_tlas.body_aabbs[i]
{
it := bvh.iterator_intersect_leaf_aabb(&dyn_tlas.bvh_tree, body_aabb)
for leaf_node in bvh.iterator_intersect_leaf_next(&it) {
for j in 0 ..< leaf_node.prim_len {
other_body_idx := tlas.bvh_tree.primitives[leaf_node.child_or_prim_start + j]
prim_aabb := tlas.body_aabbs[other_body_idx]
for leaf_node in bvh.iterator_intersect_leaf_next(&it) {
for j in 0 ..< leaf_node.prim_len {
other_body_idx :=
dyn_tlas.bvh_tree.primitives[leaf_node.child_or_prim_start + j]
prim_aabb := dyn_tlas.body_aabbs[other_body_idx]
pair := make_contact_pair(i32(body_idx), i32(other_body_idx))
if body_idx != other_body_idx &&
(bvh.test_aabb_vs_aabb(body_aabb, prim_aabb)) &&
!(pair in sim_state.contact_container.lookup) {
pair := make_contact_pair_bodies(i32(body_idx), i32(other_body_idx))
if body_idx != other_body_idx &&
(bvh.test_aabb_vs_aabb(body_aabb, prim_aabb)) &&
!(pair in sim_state.contact_container.lookup) {
new_contact_idx := len(sim_state.contact_container.contacts)
resize_soa(&sim_state.contact_container.contacts, new_contact_idx + 1)
contact := &sim_state.contact_container.contacts[new_contact_idx]
new_contact_idx := len(sim_state.contact_container.contacts)
resize_soa(&sim_state.contact_container.contacts, new_contact_idx + 1)
contact := &sim_state.contact_container.contacts[new_contact_idx]
contact^ = Contact {
a = Body_Handle(i + 1),
b = Body_Handle(other_body_idx + 1),
contact^ = Contact {
type = .Body_vs_Body,
a = Body_Handle(i + 1),
b = i32(other_body_idx + 1),
}
sim_state.contact_container.lookup[pair] = i32(new_contact_idx)
}
}
}
}
sim_state.contact_container.lookup[pair] = i32(new_contact_idx)
{
it := bvh.iterator_intersect_leaf_aabb(&static_tlas.bvh_tree.bvh, body_aabb)
for leaf_node in bvh.iterator_intersect_leaf_next(&it) {
for j in 0 ..< leaf_node.prim_len {
tri_idx :=
static_tlas.bvh_tree.bvh.primitives[leaf_node.child_or_prim_start + j]
tri := get_triangle(
static_tlas.bvh_tree.mesh.vertices,
static_tlas.bvh_tree.mesh.indices,
int(tri_idx),
)
prim_aabb := get_triangle_aabb(tri)
level_geom_handle := static_tlas.tri_to_level_geom[tri_idx]
pair := make_contact_pair_body_level(i32(body_idx), i32(tri_idx))
aabb_overlaps := bvh.test_aabb_vs_aabb(body_aabb, prim_aabb) || true
if aabb_overlaps && !(pair in sim_state.contact_container.lookup) {
new_contact_idx := len(sim_state.contact_container.contacts)
resize_soa(&sim_state.contact_container.contacts, new_contact_idx + 1)
contact := &sim_state.contact_container.contacts[new_contact_idx]
contact^ = Contact {
type = .Body_vs_Level,
a = index_to_body_handle(i),
b = i32(level_geom_handle),
tri_idx = i32(tri_idx),
}
sim_state.contact_container.lookup[pair] = i32(new_contact_idx)
}
}
}
}
@ -237,6 +457,7 @@ simulate :: proc(
state.immediate_bodies.num_items = 0
state.immediate_suspension_constraints.num_items = 0
state.immediate_engines.num_items = 0
state.immediate_level_geoms.num_items = 0
}
GLOBAL_PLANE :: collision.Plane {
@ -244,8 +465,17 @@ GLOBAL_PLANE :: collision.Plane {
dist = 0,
}
Contact_Type :: enum {
Body_vs_Body,
Body_vs_Level,
}
Contact :: struct {
a, b: Body_Handle,
type: Contact_Type,
a: Body_Handle,
// Body_vs_Body - Body_Handle, Body_vs_Level - Level_Geom_Handle
b: i32,
tri_idx: i32,
prev_x_a, prev_x_b: Vec3,
prev_q_a, prev_q_b: Quat,
manifold: collision.Contact_Manifold,
@ -263,7 +493,7 @@ Contact :: struct {
applied_normal_correction: [4]f32,
}
update_contacts :: proc(sim_state: ^Sim_State) {
update_contacts :: proc(sim_state: ^Sim_State, static_tlas: ^Static_TLAS) {
tracy.Zone()
graph_color_bitmask: [4]bit_array.Bit_Array
@ -279,12 +509,10 @@ update_contacts :: proc(sim_state: ^Sim_State) {
for contact_idx in 0 ..< len(sim_state.contact_container.contacts) {
contact := &sim_state.contact_container.contacts[contact_idx]
i, j := i32(contact.a) - 1, i32(contact.b) - 1
body, body2 := &sim_state.bodies_slice[i], &sim_state.bodies_slice[j]
b_handle := Body_Handle(contact.b) if contact.type == .Body_vs_Body else INVALID_BODY
body, body2 := get_body(sim_state, contact.a), get_body(sim_state, b_handle)
assert(body.alive)
assert(body2.alive)
old_manifold := contact.manifold
old_total_normal_impulse := contact.total_normal_impulse
@ -302,19 +530,35 @@ update_contacts :: proc(sim_state: ^Sim_State) {
contact.applied_static_friction = false
contact.applied_normal_correction = 0
aabb1, aabb2 := body_get_aabb(body), body_get_aabb(body2)
// aabb1, aabb2 := body_get_aabb(body), body_get_aabb(body2)
// TODO: extract common math functions into a sane place
if !collision.test_aabb_vs_aabb(
{min = aabb1.center - aabb1.extent, max = aabb1.center + aabb1.extent},
{min = aabb2.center - aabb2.extent, max = aabb2.center + aabb2.extent},
) {
continue
}
// if !collision.test_aabb_vs_aabb(
// {min = aabb1.center - aabb1.extent, max = aabb1.center + aabb1.extent},
// {min = aabb2.center - aabb2.extent, max = aabb2.center + aabb2.extent},
// ) {
// continue
// }
m1, m2 :=
body_get_convex_shape_world(sim_state, body),
body_get_convex_shape_world(sim_state, body2)
m1 := body_get_convex_shape_world(sim_state, body)
m2: collision.Convex
switch contact.type {
case .Body_vs_Body:
m2 = body_get_convex_shape_world(sim_state, body2)
case .Body_vs_Level:
// level_geom := get_level_geom(sim_state, Level_Geom_Handle(contact.b))
tri := get_triangle(
static_tlas.bvh_tree.mesh.vertices,
static_tlas.bvh_tree.mesh.indices,
int(contact.tri_idx),
)
// rl.DrawTriangle3D(tri[0], tri[1], tri[2], rl.BLUE)
m2 = collision.double_sided_triangle_to_convex(tri, context.temp_allocator)
// m2 = level_geom_get_convex_shape(sim_state, level_geom, int(contact.tri_idx))
he.debug_draw_mesh_wires(m2, rl.BLUE)
}
// Raw manifold has contact points in world space
raw_manifold, collision := collision.convex_vs_convex_sat(m1, m2)
@ -384,7 +628,7 @@ pgs_solve_contacts :: proc(
inv_dt: f32,
apply_bias: bool,
) {
bias_rate, mass_coef, impulse_coef := calculate_soft_constraint_params(30, 0.8, dt)
bias_rate, mass_coef, impulse_coef := calculate_soft_constraint_params(16, 1, dt)
if !apply_bias {
mass_coef = 1
bias_rate = 0
@ -397,17 +641,18 @@ pgs_solve_contacts :: proc(
random_order[i] = i32(i)
}
// for i in 0 ..< len(random_order) - 1 {
// j := rand.int_max(len(random_order))
// slice.swap(random_order, i, j)
// }
for i in 0 ..< len(random_order) - 1 {
j := rand.int_max(len(random_order))
slice.swap(random_order, i, j)
}
}
for i in 0 ..< len(sim_state.contact_container.contacts) {
contact := &sim_state.contact_container.contacts[random_order[i]]
manifold := &contact.manifold
body1, body2 := get_body(sim_state, contact.a), get_body(sim_state, contact.b)
b_handle := Body_Handle(contact.b) if contact.type == .Body_vs_Body else INVALID_BODY
body1, body2 := get_body(sim_state, contact.a), get_body(sim_state, b_handle)
for point_idx in 0 ..< manifold.points_len {
p1, p2 :=
@ -710,7 +955,7 @@ calculate_ground_vel :: proc(
pgs_solve_suspension :: proc(
sim_state: ^Sim_State,
tlas: ^TLAS,
tlas: ^Dynamic_TLAS,
config: Solver_Config,
dt: f32,
inv_dt: f32,
@ -914,7 +1159,7 @@ pgs_solve_suspension :: proc(
pgs_substep :: proc(
sim_state: ^Sim_State,
tlas: ^TLAS,
tlas: ^Dynamic_TLAS,
config: Solver_Config,
dt: f32,
inv_dt: f32,
@ -933,7 +1178,8 @@ pgs_substep :: proc(
contact := &sim_state.contact_container.contacts[i]
manifold := &contact.manifold
body1, body2 := get_body(sim_state, contact.a), get_body(sim_state, contact.b)
body1, body2 :=
get_body(sim_state, contact.a), get_body(sim_state, Body_Handle(contact.b))
for point_idx in 0 ..< manifold.points_len {
p1, p2 :=
@ -1078,49 +1324,12 @@ simulate_step :: proc(scene: ^Scene, sim_state: ^Sim_State, config: Solver_Confi
dt := f32(dt_64)
inv_dt := f32(inv_dt_64)
tlas := build_tlas(sim_state, config)
static_tlas := build_static_tlas(sim_state)
dyn_tlas := build_dynamic_tlas(sim_state, config)
{
tracy.ZoneN("simulate_step::remove_invalid_contacts")
i := 0
for i < len(sim_state.contact_container.contacts) {
contact := sim_state.contact_container.contacts[i]
should_remove := false
should_remove |= !get_body(sim_state, contact.a).alive
should_remove |= !get_body(sim_state, contact.b).alive
if !should_remove {
aabb_a := tlas.body_aabbs[int(contact.a) - 1]
aabb_b := tlas.body_aabbs[int(contact.b) - 1]
should_remove |= !bvh.test_aabb_vs_aabb(aabb_a, aabb_b)
}
if should_remove {
removed_pair := make_contact_pair(i32(contact.a) - 1, i32(contact.b) - 1)
delete_key(&sim_state.contact_container.lookup, removed_pair)
unordered_remove_soa(&sim_state.contact_container.contacts, i)
if i < len(sim_state.contact_container.contacts) {
moved_contact := &sim_state.contact_container.contacts[i]
moved_pair := make_contact_pair(
i32(moved_contact.a) - 1,
i32(moved_contact.b) - 1,
)
sim_state.contact_container.lookup[moved_pair] = i32(i)
}
} else {
i += 1
}
}
}
find_new_contacts(sim_state, &tlas)
update_contacts(sim_state)
remove_invalid_contacts(sim_state, static_tlas, dyn_tlas)
find_new_contacts(sim_state, &static_tlas, &dyn_tlas)
update_contacts(sim_state, &static_tlas)
Solver :: enum {
XPBD,
@ -1136,7 +1345,7 @@ simulate_step :: proc(scene: ^Scene, sim_state: ^Sim_State, config: Solver_Confi
}
case .PGS:
for _ in 0 ..< substeps {
pgs_substep(sim_state, &tlas, config, dt, inv_dt)
pgs_substep(sim_state, &dyn_tlas, config, dt, inv_dt)
}
}

View File

@ -43,7 +43,9 @@ xpbd_substep :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_
tracy.ZoneN("simulate_step::solve_collisions")
for i in 0 ..< len(sim_state.contact_container.contacts) {
contact := &sim_state.contact_container.contacts[i]
body, body2 := get_body(sim_state, contact.a), get_body(sim_state, contact.b)
body, body2 :=
get_body(sim_state, contact.a), get_body(sim_state, Body_Handle(contact.b))
contact^ = Contact {
a = contact.a,
@ -137,7 +139,8 @@ xpbd_substep :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_
for &contact in sim_state.contact_container.contacts {
manifold := contact.manifold
body1, body2 := get_body(sim_state, contact.a), get_body(sim_state, contact.b)
body1, body2 :=
get_body(sim_state, contact.a), get_body(sim_state, Body_Handle(contact.b))
prev_p1, prev_p2: Vec3
p1, p2: Vec3
@ -227,7 +230,8 @@ xpbd_substep :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_
for &contact in sim_state.contact_container.contacts {
manifold := &contact.manifold
body, body2 := get_body(sim_state, contact.a), get_body(sim_state, contact.b)
body, body2 :=
get_body(sim_state, contact.a), get_body(sim_state, Body_Handle(contact.b))
prev_q1, prev_q2 := body.prev_q, body2.prev_q
for point_idx in 0 ..< manifold.points_len {
@ -285,7 +289,7 @@ xpbd_substep :: proc(sim_state: ^Sim_State, config: Solver_Config, dt: f32, inv_
manifold := &contact.manifold
body1 := get_body(sim_state, contact.a)
body2 := get_body(sim_state, contact.b)
body2 := get_body(sim_state, Body_Handle(contact.b))
friction_p1, friction_p2: Vec3
total_lambda_normal := f32(0)

145
game/render/debug.odin Normal file
View File

@ -0,0 +1,145 @@
package render
import rl "libs:raylib"
import gl "vendor:OpenGL"
MAX_VERTICES :: 4096
MAX_INDICES :: 4096
MAX_DRAWS :: 4096
Vec3 :: [3]f32
Draw_Call :: struct {
color: rl.Color,
first_index, num_indices: i32,
base_vertex: i32,
}
Immediate_Buffer :: enum {
Vertices = 0,
Indices,
}
Immediate_Draw_State :: struct {
vertices: [MAX_VERTICES]Vec3,
indices: [MAX_INDICES]u16,
draws: [MAX_DRAWS]Draw_Call,
num_vertices, num_indices, num_draws: int,
vbo_ids: [Immediate_Buffer]u32,
vao_id: u32,
}
push_mesh :: proc(ds: ^Immediate_Draw_State, vertices: []Vec3, indices: []u16, color: rl.Color) {
copy(ds.vertices[ds.num_vertices:][:len(vertices)], vertices)
copy(ds.indices[ds.num_indices:][:len(indices)], indices)
ds.draws[ds.num_draws] = {
color = color,
first_index = i32(ds.num_indices),
num_indices = i32(len(indices)),
base_vertex = i32(ds.num_vertices),
}
ds.num_vertices += len(vertices)
ds.num_indices += len(indices)
ds.num_draws += 1
}
// rudimentary batching
find_or_push_cmd :: proc(ds: ^Immediate_Draw_State, color: rl.Color) -> (cmd: ^Draw_Call) {
if ds.num_draws > 0 && ds.draws[ds.num_draws - 1].color == color {
cmd = &ds.draws[ds.num_draws - 1]
assert(i32(ds.num_indices) == cmd.first_index + cmd.num_indices)
} else {
ds.draws[ds.num_draws] = {
color = color,
first_index = i32(ds.num_indices),
num_indices = 0,
base_vertex = i32(ds.num_vertices),
}
cmd = &ds.draws[ds.num_draws]
ds.num_draws += 1
}
return
}
push_tri :: proc(ds: ^Immediate_Draw_State, v1, v2, v3: Vec3, color: rl.Color) {
draw := find_or_push_cmd(ds, color)
ds.vertices[ds.num_vertices + 0] = v1
ds.vertices[ds.num_vertices + 1] = v2
ds.vertices[ds.num_vertices + 2] = v3
ds.indices[ds.num_indices + 0] = u16(ds.num_vertices + 0)
ds.indices[ds.num_indices + 1] = u16(ds.num_vertices + 1)
ds.indices[ds.num_indices + 2] = u16(ds.num_vertices + 2)
ds.num_vertices += 3
ds.num_indices += 3
draw.num_indices += 3
}
push_quad :: proc(ds: ^Immediate_Draw_State, v1, v2, v3, v4: Vec3, color: rl.Color) {
push_tri(ds, v1, v2, v3, color)
push_tri(ds, v1, v3, v4, color)
}
draw_batch :: proc(ds: ^Immediate_Draw_State) {
if ds.vbo_ids == {} {
gl.GenBuffers(len(ds.vbo_ids), &ds.vbo_ids[.Vertices])
gl.GenVertexArrays(1, &ds.vao_id)
gl.BindVertexArray(ds.vao_id)
gl.EnableVertexAttribArray(u32(rl.ShaderLocationIndex.VERTEX_POSITION))
gl.BindBuffer(gl.ARRAY_BUFFER, ds.vbo_ids[.Vertices])
gl.VertexAttribPointer(
u32(rl.ShaderLocationIndex.VERTEX_POSITION),
3,
gl.FLOAT,
false,
0,
0,
)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ds.vbo_ids[.Indices])
} else {
gl.BindVertexArray(ds.vao_id)
}
gl.BindVertexArray(0)
defer gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BufferData(gl.ARRAY_BUFFER, MAX_VERTICES * size_of(Vec3), nil, gl.STREAM_DRAW)
gl.BufferSubData(gl.ARRAY_BUFFER, 0, ds.num_vertices * size_of(Vec3), raw_data(ds.vertices[:]))
gl.VertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 0, 0)
gl.EnableVertexAttribArray(0)
defer gl.DisableVertexAttribArray(0)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ds.vbo_ids[.Indices])
defer gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, MAX_INDICES * size_of(u16), nil, gl.STREAM_DRAW)
gl.BufferSubData(
gl.ELEMENT_ARRAY_BUFFER,
0,
ds.num_indices * size_of(u16),
raw_data(ds.indices[:]),
)
shader := rl.LoadMaterialDefault().shader
rl.BeginShaderMode(shader)
defer rl.EndShaderMode()
for draw in ds.draws[:ds.num_draws] {
color: [4]f32 = {
f32(draw.color.r) / 255.0,
f32(draw.color.g) / 255.0,
f32(draw.color.b) / 255.0,
f32(draw.color.a) / 255.0,
}
gl.Uniform4fv(shader.locs[rl.ShaderLocationIndex.COLOR_DIFFUSE], 1, raw_data(color[:]))
gl.DrawElementsBaseVertex(
gl.TRIANGLES,
i32(ds.num_indices / 3),
gl.UNSIGNED_SHORT,
rawptr(uintptr(draw.first_index) * size_of(u16)),
draw.base_vertex,
)
}
}

View File

@ -258,6 +258,70 @@ calculate_spline_interpolated_points :: proc(
return nil
}
spline_generate_mesh :: proc(
interpolated_points: #soa[]Interpolated_Point,
allocator := context.allocator,
) -> (
vertices: []rl.Vector3,
indices: []u16,
) {
vertices = make([]rl.Vector3, (len(interpolated_points) - 1) * 4, allocator)
indices = make([]u16, (len(interpolated_points) - 1) * 6, allocator)
for i in 0 ..< len(interpolated_points) - 1 {
cur, next := interpolated_points[i], interpolated_points[i + 1]
tangent := lg.normalize0(next.pos - cur.pos)
normal := interpolated_points[i].normal
bitangent := lg.normalize0(lg.cross(tangent, normal))
next_tangent: rl.Vector3
if i < len(interpolated_points) - 2 {
next2 := interpolated_points[i + 2]
next_tangent = next2.pos - next.pos
} else {
next_tangent = tangent
}
next_normal := interpolated_points[i + 1].normal
next_bitangent := lg.normalize0(lg.cross(next_tangent, next_normal))
base_vert, base_ind := i * 4, i * 6
u_dt := 1.0 / f32(SPLINE_SUBDIVS_U)
for u in 0 ..< SPLINE_SUBDIVS_U {
u_t := u_dt * f32(u)
u_t2 := u_t + u_dt
// [-1, 1]
u_t = u_t * 2 - 1
u_t2 = u_t2 * 2 - 1
u_t *= ROAD_WIDTH
u_t2 *= ROAD_WIDTH
p1 := cur.pos + bitangent * u_t
p2 := cur.pos + bitangent * u_t2
p3 := next.pos + next_bitangent * u_t
p4 := next.pos + next_bitangent * u_t2
vertices[base_vert + 0] = p1
vertices[base_vert + 1] = p2
vertices[base_vert + 2] = p3
vertices[base_vert + 3] = p4
indices[base_ind + 0] = u16(base_vert + 0)
indices[base_ind + 1] = u16(base_vert + 1)
indices[base_ind + 2] = u16(base_vert + 2)
indices[base_ind + 3] = u16(base_vert + 1)
indices[base_ind + 4] = u16(base_vert + 3)
indices[base_ind + 5] = u16(base_vert + 2)
}
}
return
}
debug_draw_spline :: proc(interpolated_points: #soa[]Interpolated_Point) {
rlgl.Begin(rlgl.LINES)
defer rlgl.End()