Implement blender scene export script, add support for asset based level geom instancing

This commit is contained in:
sergeypdev 2025-07-12 23:16:57 +04:00
parent 5f4fa12040
commit a81e81c52c
28 changed files with 475 additions and 139 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
layout python3

1
.gitignore vendored
View File

@ -19,3 +19,4 @@ game_web/
.venv
build/
bin/
.direnv/

1
.tool-versions Normal file
View File

@ -0,0 +1 @@
python 3.11.13

BIN
assets/blender/ice_cream_truck_blend/Split.glb (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/test_level_blend/NurbsPath.glb (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/test_level_blend/Plane.glb (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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))

BIN
assets/blender/testblend_blend/Bakery.glb (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/testblend_blend/Fire_Station.glb (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/testblend_blend/Gas_Station_Shop.glb (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/testblend_blend/Green_House.glb (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/testblend_blend/Hotel.glb (Stored with Git LFS) Normal file

Binary file not shown.

87
blender/batch_export.py Normal file
View File

@ -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}")

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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

BIN
src_assets/ice_cream_truck.blend (Stored with Git LFS)

Binary file not shown.

BIN
src_assets/ice_cream_truck.blend1 (Stored with Git LFS)

Binary file not shown.

BIN
src_assets/test_level.blend (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src_assets/test_level.blend1 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src_assets/testblend.blend (Stored with Git LFS)

Binary file not shown.

BIN
src_assets/testblend.blend1 (Stored with Git LFS)

Binary file not shown.

BIN
src_assets/tools.blend (Stored with Git LFS) Normal file

Binary file not shown.

BIN
src_assets/tools.blend1 (Stored with Git LFS) Normal file

Binary file not shown.