Calculate inertia tensor for the convex hull, convert simulation to use full inertia tensor matrix

I couldn't figure out how to diagonalize the inertia tensor matrix so I will go the simle route and just use the full 3x3 matrix
This commit is contained in:
sergeypdev 2025-02-02 01:17:35 +04:00
parent 660a535a7c
commit 999a7a4631
6 changed files with 136 additions and 27 deletions

View File

@ -35,7 +35,7 @@ Loaded_BVH :: struct {
Loaded_Convex :: struct { Loaded_Convex :: struct {
mesh: collision.Convex, mesh: collision.Convex,
center_of_mass: rl.Vector3, center_of_mass: rl.Vector3,
center_of_mass_tris: rl.Vector3, inertia_tensor: lg.Matrix3f32,
} }
destroy_loaded_bvh :: proc(loaded_bvh: Loaded_BVH) { destroy_loaded_bvh :: proc(loaded_bvh: Loaded_BVH) {
@ -364,7 +364,6 @@ get_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_C
center /= f32(len(vertices)) center /= f32(len(vertices))
center_of_mass: rl.Vector3 center_of_mass: rl.Vector3
center_of_mass_tris: rl.Vector3
mesh := halfedge.Half_Edge_Mesh { mesh := halfedge.Half_Edge_Mesh {
vertices = vertices[:], vertices = vertices[:],
@ -375,7 +374,6 @@ get_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_C
// Center of mass calculation // Center of mass calculation
total_volume := f32(0.0) total_volume := f32(0.0)
total_area_tris := f32(0.0)
{ {
rlgl.Begin(rlgl.TRIANGLES) rlgl.Begin(rlgl.TRIANGLES)
rlgl.End() rlgl.End()
@ -410,8 +408,6 @@ get_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_C
tetra_centroid := (tri[0] + tri[1] + tri[2] + center) * 0.25 tetra_centroid := (tri[0] + tri[1] + tri[2] + center) * 0.25
center_of_mass += tetra_volume * tetra_centroid center_of_mass += tetra_volume * tetra_centroid
center_of_mass_tris += 1.0 / 3.0 * (tri[0] + tri[1] + tri[2]) * tri_area
total_area_tris += tri_area
tri_idx += 1 tri_idx += 1
} }
@ -423,13 +419,122 @@ get_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_C
assert(total_volume > 0, "degenerate convex hull") assert(total_volume > 0, "degenerate convex hull")
center_of_mass /= total_volume center_of_mass /= total_volume
center_of_mass_tris /= total_area_tris
return { inertia_tensor: lg.Matrix3f32
mesh = mesh, // Find inertia tensor
center_of_mass = center_of_mass, {
center_of_mass_tris = center_of_mass_tris, tri_idx := 0
for face_idx in 0 ..< len(faces) {
// for all triangles
it := halfedge.iterator_face_edges(mesh, halfedge.Face_Index(face_idx))
i := 0
tri: [3]rl.Vector3
for edge in halfedge.iterate_next_edge(&it) {
switch i {
case 0 ..< 3:
tri[i] = mesh.vertices[edge.origin].pos
case:
tri[1] = tri[2]
tri[2] = mesh.vertices[edge.origin].pos
} }
if i >= 2 {
tet := Tetrahedron {
p = {tri[0], tri[1], tri[2], center_of_mass},
}
inertia_tensor += tetrahedron_inertia_tensor(tet, center_of_mass)
tri_idx += 1
}
i += 1
}
}
}
log.infof("inertia tensor: %v", inertia_tensor)
return {mesh = mesh, center_of_mass = center_of_mass, inertia_tensor = inertia_tensor}
}
Tetrahedron :: struct {
p: [4]rl.Vector3,
}
tetrahedron_volume :: #force_inline proc(tet: Tetrahedron) -> f32 {
return(
1.0 /
6.0 *
abs(lg.dot(lg.cross(tet.p[1] - tet.p[0], tet.p[2] - tet.p[0]), tet.p[3] - tet.p[0])) \
)
}
square :: #force_inline proc(val: f32) -> f32 {
return val * val
}
tetrahedron_inertia_tensor :: proc(tet: Tetrahedron, o: rl.Vector3) -> lg.Matrix3f32 {
p1, p2, p3, p4 := tet.p[0] - o, tet.p[1] - o, tet.p[2] - o, tet.p[3] - o
// Jacobian determinant is 6*Volume
det_j := abs(6.0 * tetrahedron_volume(tet))
moment_of_inertia_term :: proc(p1, p2, p3, p4: rl.Vector3, axis: int) -> f32 {
return(
square(p1[axis]) +
p1[axis] * p2[axis] +
square(p2[axis]) +
p1[axis] * p3[axis] +
p2[axis] * p3[axis] +
square(p3[axis]) +
p1[axis] * p4[axis] +
p2[axis] * p4[axis] +
p3[axis] * p4[axis] +
square(p4[axis]) \
)
}
product_of_inertia_term :: proc(p1, p2, p3, p4: rl.Vector3, axis1, axis2: int) -> f32 {
return(
2.0 * p1[axis1] * p1[axis2] +
p2[axis1] * p1[axis2] +
p3[axis1] * p1[axis2] +
p4[axis1] * p1[axis2] +
p1[axis1] * p2[axis2] +
2.0 * p2[axis1] * p2[axis2] +
p3[axis1] * p2[axis2] +
p4[axis1] * p2[axis2] +
p1[axis1] * p3[axis2] +
p2[axis1] * p3[axis2] +
2.0 * p3[axis1] * p3[axis2] +
p4[axis1] * p3[axis2] +
p1[axis1] * p4[axis2] +
p2[axis1] * p4[axis2] +
p3[axis1] * p4[axis2] +
2.0 * p4[axis1] * p4[axis2] \
)
}
MOMENT_OF_INERTIA_DENOM :: 1.0 / 60.0
PRODUCT_OF_INERTIA_DENOM :: 1.0 / 120.0
x_term := moment_of_inertia_term(p1, p2, p3, p4, 0)
y_term := moment_of_inertia_term(p1, p2, p3, p4, 1)
z_term := moment_of_inertia_term(p1, p2, p3, p4, 2)
// Moments of intertia with respect to XYZ
// Integral(y^2 + z^2)
a := det_j * (y_term + z_term) * MOMENT_OF_INERTIA_DENOM
// Integral(x^2 + z^2)
b := det_j * (x_term + z_term) * MOMENT_OF_INERTIA_DENOM
// Integral(x^2 + y^2)
c := det_j * (x_term + y_term) * MOMENT_OF_INERTIA_DENOM
// Products of inertia
a_ := product_of_inertia_term(p1, p2, p3, p4, axis1 = 1, axis2 = 2) * PRODUCT_OF_INERTIA_DENOM
b_ := product_of_inertia_term(p1, p2, p3, p4, axis1 = 0, axis2 = 2) * PRODUCT_OF_INERTIA_DENOM
c_ := product_of_inertia_term(p1, p2, p3, p4, axis1 = 0, axis2 = 1) * PRODUCT_OF_INERTIA_DENOM
return {a, -b_, -c_, -b_, b, -a_, -c_, -a_, c}
} }
debug_draw_tetrahedron_wires :: proc(tri: [3]rl.Vector3, p: rl.Vector3, color: rl.Color) { debug_draw_tetrahedron_wires :: proc(tri: [3]rl.Vector3, p: rl.Vector3, color: rl.Color) {

View File

@ -557,7 +557,6 @@ draw :: proc() {
halfedge.debug_draw_mesh_wires(car_convex.mesh, rl.RED) halfedge.debug_draw_mesh_wires(car_convex.mesh, rl.RED)
rl.DrawSphereWires(car_convex.mesh.center, 0.0, 8, 8, rl.BLUE) rl.DrawSphereWires(car_convex.mesh.center, 0.0, 8, 8, rl.BLUE)
rl.DrawSphereWires(car_convex.center_of_mass, 0.5, 8, 8, rl.RED) rl.DrawSphereWires(car_convex.center_of_mass, 0.5, 8, 8, rl.RED)
rl.DrawSphereWires(car_convex.center_of_mass_tris, 0.5, 8, 8, rl.GREEN)
box1_convex := collision.box_to_convex(box1, context.temp_allocator) box1_convex := collision.box_to_convex(box1, context.temp_allocator)
box2_convex := collision.box_to_convex(box2, context.temp_allocator) box2_convex := collision.box_to_convex(box2, context.temp_allocator)

View File

@ -3,25 +3,25 @@ package physics
import lg "core:math/linalg" import lg "core:math/linalg"
import rl "vendor:raylib" import rl "vendor:raylib"
inertia_tensor_sphere :: proc(radius: f32) -> (tensor: rl.Vector3) { inertia_tensor_sphere :: proc(radius: f32) -> (tensor: Matrix3) {
tensor = radius * radius * (2.0 / 3.0) tensor = radius * radius * (2.0 / 3.0)
return return
} }
inertia_tensor_box :: proc(size: rl.Vector3) -> (tensor: rl.Vector3) { inertia_tensor_box :: proc(size: rl.Vector3) -> (tensor: Matrix3) {
CONSTANT :: f32(1.0 / 12.0) CONSTANT :: f32(1.0 / 12.0)
tensor.x = size.z * size.z + size.y * size.y tensor[0][0] = size.z * size.z + size.y * size.y
tensor.y = size.x * size.x + size.z * size.z tensor[1][1] = size.x * size.x + size.z * size.z
tensor.z = size.x * size.x + size.y * size.y tensor[2][2] = size.x * size.x + size.y * size.y
tensor *= CONSTANT tensor = tensor * Matrix3(CONSTANT)
return return
} }
inertia_tensor_collision_shape :: proc(shape: Collision_Shape) -> (tensor: rl.Vector3) { inertia_tensor_collision_shape :: proc(shape: Collision_Shape) -> (tensor: Matrix3) {
switch s in shape { switch s in shape {
case Shape_Sphere: case Shape_Sphere:
tensor = inertia_tensor_sphere(s.radius) tensor = inertia_tensor_sphere(s.radius)

View File

@ -1,8 +1,11 @@
package physics package physics
import "core:log"
import lg "core:math/linalg" import lg "core:math/linalg"
import rl "vendor:raylib" import rl "vendor:raylib"
_ :: log
Body_Config_Inertia_Mode :: enum { Body_Config_Inertia_Mode :: enum {
From_Shape, From_Shape,
Explicit, Explicit,
@ -18,7 +21,7 @@ Body_Config :: struct {
mass: f32, mass: f32,
inertia_mode: Body_Config_Inertia_Mode, inertia_mode: Body_Config_Inertia_Mode,
// Unit inertia tensor // Unit inertia tensor
inertia_tensor: rl.Vector3, inertia_tensor: Matrix3,
} }
// TODO: rename to wheel // TODO: rename to wheel
@ -36,19 +39,19 @@ calculate_body_params_from_config :: proc(
config: Body_Config, config: Body_Config,
) -> ( ) -> (
inv_mass: f32, inv_mass: f32,
inv_inertia_tensor: rl.Vector3, inv_inertia_tensor: Matrix3,
) { ) {
inv_mass = config.mass == 0 ? 0 : 1.0 / config.mass inv_mass = config.mass == 0 ? 0 : 1.0 / config.mass
inertia_tensor: rl.Vector3 inertia_tensor: Matrix3
if config.inertia_mode == .Explicit { if config.inertia_mode == .Explicit {
inertia_tensor = config.inertia_tensor inertia_tensor = config.inertia_tensor
} else { } else {
inertia_tensor = inertia_tensor_collision_shape(config.shape) inertia_tensor = inertia_tensor_collision_shape(config.shape)
} }
inertia_tensor *= config.mass inertia_tensor = inertia_tensor * Matrix3(config.mass)
inv_inertia_tensor = lg.all(lg.equal(inertia_tensor, rl.Vector3(0))) ? 0 : 1.0 / inertia_tensor inv_inertia_tensor = lg.determinant(inertia_tensor) == 0 ? 0 : lg.inverse(inertia_tensor)
return return
} }

View File

@ -4,6 +4,8 @@ import rl "vendor:raylib"
MAX_CONTACTS :: 1024 MAX_CONTACTS :: 1024
Matrix3 :: # row_major matrix[3, 3]f32
Scene :: struct { Scene :: struct {
bodies: #soa[dynamic]Body, bodies: #soa[dynamic]Body,
suspension_constraints: #soa[dynamic]Suspension_Constraint, suspension_constraints: #soa[dynamic]Suspension_Constraint,
@ -33,7 +35,7 @@ Body :: struct {
// Mass // Mass
inv_mass: f32, inv_mass: f32,
// Moment of inertia // Moment of inertia
inv_inertia_tensor: rl.Vector3, inv_inertia_tensor: Matrix3,
shape: Collision_Shape, shape: Collision_Shape,
// //
next_plus_one: i32, next_plus_one: i32,

View File

@ -594,7 +594,7 @@ get_body_inverse_mass :: proc(body: Body_Ptr, normal, pos: rl.Vector3) -> f32 {
rn = lg.cross(rn, normal) rn = lg.cross(rn, normal)
rn = lg.quaternion_mul_vector3(inv_q, rn) rn = lg.quaternion_mul_vector3(inv_q, rn)
w := lg.dot(rn * rn, body.inv_inertia_tensor) w := lg.dot(rn, rn * body.inv_inertia_tensor)
w += body.inv_mass w += body.inv_mass
return w return w