Duplicate same model in a canvas? react-three-fiber

I have an object that’s displaying on the canvas fine and it’s in a function but when I try to re-use that function and display it a second time, it only shows one?

Anyone know how I can copy the same object many times in a scene?

Below is my car object

function Car(props){
  const material = useLoader(MTLLoader, require('./OBJAssests/car.mtl'));
  
  const obj = useLoader(
    OBJLoader, 
    require('./OBJAssests/car.obj'),
    (loader) => {
      material.preload();
      loader.setMaterials(material)
    }
    )

  return(
      <mesh {...props} rotation={[.1, .5, 0]} scale={[0.5, 0.5, 0.5]}>
        <primitive object={obj}/>
      </mesh>
  )
}

Below is my app

export default function App() {
  return (
    <Canvas>
      <pointLight position={[20,30,5]}/>    
     
      <Suspense fallback={null}>
        <Car position={[-0.5, -1, 2]} /> 
        <Car position={[0.5, -1, 2]} /> 
      </Suspense> 

    </Canvas>
  )
}

Either load the model in useEffect or use useGLTF hook instead - useLoader current API is a bit not hehe and confusing :smiling_face_with_tear:

I’m new to three-fiber, how would I implement this? I can’t find anything simple online to replicate into my own code.

As well, my car is an .obj? I can’t use a GLTF?

For example:

const modelRef = useRef();

useEffect(() => {
  new OBJLoader(url, model => modelRef.current = model);
}, []);

return (
  <primitive object={modelRef.current} />
);

you probably want <primitive object={obj.clone()}/>

1 Like

I can’t believe it was that simple

yea but you kinda waste a bit of memory in return

You kinda absolutely do not want that - use ref, otherwise you’ll recreate the entire model on each state update.

not the enrite model just the Object3D. geometries and materials are still the same (unlike with the useEffect method lol).

edit: since pika was overwhelmingly insulted by this simple answer, I thought I would add that you might want to wrap obj.clone() call in useMemo(…) to make it more garbage collector friendly.

1 Like

objects can only be in one place in three.

parent1.add(model)
parent2.add(model)

model will be in parent2 bc three unmounts it. thats all that is happening here. useLoader is perfectly fine, it isn’t the cause of your problem.

if you want to duplicate, just like in vanilla you have to clone, or use gltfjsx. drei has a nice helper for cloning btw, it supports things that plain three object.clone() wouldn’t support like skinned meshes and has some shortcuts (for settings shadows and injecting materials).

import { Clone } from '@react-three/drei'

function Car(props){
  const material = useLoader(MTLLoader, require('./OBJAssests/car.mtl'))
  const obj = useLoader(
    OBJLoader, 
    require('./OBJAssests/car.obj'),
    (loader) => {
      material.preload();
      loader.setMaterials(material)
    }
  )
  return <Clone object={obj} {...props}>
}

ofc you can also do what @makc3d said, though i’d prefer drei/clone for the reasons above

  const clone = useMemo(() => obj.clone(), [obj])
  return <primitive object={clone} {...props}>

ps, you’re dumping a scene into a THREE.Mesh above. that wouldn’t make sense.

useloader is nothing magical. i think it would be not good to use useEffect + fooloader directly because that way you don’t have react integration:

nothing knows when anything has loaded, nothing can respond, 90% of drei wouldn’t work, all interop down the drain. think <Center><Car /></Center>, :poop: luck bc center can’t expect model to be ready in useEffect, model is ready … whenever, basically pure chaos.

useloader executes new FooLoader().load(url, set... in a useEffect and integrates the result into react suspense. this is how other components can have interop. the react eco system couldn’t exist w/o it. i get that suspense is … different, even for devs familiar with react, but it’s such a critical feature.

also, avoiding it would be very bad for re-use and memory. :smile:

yeah but you could put something like const [loaded, setLoaded] = useState(false) and flip it once it loaded, no? you probably would have to put it up above in “CenteredCar” component

1 Like

not so much. only the component itself would know loading state. nothing else outside would, unless you introduce leaks that tell the outside world, but that would require an api surface and couldn’t be dynamic. interop is perhaps the most important aspect about fiber, it means components that do not know one another can be orchestrated, like you chain functions in fp.

<Center>
  <Car />
</Center>

this can only work if Center knows when Car has finished loading, it must calculate a box3 after all for bounds. async is an integral part of react, they call it “suspense”.

that’s how everything is built. even physics. great-robinson-i7nmsp - CodeSandbox <RigidBody> takes models because react ensures that useEffect means “the end of all processes” and if a component suspends that includes loading.