403 lines
10 KiB
Odin
403 lines
10 KiB
Odin
package physics
|
|
|
|
import "collision"
|
|
import "core:log"
|
|
import "core:math"
|
|
import lg "core:math/linalg"
|
|
import "game:halfedge"
|
|
import "game:name"
|
|
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()
|
|
if shape_linear_idx == 1 {
|
|
log.debugf("linear idx: %v, body %v", shape_linear_idx, name.to_string(body.name))
|
|
}
|
|
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
|
|
}
|