gutter_runner/game/physics/helpers.odin
2025-06-28 23:24:03 +04:00

399 lines
10 KiB
Odin

package physics
import "collision"
import "core:log"
import "core:math"
import lg "core:math/linalg"
import "game:halfedge"
import "libs:tracy"
_ :: log
inertia_tensor_sphere :: proc(radius: f32) -> (tensor: Matrix3) {
tensor = radius * radius * (2.0 / 3.0)
return
}
inertia_tensor_box :: proc(size: Vec3) -> (tensor: Matrix3) {
CONSTANT :: f32(1.0 / 12.0)
tensor[0][0] = size.z * size.z + size.y * size.y
tensor[1][1] = size.x * size.x + size.z * size.z
tensor[2][2] = size.x * size.x + size.y * size.y
tensor = tensor * Matrix3(CONSTANT)
return
}
inertia_tensor_collision_shape :: proc(shape: Input_Shape_Internal) -> (tensor: Matrix3) {
switch s in shape {
case Shape_Box:
tensor = inertia_tensor_box(s.size)
case Shape_Convex:
// TODO: assuming precomputed
tensor = s.inertia_tensor
}
return
}
center_of_mass_shape :: proc(shape: Input_Shape_Internal) -> (com: Vec3) {
switch s in shape {
case Shape_Box:
com = 0
case Shape_Convex:
com = s.center_of_mass
}
return
}
volume_shape :: proc(shape: Input_Shape_Internal) -> (vol: f32) {
switch s in shape {
case Shape_Box:
vol = s.size.x * s.size.y * s.size.z
case Shape_Convex:
vol = s.total_volume
}
return
}
combined_center_of_mass :: proc(shapes: []Input_Shape) -> (com: Vec3) {
total_weight := f32(0)
for shape in shapes {
inner_com := center_of_mass_shape(shape.inner_shape) + shape.rel_x
volume := volume_shape(shape.inner_shape) * (shape.density_minus_one + 1)
com += inner_com * volume
total_weight += volume
}
com = total_weight > 0 ? com / total_weight : 0
return
}
// Returns normalized inertia
// https://pybullet.org/Bullet/phpBB3/viewtopic.php?t=246
combined_inertia_tensor :: proc(shapes: []Input_Shape) -> (inertia: Matrix3) {
combined_com := combined_center_of_mass(shapes)
total_volume := f32(0)
for shape in shapes {
com := center_of_mass_shape(shape.inner_shape) + shape.rel_x
r := com - combined_com
local_inertia := inertia_tensor_collision_shape(shape.inner_shape)
rotation_mat: Matrix3 = auto_cast lg.matrix3_from_quaternion(shape.rel_q)
inertia_common_align := lg.transpose(rotation_mat) * local_inertia * rotation_mat
volume := volume_shape(shape.inner_shape) * (shape.density_minus_one + 1)
dot_r := lg.dot(r, r)
// outer_r: Matrix3 = auto_cast lg.outer_product(r, r)
#unroll for i in 0 ..< 3 {
#unroll for j in 0 ..< 3 {
kron: f32 = i == j ? 1 : 0
inertia[i][j] += inertia_common_align[i][j] + volume * (dot_r * kron - r[i] * r[j])
}
}
// inertia = inertia + inertia_common_align * (Matrix3(dot_r) - outer_r) * Matrix3(volume)
total_volume += volume
}
inertia *= Matrix3(1.0 / total_volume)
return
}
body_local_to_world :: #force_inline proc(body: Body_Ptr, pos: Vec3) -> Vec3 {
return body.x + lg.quaternion_mul_vector3(body.q, pos)
}
body_local_to_world_vec :: #force_inline proc(body: Body_Ptr, vec: Vec3) -> Vec3 {
return lg.normalize0(lg.quaternion_mul_vector3(body.q, vec))
}
body_world_to_local :: #force_inline proc(body: Body_Ptr, pos: Vec3) -> Vec3 {
inv_q := lg.quaternion_normalize0(lg.quaternion_inverse(body.q))
return lg.quaternion_mul_vector3(inv_q, pos - body.x)
}
body_world_to_local_vec :: #force_inline proc(body: Body_Ptr, vec: Vec3) -> Vec3 {
inv_q := lg.quaternion_inverse(body.q)
return lg.normalize0(lg.quaternion_mul_vector3(inv_q, vec))
}
body_velocity_at_point :: #force_inline proc(body: Body_Ptr, pos: Vec3) -> Vec3 {
return body.v + lg.cross(body.w, pos - body.x)
}
wheel_get_rel_wheel_pos :: #force_inline proc(
body: Body_Ptr,
wheel: Suspension_Constraint_Ptr,
) -> Vec3 {
t := wheel.hit_t > 0 ? wheel.hit_t : wheel.rest
return wheel.rel_pos + body.shape_offset + wheel.rel_dir * (t - wheel.radius)
}
wheel_get_right_vec :: #force_inline proc(
body: Body_Ptr,
wheel: Suspension_Constraint_Ptr,
) -> Vec3 {
local_right := lg.quaternion_mul_vector3(
lg.quaternion_angle_axis(wheel.turn_angle, Vec3{0, 1, 0}),
Vec3{1, 0, 0},
)
return body_local_to_world_vec(body, local_right)
}
wheel_get_forward_vec :: #force_inline proc(
body: Body_Ptr,
wheel: Suspension_Constraint_Ptr,
) -> Vec3 {
local_right := lg.quaternion_mul_vector3(
lg.quaternion_angle_axis(wheel.turn_angle, Vec3{0, 1, 0}),
Vec3{0, 0, 1},
)
return body_local_to_world_vec(body, local_right)
}
body_get_shape_offset_local :: proc(body: Body_Ptr) -> (offset: Vec3) {
return body.shape_offset
}
// Returns the shape's world position
// Shape can be offset from COM
body_get_shape_pos :: proc(body: Body_Ptr) -> Vec3 {
offset := body_get_shape_offset_local(body)
return body_local_to_world(body, offset)
}
shape_get_convex_local :: proc(
sim_state: ^Sim_State,
shape: Collision_Shape_Internal,
allocator := context.temp_allocator,
) -> (
mesh: collision.Convex,
) {
switch s in shape {
case Shape_Box:
mesh = collision.box_to_convex(collision.Box{rad = s.size * 0.5}, allocator)
case Internal_Shape_Convex:
mesh = convex_container_get_mesh(&sim_state.convex_container, s.mesh)
// TODO: make sure this works as intendent
mesh = halfedge.copy_mesh(mesh, allocator)
}
return
}
Shapes_Iterator :: struct {
sim_state: ^Sim_State,
first_idx: i32,
cur_idx: i32,
counter: i32,
}
shapes_iterator :: proc(sim_state: ^Sim_State, first_shape: i32) -> (it: Shapes_Iterator) {
it.sim_state = sim_state
it.first_idx = first_shape
it.cur_idx = it.first_idx
return
}
shapes_iterator_next :: proc(
it: ^Shapes_Iterator,
) -> (
shape: ^Collision_Shape,
idx: i32,
ok: bool,
) {
if it.cur_idx == it.first_idx && it.counter != 0 {
return nil, it.counter, false
}
shape = &it.sim_state.shapes[it.cur_idx]
idx = it.cur_idx
ok = true
it.cur_idx = shape.next_index
it.counter += 1
return
}
get_shape_by_index :: proc(
sim_state: ^Sim_State,
first_shape: i32,
index: i32,
) -> (
shape_idx: i32,
) {
tracy.Zone()
shape_idx = -1
i := i32(0)
it := shapes_iterator(sim_state, first_shape)
for _, idx in shapes_iterator_next(&it) {
if i == index {
shape_idx = idx
break
}
i += 1
}
return
}
body_get_convex_shape_world :: proc(
sim_state: ^Sim_State,
body: Body_Ptr,
shape: Collision_Shape,
allocator := context.temp_allocator,
) -> (
mesh: collision.Convex,
) {
tracy.Zone()
body_transform := lg.matrix4_from_trs_f32(body_get_shape_pos(body), body.q, 1)
shape_transform := lg.matrix4_translate(shape.rel_x) * lg.matrix4_from_quaternion(shape.rel_q)
transform := body_transform * shape_transform
mesh = shape_get_convex_local(sim_state, shape.inner_shape, allocator)
halfedge.transform_mesh(&mesh, transform)
return
}
body_get_convex_shape_by_linear_index_world :: proc(
sim_state: ^Sim_State,
body: Body_Ptr,
shape_linear_idx: i32,
allocator := context.temp_allocator,
) -> (
mesh: collision.Convex,
) {
tracy.Zone()
shape_idx := get_shape_by_index(sim_state, body.shape, shape_linear_idx)
return body_get_convex_shape_world(sim_state, body, sim_state.shapes[shape_idx], allocator)
}
body_get_convex_shapes_world :: proc(
sim_state: ^Sim_State,
body: Body_Ptr,
allocator := context.temp_allocator,
) -> (
meshes: []collision.Convex,
) {
tracy.Zone()
body_transform :=
lg.matrix4_translate_f32(body_get_shape_pos(body)) * lg.matrix4_from_quaternion_f32(body.q)
meshes_arr := make([dynamic]collision.Convex, context.temp_allocator)
defer delete(meshes_arr)
it := shapes_iterator(sim_state, body.shape)
for shape in shapes_iterator_next(&it) {
shape_transform :=
lg.matrix4_translate(shape.rel_x) * lg.matrix4_from_quaternion(shape.rel_q)
transform := body_transform * shape_transform
mesh := shape_get_convex_local(sim_state, shape.inner_shape, allocator)
halfedge.transform_mesh(&mesh, transform)
append(&meshes_arr, mesh)
}
meshes = make([]collision.Convex, len(meshes_arr), allocator)
copy(meshes, meshes_arr[:])
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,
)
}
input_shape_internal_get_aabb :: proc(shape: Input_Shape_Internal) -> (aabb: AABB) {
switch s in shape {
case Shape_Box:
aabb.center = 0
aabb.extent = s.size * 0.5
case Shape_Convex:
aabb.center = s.mesh.center
aabb.extent = s.mesh.extent
}
return aabb
}
internal_shape_get_aabb :: proc(shape: Collision_Shape_Internal) -> (aabb: AABB) {
switch s in shape {
case Shape_Box:
aabb.center = 0
aabb.extent = s.size * 0.5
case Internal_Shape_Convex:
aabb.center = s.center
aabb.extent = s.extent
}
return
}
shape_get_aabb :: proc(shape: Collision_Shape) -> (aabb: AABB) {
local_aabb := internal_shape_get_aabb(shape.inner_shape)
aabb = aabb_transform(local_aabb, shape.rel_x, shape.rel_q)
return
}
rotate_extent :: proc(extent: Vec3, q: Quat) -> Vec3 {
rotation_mat := lg.matrix3_from_quaternion(q)
result := lg.abs(extent.xxx * rotation_mat[0])
result += lg.abs(extent.yyy * rotation_mat[1])
result += lg.abs(extent.zzz * rotation_mat[2])
return result
}
aabb_transform :: proc(local_aabb: AABB, x: Vec3, q: Quat) -> (aabb: AABB) {
aabb.center = lg.quaternion_mul_vector3(q, local_aabb.center) + x
aabb.extent = rotate_extent(local_aabb.extent, q)
return
}
input_shape_get_aabb :: proc(shapes: []Input_Shape) -> (aabb: AABB) {
min, max: Vec3 = max(f32), min(f32)
for shape in shapes {
local_aabb := input_shape_internal_get_aabb(shape.inner_shape)
aabb = aabb_transform(local_aabb, shape.rel_x, shape.rel_q)
shape_min := aabb.center - aabb.extent
shape_max := aabb.center + aabb.extent
min, max = lg.min(shape_min, min), lg.max(shape_max, max)
}
aabb.center = (max + min) * 0.5
aabb.extent = (max - min) * 0.5
return
}
body_transform_shape_aabb :: proc(body: Body_Ptr, local_aabb: AABB) -> (aabb: AABB) {
offset_world := body_local_to_world(body, body.shape_offset)
aabb = aabb_transform(local_aabb, offset_world, body.q)
return
}
body_get_aabb :: proc(body: Body_Ptr) -> (aabb: AABB) {
local_aabb := body.shape_aabb
return body_transform_shape_aabb(body, local_aabb)
}
rpm_to_angular_velocity :: proc(rpm: f32) -> f32 {
return 2.0 * math.PI * (rpm / 60.0)
}
angular_velocity_to_rpm :: proc(w: f32) -> f32 {
return (w / (2.0 * math.PI)) * 60.0
}