How to replace a part of skinnedMesh in R3F

Hello, everyone.
I’m currently trying to implement the swapping of items for a character.

Typically, I’ve been loading GLTF using code like below.

import React, { useEffect, useRef } from "react";
import { useGLTF, useAnimations } from "@react-three/drei";

export function Soldier({
    animation = "Idle",
    ...props}) {
  const group = useRef();
  const { nodes, materials, animations } = useGLTF("/models/Soldier.glb");
  const { actions } = useAnimations(animations, group);
  console.log(animations);

  useEffect( () => {
    actions[animation].reset().fadeIn(0.2).play();
    return () => actions[animation]?.fadeOut(0.2);
  }, [animation]);

  return (
    <group ref={group} {...props} dispose={null}>
      <group name="Scene">
        <group name="Character" rotation={[-Math.PI / 2, 0, 0]} scale={0.01}>
          <skinnedMesh
            name="vanguard_Mesh"
            geometry={nodes.vanguard_Mesh.geometry}
            material={materials.VanguardBodyMat}
            skeleton={nodes.vanguard_Mesh.skeleton}
          />
          <skinnedMesh
            name="vanguard_visor"
            geometry={nodes.vanguard_visor.geometry}
            material={materials.Vanguard_VisorMat}
            skeleton={nodes.vanguard_visor.skeleton}
          />
          <primitive object={nodes.mixamorigHips} />
        </group>
      </group>
    </group>
  );
}

useGLTF.preload("/models/Soldier.glb");

In my understanding, I might need to replace the skinnedMesh, but I’m not sure how to go about it. Should I componentize skinnedMesh separately to handle this? If you have any examples or ideas, please let me know!

You could conditionally render via state. Set a string to the default visor and then each visor gets its own name to plug into the state, and render based off which name is the present state.

I’m struggling with the same question at the moment.

will let you know if I find something :slight_smile:

I’ve tried this

but didn’t work for me :frowning: also it seems like it is breaking out of the React pattern quite badly :confused:

EDIT:
ok, for me this solution linked to above did work after all…

However, for some quirk of my model + useGltf the returned scene groups material + geometry were empty… So I had to construct my own group + mesh from the provided materials and nodes from the useGltf results and then add that as a child of the hand bones instead.

In code it looks something like this:

const result = useGLTF("/3d-assets/glb/enemies/Skeleton Staff-transformed.glb") as GLTFResult;

useEffect(() => {
  const { nodes } = result;
  
  const staff = new Group();
  const staffMesh = new Mesh(
    nodes.Skeleton_Staff.geometry,
    result.materials.skeleton
  );
  
  staff.add(staffMesh);
  
  const staff2 = new Group();
  const staffMesh2 = new Mesh(
    nodes.Skeleton_Staff.geometry,
    result.materials.skeleton
  );
  staff2.add(staffMesh2);
  let leftHand: Object3D<Bone>;
  let rightHand: Object3D<Bone>;
  group.current.traverse((child) => {
    // check for the child name of your bone where you want to attach your other mesh to... 
    // for me it was handslotl and handslotr but for you it's most likely different because it depends on the model
    if (child.name === "handslotl" && ItemLeft) {
      child.add(staff);
      leftHand = child as Object3D<Bone>;
    }
    if (child.name === "handslotr" && ItemRight) {
      child.add(staff2);
      rightHand = child as Object3D<Bone>;
    }
  });
  
  return () => {
    if (leftHand && ItemLeft) {
      leftHand.remove(ItemLeft);
    }
    if (rightHand && ItemRight) {
      rightHand.remove(ItemRight);
    }
  };
  }, []);

I’m thinking that I probably don’t need the wrapping group.

I am also not sure if I need to do more cleanup of the created new Group and new Mesh or if it’s enough to remove them as children… probably need to call dispose or something too, but step by step :slight_smile:

Lastly, there might be a better way using createPortal from React Drei—at least that’s what I gleaned from looking at this thread.

But haven’t tried any of this yet and am also not sure how the two approaches compare… Maybe @drcmda knows more? ^^