Importing multiple meshes per model with AssetsManager

I have a model in Blender with multiple parts (meshes).

At the moment it only has 4 different meshes and I call:
scene.getMeshByName(mesh_name).clone(new_mesh_name, parent_node) on all 4 of them every time I want to make a new instance of this model with its multiple meshes:

function get_mesh (scene: Scene, mesh_name: string, new_mesh_name: string, parent_node: Node)
{
    return scene.getMeshByName(mesh_name)?.clone(new_mesh_name, parent_node)!
}

export function create_house (scene: Scene, shadow_generator: ShadowGenerator, position: Vector3, name: string)
{
    const house_parent = new TransformNode("house_parent_" + name)

    const house_walls = get_mesh(scene, "low_poly_house_2_walls", "house_walls_" + name, house_parent)
    const house_roof = get_mesh(scene, "low_poly_house_2_roof", "house_roof_" + name, house_parent)
    const house_door_frame = get_mesh(scene, "low_poly_house_2_door_frame", "house_door_frame_" + name, house_parent)
    const house_door = get_mesh(scene, "low_poly_house_2_door," "house_door_" + name, house_parent)
    ...

I can see in the pirate fort example here that the example of the cannon relies on being able to call: cannon = cannonImportResult.meshes[0].getChildren()[0].

Even though my 4 meshes are nested under what I think is a top level “Mesh Object” (orange triangle in Blender, with name low_poly_house_2), when I use the assets_manager.onTaskSuccess, the task.loadedMeshes[1].getChildren() returns an empty list despite task.loadedMeshes[1].name === “low_poly_house_2”. And later, using scene.getMeshByName("low_poly_house_2").getChildren() also returns an empty list…?

Obviously once you get more models and those have more sub meshes, it gets much more brittle & expensive to remember to individually clone all the submeshes to form a model.

Is this something that exporting from Blender to GLTF / GLB affords you that exporting to OBJ does not? Or is there a different way to go about modelling in blender that allows for the BabylonJS import to hold all the child meshes under a single parent mesh?

Demo repo here with specific code in question here and blender model here.

1 Like

In blender I figured out how to merge meshes together but this does not help as it now looses all the names of the mesh parts and Babylonjs still does not nest them when they are imported.

What I did (that I do not think is useful) is whilst in object mode, select all the objects and then choose Object > Join.

I found a solution. Not sure it’s the best but it works. In blender, I reverted to the previous pattern, with a top level object with an empty mesh and several child objects with their mesh and materials e.g.:

Then for the import I use:


const parent_mesh_names = new Set([
    mesh_name_low_poly_tree_1,
    mesh_name_low_poly_house_2,  // === "low_poly_house_2" same as in blender
])
function load_assets (assets_manager: BABYLON.AssetsManager)
{
    assets_manager.addMeshTask("load low_poly_tree_1", null, "public/models/low_poly_tree/", "low_poly_tree_1.obj")
    assets_manager.addMeshTask("load low_poly_house_2", null, "public/models/low_poly_house/", "low_poly_house_2.obj")

    assets_manager.onTaskSuccess = task =>
    {
        if (is_MeshAssetTask(task))
        {
            const parent_mesh = task.loadedMeshes.find(mesh => parent_mesh_names.has(mesh.name))
            if (!parent_mesh)
            {
                console.error("No parent mesh found whilst loading ", task.name)
                return
            }

            task.loadedMeshes.forEach(mesh =>
            {
                if (mesh.name !== parent_mesh.name) parent_mesh.addChild(mesh)
                mesh.visibility = 0
            })
        }
    }
}

function is_MeshAssetTask (task: BABYLON.AbstractAssetTask): task is BABYLON.MeshAssetTask
{
    return (task as BABYLON.MeshAssetTask).loadedMeshes !== undefined
}

And then when I come to use them I use the following utility functions:

interface GetMeshOptions
{
    parent_node?: Node | null
    position?: Vector3
    receive_shadows?: boolean
    shadow_generator?: ShadowGenerator
    visibility?: number
}

export function get_mesh (scene: Scene, mesh_name: string, new_mesh_name: string, options: GetMeshOptions = {})
{
    const mesh = scene.getMeshByName(mesh_name)?.clone(new_mesh_name, options.parent_node || null)!

    const { position, receive_shadows, shadow_generator, visibility } = options

    if (position) mesh.position = position


    mesh.getChildMeshes().forEach(mesh =>
    {
        if (receive_shadows) mesh.receiveShadows = true
        if (shadow_generator) shadow_generator.addShadowCaster(mesh)
    })


    if (visibility !== undefined) set_mesh_visiblilty(mesh, visibility)


    return mesh
}


function set_mesh_visiblilty (mesh: AbstractMesh | Node, visibility: number)
{
    if (is_mesh(mesh)) mesh.visibility = visibility
    mesh.getChildMeshes().forEach(mesh => mesh.visibility = visibility)
}

Which I am using like:

// create_house.ts

export const mesh_name_low_poly_house_2 = "low_poly_house_2"

export function create_house (scene: Scene, shadow_generator: ShadowGenerator, position: Vector3, name: string)
{
    const house = get_mesh(scene, mesh_name_low_poly_house_2, "house_" + name, {
        position,
        receive_shadows: true,
        shadow_generator,
        visibility: 1,
    })
}

Full code here: Fix importing nested models · AJamesPhillips/3d_sandbox@6266318 · GitHub

Hey @ajp , how are you doing?

The problem you are facing might be due to the fact that the .obj format does no support object hierarchy. Due to that, the door, roof and walls are not going to be store as child node of low_poly_house_2 but they will be at the same level. This is a limitation of the obj file format itself. Those objects should be stored in task.loadedMeshes[2] 3 and 4 (As you can see in the following Playground: Babylon.js Playground (babylonjs.com))

From a quick search it looks like GLTF / GLB supports object/mesh heirarchies?

GLB and GLTF do support it.

1 Like