375 lines
9.9 KiB
Odin

package physics
import "collision"
import lg "core:math/linalg"
import rl "vendor:raylib"
MAX_CONTACTS :: 1024
Matrix3 :: # row_major matrix[3, 3]f32
Sim_State :: struct {
bodies: #soa[dynamic]Body,
suspension_constraints: #soa[dynamic]Suspension_Constraint,
// 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,
suspension_constraints_slice: #soa[]Suspension_Constraint,
first_free_body_plus_one: i32,
first_free_suspension_constraint_plus_one: i32,
// Persistent stuff for simulation
contact_pairs: [MAX_CONTACTS]Contact_Pair,
contact_pairs_len: int,
convex_container: Convex_Container,
}
Scene :: struct {
simulation_states: []Sim_State,
simulation_state_index: i32,
}
init_physics_scene :: proc(scene: ^Scene, max_history := int(2)) {
scene.simulation_states = make([]Sim_State, max(max_history, 2))
}
Body :: struct {
// Is this body alive (if not it doesn't exist)
alive: bool,
// Pos
x: rl.Vector3,
// Linear vel
v: rl.Vector3,
// Orientation
q: rl.Quaternion,
// Angular vel (omega)
w: rl.Vector3,
// Mass
inv_mass: f32,
// Moment of inertia
inv_inertia_tensor: Matrix3,
shape: Collision_Shape,
//
next_plus_one: i32,
}
Shape_Sphere :: struct {
radius: f32,
}
Shape_Box :: struct {
size: rl.Vector3,
}
Internal_Shape_Convex :: struct {
mesh: Convex_Handle,
center_of_mass: rl.Vector3,
inertia_tensor: Matrix3,
}
Shape_Convex :: struct {
mesh: collision.Convex,
center_of_mass: rl.Vector3,
inertia_tensor: Matrix3,
}
Collision_Shape :: union {
Shape_Box,
Internal_Shape_Convex,
}
Suspension_Constraint :: struct {
alive: bool,
// Pos relative to the body
rel_pos: rl.Vector3,
// Dir relative to the body
rel_dir: rl.Vector3,
// Handle of the rigid body
body: Body_Handle,
// Wheel radius
radius: f32,
// Rest distance
rest: f32,
// Inverse stiffness
compliance: f32,
// How much to damp velocity of the spring
damping: f32,
// Runtime state
hit: bool,
hit_point: rl.Vector3,
// rel_hit_point = rel_pos + rel_dir * hit_t
hit_t: f32,
turn_angle: f32,
drive_impulse: f32,
brake_impulse: f32,
applied_impulse: rl.Vector3,
// Free list
next_plus_one: i32,
}
// Index plus one, so handle 0 maps to invalid body
Body_Handle :: distinct i32
Suspension_Constraint_Handle :: distinct i32
INVALID_BODY :: Body_Handle(0)
INVALID_SUSPENSION_CONSTRAINT :: Suspension_Constraint_Handle(0)
is_body_handle_valid :: proc(handle: Body_Handle) -> bool {
return i32(handle) > 0
}
is_suspension_constraint_handle_valid :: proc(handle: Suspension_Constraint_Handle) -> bool {
return i32(handle) > 0
}
is_handle_valid :: proc {
is_body_handle_valid,
is_suspension_constraint_handle_valid,
}
Body_Ptr :: #soa^#soa[]Body
Suspension_Constraint_Ptr :: #soa^#soa[]Suspension_Constraint
_invalid_body: #soa[1]Body
_invalid_body_slice := _invalid_body[:]
_invalid_suspension_constraint: #soa[1]Suspension_Constraint
_invalid_suspension_constraint_slice := _invalid_suspension_constraint[:]
get_prev_sim_state_index :: proc(scene: ^Scene) -> i32 {
return (scene.simulation_state_index - 1) %% i32(len(scene.simulation_states))
}
get_sim_state :: proc(scene: ^Scene) -> ^Sim_State {
return &scene.simulation_states[scene.simulation_state_index]
}
get_prev_sim_state :: proc(scene: ^Scene) -> ^Sim_State {
return &scene.simulation_states[get_prev_sim_state_index(scene)]
}
get_next_sim_state :: proc(scene: ^Scene) -> ^Sim_State {
return &scene.simulation_states[(scene.simulation_state_index + 1) %% i32(len(scene.simulation_states))]
}
flip_sim_state :: proc(scene: ^Scene) {
scene.simulation_state_index = (scene.simulation_state_index + 1) %% i32(len(scene.simulation_states))
}
/// Returns pointer to soa slice. NEVER STORE IT
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) {
return &_invalid_body_slice[0]
}
return &sim_state.bodies_slice[index]
}
remove_shape :: proc(sim_state: ^Sim_State, shape: Collision_Shape) {
switch &s in shape {
case Shape_Box:
case Internal_Shape_Convex:
convex_container_remove(&sim_state.convex_container, s.mesh)
}
}
Input_Shape :: union {
Shape_Box,
Shape_Convex,
}
Body_Config :: struct {
initial_pos: rl.Vector3,
initial_rot: rl.Quaternion,
initial_vel: rl.Vector3,
initial_ang_vel: rl.Vector3,
shape: Input_Shape,
mass: f32,
inertia_mode: Body_Config_Inertia_Mode,
// Unit inertia tensor
inertia_tensor: Matrix3,
}
// TODO: rename to wheel
Suspension_Constraint_Config :: struct {
rel_pos: rl.Vector3,
rel_dir: rl.Vector3,
body: Body_Handle,
rest: f32,
compliance: f32,
damping: f32,
radius: f32,
}
calculate_body_params_from_config :: proc(
config: Body_Config,
) -> (
inv_mass: f32,
inv_inertia_tensor: Matrix3,
) {
inv_mass = config.mass == 0 ? 0 : 1.0 / config.mass
inertia_tensor: Matrix3
if config.inertia_mode == .Explicit {
inertia_tensor = config.inertia_tensor
} else {
inertia_tensor = inertia_tensor_collision_shape(config.shape)
}
inertia_tensor = inertia_tensor * Matrix3(config.mass)
inv_inertia_tensor = lg.determinant(inertia_tensor) == 0 ? 0 : lg.inverse(inertia_tensor)
return
}
add_shape :: proc(sim_state: ^Sim_State, shape: Input_Shape) -> (result: Collision_Shape) {
switch s in shape {
case Shape_Box:
result = s
case Shape_Convex:
convex: Internal_Shape_Convex
convex.mesh = convex_container_add(&sim_state.convex_container, s.mesh)
convex.center_of_mass = s.center_of_mass
convex.inertia_tensor = s.inertia_tensor
result = convex
}
return
}
initialize_body_from_config :: proc(sim_state: ^Sim_State, body: ^Body, config: Body_Config) {
body.x = config.initial_pos
body.q = config.initial_rot
body.v = config.initial_vel
body.w = config.initial_ang_vel
body.shape = add_shape(sim_state, config.shape)
body.inv_mass, body.inv_inertia_tensor = calculate_body_params_from_config(config)
}
update_body_from_config :: proc(sim_state: ^Sim_State, body: Body_Ptr, config: Body_Config) {
// Inefficient, but eh
remove_shape(sim_state, body.shape)
body.shape = add_shape(sim_state, config.shape)
body.inv_mass, body.inv_inertia_tensor = calculate_body_params_from_config(config)
}
update_suspension_constraint_from_config :: proc(
constraint: Suspension_Constraint_Ptr,
config: Suspension_Constraint_Config,
) {
constraint.rel_pos = config.rel_pos
constraint.rel_dir = config.rel_dir
constraint.body = config.body
constraint.rest = config.rest
constraint.compliance = config.compliance
constraint.damping = config.damping
constraint.radius = config.radius
}
add_body :: proc(sim_state: ^Sim_State, config: Body_Config) -> Body_Handle {
body: Body
initialize_body_from_config(sim_state, &body, config)
body.alive = true
if sim_state.first_free_body_plus_one > 0 {
index := sim_state.first_free_body_plus_one
new_body := get_body(sim_state, Body_Handle(index))
next_plus_one := new_body.next_plus_one
new_body^ = body
sim_state.first_free_body_plus_one = next_plus_one
return Body_Handle(index)
}
append_soa(&sim_state.bodies, body)
index := len(sim_state.bodies)
sim_state.bodies_slice = sim_state.bodies[:]
return Body_Handle(index)
}
remove_body :: proc(sim_state: ^Sim_State, handle: Body_Handle) {
if int(handle) > 1 {
body := get_body(sim_state, handle)
body.alive = false
remove_shape(sim_state, body.shape)
body.next_plus_one = sim_state.first_free_body_plus_one
sim_state.first_free_body_plus_one = i32(handle)
}
}
/// Returns pointer to soa slice. NEVER STORE IT
get_suspension_constraint :: proc(
sim_state: ^Sim_State,
handle: Suspension_Constraint_Handle,
) -> Suspension_Constraint_Ptr {
if !is_handle_valid(handle) {
return &_invalid_suspension_constraint_slice[0]
}
index := int(handle) - 1
return &sim_state.suspension_constraints_slice[index]
}
add_suspension_constraint :: proc(
sim_state: ^Sim_State,
constraint: Suspension_Constraint,
) -> Suspension_Constraint_Handle {
copy := constraint
copy.alive = true
copy.next_plus_one = 0
if sim_state.first_free_suspension_constraint_plus_one > 0 {
index := sim_state.first_free_suspension_constraint_plus_one
new_constraint := get_suspension_constraint(sim_state, Suspension_Constraint_Handle(index))
next_plus_one := new_constraint.next_plus_one
new_constraint^ = copy
sim_state.first_free_suspension_constraint_plus_one = next_plus_one
return Suspension_Constraint_Handle(index)
}
append_soa(&sim_state.suspension_constraints, copy)
sim_state.suspension_constraints_slice = sim_state.suspension_constraints[:]
index := len(sim_state.suspension_constraints)
return Suspension_Constraint_Handle(index)
}
remove_suspension_constraint :: proc(sim_state: ^Sim_State, handle: Suspension_Constraint_Handle) {
if is_handle_valid(handle) {
constraint := get_suspension_constraint(sim_state, handle)
constraint.alive = false
constraint.next_plus_one = sim_state.first_free_suspension_constraint_plus_one
sim_state.first_free_suspension_constraint_plus_one = i32(handle)
}
}
_get_first_free_body :: proc(sim_state: ^Sim_State) -> i32 {
return sim_state.first_free_body_plus_one - 1
}
destry_sim_state :: proc(sim_state: ^Sim_State) {
delete_soa(sim_state.bodies)
delete_soa(sim_state.suspension_constraints)
convex_container_destroy(&sim_state.convex_container)
}
destroy_physics_scene :: proc(scene: ^Scene) {
for &sim_state in scene.simulation_states {
destry_sim_state(&sim_state)
}
delete(scene.simulation_states)
}