SkinnedMesh cloning issues

Hey guys,

I have project in which I’m importing an FBX model with its own bones, and want to clone it multiple times, set a specific position, rotation, and scale of each clone, and then add them to the scene. I’ve run into a series of issues preventing me from moving forward however and would be grateful if someone could explain some them to me.

The FBX imports just fine however the first issue arises when I clone it and add it to the scene. I read in this topic that I should use SkeletonUtils.clone() to clone it, however doing so seems to only cause the first vertex group of the model to be cloned.

Original model:


Cloned model:

Also when I look into the skeleton property of the cloned object, the bone inverses and matrice arrays are fine but the bones array seems to be full of undefineds?

3

I also tried the native SkinnedMesh.clone() method and that fixed the above issues, all the vertex groups were copied over and the skeleton property contained all the bones. When I set the position, rotation, and scale of the mesh and add it to the scene though, the original mesh that I made the clone from is seemingly added? This here is what I see after creating 250 clones with randomised position, rotation, and scale:

In case I may be doing something wrong in the cloning code, this is it, I tested it on a normal Mesh however and it worked fine for that:

   const onLoad = skinnedMesh => {
      for(let i=0; i<250; i++){
        const clone = skinnedMesh.clone();

        clone.position.x = randomNum(-150, 150);
        clone.position.z = randomNum(-150, 150);

        clone.rotation.z = Math.PI * randomNum(0, 1);

        const scaleNum = randomNum(0.5, 1.25);
        clone.scale.x = scaleNum;
        clone.scale.y = scaleNum;
        clone.scale.z = scaleNum;

        this.scene.add(clone);
      }
    }

    const loader = new FBXLoader();
    const self = this;
    loader.load(bambooModelDir, function (group) {
      const skinnedMesh = group.children[1];
      skinnedMesh.material = self.bambooMaterial;

      onLoad(skinnedMesh);
    });

Thanks in advance.

Perform the clone operation like so:

const clone = SkeletonUtils.clone( skinnedMesh );

SkeletonUtils is part of the examples and located at examples/jsm/utils/SkeletonUtils.js.

Hey, thanks for the quick reply.

In the first section of the post where I mention issues I have with the SkeletonUtils clone that’s in fact what I was doing. Here’s the full code but it’s really the same as above but uses the SkeletonUtils clone.

   const onLoad = skinnedMesh => {
      for(let i=0; i<250; i++){
        const clone = SkeletonUtils.clone(skinnedMesh);

        clone.position.x = randomNum(-150, 150);
        clone.position.z = randomNum(-150, 150);

        clone.rotation.z = Math.PI * randomNum(0, 1);

        const scaleNum = randomNum(0.5, 1.25);
        clone.scale.x = scaleNum;
        clone.scale.y = scaleNum;
        clone.scale.z = scaleNum;

        console.log(skinnedMesh);
        console.log(clone);

        this.scene.add(clone);
      }
    }

The SkeletonUtils clone also has the same issue as the SkinnedMesh clone in that only one model is added to the scene as opposed to all of the the clones that the for loop should produce.

Do you mind sharing the FBX asset?

Sure, here it is. I exported it from Blender.
BambooWithBones.fbx (84.4 KB)

Seems to work just fine: Glitch :・゚✧

Full code:

      import * as THREE from "https://cdn.skypack.dev/three@0.129.0/build/three.module.js";
      import { OrbitControls } from "https://cdn.skypack.dev/three@0.129.0/examples/jsm/controls/OrbitControls.js";
      import { FBXLoader } from "https://cdn.skypack.dev/three@0.129.0/examples/jsm/loaders/FBXLoader.js";
      import { SkeletonUtils } from "https://cdn.skypack.dev/three@0.129.0/examples/jsm/utils/SkeletonUtils.js";

      let camera, scene, renderer;

      init();
      animate();

      function init() {
        const container = document.createElement("div");
        document.body.appendChild(container);

        camera = new THREE.PerspectiveCamera(
          45,
          window.innerWidth / window.innerHeight,
          1,
          2000
        );
        camera.position.set(200, 200, 500);

        scene = new THREE.Scene();
        scene.background = new THREE.Color(0xa0a0a0);
        scene.fog = new THREE.Fog(0xa0a0a0, 200, 2000);

        const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
        hemiLight.position.set(0, 200, 0);
        scene.add(hemiLight);

        const dirLight = new THREE.DirectionalLight(0xffffff);
        dirLight.position.set(0, 200, 100);
        scene.add(dirLight);

        // ground
        const mesh = new THREE.Mesh(
          new THREE.PlaneGeometry(5000, 5000),
          new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false })
        );
        mesh.rotation.x = -Math.PI / 2;
        mesh.receiveShadow = true;
        scene.add(mesh);

        const grid = new THREE.GridHelper(5000, 50, 0x000000, 0x000000);
        grid.material.opacity = 0.2;
        grid.material.transparent = true;
        scene.add(grid);

        // model
        const loader = new FBXLoader();
        loader.load(
          "https://cdn.glitch.com/809b1831-4fc2-47a6-936f-a35d5362c101%2FBambooWithBones.fbx?v=1624645249753",
          function(object) {
            for (let i = 0; i < 50; i++) {
              const clone = SkeletonUtils.clone(object);

              clone.position.x = THREE.MathUtils.randFloat(-150, 150);
              clone.position.z = THREE.MathUtils.randFloat(-150, 150);

              clone.rotation.y = Math.PI * THREE.MathUtils.randFloat(0, 1);

              const scaleNum = THREE.MathUtils.randFloat(0.5, 1.25);
              clone.scale.x = scaleNum;
              clone.scale.y = scaleNum;
              clone.scale.z = scaleNum;

              scene.add(clone);
            }
          }
        );

        renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        container.appendChild(renderer.domElement);

        const controls = new OrbitControls(camera, renderer.domElement);
        controls.target.set(0, 100, 0);
        controls.update();

        window.addEventListener("resize", onWindowResize);
      }

      function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();

        renderer.setSize(window.innerWidth, window.innerHeight);
      }

      function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
      }

BTW: If you export the object by yourself, use glTF instead of FBX. glTF is the recommended 3D format of three.js.

1 Like

Ah, looking at your example I see now what I was doing wrong. I was only applying the SkeletonUtils.clone() method to the SkinnedMesh object inside the Group the FBXLoader created, instead of applying it to the Group itself. Works just fine now.

Thanks for taking the time to help!