sergeypdev 88fac29a6c Fix suspension when driving on other rigid bodies, super solid now
The key was to limit the total spring impulse similar to how contact impulse is limited, so that spring always pushes and never pulls
2025-07-29 01:58:33 +04:00

1150 lines
32 KiB
Odin

package physics
import "bvh"
import "collision"
import "common:name"
import lg "core:math/linalg"
import "game:assets"
import "game:container/spanpool"
import "libs:tracy"
MAX_CONTACTS :: 1024 * 16
Vec3 :: [3]f32
Vec2 :: [2]f32
Quat :: quaternion128
Matrix3 :: # row_major matrix[3, 3]f32
AABB :: struct {
center: Vec3,
extent: Vec3,
}
Contact_Pair_Bodies :: struct {
a, b: Body_Handle,
shape_a, shape_b: i32,
}
Contact_Pair_Body_Level :: struct {
a: Body_Handle,
b: Level_Geom_Handle,
shape_a: i32,
tri_idx: i32,
}
Contact_Pair :: union {
Contact_Pair_Bodies,
Contact_Pair_Body_Level,
}
make_contact_pair_bodies :: proc(
body_a: int,
body_b: int,
shape_a: i32,
shape_b: i32,
) -> Contact_Pair_Bodies {
return {
index_to_body_handle(min(body_a, body_b)),
index_to_body_handle(max(body_a, body_b)),
shape_a,
shape_b,
}
}
make_contact_pair_body_level :: proc(
body: Body_Handle,
level_geom_handle: Level_Geom_Handle,
shape_a: i32,
tri_idx: i32,
) -> Contact_Pair_Body_Level {
return {body, level_geom_handle, shape_a, tri_idx}
}
Contact_Container :: struct {
// body index pair to contact index
lookup: map[Contact_Pair]i32,
contacts: #soa[dynamic]Contact,
}
contact_container_copy :: proc(dst: ^Contact_Container, src: Contact_Container) {
clear(&dst.lookup)
reserve(&dst.lookup, cap(src.lookup))
resize_soa(&dst.contacts, len(src.contacts))
for k, v in src.lookup {
dst.lookup[k] = v
}
for i in 0 ..< len(src.contacts) {
dst.contacts[i] = src.contacts[i]
}
}
Sim_State :: struct {
scene: ^Scene,
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,
suspension_constraints_slice: #soa[]Suspension_Constraint,
shapes: [dynamic]Collision_Shape,
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,
first_free_shape_plus_one: i32,
// Persistent stuff for simulation
contact_container: Contact_Container,
convex_container: Convex_Container,
// NOTE: kinda overkill, but it simplifies copying sim states around a lot
// 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),
// BLAS for level geom
blas_nodes_pool: spanpool.Span_Pool(bvh.Node),
blas_primitives_pool: spanpool.Span_Pool(u16),
// Temp stuff, kept for one frame, allocated from temp_allocator
static_tlas: Static_TLAS,
dynamic_tlas: Dynamic_TLAS,
}
DEV_BUILD :: #config(DEV, false)
NUM_SIM_STATES :: 2 when DEV_BUILD else 1
Scene :: struct {
assetman: ^assets.Asset_Manager,
simulation_states: [NUM_SIM_STATES]Sim_State,
simulation_state_index: i32,
solver_state: Solver_State,
}
// Copy current state to next
copy_sim_state :: proc(dst: ^Sim_State, src: ^Sim_State) {
if dst == src {
return
}
tracy.Zone()
convex_container_reconcile(&src.convex_container)
dst.scene = src.scene
dst.num_bodies = src.num_bodies
dst.first_free_body_plus_one = src.first_free_body_plus_one
dst.first_free_suspension_constraint_plus_one = src.first_free_suspension_constraint_plus_one
dst.first_free_engine_plus_one = src.first_free_engine_plus_one
dst.first_free_shape_plus_one = src.first_free_shape_plus_one
// TODO: shrink when possible
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))
resize(&dst.shapes, len(src.shapes))
dst.bodies_slice = dst.bodies[:]
dst.suspension_constraints_slice = dst.suspension_constraints[:]
for i in 0 ..< len(dst.bodies) {
dst.bodies[i] = src.bodies[i]
}
for i in 0 ..< len(dst.suspension_constraints) {
dst.suspension_constraints[i] = src.suspension_constraints[i]
}
copy(dst.engines[:], src.engines[:])
copy(dst.level_geoms[:], src.level_geoms[:])
copy(dst.shapes[:], src.shapes[:])
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)
spanpool.copy(&dst.blas_nodes_pool, src.blas_nodes_pool)
spanpool.copy(&dst.blas_primitives_pool, src.blas_primitives_pool)
}
copy_physics_scene :: proc(dst, src: ^Scene) {
tracy.Zone()
dst.assetman = src.assetman
dst.simulation_state_index = src.simulation_state_index
for i in 0 ..< NUM_SIM_STATES {
copy_sim_state(&dst.simulation_states[i], &src.simulation_states[i])
}
copy_solver_state(&dst.solver_state, &src.solver_state)
}
Body :: struct {
// Is this body alive (if not it doesn't exist)
alive: bool,
// Pos
x: Vec3,
// Linear vel
v: Vec3,
// Orientation
q: Quat,
// Angular vel (omega)
w: Vec3,
prev_x: Vec3,
prev_v: Vec3,
prev_q: Quat,
prev_w: Vec3,
// Mass
inv_mass: f32,
// Moment of inertia
inv_inertia_tensor: Matrix3,
shape: i32,
// Collision shape offset = -combined_center_of_mass
shape_offset: Vec3,
shape_aabb: AABB,
name: name.Name,
//
next_plus_one: i32,
}
Shape_Sphere :: struct {
radius: f32,
}
Shape_Box :: struct {
size: Vec3,
}
Internal_Shape_Convex :: struct {
mesh: Convex_Handle,
center_of_mass: Vec3,
inertia_tensor: Matrix3,
center: Vec3,
extent: Vec3,
}
Shape_Convex :: struct {
mesh: collision.Convex,
center_of_mass: Vec3,
inertia_tensor: Matrix3,
total_volume: f32,
}
Collision_Shape_Internal :: union {
Shape_Box,
Internal_Shape_Convex,
}
Shape_Compound :: struct {
rel_x: Vec3,
rel_q: Quat,
inner_shape: Collision_Shape_Internal,
next_index: i32,
prev_index: i32,
}
Collision_Shape :: struct {
rel_x: Vec3,
rel_q: Quat,
inner_shape: Collision_Shape_Internal,
next_index: i32,
prev_index: i32,
}
Suspension_Constraint :: struct {
alive: bool,
// Pos relative to the body
rel_pos: Vec3,
// Dir relative to the body
rel_dir: Vec3,
// Handle of the rigid body
body: Body_Handle,
// Wheel radius
radius: f32,
// Wheel mass
mass: f32,
// Rest distance
rest: f32,
// Frequency of the spring, affects stiffness
natural_frequency: f32,
// How much to damp velocity of the spring
damping: f32,
pacejka_long: Pacejka94_Longitudinal_Params,
pacejka_lat: Pacejka94_Lateral_Params,
// Runtime state
// Accumulated impulse
spring_impulse: f32,
lateral_impulse: f32,
longitudinal_impulse: f32,
brake_friction_impulse: f32,
inv_mass: f32,
// Inverse inertia along wheel rotation axis
inv_inertia: f32,
// Wheel pos along suspension dir
x: f32,
// Rotation
q: f32,
// Linear velocity along suspension dir
v: f32,
// Angular velocity
w: f32,
hit: bool,
hit_normal: Vec3,
hit_body: Body_Handle,
hit_point: Vec3,
// rel_hit_point = rel_pos + rel_dir * hit_t
hit_t: f32,
turn_angle: f32,
drive_impulse: f32,
// Impulse of brake pad on brake disc, set outsie
brake_impulse: f32,
applied_impulse: Vec3,
// Convenience for debug visualization to avoid recomputing
slip_angle: f32,
slip_ratio: f32,
// Multipliers for combined friction
slip_vec: Vec2,
// Free list
next_plus_one: i32,
}
Diff_Type :: enum {
Open,
Fixed,
// TODO: LSD
}
Drive_Wheel :: struct {
wheel: Suspension_Constraint_Handle,
impulse: f32,
}
Drive_Axle :: struct {
// Params
wheels: [2]Drive_Wheel,
wheel_count: i32,
diff_type: Diff_Type,
final_drive_ratio: f32,
// State
// Diff angular vel
w: f32,
// Impulse that constrains diff and engine motion
engine_impulse: f32,
// Impulse that constrains wheels motion relative to each other
diff_impulse: f32,
}
Engine_Curve_Handle :: distinct spanpool.Handle
Gear_Ratios_Handle :: distinct spanpool.Handle
// This actually handles everything, engine, transmission, differential, etc. It's easier to keep it in one place
Engine :: struct {
alive: bool,
// Engine Params
rpm_torque_curve: Engine_Curve_Handle,
lowest_rpm: f32,
// Rpm when rev limiter activates
rev_limit_rpm: f32,
// Time in seconds for how long rev limiter cuts the throttle
rev_limit_interval: f32,
inertia: f32,
internal_friction: f32,
// Transmission Params
// 0 - reverse, 1 - first, etc.
gear_ratios: Gear_Ratios_Handle,
axle: Drive_Axle,
// Engine State
// Angular velocity, omega
q: f32,
w: f32,
// Reset to -rev_limit_interval when rpm exceeds rev_limit_rpm, has to be >= 0 for throttle to work
rev_limit_time: f32,
// Impulse applied when engine is stalling (rpm < lowest_rpm)
unstall_impulse: f32,
// Friction that makes rpm go down when you're not accelerating
friction_impulse: f32,
// Impulse applied from releasing throttle
throttle_impulse: f32,
// Transmission State
// -1 - reeverse, 0 - neutral, 1 - first, etc.
gear: i32,
// Controls
throttle: f32,
clutch: f32,
// Free list
next_plus_one: i32,
}
Geometry_Handle :: struct {
vertices: spanpool.Handle,
indices: spanpool.Handle,
}
BLAS_Handle :: struct {
nodes: spanpool.Handle,
primitives: spanpool.Handle,
}
Level_Geom_Source_Local :: struct {
geometry: Geometry_Handle,
blas: BLAS_Handle,
}
Level_Geom_Source_Asset :: struct {
assetpath: name.Name,
}
Level_Geom_Source :: union #no_nil {
Level_Geom_Source_Local,
Level_Geom_Source_Asset,
}
Level_Geom :: struct {
alive: bool,
aabb: AABB,
source: Level_Geom_Source,
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
}
is_suspension_constraint_handle_valid :: proc(handle: Suspension_Constraint_Handle) -> bool {
return i32(handle) > 0
}
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 :: #force_inline proc(idx: int) -> Body_Handle {
return Body_Handle(idx + 1)
}
index_to_level_geom :: #force_inline proc(idx: int) -> Level_Geom_Handle {
return Level_Geom_Handle(idx + 1)
}
body_handle_to_index :: #force_inline proc(handle: Body_Handle) -> int {
return int(handle) - 1
}
level_geom_handle_to_index :: #force_inline 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_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 {
@(static) _invalid_body: #soa[1]Body
@(static) _invalid_body_slice: #soa[]Body
index := int(handle) - 1
if index < 0 || index >= len(sim_state.bodies_slice) {
_invalid_body_slice = _invalid_body[:]
_invalid_body_slice[0] = {
alive = true,
q = lg.QUATERNIONF32_IDENTITY,
prev_q = lg.QUATERNIONF32_IDENTITY,
}
return &_invalid_body_slice[0]
}
return &sim_state.bodies_slice[index]
}
remove_shape :: proc(sim_state: ^Sim_State, idx: i32) {
cur_idx := idx
for {
shape := &sim_state.shapes[cur_idx]
switch &s in shape.inner_shape {
case Shape_Box:
case Internal_Shape_Convex:
convex_container_remove(&sim_state.convex_container, s.mesh)
}
prev_idx := cur_idx
cur_idx = shape.next_index
free_shape(sim_state, prev_idx)
if cur_idx == idx {
break
}
}
}
Input_Shape_Internal :: union {
Shape_Box,
Shape_Convex,
}
Input_Shape :: struct {
rel_x: Vec3,
rel_q: Quat,
density_minus_one: f32,
inner_shape: Input_Shape_Internal,
}
Body_Config :: struct {
name: name.Name,
initial_pos: Vec3,
initial_rot: Quat,
initial_vel: Vec3,
initial_ang_vel: Vec3,
shapes: []Input_Shape,
mass: f32,
inertia_mode: Body_Config_Inertia_Mode,
// Unit inertia tensor
inertia_tensor: Matrix3,
// Allows shifting the autocalculated center of mass
com_shift: Vec3,
}
// TODO: rename to wheel
Suspension_Constraint_Config :: struct {
rel_pos: Vec3,
rel_dir: Vec3,
body: Body_Handle,
rest: f32,
natural_frequency: f32,
damping: f32,
radius: f32,
mass: f32,
pacejka_long: Pacejka94_Longitudinal_Params,
pacejka_lat: Pacejka94_Lateral_Params,
}
Drive_Axle_Config :: struct {
wheels: [2]Suspension_Constraint_Handle,
wheel_count: i32,
diff_type: Diff_Type,
final_drive_ratio: f32,
}
Engine_Config :: struct {
rpm_torque_curve: [][2]f32,
lowest_rpm: f32,
rev_limit_rpm: f32,
rev_limit_interval: f32,
inertia: f32,
internal_friction: f32,
// Transmission Params
// 0 - reverse, 1 - first, etc.
gear_ratios: []f32,
axle: Drive_Axle_Config,
}
Level_Geometry_Asset :: distinct name.Name
Level_Geometry_Mesh :: struct {
vertices: []Vec3,
indices: []u16,
}
// Either runtime mesh or asset reference which will be fetched from assetmanager
Level_Geometry_Source :: union #no_nil {
Level_Geometry_Mesh,
Level_Geometry_Asset,
}
Level_Geom_Config :: struct {
position: Vec3,
rotation: Quat,
source: Level_Geometry_Source,
}
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 = combined_inertia_tensor(config.shapes)
}
inertia_tensor = inertia_tensor * Matrix3(config.mass)
inv_inertia_tensor = lg.determinant(inertia_tensor) == 0 ? 0 : lg.inverse(inertia_tensor)
return
}
alloc_shape :: proc(sim_state: ^Sim_State) -> (idx: i32) {
if sim_state.first_free_shape_plus_one != 0 {
idx = sim_state.first_free_shape_plus_one - 1
new_shape := &sim_state.shapes[idx]
sim_state.first_free_shape_plus_one = new_shape.next_index + 1
new_shape.prev_index = idx
new_shape.next_index = idx
return idx
} else {
idx = i32(len(sim_state.shapes))
append(&sim_state.shapes, Collision_Shape{})
new_shape := &sim_state.shapes[idx]
new_shape.prev_index = idx
new_shape.next_index = idx
return idx
}
}
free_shape :: proc(sim_state: ^Sim_State, idx: i32) {
sim_state.shapes[idx].next_index = sim_state.first_free_shape_plus_one - 1
sim_state.first_free_shape_plus_one = idx + 1
}
add_shape :: proc(sim_state: ^Sim_State, shapes: []Input_Shape) -> (first_idx: i32) {
first_idx = -1
last_idx := i32(-1)
for shape in shapes {
new_idx := alloc_shape(sim_state)
new_shape := &sim_state.shapes[new_idx]
if first_idx == -1 {
first_idx = new_idx
} else {
sim_state.shapes[last_idx].next_index = new_idx
sim_state.shapes[first_idx].prev_index = new_idx
new_shape.prev_index = last_idx
new_shape.next_index = first_idx
}
last_idx = new_idx
new_shape.rel_x = shape.rel_x
new_shape.rel_q = shape.rel_q
switch s in shape.inner_shape {
case Shape_Box:
new_shape.inner_shape = s
case Shape_Convex:
convex: Internal_Shape_Convex
convex.mesh = convex_container_add(&sim_state.convex_container, s.mesh)
convex.center = s.mesh.center
convex.extent = s.mesh.extent
convex.center_of_mass = s.center_of_mass
convex.inertia_tensor = s.inertia_tensor
new_shape.inner_shape = 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.shapes)
body.shape_offset = -combined_center_of_mass(config.shapes) + config.com_shift
body.shape_aabb = input_shape_get_aabb(config.shapes)
body.name = config.name
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.shapes)
body.shape_offset = -combined_center_of_mass(config.shapes) + config.com_shift
body.shape_aabb = input_shape_get_aabb(config.shapes)
body.name = config.name
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.natural_frequency = config.natural_frequency
constraint.damping = config.damping
constraint.radius = config.radius
constraint.inv_inertia = 1.0 / (0.5 * config.mass * config.radius * config.radius)
constraint.pacejka_long = config.pacejka_long
constraint.pacejka_lat = config.pacejka_lat
}
add_engine_curve :: proc(sim_state: ^Sim_State, curve: [][2]f32) -> Engine_Curve_Handle {
handle := spanpool.allocate_elems(&sim_state.rpm_torque_curves_pool, ..curve)
return Engine_Curve_Handle(handle)
}
remove_engine_curve :: proc(sim_state: ^Sim_State, handle: Engine_Curve_Handle) {
spanpool.free(&sim_state.rpm_torque_curves_pool, spanpool.Handle(handle))
}
get_engine_curve :: proc(sim_state: ^Sim_State, handle: Engine_Curve_Handle) -> [][2]f32 {
return spanpool.resolve_slice(&sim_state.rpm_torque_curves_pool, spanpool.Handle(handle))
}
add_gear_ratios :: proc(sim_state: ^Sim_State, gear_ratios: []f32) -> Gear_Ratios_Handle {
handle := spanpool.allocate_elems(&sim_state.gear_ratios_pool, ..gear_ratios)
return Gear_Ratios_Handle(handle)
}
remove_gear_ratios :: proc(sim_state: ^Sim_State, handle: Gear_Ratios_Handle) {
spanpool.free(&sim_state.gear_ratios_pool, spanpool.Handle(handle))
}
get_gear_ratios :: proc(
sim_state: ^Sim_State,
handle: Gear_Ratios_Handle,
loc := #caller_location,
) -> []f32 {
return spanpool.resolve_slice(&sim_state.gear_ratios_pool, spanpool.Handle(handle), loc)
}
update_engine_from_config :: proc(
sim_state: ^Sim_State,
engine: Engine_Ptr,
config: Engine_Config,
) {
remove_engine_curve(sim_state, engine.rpm_torque_curve)
engine.rpm_torque_curve = add_engine_curve(sim_state, config.rpm_torque_curve)
remove_gear_ratios(sim_state, engine.gear_ratios)
engine.gear_ratios = add_gear_ratios(sim_state, config.gear_ratios)
engine.lowest_rpm = config.lowest_rpm
engine.rev_limit_rpm = config.rev_limit_rpm
engine.rev_limit_interval = config.rev_limit_interval
engine.inertia = config.inertia
engine.internal_friction = config.internal_friction
engine.axle.final_drive_ratio = config.axle.final_drive_ratio
engine.axle.wheels[0].wheel = config.axle.wheels[0]
engine.axle.wheels[1].wheel = config.axle.wheels[1]
engine.axle.wheel_count = config.axle.wheel_count
engine.axle.diff_type = config.axle.diff_type
}
add_body :: proc(sim_state: ^Sim_State, config: Body_Config) -> Body_Handle {
body: Body
sim_state.num_bodies += 1
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)
sim_state.num_bodies -= 1
}
}
/// 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)
}
}
invalid_engine: Engine
get_engine :: proc(sim_state: ^Sim_State, handle: Engine_Handle) -> Engine_Ptr {
index := int(handle) - 1
if index < 0 || index >= len(sim_state.bodies_slice) {
return &invalid_engine
}
return &sim_state.engines[index]
}
add_engine :: proc(sim_state: ^Sim_State, config: Engine_Config) -> Engine_Handle {
sim_state.num_engines += 1
engine: Engine
update_engine_from_config(sim_state, &engine, config)
engine.alive = true
if sim_state.first_free_engine_plus_one > 0 {
index := sim_state.first_free_engine_plus_one
new_engine := get_engine(sim_state, Engine_Handle(index))
next_plus_one := new_engine.next_plus_one
new_engine^ = engine
sim_state.first_free_engine_plus_one = next_plus_one
return Engine_Handle(index)
}
append(&sim_state.engines, engine)
index := len(sim_state.engines)
return Engine_Handle(index)
}
remove_engine :: proc(sim_state: ^Sim_State, handle: Engine_Handle) {
if is_handle_valid(handle) {
engine := get_engine(sim_state, handle)
remove_engine_curve(sim_state, engine.rpm_torque_curve)
remove_gear_ratios(sim_state, engine.gear_ratios)
engine.alive = false
engine.next_plus_one = sim_state.first_free_engine_plus_one
sim_state.first_free_engine_plus_one = i32(handle)
sim_state.num_engines -= 1
}
}
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,
sim_cache: ^Sim_Cache,
handle: Level_Geom_Handle,
) -> (
blas: bvh.BVH,
vertices: []Vec3,
indices: []u16,
) {
level_geom := get_level_geom(sim_state, handle)
switch s in level_geom.source {
case Level_Geom_Source_Local:
blas.nodes = spanpool.resolve_slice(&sim_state.blas_nodes_pool, s.blas.nodes)
blas.primitives = spanpool.resolve_slice(
&sim_state.blas_primitives_pool,
s.blas.primitives,
)
blas.nodes_used = i32(len(blas.nodes))
vertices = spanpool.resolve_slice(&sim_state.geometry_vertices_pool, s.geometry.vertices)
indices = spanpool.resolve_slice(&sim_state.geometry_indices_pool, s.geometry.indices)
case Level_Geom_Source_Asset:
loaded_bvh, ok := sim_cache.level_geom_asset_bvh[handle]
if !ok {
reloaded: bool
loaded_bvh, reloaded = assets.get_bvh(sim_state.scene.assetman, s.assetpath)
if reloaded {
level_geom.aabb = bvh_aabb_to_phys_aabb(loaded_bvh.aabb)
level_geom.aabb = aabb_transform(level_geom.aabb, level_geom.x, level_geom.q)
}
// sim_cache.level_geom_asset_bvh[handle] = loaded_bvh
}
blas = loaded_bvh.bvh
vertices = loaded_bvh.vertices
indices = loaded_bvh.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
// TODO: figure out if asset changed and rebuild only then
}
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)
switch s in config.source {
case Level_Geometry_Mesh:
assert(len(s.vertices) > 0)
assert(len(s.vertices) <= int(max(u16)))
blas :=
bvh.build_bvh_from_mesh(bvh.Mesh{vertices = s.vertices, indices = s.indices}, context.temp_allocator).bvh
source: Level_Geom_Source_Local
source.blas.nodes = spanpool.allocate_elems(
&sim_state.blas_nodes_pool,
..blas.nodes[:blas.nodes_used],
)
source.blas.primitives = spanpool.allocate_elems(
&sim_state.blas_primitives_pool,
..blas.primitives,
)
aabb_min, aabb_max: Vec3 = max(f32), min(f32)
for v in s.vertices {
aabb_min = lg.min(aabb_min, v)
aabb_max = lg.max(aabb_max, v)
}
level_geom.aabb.center = (aabb_max + aabb_min) * 0.5
level_geom.aabb.extent = (aabb_max - aabb_min) * 0.5
// 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)
// }
source.geometry.vertices = spanpool.allocate_elems(
&sim_state.geometry_vertices_pool,
..s.vertices,
)
source.geometry.indices = spanpool.allocate_elems(
&sim_state.geometry_indices_pool,
..s.indices,
)
level_geom.source = source
case Level_Geometry_Asset:
level_geom.source = Level_Geom_Source_Asset {
assetpath = name.Name(s),
}
bvh, _ := assets.get_bvh(sim_state.scene.assetman, name.Name(s))
level_geom.aabb = AABB {
center = (bvh.aabb.max + bvh.aabb.min) * 0.5,
extent = (bvh.aabb.max - bvh.aabb.min) * 0.5,
}
}
level_geom.aabb = aabb_transform(level_geom.aabb, level_geom.x, level_geom.q)
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)
switch s in level_geom.source {
case Level_Geom_Source_Local:
spanpool.free(&sim_state.geometry_vertices_pool, s.geometry.vertices)
spanpool.free(&sim_state.geometry_indices_pool, s.geometry.indices)
spanpool.free(&sim_state.blas_nodes_pool, s.blas.nodes)
spanpool.free(&sim_state.blas_primitives_pool, s.blas.primitives)
case Level_Geom_Source_Asset:
}
level_geom.alive = false
level_geom.next_plus_one = sim_state.first_free_level_geom_plus_one
sim_state.first_free_level_geom_plus_one = i32(handle)
sim_state.num_level_geoms -= 1
}
_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)
delete(sim_state.engines)
delete(sim_state.level_geoms)
delete(sim_state.shapes)
delete_soa(sim_state.contact_container.contacts)
delete_map(sim_state.contact_container.lookup)
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)
static_tlas_destroy(&sim_state.static_tlas)
dynamic_tlas_destroy(&sim_state.dynamic_tlas)
}
scene_init :: proc(scene: ^Scene, assetman: ^assets.Asset_Manager) {
scene.assetman = assetman
for &sim_state in scene.simulation_states {
sim_state.scene = scene
}
}
scene_destroy :: proc(scene: ^Scene) {
for &sim_state in scene.simulation_states {
destry_sim_state(&sim_state)
}
destroy_solver_state(&scene.solver_state)
}