Cloning Skinned Mesh results in problems

import * as THREE from "three";
import gsap from "gsap";
import Experience from "../Experience.js";

export default class PrinceGreen {
  constructor({ attackRange = 30, x = 0 }) {
    this.experience = new Experience();
    this.scene = this.experience.scene;
    this.resources = this.experience.resources;
    this.time = this.experience.time;
    this.debug = this.experience.debug;

    // Debug
    if (this.debug.active) {
      this.debugFolder = this.debug.ui.addFolder("PrinceGreen");
    }

    // Resource
    this.resource = this.resources.items.prince_green;

    // Array of meshes meteor can collide with
    this.targets = [];
    this.x =  x
    // Dynamic attack range
    this.attackRange = attackRange;

    this.setModel();
    this.setAnimation();
  }

  setModel() {
    this.model = this.resource.scene;
    this.model.position.set(this.x, -0.1, 0);
    this.scene.add(this.model);

    this.model.traverse((child) => {
      if (child instanceof THREE.Mesh) {
        child.castShadow = true;
      }
    });
  }

  setParticles() {
    this.particles = {};

    // Meteor mesh
    this.meteor = new THREE.Mesh(
      new THREE.IcosahedronGeometry(0.25),
      new THREE.MeshPhysicalMaterial({
        color: "silver",
        roughness: 0,
        metalness: 0.6,
        flatShading: true,
      })
    );

    // spawn in front of character
    const forward = new THREE.Vector3(0, 0, 1)
      .applyQuaternion(this.model.quaternion)
      .normalize();

    const spawnPos = this.model.position.clone()
      .add(forward.clone().multiplyScalar(1.0)) // 1 unit forward
      .add(new THREE.Vector3(0, 1.0, 0));       // slightly above

    this.meteor.position.copy(spawnPos);
    this.scene.add(this.meteor);

    this.meteorBox = new THREE.Box3().setFromObject(this.meteor);

    // Create particle trail
    const canvas = document.createElement("CANVAS");
    canvas.width = 128;
    canvas.height = 128;

    const context = canvas.getContext("2d");
    context.globalAlpha = 0.3;
    context.filter = "blur(16px)";
    context.fillStyle = "white";
    context.beginPath();
    context.arc(64, 64, 40, 0, 2 * Math.PI);
    context.fill();
    context.globalAlpha = 1;
    context.filter = "blur(5px)";
    context.fillStyle = "white";
    context.beginPath();
    context.arc(64, 64, 16, 0, 2 * Math.PI);
    context.fill();

    const texture = new THREE.CanvasTexture(canvas);

    const N = 200,
      M = 3;
    const position = new THREE.BufferAttribute(new Float32Array(3 * N), 3);
    const color = new THREE.BufferAttribute(new Float32Array(3 * N), 3);
    const v = new THREE.Vector3();

    for (let i = 0; i < N; i++) {
      v.randomDirection().setLength(3 + 2 * Math.pow(Math.random(), 1 / 3));
      position.setXYZ(
        i,
        this.meteor.position.x,
        this.meteor.position.y,
        this.meteor.position.z
      );
      color.setXYZ(i, Math.random(), Math.random(), Math.random());
    }

    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute("position", position);
    geometry.setAttribute("color", color);

    const material = new THREE.PointsMaterial({
      color: "white",
      vertexColors: true,
      size: 2,
      sizeAttenuation: true,
      map: texture,
      transparent: true,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
    });

    const cloud = new THREE.Points(geometry, material);
    this.scene.add(cloud);

    let idx = 0;

    this.particles.burstBall = this.meteor;
    this.particles.cloud = cloud;

    this.particles.update = () => {
      if (!this.meteor) return; // <---- safeguard
      for (let j = 0; j < M; j++) {
        v.randomDirection().divideScalar(4).add(this.meteor.position);
        position.setXYZ(idx, v.x, v.y, v.z);
        color.setXYZ(idx, 1, 1, 2);
        idx = (idx + 1) % N;
      }

      // recolor all the rest particles
      let k = 1;
      for (let j = idx + N; j > idx - M; j--) {
        color.setXYZ(j % N, k, k ** 1.5, 5 * k ** 3);
        k = 0.98 * k;
      }
      position.needsUpdate = true;
      color.needsUpdate = true;
    };
  }

