camera-controls

repository·dev·Indexed 25 days ago

https://github.com/yomotsu/camera-controls

A high-level camera control library for three.js providing smooth transitions, advanced movement modes (Orbit, Dolly, Truck), collision detection, and infinity dolly. It supports flexible mouse and touch input mapping, JSON serialization for state restoration, and utilities to fit the camera to Box3 or Sphere objects.

Tokens
8.6K
Snippets
12
Records
53
Agent score
81%

What's inside camera-controls

  1. Understand Orbit rotations, Dolly, and Zoom in camera-controls

    dev

    Orbit rotations

    camera-controls uses Spherical Coordinates for orbit rotations. For a Y-up camera system:

    • Azimuthal angle: The angle for rotation around the Y-axis.
    • Polar angle: The angle for vertical position.

    Note: The .azimuthAngle is accumulative. Every 360-degree turn adds to the value (e.g., 720° = Math.PI * 4).

    Dolly vs Zoom

    • Zoom: Changes the lens focal length (in three.js, this changes the camera FOV). The camera position remains stationary.
    • Dolly: Physically moves the camera to change the composition of the image in the frame.
  2. Migrate from v2 to v3

    dev

    In v3, azimuth angle normalization is no longer automatic in certain methods. To maintain normalized behavior (where the angle range is -180° to 180°), you must call .normalizeRotations() before using the following methods:

    • .setLookAt()
    • .lerpLookAt()
    • .setTarget()
    • .setPosition()
    • .reset()

    Note that .normalizeRotations() is chainable.

  3. Use camera-controls in a three.js animation loop

    dev

    After initializing CameraControls, you must call .update(delta) within your animation loop. The update method returns a boolean indicating if the controls have updated the camera state, which you can use to optimize your rendering loop.

    import * as THREE from 'three';
    import CameraControls from 'camera-controls';
    
    CameraControls.install( { THREE: THREE } );
    
    const clock = new THREE.Clock();
    const camera = new THREE.PerspectiveCamera( 60, width / height, 0.01, 1000 );
    const cameraControls = new CameraControls( camera, renderer.domElement );
    
    ( function anim () {
    	const delta = clock.getDelta();
    	const hasControlsUpdated = cameraControls.update( delta );
    
    	requestAnimationFrame( anim );
    
    	if ( hasControlsUpdated ) {
    		renderer.render( scene, camera );
    	}
    } )();
  4. Calculate the absolute angle for the shortest azimuth rotation

    dev

    To find the shortest rotation path between a source angle and a target angle (e.g., converting a 380° rotation into a -20° rotation), use the following logic with THREE.MathUtils.euclideanModulo.

    const TAU = Math.PI * 2;
    
    function absoluteAngle( targetAngle, sourceAngle ){ 
    
      const angle = targetAngle - sourceAngle
      return THREE.MathUtils.euclideanModulo( angle + Math.PI, TAU ) - Math.PI;
    
    }
    
    console.log( absoluteAngle( 380 * THREE.MathUtils.DEG2RAD, 0 ) * THREE.MathUtils.RAD2DEG ); // -20deg
    console.log( absoluteAngle( -1000 * THREE.MathUtils.DEG2RAD, 0 ) * THREE.MathUtils.RAD2DEG ); // 80deg
  5. Install and setup camera-controls for three.js

    dev

    To use camera-controls, you must first install three.js. Before creating any CameraControls instances, you must call CameraControls.install() and pass an object containing the THREE namespace. This allows the library to access necessary three.js classes.

    If you want to reduce bundle size via tree-shaking, you can provide a subset of THREE instead of the full library.

    import * as THREE from 'three';
    import CameraControls from 'camera-controls';
    
    // Required before creating any instance
    CameraControls.install( { THREE: THREE } );
    
    // Example of tree-shaking a subset
    const subsetOfTHREE = {
    	Vector2   : Vector2,
    	Vector3   : Vector3,
    	Vector4   : Vector4,
    	Quaternion: Quaternion,
    	Matrix4   : Matrix4,
    	Spherical : Spherical,
    	Box3      : Box3,
    	Sphere    : Sphere,
    	Raycaster : Raycaster,
    };
    CameraControls.install( { THREE: subsetOfTHREE } );
  6. Normalize the accumulated azimuth angle

    dev

    If you require a normalized azimuth angle (between 0 and 360 degrees) instead of the raw accumulated value, use THREE.MathUtils.euclideanModulo with Math.PI * 2 (TAU).

    const TAU = Math.PI * 2;
    
    function normalizeAngle( angle ) {
    	return THREE.MathUtils.euclideanModulo( angle, TAU );
    }
    
    const normalizedAzimuthAngle = normalizeAngle( cameraControls.azimuthAngle );
  7. Migrate from v1 to v2

    dev

    Version 2 replaced simple damping with SmoothDamp. This introduces smoothTime (the approximate time to reach the target) and maxSpeed control.

    Deprecated properties:

    • dampingFactor (use smoothTime instead)
    • draggingDampingFactor (use draggingSmoothTime instead)

    New properties:

    • smoothTime
    • draggingSmoothTime
    • maxSpeed
  8. Create complex transitions using Promises

    dev

    Methods that accept an enableTransition parameter return a Promise. You can await these methods to chain camera movements sequentially. If enableTransition is set to false, the promise resolves immediately.

    Transition speed and timing can be tuned using the .restThreshold and .smoothTime properties.

    async function complexTransition() {
    	await cameraControls.rotateTo( Math.PI / 2, Math.PI / 4, true );
    	await cameraControls.dollyTo( 3, true );
    	await cameraControls.fitToSphere( mesh, true );
    }
    
    // will resolve immediately
    await cameraControls.dollyTo( 3, false );
  9. Update and Lifecycle Management

    dev

    The Update Loop

    You must call .update(delta) in your animation/tick loop to update camera position and directions. It returns true if a re-render is required.

    // In your animation loop
    const delta = clock.getDelta();
    if (cameraControls.update(delta)) {
      renderer.render(scene, camera);
    }

    Lifecycle

    • connect(): Attaches internal event handlers to enable drag/user input control.
    • disconnect(): Detaches event handlers to disable drag control.
    • dispose(): Removes all event listeners and cleans up the instance.
    • stop(): Immediately stops all active transitions/animations.
  10. Configure mouse and touch input behaviors

    dev

    You can customize how different mouse buttons and touch gestures map to camera actions using the user input configuration.

    Mouse Input Mapping

    ButtonAvailable Behaviors
    mouseButtons.leftROTATE (default), TRUCK, SCREEN_PAN, OFFSET, DOLLY, ZOOM, NONE
    mouseButtons.rightROTATE, TRUCK (default), SCREEN_PAN, OFFSET, DOLLY, ZOOM, NONE
    mouseButtons.wheelROTATE, TRUCK, SCREEN_PAN, OFFSET, DOLLY (Perspective default), ZOOM (Orthographic default), NONE
    mouseButtons.middleROTATE, TRUCK, SCREEN_PAN, OFFSET, DOLLY (default), ZOOM, NONE

    Note: mouseButtons.wheel uses scroll events and does not emit 'controlstart' or 'controlend' events.

    Touch Input Mapping

    FingersAvailable Behaviors
    touches.oneTOUCH_ROTATE (default), TOUCH_TRUCK, TOUCH_SCREEN_PAN, TOUCH_OFFSET, DOLLY, ZOOM, NONE
    touches.twoTOUCH_DOLLY_TRUCK (Perspective default), TOUCH_ZOOM_TRUCK (Orthographic default), and various combinations of TOUCH_DOLLY_... and TOUCH_ZOOM_... actions, or NONE
    touches.threeSimilar to touches.two, allows mapping complex multi-finger gestures to combined actions or NONE
  11. Instantiate CameraControls

    dev

    Create a new instance of CameraControls by passing the camera you wish to control and the DOM element that will act as the interaction area (typically the renderer's canvas).

    import * as THREE from 'three';
    import { CameraControls } from 'camera-controls';
    
    const camera = new THREE.PerspectiveCamera();
    const domElement = renderer.domElement;
    
    CameraControls.install({ THREE });
    const cameraControls = new CameraControls(camera, domElement);
    CameraControls.install( { THREE } );
    const cameraControls = new CameraControls( camera, domElement );
  12. Install CameraControls and inject THREE dependency

    dev

    Before instantiating CameraControls, you must inject the THREE library using the static install method. This allows the library to use Three.js types and math utilities. To reduce bundle size via tree-shaking, you can provide a subset of the THREE object containing only the necessary classes.

    import * as THREE from 'three';
    import { CameraControls } from 'camera-controls';
    
    // Full installation
    CameraControls.install({ THREE });
    
    // Or subset installation for smaller bundles
    const subsetOfTHREE = {
        Vector2: THREE.Vector2,
        Vector3: THREE.Vector3,
        Vector4: THREE.Vector4,
        Quaternion: THREE.Quaternion,
        Matrix4: THREE.Matrix4,
        Spherical: THREE.Spherical,
        Box3: THREE.Box3,
        Sphere: THREE.Sphere,
        Raycaster: THREE.Raycaster,
    };
    CameraControls.install({ THREE: subsetOfTHREE });
    CameraControls.install( { THREE: THREE } );