3

I‘m using React Three Fiber and drei. I'm wondering how to limit the maximum panning distance with the MapControls. There are some solutions online on how to archive it with plain three.js but nothing using the MapControls or OrbitControls from drei and r3f.

I tried this but once I reach the limit the camera glitches weirdly.

function Controls() {
  const { camera } = useThree();

  useFrame(() => {
    camera.position.x = THREE.MathUtils.clamp(camera.position.x, -90, 90)
    camera.position.y = THREE.MathUtils.clamp(camera.position.y, -90, 90)
  })
  
  return (
    <MapControls />
  )
}

Thanks for your help

Alexander

Alexander Hörl
  • 578
  • 4
  • 11
  • 24

3 Answers3

1

Based on this answer a solution would be to create a custom 'Controls' component looking like this.

const Controls = () => {
  const { camera } = useThree()
  const controlsRef = useRef()    

  useEffect(() => {
    controlsRef.current.addEventListener('change', function () {
      if (this.target.y < -10) {
        this.target.y = -10
        camera.position.y = -10
      } else if (this.target.y > 10) {
        this.target.y = 10
        camera.position.y = 10
      }
    })
  }, [])

  return (
    <MapControls ref={controlsRef} enableZoom={false} enableRotate={false} />
  )
}

Which can then be used as a child to the Canvas component.

<Canvas>
  <Controls />
</Canvas>
Alexander Hörl
  • 578
  • 4
  • 11
  • 24
1

My solution is based on the answers above

  const { camera, size } = useThree();

  const limitPanningDistance = useCallback(
    (e?: THREE.Event) => {
      // 704.5 102
      // 1056.75 320

      // Returns the drag container width and height
      const [w, h] = [1920, 1080]

      const pan = (w * camera.zoom - size.width) / 2 / camera.zoom;
      const vertical = (h * camera.zoom - size.height) / 2 / camera.zoom;

      // console.log('pan vertical', pan, vertical);

      const maxX = pan;
      const minX = -pan;
      const maxY = vertical;
      const minY = -vertical;
      const x = e?.target.target.x;
      const y = e?.target.target.y;
      if (x < minX || x > maxX) {
        e?.target.target.setX(x < minX ? minX : maxX);
        camera.position.setX(x < minX ? minX : maxX);
      }
      if (y < minY || y > maxY) {
        e?.target.target.setY(y < minY ? minY : maxY);
        camera.position.setY(y < minY ? minY : maxY);
      }
    },
    [camera.zoom, size]
  );
      <MapControls
        enableRotate={false}
        minZoom={1}
        maxZoom={2}
        onChange={(e) => {
          // console.log(e?.target);
          limitPanningDistance(e);
        }}
        makeDefault
      />
xiaotian
  • 59
  • 4
0

Here is my solution with using onChange property of MapControls.

Controls component:

import React, { useRef } from 'react'
import { MapControls } from '@react-three/drei'
import { useThree } from '@react-three/fiber'

const Controls = () => {
  const { camera } = useThree()
  const cameraLastPosition = useRef({
    x: camera.position.x,
    y: camera.position.y,
  })

  return (
    <MapControls
      onChange={(e) => {
        const maxX = 90
        const minX = -90
        const maxY = 90
        const minY = -90
        const x = e?.target.target.x
        const y = e?.target.target.y

        if (x < minX || x > maxX) {
          e?.target.target.setX(x < minX ? minX : maxX)
          camera.position.setX(cameraLastPosition.current.x)
        }
        if (y < minY || y > maxY) {
          e?.target.target.setY(y < minY ? minY : maxY)
          camera.position.setY(cameraLastPosition.current.y)
        }
        cameraLastPosition.current.x = camera.position.x
        cameraLastPosition.current.y = camera.position.y
      }}
    />
  )
}

export default Controls

Import that component and use it in your Canvas:

<Canvas>
  <Controls />
</Canvas>