  findNearestTarget(maxRange = this.attackRange) { // use dynamic range
    let nearest = null;
    let minDist = Infinity;

    for (let target of this.targets) {
      if (!target) continue;
      const dist = this.model.position.distanceTo(target.position);
      if (dist < minDist && dist <= maxRange) {
        minDist = dist;
        nearest = target;
      }
    }
    return nearest;
  }

  throwBall(target) {
    if (!this.meteor) return;
    const meteor = this.meteor;

    // direction character is facing
    const forward = new THREE.Vector3(0, 0, 1)
      .applyQuaternion(this.model.quaternion)
      .normalize();

    // rise up position: a bit forward & up from current meteor
    const risePos = meteor.position.clone()
      .add(forward.clone().multiplyScalar(1.0)) // 1 unit forward
      .add(new THREE.Vector3(0, 1.5, 0));       // 1.5 units upward

    // Save timeline reference
    this.meteorTimeline = gsap.timeline({
      onUpdate: () => {
        if (meteor && this.meteorBox) {
          this.meteorBox.setFromObject(meteor);
        }
        this.particles?.update();
      },
      onComplete: () => {
        this.destroyMeteor(target);
      },
    });

    // Phase 1: rise up
    this.meteorTimeline.to(meteor.position, {
      x: risePos.x,
      y: risePos.y,
      z: risePos.z,
      duration: 0.75,
      delay: 0.25,
    });

    // Phase 2: chase toward *current* target position
    const speed = 25; // units per second
    this.meteorTimeline.to(meteor.position, {
      duration: 3, // just a max duration; GSAP will overwrite each frame
      ease: "none",
      delay: 0.25,
      onUpdate: () => {
        if (!target) return;
        const targetPos = target.position.clone();
        const direction = targetPos.clone().sub(meteor.position);
        const distance = direction.length();

        // move step toward current target position
        if (distance > 0.01) {
          direction.normalize().multiplyScalar(speed * (gsap.ticker.deltaRatio() / 60));
          meteor.position.add(direction);
        }
      },
    });
  }


  destroyMeteor(target = null) {
    if (this.meteorTimeline) {
      this.meteorTimeline.kill();
      this.meteorTimeline = null;
    }

    if (this.meteor) {
      const splashPos = this.meteor.position.clone();

      this.scene.remove(this.meteor);
      this.meteor.geometry.dispose();
      this.meteor.material.dispose();
      this.meteor = null;
      this.meteorBox = null;

      if (this.particles?.cloud) {
        const cloud = this.particles.cloud;
        const geometry = cloud.geometry;
        const positions = geometry.getAttribute("position");
        const colors = geometry.getAttribute("color");
        const N = positions.count;

        // get flight direction (use target if available)
        let normal = new THREE.Vector3(0, 0, 1);
        if (target) {
          normal = new THREE.Vector3()
            .subVectors(target.position, splashPos)
            .normalize();
        }

        // find a vector perpendicular to normal (to form ring plane)
        const tangent = new THREE.Vector3(0, 1, 0);
        if (Math.abs(normal.dot(tangent)) > 0.9) tangent.set(1, 0, 0);
        const bitangent = new THREE.Vector3().crossVectors(normal, tangent).normalize();
        tangent.crossVectors(bitangent, normal).normalize();

        for (let i = 0; i < N; i++) {
          const angle = (i / N) * Math.PI * 2;
          const distortion = (Math.random() - 0.5) * 0.1;
          const r = 0.05 + distortion;

          // parametric circle in tangent/bitangent plane
          const x = splashPos.x + r * (tangent.x * Math.cos(angle) + bitangent.x * Math.sin(angle));
          const y = splashPos.y + r * (tangent.y * Math.cos(angle) + bitangent.y * Math.sin(angle));
          const z = splashPos.z + r * (tangent.z * Math.cos(angle) + bitangent.z * Math.sin(angle));

          positions.setXYZ(i, x, y, z);

          const t = i / N;
          colors.setXYZ(i, 1, 1 - t, 1 - t);
        }
        positions.needsUpdate = true;
        colors.needsUpdate = true;

        const radiusObj = { r: 0.05, opacity: 1 };
        gsap.to(radiusObj, {
          r: 1.2,
          opacity: 0,
          duration: 0.4,
          ease: "power2.out",
          onUpdate: () => {
            cloud.material.opacity = radiusObj.opacity;

            for (let i = 0; i < N; i++) {
              const angle = (i / N) * Math.PI * 2;
              const distortion = (Math.random() - 0.5) * 0.1;
              const r = radiusObj.r + distortion;

              const x = splashPos.x + r * (tangent.x * Math.cos(angle) + bitangent.x * Math.sin(angle));
              const y = splashPos.y + r * (tangent.y * Math.cos(angle) + bitangent.y * Math.sin(angle));
              const z = splashPos.z + r * (tangent.z * Math.cos(angle) + bitangent.z * Math.sin(angle));

              positions.setXYZ(i, x, y, z);
            }
            positions.needsUpdate = true;
          },
          onComplete: () => {
            if (cloud.parent) this.scene.remove(cloud);
            geometry.dispose();
            cloud.material.dispose();
            this.particles.cloud = null;
            this.particles.update = () => { };
          },
        });
      }
    }
  }

