Merge Meshes on Import

Hello. In my source 3D model (Autodesk Inventor) there are several items which are each instanced over 300 times. Each instance of the items is positioned in a different location. It’s quite a complex assembly. We export through 3D Studio Max to create a gltf file and find that we need to retain the instances to keep the export process manageable and the gltf file size reasonable.

If I load the gltf file in Three and just render it the huge number of individual objects overwhelms the renderer and it is predictable too slow. None of the items move and similar objects can be assigned the same material so merging the identical objects into a single object seems sensible.

I tried the following:

    // Get the objects.
    const importedObjects: THREE.Object3D = scene.getObjectByName("Part_Group");

    // Check we have found the Objects.
    if (importedObjects !== null) {

      // Create an empty Object to hold the Geometry.
      let mergedGeometry = new THREE.Geometry();

      // Create a material.
      const objectMaterial = new THREE.MeshLambertMaterial({ color: 0xffff00, side: THREE.DoubleSide })

      // Iterate through the children
      importedObjects.children.forEach(child => {

        if (child instanceof THREE.Mesh) {

          mergedGeometry.mergeMesh(child);

        }

      });

      const mergedGeom = BufferGeometryUtils.mergeBufferGeometries(tundishGeometryArray);

      // Create an object3D from the tundishes Geometry.
      const mergedMesh = new THREE.Mesh(mergedGeom, objectMaterial);

      // Add the tundishes to the scene.
      this.sceneMaster.add(mergedMesh);

…but I get the following error: “THREE.Geometry.merge(): geometry not an instance of THREE.Geometry”. The docs seem to indicate that mergeMesh takes a THREE.Mesh as an argument. I have also tried many variations around this theme but can’t figure out where I am going wrong. Perhaps it’s something obvious I just can’t see.

Any advise or guidance very welcome.

Please try the merge operation with BufferGeometryUtils.mergeBufferGeometries().

Take a look at the following example to see its usage in action: https://threejs.org/examples/webgl_geometry_minecraft

1 Like

Thanks for such a prompt reply. So I tried mergeBufferGeometries() as follows but all of the items appear in the same location. Performance doesn’t seen great either. I must be missing something obvious:

    // Get the objects.
    const importedObjects: THREE.Object3D = scene.getObjectByName("Part_Group");

    // Check we have found the Objects.
    if (importedObjects !== null) {

      // Create an empty Object to hold the Geometry.
      const geometryArray = [];

      // Create a material.
      const objectMaterial = new THREE.MeshLambertMaterial({ color: 0xffff00, side: THREE.DoubleSide })

      // Iterate through the children
      importedObjects.children.forEach(child => {

        if (child instanceof THREE.Mesh) {

          geometryArray.push(child.geometry);

        }

      });

      let mergedGeom = new THREE.BufferGeometry();
      mergedGeom = BufferGeometryUtils.mergeBufferGeometries(geometryArray);

      // Create an object3D from the tundishes Geometry.
      const mergedMesh = new THREE.Mesh(mergedGeom, objectMaterial);

      // Add the tundishes to the scene.
      this.sceneMaster.add(mergedMesh);

As shown in the example, you can’t just push the geometries directly into the array. Instead, you have to clone it and apply the world matrix of the respective mesh to it. Try it with:

geometryArray.push( child.geometry.clone().applyMatrix4( child.matrixWorld ) );

It makes sense that the geometry object has no knowledge of where it is in the world and that I need to use the child’s position to set the position of the geometry. I followed your suggestion but still all the objects appear in the same position on top of each other and it still seems to perform poorly. My geometry objects only had a applyMatrix method, not applyMatrix4, I wondered if that was a THREE version issue. I am using v17.1.0. My code now reads as follows:

    // Get the objects.
    const importedObjects: THREE.Object3D = scene.getObjectByName("Part_Group");

    // Check we have found the Objects.
    if (importedObjects !== null) {

      // Create an empty Object to hold the Geometry.
      const geometryArray = [];

      // Create a material.
      const objectMaterial = new THREE.MeshLambertMaterial({ color: 0xffff00, side: THREE.DoubleSide })

      // Iterate through the children
      importedObjects.children.forEach((child: THREE.Mesh) => {

        if (child instanceof THREE.Mesh) {

          // Create a clone of the current child Mesh's geometry.
          const geometryClone = child.geometry.clone();

          // Bake the current child's world transform matrix into the new clone.
          geometryClone.applyMatrix(child.matrixWorld);

          // Add the cloned geometry into the array.
          geometryArray.push(geometryClone);

        }

      });

      let mergedGeom = new THREE.BufferGeometry();
      mergedGeom = BufferGeometryUtils.mergeBufferGeometries(geometryArray);

      // Create a Mesh from the Geometry.
      const mergedMesh = new THREE.Mesh(mergedGeom, objectMaterial);

      // As the object is not moving, prevent THREE from calculating the object's position.
      mergedMesh.matrixAutoUpdate = false;

      // Add the Mesh to the scene.
      this.sceneMaster.add(mergedMesh);

This version looks strange. Where have you downloaded your three.js copy?

I am developing a web application using Angular and I installed Three using NPM. In my package.json file I have the following:

"three": "^0.104.0",
"three-dat.gui": "^1.0.2",
"three-fbxloader-offical": "^1.0.0",
"three-full": "^17.1.0",
"three-gltf-loader": "^1.104.0",
"three-orbit-controls": "^82.1.0",

Perhaps I mis-informed you by giving the version of the wrong item. Hope this clears it up.

This setup is not right. You only need three and can delete everything else.

Example files like OrbitControls can be imported like so:

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

Ah. So much to learn! Thanks, I will review my code.

As a test we combined the objects at source and then exported a gltf file (23Mb) and once loaded it performed brilliantly as it only had about 32 objects. Obviously we would prefer to not have such a large file. Using instances at source the gltf file came in at about 200Kb but it had thousands of individual parts which performed badly. I am hoping to have my cake and eat it by loading the small file, identify and push the core parts into the main scene and then identify the instances and merge them before adding them to the scene. After this I don’t need the loaded object any more. Does that make sense?