React Three Fiber: Toggling between cameras unexpected behaviour

Hello legends, @drcmda this might be your wheelhouse.

I’m battling with the task of toggling between cameras in R3F. There is a sandbox provided below.

I have a standard <OrbitControls> that lets me rotate around a skateboard

I also have a perspective camera that I can render that zooms in on a detail

It appears that I can safely jump TO the perspective camera, but the OrbitControls are messed up when I jump back. I know this is something to do with the correct recipe of makeDefault and the fact that:

All controls react to the default camera. If you have a <PerspectiveCamera makeDefault /> in your scene, they will control it. If you need to inject an imperative camera or one that isn’t the default, use the camera prop: <OrbitControls camera={MyCamera} />

Here’s a distilled sandbox that I might be able to get some clarification on? I feel like I’ve tried a few approaches. Thanks!

it behaved so strange … sure enough this is a bug. pc is supposed to set itself as the default and restore the old camera on unmount. it’s fixed, should come live in a few minutes: fix: perspective & orthographic camera have confused side effects · pmndrs/drei@0f44e35 · GitHub

ps. makeDefault on the controls … when you set it it will write these controls into “state.controls” that’s all. there are a few components that will either change the camera (camera-shake, …) or conflict with event handlers (for instance transform-controls and orbit-controls at the same time). if you have two conflicting camera-controls, or more, then makeDefault allows them to “know” one another. for instance transform-controls can then disable orbit-controls temporarily when you drag one of its handles, or camera-shake can call “controls.update()” after it applied a shake.

1 Like

it’s up, seems OK now R3F Camera Toggling (forked) - CodeSandbox

1 Like

ps, just in case, we added setAzimuthalAngle and setPolarAngle to orbitcontrols so that you can animate to a certain degree by code. Controlling OrbitControls - CodeSandbox

1 Like

Thanks a bunch mate, excellent!

In regards to controlling the angles, do you see any obvious re-rendering issues mixing useSpring and useThree like this? It does work really well though.

Ignore [distance, setDistance] for now, have a seperate question below the code block

const ActiveCamera: React.FC<{ cameraCoordinates: Camera | null }> = ({
  cameraCoordinates,
}) => {
  const {controls} = useThree();
  const [limitedControl, setLimitedControl] = useState<boolean>(false);
  const [distance, setDistance] = useState<number>(1);

  useSpring({
    azimuthal: cameraCoordinates ? cameraCoordinates.azimuthal : 0,
    polar: cameraCoordinates ? cameraCoordinates.polar : 1.5708,
    distance: cameraCoordinates ? 0.5 : 1,
    onChange: (state) => {
      const { azimuthal, polar, distance: animDistance } = state.value;
      (controls as IOrbitControls)?.setAzimuthalAngle(azimuthal);
      (controls as IOrbitControls)?.setPolarAngle(polar);
      setDistance(animDistance);
    },
    config: cameraCoordinates ? config.stiff : config.wobbly,
    onStart: () => {setLimitedControl(false)},
    onRest: () => {if (cameraCoordinates) setLimitedControl(true)},
  });

  return (
    <OrbitControls
      minDistance={distance}
      maxDistance={distance}
      makeDefault
      enableZoom={false}
      rotateSpeed={limitedControl ? 0.1 : 1}
    />
  );
};

So yeah, about the [distance, setDistance], absolutely illegal to call useState that often in a component, but can’t for the life of me figure out how else to animate the distance of the camera to it’s target? I tried animating target itself, but there were some issues

You might find this sansdbox useful as well, I set it up to switch between orthographic and perspective but it should work fine with two perspective cameras. I found OrbitControls a bit problematic when doing complex transitions like this so I’m using CameraControls instead.

1 Like
const r = useRef()

onChange: state => {
  ...
  r.current.minDistance = animDistance
  r.current.maxDistance = animDistance
}

<OrbitControls ref={r} ... />

like @looeee said, probably better to look into camera controls if it gets too complex, orbit controls isn’t the most friendly thing to extend.

1 Like

Thanks @drcmda @looeee , I was thinking of using a ref for a number but didn’t think to add OrbitControls as a ref itself, was having trouble with this and types and realised that I’d already solved that.

I’d love to play around with more in-depth camera options, I’m familiar with orbit controls and for this project it seems to be what I want, here’s a small run through, you can see why I need to animate the min/max distance, and this is actually running the useState, which is surprisingly performant but I also need to be able to live with myself…

setState is by far not as slow as it’s sometimes made out to be, just a bit of diffing, but if you can avoid it somehow you’ll for sure have more headroom on older mobile devices and weaker platforms in general. looks great btw.