#How to intercept GLTF scene spawning in Bevy?

17 messages · Page 1 of 1 (latest)

waxen wyvern
#

Hi, I have a level exported as .gltf and currently spawn the whole thing with SceneRoot + Avian3D colliders, then walk through entities afterwards to replace them (e.g. coins, random tiles, simplified colliders) using an observer.

Is there a way to intercept GLTF scene spawning so I can decide what gets spawned before Bevy instantiates everything, instead of doing a spawn-then-despawn workflow?

#

I currently spawn the entire scene like this:

commands
    .spawn((
        SceneRoot(world_assets.level_scene.clone()),
        ColliderConstructorHierarchy::new(ColliderConstructor::TrimeshFromMesh),
        RigidBody::Static,
        WorldEntity,
    ))
    .observe(on_level_spawn);

Then I iterate over spawned entities in an observer to modify or replace them:

fn on_level_spawn(trigger: Trigger<SceneInstanceReady>, children_q: Query<&Children>, mut commands: Commands, name_q: Query<(&Name, &Transform)>, world_assets: Res<WorldAssets>) {
    let coin_scene = SceneRoot(world_assets.coin_scene.clone());

    for entity in children_q.iter_descendants(trigger.target()) {
        if let Ok((name, transform)) = name_q.get(entity) {
            match name.as_str() {
                n if n.starts_with("coin_spawn") => spawn_coin(transform, &coin_scene, &mut commands),
                n if n.starts_with("panel_base") => spawn_random_panel(transform, &mut commands),
                _ => {}
            }
        }
    }
}

The problem with this approach is that the entire scene is spawned first, and I patch it afterward. I’d like to avoid this “spawn then despawn/replace” workflow.

Ideally, I want to intercept the GLTF scene instancing process and decide how each node gets spawned (random tile, simplified collider, custom entity, etc.) during the spawn itself.

Pseudocode of what I’m aiming for:

for node in gltf_scene {
    match node.name.as_str() {
        n if n.starts_with("coin_spawn") => spawn_coin(node.transform),
        n if n.starts_with("panel_base") => spawn_random_panel(node.transform),
        n if n.starts_with("complex_shape") => spawn_with_simplified_collider(node.transform),
        _ => spawn_default_gltf(node),
    }
}

Is there a way in Bevy to hook into or override the GLTF scene instantiation so I can control what gets spawned for each node, instead of modifying it after the fact?

wicked bay
#

It's only a couple hundred LOC so it should be a simple port

boreal valve
#

Otherwise, there's no real way to do this without wrapping the GLTF loader.

weak owl
#

If you put RUST_LOG=trace on, and fish out the meshing etc action when you load up a gltf are there any Event<T>s going on that you could listen for? And if there is anything useful there at least start the post-processing sooner?

waxen wyvern
#

thanks everyone, i'll give this a try and report back!

marble ruin
waxen wyvern
#

i had a look at bevy_scene_postprocess and while it is better than processing every time i spawn a scene, it still doesn't get away from the "despawn and replace" workflow that i wanted to get away from.

I also couldn't find any gltf spawn events i could hook into with trace logs sadly.

I ended up trying to iterate over the loaded gltf manually and spawn scenes that way, but that seems too painful and I can see it getting too complicated very quickly:

fn setup_world(mut commands: Commands, world_assets: Res<WorldAssets>, gltfs: Res<Assets<Gltf>>, gltf_nodes: Res<Assets<GltfNode>>,) {
      if let Some(gltf) = gltfs.get(&world_assets.level_gltf) {
        for (name, node_handle) in &gltf.named_nodes {
            if name.starts_with("coin") {
                if let Some(node) = gltf_nodes.get(node_handle) {
                    commands.spawn((
                        Coin,
                        SceneRoot(world_assets.coin_scene.clone()),
                        node.transform,
                        ColliderConstructor::Sphere { radius: 0.5 },
                    ));
                }
            } else {
                // Spawn some other scene
            }
        }
    }
}
#

It looks like I just need to embrace the "modify or replace on spawn" approach. Tools like Skein seem to make this workflow quite ergonomic so I will be using that instead 🙂 thank you again everyone!

Skein is a Bevy Plugin and a Blender extension that integrates your Bevy App's Component data into Blender's UI

boreal valve
#

But anyway good luck!

waxen wyvern
# boreal valve bevy_scene_postprocess explicitly does not use the spawn and replace workflow? L...

Ah, thanks for clarifying! By “spawn and replace workflow” I meant: spawn the whole level from the GLTF, then go through and remove marker components, spawning things like coin_scene in their place.

From your explanation, it sounds like bevy_scene_postprocess lets you intercept and modify nodes before they’re spawned. Did I get that right? If so, that sounds like exactly what I want — preventing certain GLTF nodes (like marker components) from being spawned at all, and substituting them with something else instead

boreal valve
#

There are some things I would redo if I were to revive the crate, like using Assets::remove to prevent the GLTF scene from being in memory after you've done the conversion

waxen wyvern