React-three-fiber: example of zoom to fit for a group of meshes with react three fiber

Created on 11 Apr 2019  路  10Comments  路  Source: pmndrs/react-three-fiber

does anyone have an example of how to call a zoom to fit function with this library?

Most helpful comment

Just to help others out, I got the desired effect working with an orthographic camera by setting the zoom.

const { camera, size: { width, height } } = useThree();

const aabb = new THREE.Box3().setFromObject(someObject3D);

camera.zoom = Math.min(
  width / (aabb.max.x - aabb.min.x),
  height / (aabb.max.y - aabb.min.y)
);
camera.updateProjectionMatrix();

All 10 comments

You could compute bounds in the scene and put your camera at the right place, or render your content according to the viewport. The following will create a plane that fits exactly the screen, and if you resize the window, it will re-render accordingly.

const { viewport } = useThree()

return (
  <mesh>
    <planeBufferGeometry attach="geometry" args={[viewport.width, viewport.height ]} />
    <meshBasicMaterial attach="material" color="hotpink" />
  </mesh>
)

@drcmda i am looking for an example where i have a group of meshes that are made up of shape geometry. Once the scene has updated i can call a function like below where object is the group of meshes.

const fitCameraToObject = function(camera, object, offset, controls) {
    offset = offset || 1.25;

    const boundingBox = new THREE.Box3();

    // get bounding box of object - this will be used to setup controls and camera
    boundingBox.setFromObject(object);

    const center = boundingBox.getCenter();

    const size = boundingBox.getSize();

    // get the max side of the bounding box (fits to width OR height as needed )
    const maxDim = Math.max(size.x, size.y, size.z);
    const fov = camera.fov * (Math.PI / 180);
    let cameraZ = Math.abs(maxDim / 4 * Math.tan(fov * 2));

    cameraZ *= offset; // zoom out a little so that objects don't fill the screen

    camera.position.z = cameraZ;

    const minZ = boundingBox.min.z;
    const cameraToFarEdge = minZ < 0 ? -minZ + cameraZ : cameraZ - minZ;

    camera.far = cameraToFarEdge * 3;
    camera.updateProjectionMatrix();

    if (controls) {
        // set camera to rotate around center of loaded object
        controls.target = center;

        // prevent camera from zooming out far enough to create far plane cutoff
        controls.maxDistance = cameraToFarEdge * 2;

        controls.saveState();
    } else {
        camera.lookAt(center);
    }
};

You can do that. UseThree gives you access to the scene and the camera.

Here's an example of how I'm adjusting the camera to a desired size. You could probably combine it with the code above for your needs.

const Camera = () => {
  const camera = useRef()
  const { aspect, size, setDefaultCamera } = useThree()
  const pixelToThreeUnitRatio = 1
  const planeDistance = 0
  const cameraDistance = 500
  const distance = cameraDistance - planeDistance
  const height = size.height / pixelToThreeUnitRatio
  const halfFovRadians = Math.atan((height / 2) / distance)
  const fov = 2 * halfFovRadians * (180/Math.PI)
  useEffect(() => void setDefaultCamera(camera.current), [])
  return <perspectiveCamera
    ref={camera}
    aspect={aspect}
    fov={fov}
    position={[0, 0, cameraDistance]}
    onUpdate={self => self.updateProjectionMatrix()}
  />
}

const App = () => { 
 return (
    <Canvas >
      <Camera />
      <Content />
    </Canvas>
  );
}

thanks @drcmda @ryanking1809

const Content = ({ url }) => {
    let group = useRef();
    let axis = useRef();
    let controls = useRef();
    const [ data, loading ] = useFetch(url);
    const { camera } = useThree();
    camera.up.set(0, 0, 1);
    camera.position.z = 4;

    if (!loading) {
        const json = parser.parse(data, options);
        const surfaceJson = json.gbXML.Campus.Surface;
        const surfaceMeshes = getSurfaceMeshes(surfaceJson);
        if (group.current) {
            const bbox = new THREE.Box3().setFromObject(group.current);

            const sphere = bbox.getBoundingSphere(new THREE.Sphere());
            const { center, radius } = sphere;

            controls.current.reset();
            controls.current.target.copy(center);
            controls.current.maxDistance = 5 * radius;

            camera.position.copy(center.clone().add(new THREE.Vector3(1.0 * radius, -1.0 * radius, 1.0 * radius)));
            camera.far = 10 * radius;
            camera.updateProjectionMatrix();
            if (axis.current) {
                axis.current.scale.set(radius, radius, radius);
                axis.current.position.copy(center);
            }
        }

        //console.log(scene, controls, camera);

        return (
            <Fragment>
                <ambientLight color="#444444" />
                <pointLight color="white" intensity={0.5} position={[ 0, 0, 1 ]} />
                <directionalLight castShadows />
                <axesHelper ref={axis} />
                <orbitControls ref={controls} args={[ camera ]} />
                <group ref={group} name="meshes">
                    {surfaceMeshes.map((mesh, index) => (
                        <primitive
                            key={index}
                            onClick={({ object: { userData: data } }) => console.log(data)}
                            object={mesh}
                        />
                    ))}
                </group>
            </Fragment>
        );
    }
    return <group />;
};

@drcmda love the library, tying this in with graphql and redux is gonna be so sweet.

Just to help others out, I got the desired effect working with an orthographic camera by setting the zoom.

const { camera, size: { width, height } } = useThree();

const aabb = new THREE.Box3().setFromObject(someObject3D);

camera.zoom = Math.min(
  width / (aabb.max.x - aabb.min.x),
  height / (aabb.max.y - aabb.min.y)
);
camera.updateProjectionMatrix();

Sorry for the Noob qestion @partynikko.

Where do I put this code inside a React.Component? I have an array of meshes I get asynchronously and render , and I want to adjust the camera once I'm done..

better not a react class component, this lib's targeted for function components and hooks. there's only one rule: your function shouldn't have any side-effects in it, do not create or mutate objects in it. run these effects inside of a useEffect.

function Meshes({ url }) {
  const { camera }聽= useThree()
  const [meshes, setMeshes] = useState([])
  useEffect(() => void fetchMeshes(url).then(meshes => {
    setMeshes(meshes)
    adjustCamera(camera)
  }), [url])
return meshes.map(data => (
  <mesh>
    ...

Sorry for the Noob qestion @partynikko.

Where do I put this code inside a React.Component? I have an array of meshes I get asynchronously and render , and I want to adjust the camera once I'm done..

Hi there, no issues!

I agree what drcmda wrote. However, if you post the code you are working with it will be easier to suggest an approach for you :)

Was this page helpful?
0 / 5 - 0 ratings