  setAnimation() {
    this.animation = {};

    // Mixer
    this.animation.mixer = new THREE.AnimationMixer(this.model);
    this.animation.mixer.addEventListener('finished', () => {
      this.triggered = false;
    })
    // Actions
    this.animation.actions = {};
    this.animation.actions.idle = this.animation.mixer.clipAction(
      this.resource.animations[0]
    );
    this.animation.actions.fire = this.animation.mixer.clipAction(
      this.resource.animations[1]
    );

    this.animation.actions.current = this.animation.actions.idle;
    this.animation.actions.current.play();

    this.triggerTime = 1.1;
    this.triggered = false;

    this.animation.play = (name) => {
      const newAction = this.animation.actions[name];
      const oldAction = this.animation.actions.current;

      if (newAction === oldAction) return; // prevent re-triggering same anim

      newAction.reset();
      newAction.play();
      newAction.crossFadeFrom(oldAction, 0.5);

      this.animation.actions.current = newAction;

      this.triggered = false;       // reset meteor spawn control
      this.prevActionTime = 0;      // reset animation cycle tracking
    };

    if (this.debug.active) {
      const debugObject = {
        playIdle: () => this.animation.play("idle"),
        playFire: () => this.animation.play("fire"),
      };
      this.debugFolder.add(debugObject, "playIdle");
      this.debugFolder.add(debugObject, "playFire");
    }
  }

  update() {
    // Update animation mixer
    this.animation.mixer.update(this.time.delta * 0.001);

    const action = this.animation.actions.current;
    if (action) {
      // 🔄 Detect animation restart (looped back to start)
      if (action.time < this.prevActionTime) {
        this.triggered = false; // reset for new cycle
      }
      this.prevActionTime = action.time;

      // 🎯 Find nearest target within dynamic range
      let nearestTarget = null;
      let minDist = Infinity;
      for (let target of this.targets) {
        if (!target) continue;
        const dist = this.model.position.distanceTo(target.position);
        if (dist < minDist && dist <= this.attackRange) { // ✅ use dynamic range
          minDist = dist;
          nearestTarget = target;
        }
      }

      // Rotate toward nearest target if any
      if (nearestTarget) {
        this.model.lookAt(nearestTarget.position);
        if (this.animation.actions.current !== this.animation.actions.fire) {
          this.animation.play('fire');
        }
      } else {
        if (this.animation.actions.current !== this.animation.actions.idle) {
          this.animation.play('idle');
        }
      }

      // 🚀 Spawn meteor ONLY once per fire cycle
      if (
        action === this.animation.actions.fire &&
        !this.triggered &&
        action.time >= this.triggerTime
      ) {
        if (nearestTarget) {
          this.setParticles();
          this.throwBall(nearestTarget);
        }
        this.triggered = true;
      }
    }

    // ✨ Update particles
    this.particles?.update();

    // 💥 Collision check
    if (this.meteor && this.meteorBox) {
      for (let target of this.targets) {
        const targetBox = new THREE.Box3().setFromObject(target);
        if (this.meteorBox.intersectsBox(targetBox)) {
          this.destroyMeteor(target);
          break;
        }
      }
    }
  }

}

Above is my code , Am using this model,
When Tries creating multiple classes of this class, It is causing issue of model stuck with no animation playing. It’s happening with other skinned mesh models too. Can someone please help with this one ?

I’m not sure I see where a SkinnedMesh is being cloned in this code, but if you are cloning any object or group containing a SkinnedMesh, you’ll want to use SkeletonUtils#clone to maintain relationships between the skinned mesh and the bones in the resulting scene hierarchy.