Giraphi
Giraphi

Reputation: 1651

Use the same GLTF model twice in react-three-fiber/drei/Three.js

In this minimal react-three-fiber App I am trying to load and include the same GLTF model twice:

import React, { Suspense } from "react";
import { Canvas } from "@react-three/fiber";
import { useGLTF } from "@react-three/drei";

function MyContent() {
  const firstGltf = useGLTF("/eye/scene.gltf");
  const secondGltf = useGLTF("/eye/scene.gltf");

  return (
    <>
      <primitive object={firstGltf.scene} position={[-200, 0, -400]} />
      <primitive object={secondGltf.scene} position={[200, 0, -400]} />
    </>
  );
}

export default function App() {
  return (
    <Canvas>
      <ambientLight color="white" intensity={0.5} />

      <Suspense fallback={null}>
        <MyContent />
      </Suspense>
    </Canvas>
  );
}

See this codesandbox

However only the second <primitive> is visible. If i remove the second <primitive>, then the first one is visible. I'm struggling to understand why this happens and how to do it better.

(Is it because the second call to useGLTF remembers that "/eye/scene.gltf" has already been loaded and returns the same object? And is this somehow messing up the usage with <primitive>, maybe because materials/geometries haven't been re-created a second time and exist only once?)

In particular, this is what I want to achieve:

On top of that, maybe you can help me to clarify these questions as well so I get a better understanding what's actually going on here:

Thank you!

Upvotes: 7

Views: 8732

Answers (4)

Martin Bucher
Martin Bucher

Reputation: 173

Using scene.clone() fixed it for me, e.g.:

const { scene } = useGLTF(file);
<primitive ref={ref} object={scene.clone()} position={position} scale={scale} />

Upvotes: -1

Elliott Yang
Elliott Yang

Reputation: 1

Maybe you can use Clone from '@react-three/drei'

<>
  <Clone object={firstGltf.scene} position={[-200, 0, -400]} />
  <Clone object={secondGltf.scene} position={[200, 0, -400]} />
</>

Upvotes: 0

louis_guitton
louis_guitton

Reputation: 5687

You can't reuse meshes or put the same object into the scene twice in webgl/threejs, it will just unmount and remount. You can either:

  1. share the geometry: see this example https://codesandbox.io/s/re-using-gltfs-dix1y?file=/src/Shoe.js:48-55
  2. or deep clone the base object

Here is how to deep clone the base object, using useMemo:

interface ObjectProps {
  url: string;
  position: [x: number, y: number, z: number];
}

const Object = ({ url, position, ...props }: ObjectProps) => {
  const { scene } = useLoader(GLTFLoader, url)
  const copiedScene = useMemo(() => scene.clone(), [scene])

  return (
    <group>
      <primitive object={copiedScene} position={position} />
    </group>
  );
};

Ref: https://github.com/pmndrs/react-three-fiber/issues/245#issuecomment-745552102

Upvotes: 5

Mic Fung
Mic Fung

Reputation: 5692

I am not an expert on three.js, just based on what I find and try to answer your questions.


1. Only one eye is shown even there is 2 primitives defined

If you import the same model by using useGLTF(), it will refer to the same object. Therefore, the 2 primitives are pointing to the same gltf and only last/one config is applied.

const firstGltf = useGLTF("/eye/scene.gltf");
const secondGltf = useGLTF("/eye/scene.gltf");
const glassesGltf = useGLTF("/glasses/scene.gltf");

// for demonstrating first eye is same as second eye
// Output: false, true
console.log(firstGltf === glassesGltf, firstGltf === secondGltf);

2. Is <primitive> actually the correct approach to show the 3D model?

Yes, it is. but if you want to display the same gltf to the screen more than once, you need to create meshes and apply the model's geometries and materials so you can have a new object.

function Model(props) {
  const { nodes, materials } = useGLTF("/eye/scene.gltf");
  return (
    <group
      {...props}
      dispose={null}
      rotation={[Math.PI, 0, -Math.PI / 2]}
      scale={[1, 1, 1]}
    >
      <mesh
        geometry={nodes.Sphere001_Eye_0.geometry}
        material={materials.material}
      />
    </group>
  );
}
...
<Model position={[-1, 0, 1]} />
<Model position={[1, 0, 1]} />

Here is the codesandbox for demo


FYR:

You can use this library https://github.com/pmndrs/gltfjsx to generate jsx from the model.

Upvotes: 6

Related Questions