diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..94840b3 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +layout python3 diff --git a/.gitignore b/.gitignore index c1e6587..42f7666 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ game_web/ .venv build/ bin/ +.direnv/ diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..fed374d --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +python 3.11.13 diff --git a/assets/blender/ice_cream_truck_blend/Split.glb b/assets/blender/ice_cream_truck_blend/Split.glb new file mode 100644 index 0000000..d004eb9 --- /dev/null +++ b/assets/blender/ice_cream_truck_blend/Split.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4911fe796efa9912ef7ea7c3e53097352878478a57c7da90966461c7df980918 +size 605444 diff --git a/assets/blender/test_level_blend/NurbsPath.glb b/assets/blender/test_level_blend/NurbsPath.glb new file mode 100644 index 0000000..2c3d4c8 --- /dev/null +++ b/assets/blender/test_level_blend/NurbsPath.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2df3c576871ee4ab3327e70fa2dff12cf6f0ae7a715c314f87735781e65a8897 +size 19608 diff --git a/assets/blender/test_level_blend/Plane.glb b/assets/blender/test_level_blend/Plane.glb new file mode 100644 index 0000000..a9d5130 --- /dev/null +++ b/assets/blender/test_level_blend/Plane.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:631a1098a36d2ca2e4f859ecf329df96c8c5990ab8c7a0860ffbe2b00f814711 +size 1088 diff --git a/assets/blender/test_level_blend/Scene.scn b/assets/blender/test_level_blend/Scene.scn new file mode 100644 index 0000000..fda5fe8 --- /dev/null +++ b/assets/blender/test_level_blend/Scene.scn @@ -0,0 +1,50 @@ +(inst + :model "assets/blender/testblend_blend/Bakery.glb" + :pos (0.000000 12.000000 0.000000) + :rot (0.000000 0.000000 0.000000 1.000000) + :scale (1.000000 1.000000 1.000000)) +(inst + :model "assets/blender/test_level_blend/Plane.glb" + :pos (0.000000 0.000000 0.000000) + :rot (0.000000 0.000000 0.000000 1.000000) + :scale (22.469545 22.469545 22.469545)) +(inst + :model "assets/blender/testblend_blend/Fire_Station.glb" + :pos (9.000000 13.000000 0.000000) + :rot (0.000000 0.000000 0.000000 1.000000) + :scale (1.000000 1.000000 1.000000)) +(inst + :model "assets/blender/testblend_blend/Gas_Station_Shop.glb" + :pos (-8.000000 11.000000 0.000000) + :rot (0.000000 0.000000 0.000000 1.000000) + :scale (1.000000 1.000000 1.000000)) +(inst + :model "assets/blender/testblend_blend/Gas_Station_Shop.glb" + :pos (-16.000000 11.000000 0.000000) + :rot (0.000000 0.000000 0.000000 1.000000) + :scale (1.000000 1.000000 1.000000)) +(inst + :model "assets/blender/testblend_blend/Gas_Station_Shop.glb" + :pos (-15.559284 0.259853 1.609015) + :rot (0.000000 0.000000 0.000000 1.000000) + :scale (1.000000 1.000000 1.000000)) +(inst + :model "assets/blender/testblend_blend/Green_House.glb" + :pos (14.000000 -7.000000 0.000000) + :rot (0.000000 0.000000 0.000000 1.000000) + :scale (1.000000 1.000000 1.000000)) +(inst + :model "assets/blender/testblend_blend/Hotel.glb" + :pos (8.000000 -18.000000 0.000000) + :rot (0.000000 0.000000 0.000000 1.000000) + :scale (1.000000 1.000000 1.000000)) +(inst + :model "assets/blender/test_level_blend/NurbsPath.glb" + :pos (0.000000 0.000000 0.000000) + :rot (0.000000 0.000000 0.000000 1.000000) + :scale (1.000000 1.000000 1.000000)) +(inst + :model "assets/blender/ice_cream_truck_blend/Split.glb" + :pos (-1.000000 3.000000 0.000000) + :rot (0.000000 0.000000 0.000000 1.000000) + :scale (1.000000 1.000000 1.000000)) diff --git a/assets/blender/testblend_blend/Bakery.glb b/assets/blender/testblend_blend/Bakery.glb new file mode 100644 index 0000000..0b12e88 --- /dev/null +++ b/assets/blender/testblend_blend/Bakery.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f96d7e9484368541f5d0f5eb66444273f314e9ea57e9544999ce7a2d3cb90c1b +size 188908 diff --git a/assets/blender/testblend_blend/Fire_Station.glb b/assets/blender/testblend_blend/Fire_Station.glb new file mode 100644 index 0000000..d24ff01 --- /dev/null +++ b/assets/blender/testblend_blend/Fire_Station.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a506c5b05f6be06f6a4cb3092b65623ac805b9fc6c13d2295605b0730edc6034 +size 381548 diff --git a/assets/blender/testblend_blend/Gas_Station_Shop.glb b/assets/blender/testblend_blend/Gas_Station_Shop.glb new file mode 100644 index 0000000..be4c8a2 --- /dev/null +++ b/assets/blender/testblend_blend/Gas_Station_Shop.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e893c43927628cb56fc5d9d6d344a062d46eba8d8c9ef716e0acf99a09c36e4 +size 157404 diff --git a/assets/blender/testblend_blend/Green_House.glb b/assets/blender/testblend_blend/Green_House.glb new file mode 100644 index 0000000..c5e8c04 --- /dev/null +++ b/assets/blender/testblend_blend/Green_House.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97ff0340948c89b346826f2571c3206335a895eb2a222eb94ccdc929fdcda5bf +size 83088 diff --git a/assets/blender/testblend_blend/Hotel.glb b/assets/blender/testblend_blend/Hotel.glb new file mode 100644 index 0000000..c73baa5 --- /dev/null +++ b/assets/blender/testblend_blend/Hotel.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea2eda2ea37765ff154e9ee33f9bde7afe4e671bb7a74088f1087122e24db897 +size 4459408 diff --git a/blender/batch_export.py b/blender/batch_export.py new file mode 100644 index 0000000..d5686af --- /dev/null +++ b/blender/batch_export.py @@ -0,0 +1,87 @@ +import bpy +import os +import pathlib + +# Ensure blend file is saved +blend_filepath = bpy.data.filepath +if not blend_filepath: + raise Exception("Blend file is not saved") + +assert bpy.context.scene is not None + +def clean_blend_path(blend_path: str) -> str: + return os.path.join(os.path.dirname(blend_path), bpy.path.clean_name(os.path.basename(blend_path))) + +# Directory setup +abs_filepath = bpy.path.abspath(blend_filepath) +src_assets_path = os.path.dirname(abs_filepath) +project_path = os.path.dirname(src_assets_path) +assets_path = os.path.join(project_path, "assets") +rel_blend_path = os.path.relpath(abs_filepath, src_assets_path) +clean_rel_path = clean_blend_path(rel_blend_path) +scene_name = bpy.context.scene.name +instance_output_path = os.path.join(assets_path, "blender", clean_rel_path, f"{scene_name}.scn") + +# Utility: returns path to store the glb +def get_model_export_path(obj: bpy.types.Object) -> str: + if obj.instance_type == 'COLLECTION' and obj.instance_collection: + lib_path = obj.instance_collection.library.filepath + collection_name = bpy.path.clean_name(obj.instance_collection.name) + rel_lib_path = os.path.relpath(bpy.path.abspath(lib_path), src_assets_path) + rel_lib_path = clean_blend_path(rel_lib_path) + return os.path.join("blender", rel_lib_path, f"{collection_name}.glb") + else: + obj_name = bpy.path.clean_name(obj.name) + return os.path.join("blender", clean_rel_path, f"{obj_name}.glb") + +# Utility: writes an object to glb +def export_object_as_glb(obj: bpy.types.Object, export_path: str): + full_export_path = os.path.join(assets_path, export_path) + pathlib.Path(os.path.dirname(full_export_path)).mkdir(parents=True, exist_ok=True) + + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(True) + + assert bpy.context.view_layer is not None + bpy.context.view_layer.objects.active = obj + + bpy.ops.export_scene.gltf(filepath=full_export_path, use_selection=True, export_apply=True) + print(f"Exported {obj.name} -> {export_path}") + +# Collect all visible, non-hidden objects in the scene +visible_objects = [ + obj for obj in bpy.context.scene.objects + if obj.visible_get() +] + +exported = set() +instances = [] + +for obj in visible_objects: + model_path = get_model_export_path(obj) + + if model_path not in exported: + export_object_as_glb(obj, model_path) + exported.add(model_path) + + loc = obj.location + rot = obj.rotation_quaternion + scale = obj.scale + instance_sexpr = ( + '(inst' + f'\n\t:model "assets/{model_path}"' + f'\n\t:pos ({loc.x:.6f} {loc.y:.6f} {loc.z:.6f}) ' + f'\n\t:rot ({rot.x:.6f} {rot.y:.6f} {rot.z:.6f} {rot.w:.6f}) ' + f'\n\t:scale ({scale.x:.6f} {scale.y:.6f} {scale.z:.6f}))' + ) + instances.append(instance_sexpr) + +# Save instances to scene file +instance_output_abs = os.path.join(project_path, instance_output_path) +pathlib.Path(os.path.dirname(instance_output_abs)).mkdir(parents=True, exist_ok=True) + +with open(instance_output_abs, "w", encoding="utf-8") as f: + for line in instances: + f.write(line + "\n") + +print(f"Written instances to {instance_output_abs}") diff --git a/game/assets/assets.odin b/game/assets/assets.odin index f35854e..a579261 100644 --- a/game/assets/assets.odin +++ b/game/assets/assets.odin @@ -15,12 +15,25 @@ import "libs:tracy" _ :: math +@(private = "file") +g_assetman_instance: ^Asset_Manager + +init :: proc(assetman: ^Asset_Manager) { + g_assetman_instance = assetman +} + +manager :: #force_inline proc() -> ^Asset_Manager { + return g_assetman_instance +} + Loaded_BVH :: struct { // AABB of all bvhs - aabb: bvh.AABB, + aabb: bvh.AABB, // BVH for each mesh in a model - bvhs: []bvh.BVH, - modtime: physfs.sint64, + bvh: bvh.BVH, + vertices: []rl.Vector3, + indices: []u16, + modtime: physfs.sint64, } Loaded_Convex :: struct { @@ -34,13 +47,12 @@ Loaded_Curve_2D :: struct { points: [][2]f32, } -destroy_loaded_bvh :: proc(loaded_bvh: Loaded_BVH) { +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) + bvh.destroy_bvh(&loaded_bvh.bvh) + delete(loaded_bvh.vertices) + delete(loaded_bvh.indices) } Curve_2D :: [][2]f32 @@ -361,7 +373,7 @@ get_music :: proc(assetman: ^Asset_Manager, path: cstring) -> rl.Music { null_bvhs: []bvh.BVH -get_bvh :: proc(assetman: ^Asset_Manager, path: cstring) -> Loaded_BVH { +get_bvh :: proc(assetman: ^Asset_Manager, path: cstring) -> (Loaded_BVH, bool) { tracy.Zone() loaded_bvh, ok := assetman.bvhs[path] @@ -370,43 +382,57 @@ get_bvh :: proc(assetman: ^Asset_Manager, path: cstring) -> Loaded_BVH { should_recreate := reloaded || !ok if ok && should_recreate { - destroy_loaded_bvh(loaded_bvh) + 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), - } + vert_count := 0 + indices_count := 0 for i in 0 ..< model.meshCount { mesh := model.meshes[i] - vertices := (cast([^]rl.Vector3)mesh.vertices)[:mesh.vertexCount] - indices := mesh.indices[:mesh.triangleCount * 3] + vert_count += int(mesh.vertexCount) + indices_count += int(mesh.triangleCount * 3) + } + vertices := make([]bvh.Vec3, vert_count) + indices := make([]u16, indices_count) - mesh_bvh := bvh.build_bvh_from_mesh( - {vertices = vertices, indices = indices}, - context.allocator, - ) + vert_count = 0 + indices_count = 0 + for i in 0 ..< model.meshCount { + mesh := model.meshes[i] + mesh_vertices := (cast([^]rl.Vector3)mesh.vertices)[:mesh.vertexCount] + mesh_indices := mesh.indices[:mesh.triangleCount * 3] - 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) + copy(vertices[vert_count:], mesh_vertices) - new_bvhs[i] = mesh_bvh.bvh + for j in 0 ..< len(mesh_indices) { + indices[indices_count + j] = u16(vert_count) + mesh_indices[j] + } + + vert_count += len(mesh_vertices) + indices_count += len(mesh_indices) } + + mesh_bvh := bvh.build_bvh_from_mesh( + {vertices = vertices, indices = indices}, + context.allocator, + ) + + root_aabb := mesh_bvh.bvh.nodes[0].aabb + assetman.bvhs[path] = Loaded_BVH { - aabb = outer_aabb, - bvhs = new_bvhs, - modtime = modtime, + aabb = root_aabb, + bvh = mesh_bvh.bvh, + vertices = vertices, + indices = indices, + modtime = modtime, } } - return assetman.bvhs[path] + return assetman.bvhs[path], should_recreate } get_curve_1d :: proc(assetman: ^Asset_Manager, path: cstring) -> (curve: []f32) { @@ -827,8 +853,8 @@ shutdown :: proc(assetman: ^Asset_Manager) { assetcache_destroy(&assetman.curves_1d) assetcache_destroy(&assetman.curves_2d) - for _, loaded_bvh in assetman.bvhs { - destroy_loaded_bvh(loaded_bvh) + for _, &loaded_bvh in assetman.bvhs { + destroy_loaded_bvh(&loaded_bvh) } delete(assetman.bvhs) } diff --git a/game/game.odin b/game/game.odin index cf39c4e..bf0d083 100644 --- a/game/game.odin +++ b/game/game.odin @@ -22,7 +22,6 @@ import "core:log" import "core:math" import "core:math/linalg" import "game:physics" -import "game:physics/bvh" import "game:render" import rl "libs:raylib" import "libs:raylib/rlgl" @@ -59,6 +58,9 @@ World :: struct { engine_handle: physics.Engine_Handle, debug_state: Debug_Draw_State, } +world_init :: proc(world: ^World) { + physics.scene_init(&world.physics_scene, &g_mem.assetman) +} copy_world :: proc(dst, src: ^World) { copy_track(&dst.track, &src.track) physics.copy_physics_scene(&dst.physics_scene, &src.physics_scene) @@ -71,7 +73,7 @@ copy_world :: proc(dst, src: ^World) { } destroy_world :: proc(world: ^World) { destroy_track(&world.track) - physics.destroy_physics_scene(&world.physics_scene) + physics.scene_destroy(&world.physics_scene) } Runtime_World :: struct { @@ -91,6 +93,7 @@ Runtime_World :: struct { runtime_world_init :: proc(runtime_world: ^Runtime_World, num_snapshots: int = 2) { runtime_world.world_snapshots = make([]World, num_snapshots) world := runtime_world_current_world(runtime_world) + physics.scene_init(&world.physics_scene, &g_mem.assetman) world.debug_state.show_menu = true world.debug_state.draw_physics_scene = true } @@ -125,7 +128,6 @@ Game_Memory :: struct { name_container: name.Container, assetman: assets.Asset_Manager, runtime_world: Runtime_World, - fluid_scene: Fluid_Scene, es: Editor_State, ui_context: ui.Context, default_font: rl.Font, @@ -169,6 +171,8 @@ world_stack_init :: proc(stack: ^World_Stack, num_snapshots: int, allocator := c assert(num_snapshots > 0) stack.worlds = make([]World, num_snapshots, allocator) world_stack_push(stack) + + world_init(world_stack_current(stack)) } world_stack_destroy :: proc(stack: ^World_Stack, allocator := context.allocator) { @@ -695,6 +699,20 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) { wheel.turn_angle = TURN_ANGLE * turn_vel_correction * turn_input } + { + physics.immediate_level_geom( + &world.physics_scene, + u32(name.from_string("level_geom_from_asset")), + { + position = physics.Vec3{5, 0, 0}, + rotation = linalg.QUATERNIONF32_IDENTITY, + source = physics.Level_Geometry_Asset( + "assets/blender/test_level_blend/NurbsPath.glb", + ), + }, + ) + } + { level_model := assets.get_model(&g_mem.assetman, "assets/testblend.glb") @@ -712,8 +730,10 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) { { position = pos, rotation = rotation, - vertices = (cast([^]rl.Vector3)mesh.vertices)[:mesh.vertexCount], - indices = mesh.indices[:mesh.triangleCount * 3], + source = physics.Level_Geometry_Mesh { + vertices = (cast([^]rl.Vector3)mesh.vertices)[:mesh.vertexCount], + indices = mesh.indices[:mesh.triangleCount * 3], + }, }, ) } @@ -734,8 +754,10 @@ update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) { #hash("track", "fnv32a"), { rotation = linalg.QUATERNIONF32_IDENTITY, - vertices = track_verts, - indices = track_inds, + source = physics.Level_Geometry_Mesh { + vertices = track_verts, + indices = track_inds, + }, }, ) } @@ -973,33 +995,33 @@ update :: proc() { dt := rl.GetFrameTime() // Debug BVH traversal - mesh_bvh := assets.get_bvh(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb") + // mesh_bvh := assets.get_bvh(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb") - if rl.IsKeyDown(.LEFT_SHIFT) { - if g_mem.preview_bvh >= 0 && g_mem.preview_bvh < len(mesh_bvh.bvhs) { - b := mesh_bvh.bvhs[g_mem.preview_bvh] - node := &b.nodes[g_mem.preview_node] + // if rl.IsKeyDown(.LEFT_SHIFT) { + // if g_mem.preview_bvh >= 0 && g_mem.preview_bvh < len(mesh_bvh.bvhs) { + // b := mesh_bvh.bvhs[g_mem.preview_bvh] + // node := &b.nodes[g_mem.preview_node] - if !bvh.is_leaf_node(node^) { - if rl.IsKeyPressed(.LEFT_BRACKET) { - g_mem.preview_node = int(node.child_or_prim_start) - } else if rl.IsKeyPressed(.RIGHT_BRACKET) { - g_mem.preview_node = int(node.child_or_prim_start + 1) - } else if rl.IsKeyPressed(.P) { - g_mem.preview_node = 0 - } - } - } - } else { - if rl.IsKeyPressed(.LEFT_BRACKET) { - g_mem.preview_bvh -= 1 - g_mem.preview_node = 0 - } - if rl.IsKeyPressed(.RIGHT_BRACKET) { - g_mem.preview_bvh += 1 - g_mem.preview_node = 0 - } - } + // if !bvh.is_leaf_node(node^) { + // if rl.IsKeyPressed(.LEFT_BRACKET) { + // g_mem.preview_node = int(node.child_or_prim_start) + // } else if rl.IsKeyPressed(.RIGHT_BRACKET) { + // g_mem.preview_node = int(node.child_or_prim_start + 1) + // } else if rl.IsKeyPressed(.P) { + // g_mem.preview_node = 0 + // } + // } + // } + // } else { + // if rl.IsKeyPressed(.LEFT_BRACKET) { + // g_mem.preview_bvh -= 1 + // g_mem.preview_node = 0 + // } + // if rl.IsKeyPressed(.RIGHT_BRACKET) { + // g_mem.preview_bvh += 1 + // g_mem.preview_node = 0 + // } + // } if rl.IsKeyPressed(.SPACE) { g_mem.physics_pause = !g_mem.physics_pause @@ -1429,9 +1451,9 @@ game_init :: proc() { name.init(&g_mem.name_container) name.setup_global_container(&g_mem.name_container) init_physifs_raylib_callbacks() - assets.assetman_init(&g_mem.assetman) - fluid_scene_init(&g_mem.fluid_scene) + assets.assetman_init(&g_mem.assetman) + assets.init(&g_mem.assetman) editor_state_init(&g_mem.es, 100) runtime_world_init(&g_mem.runtime_world, DEV_BUILD ? 100 : 2) @@ -1454,7 +1476,6 @@ game_shutdown :: proc() { editor_state_destroy(&g_mem.es) delete(g_mem.es.point_selection) runtime_world_destroy(&g_mem.runtime_world) - fluid_scene_destroy(&g_mem.fluid_scene) free(g_mem) } diff --git a/game/physics/helpers.odin b/game/physics/helpers.odin index dd6ec5b..3f4c26e 100644 --- a/game/physics/helpers.odin +++ b/game/physics/helpers.odin @@ -1,5 +1,6 @@ package physics +import "bvh" import "collision" import "core:log" import "core:math" @@ -299,13 +300,14 @@ body_get_convex_shapes_world :: proc( level_geom_get_convex_shape :: proc( sim_state: ^Sim_State, - level_geom: Level_Geom_Ptr, + level_geom_handle: Level_Geom_Handle, tri_idx: int, allocator := context.temp_allocator, ) -> ( mesh: collision.Convex, ) { - vertices, indices := get_level_geom_data(sim_state, level_geom.geometry) + level_geom := get_level_geom(sim_state, level_geom_handle) + vertices, indices := get_level_geom_data(sim_state, level_geom_handle) return collision.double_sided_triangle_to_convex( get_transformed_triangle(vertices, indices, tri_idx, level_geom.x, level_geom.q), allocator, @@ -396,3 +398,8 @@ rpm_to_angular_velocity :: proc(rpm: f32) -> f32 { angular_velocity_to_rpm :: proc(w: f32) -> f32 { return (w / (2.0 * math.PI)) * 60.0 } + +// TODO: use one format everywhere... +bvh_aabb_to_phys_aabb :: proc(aabb: bvh.AABB) -> AABB { + return AABB{center = (aabb.max + aabb.min) * 0.5, extent = (aabb.max - aabb.min) * 0.5} +} diff --git a/game/physics/scene.odin b/game/physics/scene.odin index 1a29e17..0c11c86 100644 --- a/game/physics/scene.odin +++ b/game/physics/scene.odin @@ -4,6 +4,8 @@ import "bvh" import "collision" import "common:name" import lg "core:math/linalg" +import "core:strings" +import "game:assets" import "game:container/spanpool" import "libs:tracy" @@ -78,6 +80,7 @@ contact_container_copy :: proc(dst: ^Contact_Container, src: Contact_Container) } Sim_State :: struct { + scene: ^Scene, bodies: #soa[dynamic]Body, suspension_constraints: #soa[dynamic]Suspension_Constraint, engines: [dynamic]Engine, @@ -120,6 +123,7 @@ Sim_State :: struct { } Scene :: struct { + assetman: ^assets.Asset_Manager, simulation_states: [2]Sim_State, simulation_state_index: i32, solver_state: Solver_State, @@ -130,6 +134,7 @@ copy_sim_state :: proc(dst: ^Sim_State, src: ^Sim_State) { tracy.Zone() convex_container_reconcile(&src.convex_container) + dst.scene = src.scene dst.num_bodies = src.num_bodies dst.first_free_body_plus_one = src.first_free_body_plus_one dst.first_free_suspension_constraint_plus_one = src.first_free_suspension_constraint_plus_one @@ -168,12 +173,12 @@ copy_sim_state :: proc(dst: ^Sim_State, src: ^Sim_State) { copy_physics_scene :: proc(dst, src: ^Scene) { tracy.Zone() + dst.assetman = src.assetman dst.simulation_state_index = src.simulation_state_index src_sim_state := get_sim_state(src) dst_sim_state := get_sim_state(dst) copy_sim_state(dst_sim_state, src_sim_state) - copy_solver_state(&dst.solver_state, &src.solver_state) } @@ -391,11 +396,24 @@ BLAS_Handle :: struct { primitives: spanpool.Handle, } +Level_Geom_Source_Local :: struct { + geometry: Geometry_Handle, + blas: BLAS_Handle, +} + +Level_Geom_Source_Asset :: struct { + assetpath: name.Name, +} + +Level_Geom_Source :: union #no_nil { + Level_Geom_Source_Local, + Level_Geom_Source_Asset, +} + Level_Geom :: struct { alive: bool, aabb: AABB, - geometry: Geometry_Handle, - blas: BLAS_Handle, + source: Level_Geom_Source, x: Vec3, q: Quat, next_plus_one: i32, @@ -584,11 +602,23 @@ Engine_Config :: struct { axle: Drive_Axle_Config, } +Level_Geometry_Asset :: distinct string + +Level_Geometry_Mesh :: struct { + vertices: []Vec3, + indices: []u16, +} + +// Either runtime mesh or asset reference which will be fetched from assetmanager +Level_Geometry_Source :: union #no_nil { + Level_Geometry_Mesh, + Level_Geometry_Asset, +} + Level_Geom_Config :: struct { position: Vec3, rotation: Quat, - vertices: []Vec3, - indices: []u16, + source: Level_Geometry_Source, } calculate_body_params_from_config :: proc( @@ -919,20 +949,50 @@ get_level_geom :: proc(sim_state: ^Sim_State, handle: Level_Geom_Handle) -> Leve get_level_geom_data :: proc( sim_state: ^Sim_State, - handle: Geometry_Handle, + handle: Level_Geom_Handle, ) -> ( vertices: []Vec3, indices: []u16, ) { - vertices = spanpool.resolve_slice(&sim_state.geometry_vertices_pool, handle.vertices) - indices = spanpool.resolve_slice(&sim_state.geometry_indices_pool, handle.indices) + level_geom := get_level_geom(sim_state, handle) + + switch s in level_geom.source { + case Level_Geom_Source_Local: + vertices = spanpool.resolve_slice(&sim_state.geometry_vertices_pool, s.geometry.vertices) + indices = spanpool.resolve_slice(&sim_state.geometry_indices_pool, s.geometry.indices) + case Level_Geom_Source_Asset: + loaded_bvh, reloaded := assets.get_bvh( + sim_state.scene.assetman, + strings.unsafe_string_to_cstring(name.to_string(s.assetpath)), + ) + if reloaded { + level_geom.aabb = bvh_aabb_to_phys_aabb(loaded_bvh.aabb) + } + vertices = loaded_bvh.vertices + indices = loaded_bvh.indices + } + return } -get_level_geom_blas :: proc(sim_state: ^Sim_State, handle: BLAS_Handle) -> (bvh: bvh.BVH) { - bvh.nodes = spanpool.resolve_slice(&sim_state.blas_nodes_pool, handle.nodes) - bvh.primitives = spanpool.resolve_slice(&sim_state.blas_primitives_pool, handle.primitives) - bvh.nodes_used = i32(len(bvh.nodes)) +get_level_geom_blas :: proc(sim_state: ^Sim_State, handle: Level_Geom_Handle) -> (bvh: bvh.BVH) { + level_geom := get_level_geom(sim_state, handle) + switch s in level_geom.source { + case Level_Geom_Source_Local: + bvh.nodes = spanpool.resolve_slice(&sim_state.blas_nodes_pool, s.blas.nodes) + bvh.primitives = spanpool.resolve_slice(&sim_state.blas_primitives_pool, s.blas.primitives) + bvh.nodes_used = i32(len(bvh.nodes)) + case Level_Geom_Source_Asset: + loaded_bvh, reloaded := assets.get_bvh( + sim_state.scene.assetman, + strings.unsafe_string_to_cstring(name.to_string(s.assetpath)), + ) + if reloaded { + level_geom.aabb = bvh_aabb_to_phys_aabb(loaded_bvh.aabb) + } + + bvh = loaded_bvh.bvh + } return } @@ -945,53 +1005,75 @@ update_level_geom_from_config :: proc( level_geom.q = config.rotation // TODO: figure out if asset changed and rebuild only then + + } add_level_geom :: proc(sim_state: ^Sim_State, config: Level_Geom_Config) -> Level_Geom_Handle { - assert(len(config.vertices) > 0) - assert(len(config.vertices) <= int(max(u16))) - sim_state.num_level_geoms += 1 level_geom: Level_Geom level_geom.alive = true update_level_geom_from_config(sim_state, &level_geom, config) - blas := - bvh.build_bvh_from_mesh(bvh.Mesh{vertices = config.vertices, indices = config.indices}, context.temp_allocator).bvh + switch s in config.source { + case Level_Geometry_Mesh: + assert(len(s.vertices) > 0) + assert(len(s.vertices) <= int(max(u16))) + blas := + bvh.build_bvh_from_mesh(bvh.Mesh{vertices = s.vertices, indices = s.indices}, context.temp_allocator).bvh - level_geom.blas.nodes = spanpool.allocate_elems( - &sim_state.blas_nodes_pool, - ..blas.nodes[:blas.nodes_used], - ) - level_geom.blas.primitives = spanpool.allocate_elems( - &sim_state.blas_primitives_pool, - ..blas.primitives, - ) + source: Level_Geom_Source_Local + source.blas.nodes = spanpool.allocate_elems( + &sim_state.blas_nodes_pool, + ..blas.nodes[:blas.nodes_used], + ) + source.blas.primitives = spanpool.allocate_elems( + &sim_state.blas_primitives_pool, + ..blas.primitives, + ) - aabb_min, aabb_max: Vec3 = max(f32), min(f32) - for v in config.vertices { - aabb_min = lg.min(aabb_min, v) - aabb_max = lg.max(aabb_max, v) + aabb_min, aabb_max: Vec3 = max(f32), min(f32) + for v in s.vertices { + aabb_min = lg.min(aabb_min, v) + aabb_max = lg.max(aabb_max, v) + } + + level_geom.aabb.center = (aabb_max + aabb_min) * 0.5 + level_geom.aabb.extent = (aabb_max - aabb_min) * 0.5 + + // if spanpool.is_handle_valid( + // &sim_state.geometry_vertices_pool, + // level_geom.geometry.vertices, + // ) { + // spanpool.free(&sim_state.geometry_vertices_pool, level_geom.geometry.vertices) + // spanpool.free(&sim_state.geometry_indices_pool, level_geom.geometry.indices) + // } + + source.geometry.vertices = spanpool.allocate_elems( + &sim_state.geometry_vertices_pool, + ..s.vertices, + ) + source.geometry.indices = spanpool.allocate_elems( + &sim_state.geometry_indices_pool, + ..s.indices, + ) + level_geom.source = source + case Level_Geometry_Asset: + level_geom.source = Level_Geom_Source_Asset { + assetpath = name.from_string(string(s)), + } + + bvh, _ := assets.get_bvh( + sim_state.scene.assetman, + strings.unsafe_string_to_cstring(string(s)), + ) + level_geom.aabb = AABB { + center = (bvh.aabb.max + bvh.aabb.min) * 0.5, + extent = (bvh.aabb.max - bvh.aabb.min) * 0.5, + } } - level_geom.aabb.center = (aabb_max + aabb_min) * 0.5 - level_geom.aabb.extent = (aabb_max - aabb_min) * 0.5 - - if spanpool.is_handle_valid(&sim_state.geometry_vertices_pool, level_geom.geometry.vertices) { - spanpool.free(&sim_state.geometry_vertices_pool, level_geom.geometry.vertices) - spanpool.free(&sim_state.geometry_indices_pool, level_geom.geometry.indices) - } - - level_geom.geometry.vertices = spanpool.allocate_elems( - &sim_state.geometry_vertices_pool, - ..config.vertices, - ) - level_geom.geometry.indices = spanpool.allocate_elems( - &sim_state.geometry_indices_pool, - ..config.indices, - ) - if sim_state.first_free_level_geom_plus_one > 0 { index := sim_state.first_free_level_geom_plus_one new_level_geom := get_level_geom(sim_state, Level_Geom_Handle(index)) @@ -1009,11 +1091,15 @@ add_level_geom :: proc(sim_state: ^Sim_State, config: Level_Geom_Config) -> Leve remove_level_geom :: proc(sim_state: ^Sim_State, handle: Level_Geom_Handle) { level_geom := get_level_geom(sim_state, handle) - spanpool.free(&sim_state.geometry_vertices_pool, level_geom.geometry.vertices) - spanpool.free(&sim_state.geometry_indices_pool, level_geom.geometry.indices) + switch s in level_geom.source { + case Level_Geom_Source_Local: + spanpool.free(&sim_state.geometry_vertices_pool, s.geometry.vertices) + spanpool.free(&sim_state.geometry_indices_pool, s.geometry.indices) - spanpool.free(&sim_state.blas_nodes_pool, level_geom.blas.nodes) - spanpool.free(&sim_state.blas_primitives_pool, level_geom.blas.primitives) + spanpool.free(&sim_state.blas_nodes_pool, s.blas.nodes) + spanpool.free(&sim_state.blas_primitives_pool, s.blas.primitives) + case Level_Geom_Source_Asset: + } } _get_first_free_body :: proc(sim_state: ^Sim_State) -> i32 { @@ -1039,7 +1125,14 @@ destry_sim_state :: proc(sim_state: ^Sim_State) { dynamic_tlas_destroy(&sim_state.dynamic_tlas) } -destroy_physics_scene :: proc(scene: ^Scene) { +scene_init :: proc(scene: ^Scene, assetman: ^assets.Asset_Manager) { + scene.assetman = assetman + for &sim_state in scene.simulation_states { + sim_state.scene = scene + } +} + +scene_destroy :: proc(scene: ^Scene) { for &sim_state in scene.simulation_states { destry_sim_state(&sim_state) } diff --git a/game/physics/simulation.odin b/game/physics/simulation.odin index 4c85552..8ddd80a 100644 --- a/game/physics/simulation.odin +++ b/game/physics/simulation.odin @@ -200,9 +200,8 @@ raycasts_level :: proc( level_geom_handle := index_to_level_geom( int(tlas.bvh_tree.primitives[leaf_node.child_or_prim_start + j]), ) - level_geom := get_level_geom(sim_state, level_geom_handle) - blas := get_level_geom_blas(sim_state, level_geom.blas) - vertices, indices := get_level_geom_data(sim_state, level_geom.geometry) + blas := get_level_geom_blas(sim_state, level_geom_handle) + vertices, indices := get_level_geom_data(sim_state, level_geom_handle) // TODO: transform ray into blas space and back @@ -388,7 +387,7 @@ remove_invalid_contacts :: proc( if !should_remove { tri_idx := int(contact.local_tri_idx) - vertices, indices := get_level_geom_data(sim_state, level_geom.geometry) + vertices, indices := get_level_geom_data(sim_state, level_geom_handle) should_remove |= tri_idx * 3 >= len(indices) if !should_remove { @@ -547,11 +546,8 @@ find_new_contacts :: proc( ) level_geom := get_level_geom(sim_state, level_geom_handle) if level_geom.alive { - blas := get_level_geom_blas(sim_state, level_geom.blas) - vertices, indices := get_level_geom_data( - sim_state, - level_geom.geometry, - ) + blas := get_level_geom_blas(sim_state, level_geom_handle) + vertices, indices := get_level_geom_data(sim_state, level_geom_handle) blas_it := bvh.iterator_intersect_leaf_aabb(&blas, body_aabb) for blas_leaf_node in bvh.iterator_intersect_leaf_next(&blas_it) { @@ -808,8 +804,7 @@ update_contacts :: proc(sim_state: ^Sim_State, static_tlas: ^Static_TLAS) { ) case .Body_vs_Level: level_geom_handle := Level_Geom_Handle(contact.b) - level_geom := get_level_geom(sim_state, level_geom_handle) - vertices, indices := get_level_geom_data(sim_state, level_geom.geometry) + vertices, indices := get_level_geom_data(sim_state, level_geom_handle) tri := get_triangle(vertices, indices, int(contact.local_tri_idx)) m2 = collision.double_sided_triangle_to_convex(tri, context.temp_allocator) diff --git a/src_assets/blender_assets.cats.txt b/src_assets/blender_assets.cats.txt new file mode 100644 index 0000000..9ae4dab --- /dev/null +++ b/src_assets/blender_assets.cats.txt @@ -0,0 +1,9 @@ +# This is an Asset Catalog Definition file for Blender. +# +# Empty lines and lines starting with `#` will be ignored. +# The first non-ignored line should be the version indicator. +# Other lines are of the format "UUID:catalog/path/for/assets:simple catalog name" + +VERSION 1 + +65988f84-4b5f-463a-bcae-896eba4975c5:Buildings:Buildings diff --git a/src_assets/blender_assets.cats.txt~ b/src_assets/blender_assets.cats.txt~ new file mode 100644 index 0000000..9ae4dab --- /dev/null +++ b/src_assets/blender_assets.cats.txt~ @@ -0,0 +1,9 @@ +# This is an Asset Catalog Definition file for Blender. +# +# Empty lines and lines starting with `#` will be ignored. +# The first non-ignored line should be the version indicator. +# Other lines are of the format "UUID:catalog/path/for/assets:simple catalog name" + +VERSION 1 + +65988f84-4b5f-463a-bcae-896eba4975c5:Buildings:Buildings diff --git a/src_assets/ice_cream_truck.blend b/src_assets/ice_cream_truck.blend index 7adda5e..69c0891 100644 --- a/src_assets/ice_cream_truck.blend +++ b/src_assets/ice_cream_truck.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d4e20c76c5f8d6d48cc5542dbf7dca00d9d00169fe7f4fe5cb67ae4fb397104 -size 2340991 +oid sha256:d169f8e4fec3e92beee9185d45153bf20e1fa2fe85186b0274c89b87cadba9e3 +size 2347071 diff --git a/src_assets/ice_cream_truck.blend1 b/src_assets/ice_cream_truck.blend1 index b79e9b0..c5e3e50 100644 --- a/src_assets/ice_cream_truck.blend1 +++ b/src_assets/ice_cream_truck.blend1 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f55c53880861b9f07132c438c397cc111ec31b60984a61549844b1b58f96dcf7 -size 2340991 +oid sha256:191ddc43e00153843211a34e53db52d5f3d328b799eb55b4150e2bda06e39c23 +size 2346639 diff --git a/src_assets/test_level.blend b/src_assets/test_level.blend new file mode 100644 index 0000000..113339b --- /dev/null +++ b/src_assets/test_level.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e441b8e05842e881ba06f6836ca439780d05f3241c9667472191ceed984826a8 +size 514980 diff --git a/src_assets/test_level.blend1 b/src_assets/test_level.blend1 new file mode 100644 index 0000000..9de9cab --- /dev/null +++ b/src_assets/test_level.blend1 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9646c9e2d88c2e1cbe6127969f661abbcb1840cc626b8b5f6eb5a57151366182 +size 514980 diff --git a/src_assets/testblend.blend b/src_assets/testblend.blend index a498d98..95aaf79 100644 --- a/src_assets/testblend.blend +++ b/src_assets/testblend.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:351bc2737f5326ab8b76c8e3b5521b1a269a5460a28dde5a56de8f1c4e09b7b0 -size 12945841 +oid sha256:d13a61b14f65264a73cd2d81bd3eeb0f42e29fffcbb8cb2900b1c57815c1dd68 +size 16655460 diff --git a/src_assets/testblend.blend1 b/src_assets/testblend.blend1 index 3761c43..33d1822 100644 --- a/src_assets/testblend.blend1 +++ b/src_assets/testblend.blend1 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58f2b434d209a093693b88742b17f3f2774d275937a7a1b435d696e38d3fe058 -size 12945841 +oid sha256:89aa2aadb654926cb96b9216ca03fcd26ad0a4566e201c8b59422ad7edf908c9 +size 16655316 diff --git a/src_assets/tools.blend b/src_assets/tools.blend new file mode 100644 index 0000000..97309dd --- /dev/null +++ b/src_assets/tools.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4c1a9d361b20b08ff2118e61941e374c0ae2be53a8916e7a2f189c063b184a3 +size 666160 diff --git a/src_assets/tools.blend1 b/src_assets/tools.blend1 new file mode 100644 index 0000000..7ab9360 --- /dev/null +++ b/src_assets/tools.blend1 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d4d36edb540f831df86967c84d499dbb61b78fe5620a8fd6da070bd9496f530 +size 666160