Duplicating Model on Animation loop?

I am trying to play an animation that comes from a blender model while simultaneously having the model auto-rotate from OrbitControls. However I am getting the model(https://sketchfab.com/3d-models/veribot-9342a123c2f44661a93db71ae603d83f) to be duplicated, one model is animated on top of a stationary copy of the model that is unanimated:

image

I’ve tried removing the orbit controls code and update however I am still getting this result. Any advice would be greatly appreciated.

import {
  AmbientLight,
  Color,
  DirectionalLight,
  Mesh,
  AnimationMixer,
  MeshPhongMaterial,
  PerspectiveCamera,
  Scene,
  SphereBufferGeometry,
  UniformsUtils,
  Vector2,
  WebGLRenderer,
  sRGBEncoding,
  PointLight,
  Clock,
} from 'three';
import { cleanRenderer, 
            cleanScene, 
            removeLights,
            modelLoader,
            textureLoader, } from 'utils/three';
import { GLTFLoader, OrbitControls, RenderPixelatedPass } from 'three-stdlib';
import { render } from 'react-dom';


export const ScifiWorkerRobot = props => {
  const theme = useTheme();
  const [loaded, setLoaded] = useState(false);
  const { rgbBackground, themeId, colorWhite } = theme;
  const start = useRef(Date.now());
  const canvasRef = useRef();
  const mouse = useRef();
  const renderer = useRef();
  const camera = useRef();
  const scene = useRef();
  const lights = useRef();
  const uniforms = useRef();
  const material = useRef();
  const geometry = useRef();
  const sphere = useRef();
  const reduceMotion = useReducedMotion();
  const isInViewport = useInViewport(canvasRef);
  const windowSize = useWindowSize();
  const rotationX = useSpring(0, springConfig);
  const rotationY = useSpring(0, springConfig);
  const { measureFps, isLowFps } = useFps(isInViewport);
  const cameraXSpring = useSpring(0, cameraSpringConfig);
  const cameraYSpring = useSpring(0, cameraSpringConfig);
  const cameraZSpring = useSpring(0, cameraSpringConfig);
  const controls = useRef();
  const animations = useRef();
  const mixer = useRef();
  const clock = useRef();
  const animationFrame = useRef();
  const mounted = useRef();
  const sceneModel = useRef();
  
  //Setting up initial scene
  useEffect(() => {
    //mounted.current = true;
    const { innerWidth, innerHeight } = window;
    mouse.current = new Vector2(0.8, 0.5);
    renderer.current = new WebGLRenderer({
      canvas: canvasRef.current,
      antialias: false,
      alpha: true,
      powerPreference: 'high-performance',
      failIfMajorPerformanceCaveat: true,
    });
    renderer.current.setSize(innerWidth, innerHeight);
    renderer.current.setPixelRatio(1);
    renderer.current.outputEncoding = sRGBEncoding;

    camera.current = new PerspectiveCamera(54, innerWidth / innerHeight, 0.1, 100);
    camera.current.position.z = 5;
    camera.current.position.x = 0;
    camera.current.position.y = 0;
    
    scene.current = new Scene();
    clock.current = new Clock();
    
    const ambientLight = new AmbientLight(colorWhite, 0.9);
    const dirLight = new DirectionalLight(colorWhite, 0.8);
    dirLight.position.set(0,0,0);
    const lights = [ambientLight, dirLight];
    lights.forEach(light => scene.current.add(light));
    
    controls.current = new OrbitControls(camera.current, canvasRef.current);
    controls.current.enableZoom = true;
    controls.current.enablePan = true;
    controls.current.enableDamping = true;
    controls.current.rotateSpeed = 0.5;
    controls.current.autoRotate = true;

    const loader = new GLTFLoader();
    loader.load(veribot, function ( gltf ) {
      scene.current.add( gltf.scene );
      animations.current = gltf.animations;
      mixer.current = new AnimationMixer(gltf.scene);
      mixer.current.timeScale = 0.8;
      
      animations.current.forEach((clip, index) => {
          const animation = mixer.current.clipAction(clip);
          animation.play();
      }); 
      
    }, undefined, function ( error ) {
      console.error( error );
    } ); 
    
    return () => {
      //mounted.current = false;
      cancelAnimationFrame(animationFrame.current);

      removeLights(lights);
      cleanScene(scene.current);
      cleanRenderer(renderer.current);
    };
  }, []);
   
  useEffect(() => {
    let animation;

    const animate = () => {
      animation = requestAnimationFrame(animate);
      const delta = clock.current.getDelta();
      if (mixer.current){
      mixer.current.update(delta);
      }
      controls.current.update();
      renderer.current.render(scene.current, camera.current);
      
      measureFps();
      
    };
  
    animate();

    return () => {
      cancelAnimationFrame(animation);
    };
  }, [isInViewport, measureFps, reduceMotion, isLowFps, rotationX, rotationY]);
  


  return (
    <Transition in timeout={2000}>
      {visible => (
        <canvas
          aria-hidden
          className={styles.canvas}
          data-visible={visible}
          ref={canvasRef}
          {...props}
        />
      )}
   </Transition>
  );
};
1 Like

a slight mistake in that effect will lead to trouble. it’s probably due to splitting up everything into two effects with refs acting as bridges. the 20 useRefs should already indicate that there’s something wrong. try unifying it into one useEffect, you don’t need refs. btw, throwing three into a useEffect, you remove integration with react, state flow and any possibility of participating in an eco system.

you can also just decide to have react render threejs clean clever-robinson-hskmst - CodeSandbox from animation to effects, everything exists. react-spring has direct support for threejs and can animate cameras just like you animate divs and spans (<a.mesh {...animatedProps} />).

1 Like

Would you recommend using local variables then? Since using state variables would cause a re-render every time they are updated?

i would put everything into one useEffect and run three completely separate from react. as i wrote above, this is all kinds of bad, it’s anti-react, but splitting between multiple effects and then use refs as bridges seems even worse. if you’ve clicked the sandbox above, this would be the clean solution, letting react render threejs, just like react-dom renders divs and spans. this would then allow you to have threejs components that are integrated into react state, context, suspense etc.

I’m sorry but I’m still not quite sure what you mean by run three completely separate from react. I’m still new to three and I’d like to understand my problem here without using react-three-fiber. I’ve tried integrating into 1 useEffect with variables that are not refs and end up getting nothing on the screen. Do you see anything wrong with my animation loop?

the loop seems alright. perhaps start with a simple example, just a box, and then move up. all i was trying to say is, enclose the logic into a single useEffect instead of reaching passing things on via refs.