Smoothly dampen scrolling with a camera in orthographic mode

I’m using ArcRotateCamera in a project that can switch between perspective and orthographic modes.

When in perspective, using the mouse wheel or trackpad to zoom the camera feels great. We can tune the sensitivity and it seems to animate the input when starting or stopping a zoom. I’d like to get mouse wheel input working in a simular way in orthographic mode.

I made a playground that can switch between the two modes (default is orthographic).

  • Use the mouse wheel or trackpad to zoom the camera
  • Switch modes to see the difference in feeling between the two modes. Scrolling in orthographic mode doesn’t feel as natural as it does in perspective mode.

My hack to get orthographic mode to feel like it is scrolling is to resize the orthographic bounds based on a scaler (cameraOrthoSize). I set this scaler using mouse wheel input in an event.

How would you improve this to make it feel more like scrolling in perspective mode?

to me, it looks like orthographic scroll is just too slow. So, increasing scroll speed maybe?
cc @PolygonalSun

No, I’m not talking about the speed. I can adjust the overall speed with dampeningFactor.

In perspective mode, the zoom will start slowly and trail off when starting and stopping.

Something like this ?


Yep! That feels so much better. Thank you :blush:

1 Like

Just a quick update in case someone needs this in the future. Here is a new version on the playground

  • Switch between Orthographic and Perspective mode
  • Scrolling in Orthographic mode will resize the ortho bounds
  • Fixed an issue where zooming out happend much faster than zooming in
  • Add a section of constants to the top of the PG so you can quickly adjust the settings

Here is all the camera-related code

// Constants
const ORTHO_SIZE = 10;
const MIN_ORTHO_SIZE = 1;
const MAX_ORTHO_SIZE = 50;
const DAMPENING_FACTOR = 0.25;
const SPEED = 0.1;

let targetTop, targetBottom, targetLeft, targetRight;

// Helper Function to adjust camera ortho size based on engine dimensions
const adjustOrthoSize = (engine, value) => {
    targetTop = engine.getRenderHeight() / value / ORTHO_SIZE;
    targetBottom = -(engine.getRenderHeight() / value / ORTHO_SIZE);
    targetLeft = -(engine.getRenderWidth() / value / ORTHO_SIZE);
    targetRight = engine.getRenderWidth() / value / ORTHO_SIZE;

// Function to setup the camera
const setupCamera = (scene) => {
    let cameraOrthoSize = ORTHO_SIZE; // used to scale the orthographic bounds

    const camera = new BABYLON.ArcRotateCamera(
        Math.PI / 2,
        Math.PI / 2.5,
        new BABYLON.Vector3(0, 1, 0),
    camera.minZ = 0.1
    camera.wheelDeltaPercentage = 0.01;
    camera.upperBetaLimit = Math.PI / 1.5;
    camera.lowerRadiusLimit = 2;
    camera.upperRadiusLimit = 20;
    camera.attachControl(document.getElementById("bjsCanvas"), true);
    camera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA;
    adjustOrthoSize(engine, cameraOrthoSize); // Initial size

    // Handle scroll wheel event for camera zoom (assuming no perspective mode)
    window.addEventListener("wheel", (event) => {
        if (camera.mode.PERSPECTIVE_CAMERA) return;

        const wheelEvent = event;

        // Adjust the scaling factor based on the scroll speed
        const scalingFactor = Math.abs(wheelEvent.deltaY) / SCROLL_SPEED_FACTOR;

        // Calculate the change in ortho size as a percentage of the current size
        const delta = cameraOrthoSize * scalingFactor * DAMPENING_FACTOR;

        // Adjust the cameraOrthoSize based on the scroll input and the calculated delta
        const newCameraOrthoSize = wheelEvent.deltaY > 0 ? cameraOrthoSize - delta : cameraOrthoSize + delta;

        // Clamp the new value within some bounds
        cameraOrthoSize = Math.max(MIN_ORTHO_SIZE, Math.min(MAX_ORTHO_SIZE, newCameraOrthoSize));
        adjustOrthoSize(engine, cameraOrthoSize);

    scene.onBeforeRenderObservable.add(() => {
        camera.orthoTop += (targetTop - camera.orthoTop) * SPEED;
        camera.orthoBottom += (targetBottom - camera.orthoBottom) * SPEED;
        camera.orthoLeft += (targetLeft - camera.orthoLeft) * SPEED;
        camera.orthoRight += (targetRight - camera.orthoRight) * SPEED;

    return camera;