789 lines
19 KiB
Odin
789 lines
19 KiB
Odin
package assets
|
|
|
|
import "core:log"
|
|
import "core:math"
|
|
import lg "core:math/linalg"
|
|
import "core:strconv"
|
|
import "game:debug"
|
|
import "game:halfedge"
|
|
import "game:physics/bvh"
|
|
import "game:physics/collision"
|
|
import "libs:physfs"
|
|
import rl "libs:raylib"
|
|
import "libs:raylib/rlgl"
|
|
import "libs:tracy"
|
|
|
|
_ :: math
|
|
|
|
Loaded_BVH :: struct {
|
|
// AABB of all bvhs
|
|
aabb: bvh.AABB,
|
|
// BVH for each mesh in a model
|
|
bvhs: []bvh.BVH,
|
|
modtime: physfs.sint64,
|
|
}
|
|
|
|
Loaded_Convex :: struct {
|
|
mesh: collision.Convex,
|
|
center_of_mass: rl.Vector3,
|
|
inertia_tensor: lg.Matrix3f32,
|
|
}
|
|
|
|
Loaded_Curve_2D :: struct {
|
|
points: [][2]f32,
|
|
}
|
|
|
|
destroy_loaded_bvh :: proc(loaded_bvh: Loaded_BVH) {
|
|
tracy.Zone()
|
|
|
|
for &mesh_bvh in loaded_bvh.bvhs {
|
|
bvh.destroy_bvh(&mesh_bvh)
|
|
}
|
|
delete(loaded_bvh.bvhs)
|
|
}
|
|
|
|
Curve_2D :: [][2]f32
|
|
|
|
Asset_Manager :: struct {
|
|
textures: Asset_Cache(rl.Texture2D),
|
|
models: Asset_Cache(rl.Model),
|
|
shaders: Asset_Cache(Loaded_Shader),
|
|
curves_1d: Asset_Cache([]f32),
|
|
curves_2d: Asset_Cache(Curve_2D),
|
|
bvhs: map[cstring]Loaded_BVH,
|
|
curves: map[cstring]Loaded_Curve_2D,
|
|
}
|
|
|
|
Asset_Cache_Entry :: struct($E: typeid) {
|
|
value: E,
|
|
modtime: i64,
|
|
}
|
|
|
|
Asset_Cache_Loader_Payload :: union {
|
|
cstring,
|
|
}
|
|
|
|
Asset_Cache_Loader :: struct($E: typeid) {
|
|
load: proc(path: cstring, payload: Asset_Cache_Loader_Payload) -> (E, bool),
|
|
unload: proc(value: E),
|
|
}
|
|
|
|
Asset_Cache :: struct($E: typeid) {
|
|
cache: map[cstring]Asset_Cache_Entry(E),
|
|
loader: Asset_Cache_Loader(E),
|
|
}
|
|
|
|
|
|
Shader_Location :: enum {
|
|
Ambient,
|
|
LightDir,
|
|
LightColor,
|
|
}
|
|
Shader_Location_Set :: bit_set[Shader_Location]
|
|
Shader_Location_Array :: [Shader_Location]i32
|
|
|
|
SHADER_LOCATION_NAMES := [Shader_Location]cstring {
|
|
.Ambient = "ambient",
|
|
.LightDir = "lightDir",
|
|
.LightColor = "lightColor",
|
|
}
|
|
|
|
Loaded_Shader :: struct {
|
|
shader: rl.Shader,
|
|
location_set: Shader_Location_Set,
|
|
locations: Shader_Location_Array,
|
|
}
|
|
|
|
SHADER_LOADER :: Asset_Cache_Loader(Loaded_Shader) {
|
|
load = proc(path: cstring, payload: Asset_Cache_Loader_Payload) -> (Loaded_Shader, bool) {
|
|
shader := rl.LoadShader(path, payload.(cstring))
|
|
return Loaded_Shader{shader = shader}, rl.IsShaderValid(shader)
|
|
},
|
|
unload = proc(shader: Loaded_Shader) {
|
|
rl.UnloadShader(shader.shader)
|
|
},
|
|
}
|
|
|
|
MODEL_LOADER :: Asset_Cache_Loader(rl.Model) {
|
|
load = proc(path: cstring, payload: Asset_Cache_Loader_Payload) -> (rl.Model, bool) {
|
|
model := rl.LoadModel(path)
|
|
return model, rl.IsModelValid(model)
|
|
},
|
|
unload = proc(model: rl.Model) {
|
|
rl.UnloadModel(model)
|
|
},
|
|
}
|
|
|
|
TEXTURE_LOADER :: Asset_Cache_Loader(rl.Texture2D) {
|
|
load = proc(path: cstring, payload: Asset_Cache_Loader_Payload) -> (rl.Texture2D, bool) {
|
|
texture := rl.LoadTexture(path)
|
|
return texture, rl.IsTextureValid(texture)
|
|
},
|
|
unload = proc(texture: rl.Texture2D) {
|
|
rl.UnloadTexture(texture)
|
|
},
|
|
}
|
|
|
|
CURVE_1D_CSV_LOADER :: Asset_Cache_Loader([]f32) {
|
|
load = proc(path: cstring, payload: Asset_Cache_Loader_Payload) -> ([]f32, bool) {
|
|
data, err := physfs.read_entire_file(string(path), context.temp_allocator)
|
|
if err != nil {
|
|
log.errorf("Failed to read curve: %s, %v", path, err)
|
|
return nil, false
|
|
}
|
|
|
|
values, err2 := parse_csv_1d(data)
|
|
|
|
if err2 != nil {
|
|
log.errorf("Failed to parse curve: %s, %v", path, err2)
|
|
}
|
|
|
|
return values, true
|
|
},
|
|
unload = proc(values: []f32) {
|
|
delete(values)
|
|
},
|
|
}
|
|
|
|
CURVE_2D_CSV_LOADER :: Asset_Cache_Loader(Curve_2D) {
|
|
load = proc(path: cstring, payload: Asset_Cache_Loader_Payload) -> (Curve_2D, bool) {
|
|
data, err := physfs.read_entire_file(string(path), context.temp_allocator)
|
|
if err != nil {
|
|
log.errorf("Failed to read curve: %s, %v", path, err)
|
|
return nil, false
|
|
}
|
|
|
|
curve, err2 := parse_csv_2d(data)
|
|
|
|
if err2 != nil {
|
|
log.errorf("Failed to parse curve: %s, %v", path, err2)
|
|
}
|
|
|
|
return curve.points, true
|
|
},
|
|
unload = proc(curve: Curve_2D) {
|
|
delete(curve)
|
|
},
|
|
}
|
|
|
|
assetman_init :: proc(assetman: ^Asset_Manager) {
|
|
assetman.models = {
|
|
loader = MODEL_LOADER,
|
|
}
|
|
assetman.shaders = {
|
|
loader = SHADER_LOADER,
|
|
}
|
|
assetman.textures = {
|
|
loader = TEXTURE_LOADER,
|
|
}
|
|
assetman.curves_1d = {
|
|
loader = CURVE_1D_CSV_LOADER,
|
|
}
|
|
assetman.curves_2d = {
|
|
loader = CURVE_2D_CSV_LOADER,
|
|
}
|
|
}
|
|
|
|
Asset_Cache_Result :: enum {
|
|
Cached,
|
|
Loaded,
|
|
Reloaded,
|
|
Error,
|
|
}
|
|
|
|
assetcache_fetch_or_load :: proc(
|
|
ac: ^$T/Asset_Cache($E),
|
|
path: cstring,
|
|
payload: Asset_Cache_Loader_Payload = nil,
|
|
) -> (
|
|
value: E,
|
|
modtime: physfs.sint64,
|
|
result: Asset_Cache_Result,
|
|
) {
|
|
tracy.Zone()
|
|
|
|
existing, has_existing := ac.cache[path]
|
|
|
|
if has_existing {
|
|
new_modtime := physfs.getLastModTime(path)
|
|
|
|
if existing.modtime == new_modtime {
|
|
result = .Cached
|
|
return existing.value, new_modtime, result
|
|
} else {
|
|
// Try to load the new version
|
|
|
|
new_value, ok := ac.loader.load(path, payload)
|
|
|
|
if ok {
|
|
result = .Reloaded
|
|
ac.loader.unload(existing.value)
|
|
ac.cache[path] = {
|
|
value = new_value,
|
|
modtime = new_modtime,
|
|
}
|
|
|
|
log.debugf("reloaded asset: %s", path)
|
|
|
|
return new_value, new_modtime, result
|
|
} else {
|
|
log.warnf("failed to reload asset after modification %s", path)
|
|
result = .Cached
|
|
|
|
return existing.value, existing.modtime, result
|
|
}
|
|
}
|
|
} else {
|
|
modtime = physfs.getLastModTime(path)
|
|
ok: bool
|
|
value, ok = ac.loader.load(path, payload)
|
|
|
|
if ok {
|
|
ac.cache[path] = {
|
|
value = value,
|
|
modtime = modtime,
|
|
}
|
|
result = .Loaded
|
|
|
|
log.debugf("loaded asset: %s", path)
|
|
|
|
return value, modtime, result
|
|
} else {
|
|
log.errorf("failed to load asset %s", path)
|
|
result = .Error
|
|
return {}, 0, .Error
|
|
}
|
|
}
|
|
}
|
|
|
|
assetcache_destroy :: proc(ac: ^$T/Asset_Cache($E)) {
|
|
for _, v in ac.cache {
|
|
ac.loader.unload(v.value)
|
|
}
|
|
delete(ac.cache)
|
|
}
|
|
|
|
get_texture :: proc(assetman: ^Asset_Manager, path: cstring) -> rl.Texture2D {
|
|
tracy.Zone()
|
|
|
|
texture, _, _ := assetcache_fetch_or_load(&assetman.textures, path)
|
|
|
|
return texture
|
|
}
|
|
|
|
get_model_ex :: proc(
|
|
assetman: ^Asset_Manager,
|
|
path: cstring,
|
|
ref_modtime: physfs.sint64 = 0, // will check reload status using reference load time. When 0 reloaded will be true only if this call triggered reload
|
|
) -> (
|
|
model: rl.Model,
|
|
modtime: physfs.sint64,
|
|
reloaded: bool,
|
|
) {
|
|
tracy.Zone()
|
|
|
|
result: Asset_Cache_Result
|
|
model, modtime, result = assetcache_fetch_or_load(&assetman.models, path)
|
|
reloaded = result == .Reloaded || ref_modtime != modtime
|
|
|
|
return
|
|
}
|
|
|
|
get_model :: proc(assetman: ^Asset_Manager, path: cstring) -> rl.Model {
|
|
model, _, _ := get_model_ex(assetman, path)
|
|
|
|
return model
|
|
}
|
|
|
|
get_shader :: proc(
|
|
assetman: ^Asset_Manager,
|
|
vs_path: cstring,
|
|
ps_path: cstring,
|
|
location_set: Shader_Location_Set,
|
|
) -> Loaded_Shader {
|
|
loaded_shader, _, result := assetcache_fetch_or_load(&assetman.shaders, vs_path, ps_path)
|
|
|
|
if location_set > loaded_shader.location_set || result == .Loaded || result == .Reloaded {
|
|
loaded_shader.location_set = location_set
|
|
loaded_shader.locations = {}
|
|
|
|
for location in location_set {
|
|
loaded_shader.locations[location] = rl.GetShaderLocation(
|
|
loaded_shader.shader,
|
|
SHADER_LOCATION_NAMES[location],
|
|
)
|
|
}
|
|
}
|
|
|
|
return loaded_shader
|
|
}
|
|
|
|
null_bvhs: []bvh.BVH
|
|
|
|
get_bvh :: proc(assetman: ^Asset_Manager, path: cstring) -> Loaded_BVH {
|
|
tracy.Zone()
|
|
|
|
loaded_bvh, ok := assetman.bvhs[path]
|
|
model, modtime, reloaded := get_model_ex(assetman, path, loaded_bvh.modtime)
|
|
|
|
should_recreate := reloaded || !ok
|
|
|
|
if ok && should_recreate {
|
|
destroy_loaded_bvh(loaded_bvh)
|
|
delete_key(&assetman.bvhs, path)
|
|
}
|
|
|
|
if should_recreate {
|
|
new_bvhs := make([]bvh.BVH, model.meshCount)
|
|
|
|
outer_aabb := bvh.AABB {
|
|
min = max(f32),
|
|
max = min(f32),
|
|
}
|
|
|
|
for i in 0 ..< model.meshCount {
|
|
mesh := model.meshes[i]
|
|
vertices := (cast([^]rl.Vector3)mesh.vertices)[:mesh.vertexCount]
|
|
indices := mesh.indices[:mesh.triangleCount * 3]
|
|
|
|
mesh_bvh := bvh.build_bvh_from_mesh(
|
|
{vertices = vertices, indices = indices},
|
|
context.allocator,
|
|
)
|
|
|
|
root_aabb := mesh_bvh.bvh.nodes[0].aabb
|
|
outer_aabb.min = lg.min(outer_aabb.min, root_aabb.min)
|
|
outer_aabb.max = lg.max(outer_aabb.max, root_aabb.max)
|
|
|
|
new_bvhs[i] = mesh_bvh.bvh
|
|
}
|
|
|
|
assetman.bvhs[path] = Loaded_BVH {
|
|
aabb = outer_aabb,
|
|
bvhs = new_bvhs,
|
|
modtime = modtime,
|
|
}
|
|
}
|
|
|
|
return assetman.bvhs[path]
|
|
}
|
|
|
|
get_curve_1d :: proc(assetman: ^Asset_Manager, path: cstring) -> (curve: []f32) {
|
|
curve, _, _ = assetcache_fetch_or_load(&assetman.curves_1d, path)
|
|
return
|
|
}
|
|
|
|
// Reads a two column comma separated csv file as a curve
|
|
get_curve_2d :: proc(assetman: ^Asset_Manager, path: cstring) -> (curve: Curve_2D) {
|
|
curve, _, _ = assetcache_fetch_or_load(&assetman.curves_2d, path)
|
|
return
|
|
}
|
|
|
|
get_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_Convex) {
|
|
bytes, err := physfs.read_entire_file(string(path), context.temp_allocator)
|
|
if err != nil {
|
|
log.errorf("error reading file %v %s", err)
|
|
return
|
|
}
|
|
|
|
Parse_Ctx :: struct {
|
|
bytes: []byte,
|
|
it: int,
|
|
line: int,
|
|
}
|
|
|
|
advance :: proc(ctx: ^Parse_Ctx, by: int = 1) -> bool {
|
|
ctx.it = min(ctx.it + by, len(ctx.bytes) + 1)
|
|
return ctx.it < len(ctx.bytes)
|
|
}
|
|
|
|
is_whitespace :: proc(b: byte) -> bool {
|
|
return b == ' ' || b == '\t' || b == '\r' || b == '\n'
|
|
}
|
|
|
|
skip_line :: proc(ctx: ^Parse_Ctx) {
|
|
for ctx.it < len(ctx.bytes) && ctx.bytes[ctx.it] != '\n' {
|
|
advance(ctx) or_break
|
|
}
|
|
advance(ctx)
|
|
ctx.line += 1
|
|
}
|
|
|
|
skip_whitespase :: proc(ctx: ^Parse_Ctx) {
|
|
switch ctx.bytes[ctx.it] {
|
|
case ' ', '\t', '\r', '\n':
|
|
if ctx.bytes[ctx.it] == '\n' {
|
|
ctx.line += 1
|
|
}
|
|
advance(ctx) or_break
|
|
case '#':
|
|
skip_line(ctx)
|
|
}
|
|
}
|
|
|
|
Edge :: [2]u16
|
|
edges_map := make_map(map[Edge]halfedge.Edge_Index, context.temp_allocator)
|
|
|
|
edges := make_dynamic_array([dynamic]halfedge.Half_Edge, context.temp_allocator)
|
|
vertices := make_dynamic_array([dynamic]halfedge.Vertex, context.temp_allocator)
|
|
faces := make_dynamic_array([dynamic]halfedge.Face, context.temp_allocator)
|
|
min_pos, max_pos: rl.Vector3 = max(f32), min(f32)
|
|
|
|
// Parse obj file directly into halfedge data structure
|
|
{
|
|
ctx := Parse_Ctx {
|
|
bytes = bytes,
|
|
line = 1,
|
|
}
|
|
|
|
for ctx.it < len(ctx.bytes) {
|
|
skip_whitespase(&ctx)
|
|
switch ctx.bytes[ctx.it] {
|
|
case 'v':
|
|
// vertex
|
|
advance(&ctx) or_break
|
|
|
|
vertex: rl.Vector3
|
|
|
|
coord_idx := 0
|
|
for ctx.bytes[ctx.it] != '\n' && ctx.bytes[ctx.it] != '\r' {
|
|
skip_whitespase(&ctx)
|
|
s := string(ctx.bytes[ctx.it:])
|
|
coord_val, nr, ok := strconv.parse_f32_prefix(s)
|
|
if !ok {
|
|
log.errorf(
|
|
"failed to parse float %v %s at line %d",
|
|
coord_idx,
|
|
ctx.bytes[ctx.it:][:12],
|
|
ctx.line,
|
|
)
|
|
return
|
|
}
|
|
advance(&ctx, nr) or_break
|
|
|
|
vertex[coord_idx] = coord_val
|
|
coord_idx += 1
|
|
}
|
|
append(&vertices, halfedge.Vertex{pos = vertex, edge = -1})
|
|
min_pos = lg.min(vertex, min_pos)
|
|
max_pos = lg.max(vertex, max_pos)
|
|
|
|
if ctx.bytes[ctx.it] == '\r' {
|
|
advance(&ctx)
|
|
}
|
|
advance(&ctx)
|
|
ctx.line += 1
|
|
case 'f':
|
|
advance(&ctx) or_break
|
|
|
|
MAX_FACE_VERTS :: 10
|
|
|
|
indices_buf: [MAX_FACE_VERTS]u16
|
|
index_count := 0
|
|
|
|
for ctx.bytes[ctx.it] != '\n' && ctx.bytes[ctx.it] != '\r' {
|
|
skip_whitespase(&ctx)
|
|
index_f, nr, ok := strconv.parse_f32_prefix(string(ctx.bytes[ctx.it:]))
|
|
if !ok {
|
|
log.errorf("failed to parse index at line %d", ctx.line)
|
|
return
|
|
}
|
|
advance(&ctx, nr) or_break
|
|
index := u16(index_f) - 1
|
|
indices_buf[index_count] = u16(index)
|
|
index_count += 1
|
|
}
|
|
if ctx.bytes[ctx.it] == '\r' {
|
|
advance(&ctx)
|
|
}
|
|
advance(&ctx)
|
|
ctx.line += 1
|
|
|
|
assert(index_count >= 3)
|
|
indices := indices_buf[:index_count]
|
|
|
|
append(&faces, halfedge.Face{})
|
|
face_idx := len(faces) - 1
|
|
face := &faces[face_idx]
|
|
|
|
first_edge_idx := len(edges)
|
|
|
|
face.edge = halfedge.Edge_Index(first_edge_idx)
|
|
|
|
plane: collision.Plane
|
|
{
|
|
i1, i2, i3 := indices[0], indices[1], indices[2]
|
|
v1, v2, v3 := vertices[i1].pos, vertices[i2].pos, vertices[i3].pos
|
|
|
|
plane = collision.plane_from_point_normal(
|
|
v1,
|
|
lg.normalize0(lg.cross(v2 - v1, v3 - v1)),
|
|
)
|
|
}
|
|
face.normal = plane.normal
|
|
|
|
for index in indices[3:] {
|
|
assert(
|
|
abs(collision.signed_distance_plane(vertices[index].pos, plane)) < 0.01,
|
|
"mesh has non planar faces",
|
|
)
|
|
}
|
|
|
|
first_vert_pos := vertices[indices[0]].pos
|
|
|
|
for i in 0 ..< len(indices) {
|
|
edge_idx := halfedge.Edge_Index(first_edge_idx + i)
|
|
prev_edge_relative := i == 0 ? len(indices) - 1 : i - 1
|
|
next_edge_relative := (i + 1) % len(indices)
|
|
i1, i2 := indices[i], indices[next_edge_relative]
|
|
v1, v2 := &vertices[i1], &vertices[i2]
|
|
|
|
assert(
|
|
lg.dot(
|
|
lg.cross(v1.pos - first_vert_pos, v2.pos - first_vert_pos),
|
|
plane.normal,
|
|
) >=
|
|
0,
|
|
"non convex face or non ccw winding",
|
|
)
|
|
|
|
if v1.edge == -1 {
|
|
v1.edge = edge_idx
|
|
}
|
|
|
|
edge := halfedge.Half_Edge {
|
|
origin = halfedge.Vertex_Index(i1),
|
|
face = halfedge.Face_Index(face_idx),
|
|
twin = -1,
|
|
next = halfedge.Edge_Index(first_edge_idx + next_edge_relative),
|
|
prev = halfedge.Edge_Index(first_edge_idx + prev_edge_relative),
|
|
}
|
|
|
|
stable_index := [2]u16{min(i1, i2), max(i1, i2)}
|
|
if stable_index in edges_map {
|
|
edge.twin = edges_map[stable_index]
|
|
twin_edge := &edges[edge.twin]
|
|
assert(twin_edge.twin == -1, "edge has more than two faces attached")
|
|
twin_edge.twin = edge_idx
|
|
} else {
|
|
edges_map[stable_index] = edge_idx
|
|
}
|
|
|
|
append(&edges, edge)
|
|
}
|
|
case:
|
|
skip_line(&ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
log.debugf("num verts: %v", len(vertices))
|
|
|
|
center := (max_pos + min_pos) * 0.5
|
|
extent := (max_pos - min_pos) * 0.5
|
|
|
|
center_of_mass: rl.Vector3
|
|
|
|
mesh := halfedge.Half_Edge_Mesh {
|
|
vertices = vertices[:],
|
|
edges = edges[:],
|
|
faces = faces[:],
|
|
center = center,
|
|
extent = extent,
|
|
}
|
|
|
|
// Center of mass calculation
|
|
total_volume := f32(0.0)
|
|
{
|
|
rlgl.Begin(rlgl.TRIANGLES)
|
|
rlgl.End()
|
|
|
|
rlgl.EnableWireMode()
|
|
defer rlgl.DisableWireMode()
|
|
|
|
tri_idx := 0
|
|
for face_idx in 0 ..< len(faces) {
|
|
face := faces[face_idx]
|
|
// 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 {
|
|
plane := collision.plane_from_point_normal(tri[0], -face.normal)
|
|
|
|
h := max(0, collision.signed_distance_plane(center, plane))
|
|
tri_area :=
|
|
lg.dot(lg.cross(tri[1] - tri[0], tri[2] - tri[0]), face.normal) * 0.5
|
|
tetra_volume := 1.0 / 3.0 * tri_area * h
|
|
total_volume += tetra_volume
|
|
|
|
tetra_centroid := (tri[0] + tri[1] + tri[2] + center) * 0.25
|
|
center_of_mass += tetra_volume * tetra_centroid
|
|
|
|
tri_idx += 1
|
|
}
|
|
|
|
i += 1
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(total_volume > 0, "degenerate convex hull")
|
|
center_of_mass /= total_volume
|
|
|
|
inertia_tensor: lg.Matrix3f32
|
|
// Find inertia tensor
|
|
{
|
|
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
|
|
}
|
|
}
|
|
}
|
|
inertia_tensor = inertia_tensor * lg.Matrix3f32(1.0 / total_volume)
|
|
|
|
return {mesh = mesh, center_of_mass = center_of_mass, inertia_tensor = inertia_tensor}
|
|
}
|
|
|
|
// TODO: move convex stuff out of assets.odin
|
|
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) {
|
|
rlgl.Begin(rlgl.LINES)
|
|
defer rlgl.End()
|
|
|
|
debug.rlgl_color(color)
|
|
|
|
debug.rlgl_vertex3v2(tri[0], tri[1])
|
|
debug.rlgl_vertex3v2(tri[1], tri[2])
|
|
debug.rlgl_vertex3v2(tri[2], tri[0])
|
|
debug.rlgl_vertex3v2(tri[0], p)
|
|
debug.rlgl_vertex3v2(tri[1], p)
|
|
debug.rlgl_vertex3v2(tri[2], p)
|
|
}
|
|
|
|
shutdown :: proc(assetman: ^Asset_Manager) {
|
|
tracy.Zone()
|
|
|
|
assetcache_destroy(&assetman.textures)
|
|
assetcache_destroy(&assetman.models)
|
|
assetcache_destroy(&assetman.shaders)
|
|
assetcache_destroy(&assetman.curves_1d)
|
|
assetcache_destroy(&assetman.curves_2d)
|
|
|
|
for _, loaded_bvh in assetman.bvhs {
|
|
destroy_loaded_bvh(loaded_bvh)
|
|
}
|
|
delete(assetman.bvhs)
|
|
}
|