package assets import "core:c" import "core:log" import "core:math" import lg "core:math/linalg" import "game:physics/bvh" 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, } 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 log.debugf( "model %v\nmodtime %v\nreloaded %v\nok %v\nshould_recreate %v\nref_modtime %v", model, modtime, reloaded, ok, should_recreate, loaded_bvh.modtime, ) 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] } 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) }