370 lines
8.4 KiB
Odin
370 lines
8.4 KiB
Odin
package assets
|
|
|
|
import "core:c"
|
|
import "core:log"
|
|
import "core:math"
|
|
import lg "core:math/linalg"
|
|
import "core:os/os2"
|
|
import "core:strconv"
|
|
import "game:halfedge"
|
|
import "game:physics/bvh"
|
|
import "game:physics/collision"
|
|
import "libs:tracy"
|
|
import rl "vendor:raylib"
|
|
|
|
Loaded_Texture :: struct {
|
|
texture: rl.Texture2D,
|
|
modtime: c.long,
|
|
}
|
|
|
|
Loaded_Model :: struct {
|
|
model: rl.Model,
|
|
modtime: c.long,
|
|
}
|
|
|
|
Loaded_BVH :: struct {
|
|
// AABB of all bvhs
|
|
aabb: bvh.AABB,
|
|
// BVH for each mesh in a model
|
|
bvhs: []bvh.BVH,
|
|
modtime: c.long,
|
|
}
|
|
|
|
Loaded_Convex :: struct {
|
|
mesh: collision.Convex,
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
Asset_Manager :: struct {
|
|
textures: map[cstring]Loaded_Texture,
|
|
models: map[cstring]Loaded_Model,
|
|
bvhs: map[cstring]Loaded_BVH,
|
|
}
|
|
|
|
get_texture :: proc(assetman: ^Asset_Manager, path: cstring) -> rl.Texture2D {
|
|
tracy.Zone()
|
|
|
|
modtime := rl.GetFileModTime(path)
|
|
|
|
existing, ok := assetman.textures[path]
|
|
if ok && existing.modtime == modtime {
|
|
return existing.texture
|
|
}
|
|
|
|
if ok {
|
|
rl.UnloadTexture(existing.texture)
|
|
delete_key(&assetman.textures, path)
|
|
log.infof("deleted texture %s. New textures len: %d", path, len(assetman.textures))
|
|
}
|
|
|
|
loaded := rl.LoadTexture(path)
|
|
if rl.IsTextureValid(loaded) {
|
|
assetman.textures[path] = {
|
|
texture = loaded,
|
|
modtime = modtime,
|
|
}
|
|
return loaded
|
|
} else {
|
|
return rl.Texture2D{}
|
|
}
|
|
}
|
|
|
|
get_model_ex :: proc(
|
|
assetman: ^Asset_Manager,
|
|
path: cstring,
|
|
ref_modtime: c.long = 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: c.long,
|
|
reloaded: bool,
|
|
) {
|
|
tracy.Zone()
|
|
|
|
new_modtime := rl.GetFileModTime(path)
|
|
|
|
existing, ok := assetman.models[path]
|
|
if ok && existing.modtime == new_modtime {
|
|
return existing.model,
|
|
existing.modtime,
|
|
ref_modtime == 0 ? false : existing.modtime != ref_modtime
|
|
}
|
|
|
|
if ok {
|
|
rl.UnloadModel(existing.model)
|
|
delete_key(&assetman.textures, path)
|
|
log.infof("deleted model %s. New models len: %d", path, len(assetman.textures))
|
|
}
|
|
|
|
loaded := rl.LoadModel(path)
|
|
if rl.IsModelValid(loaded) {
|
|
assetman.models[path] = {
|
|
model = loaded,
|
|
modtime = new_modtime,
|
|
}
|
|
return loaded, new_modtime, true
|
|
} else {
|
|
return rl.Model{}, 0, true
|
|
}
|
|
}
|
|
|
|
get_model :: proc(assetman: ^Asset_Manager, path: cstring) -> rl.Model {
|
|
model, _, _ := get_model_ex(assetman, path)
|
|
|
|
return model
|
|
}
|
|
|
|
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 = math.F32_MAX,
|
|
max = -math.F32_MAX,
|
|
}
|
|
|
|
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_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_Convex) {
|
|
bytes, err := os2.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)
|
|
center: rl.Vector3
|
|
|
|
// 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' {
|
|
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 at line %d", ctx.line)
|
|
return
|
|
}
|
|
advance(&ctx, nr) or_break
|
|
|
|
vertex[coord_idx] = coord_val
|
|
coord_idx += 1
|
|
}
|
|
append(&vertices, halfedge.Vertex{pos = vertex, edge = -1})
|
|
center += vertex
|
|
|
|
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' {
|
|
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
|
|
}
|
|
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
|
|
|
|
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.00001,
|
|
"mesh has non planar faces",
|
|
)
|
|
}
|
|
|
|
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 := &vertices[i1]
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
center /= f32(len(vertices))
|
|
|
|
return {mesh = {vertices = vertices[:], edges = edges[:], faces = faces[:], center = center}}
|
|
}
|
|
|
|
shutdown :: proc(assetman: ^Asset_Manager) {
|
|
tracy.Zone()
|
|
|
|
for _, texture in assetman.textures {
|
|
rl.UnloadTexture(texture.texture)
|
|
}
|
|
for _, model in assetman.models {
|
|
rl.UnloadModel(model.model)
|
|
}
|
|
for _, loaded_bvh in assetman.bvhs {
|
|
destroy_loaded_bvh(loaded_bvh)
|
|
}
|
|
delete(assetman.textures)
|
|
delete(assetman.models)
|
|
delete(assetman.bvhs)
|
|
}
|