How to animation switch camera from perspective to orthographic (like 3d program , maya ,blender ,unity etc..) in three.js

Hi i have question about how to animation switch camera from perspective to orthographic (like 3d program , maya ,blender ,unity etc…) in three.js

i don’t know how to animate it like this. thank you for your help

What I would do for this (if I had to, but I don’t), for a pers-to-ortho animation, is a function that, at each frame, move back a little bit the camera, and reduce a little bit its FOV. This way, when the camera is very far and with a very reduced FOV, it will look almost orthographic, and you can switch to a real OrthographicCamera.

2 Likes

here is part of code i used in one of projects, mostly you need “dollyzoom” function to move perspective camera very far away (i used 5000)
i used dollyzoom in “self.viewportSetMode” function

getCameraYawPitchRadius and setCameraYawPitch is just positioning camera, you will have to just move camera backwards.
soryy i have no time now to write simple example, i’ll try to do it tomorrow or later.

the most important parts is probably this two lines:
var x = Math.tan(THREE.Math.degToRad(camera.fov / 2)) * camera.position.distanceTo(target);
this is width of camera frustum at target position
and then it should be used when you move camera to adjust camera.fov
camera.fov = 2 * THREE.Math.radToDeg(Math.atan(x / tweenValues.radius));
raduis here is distance from camera to target

then when you moved you camera far away just switch to ortho camera, and when switching back - switch to perspective camera far away and go back… i my case it was a bit easier coz i moved camera only by “Y” axes

here is a code (hope it will help):
function dollyzoom(targetDistance, targetNear, targetFar, duration, easing) {
return new Promise(function (resolve) {
var easing = easing || TWEEN.Easing.Linear.None; // TWEEN.Easing.Cubic.InOut
var duration = duration || 1000;
var target = cameraTarget;
var tweenValues = getCameraYawPitchRadius(camera, target);
var targetValues = {yaw: 0, pitch: Math.PI, radius: targetDistance};

        tweenValues.near = camera.near;
        tweenValues.far = camera.far;
        targetValues.near = targetNear;
        targetValues.far = targetFar;

        var x = Math.tan(THREE.Math.degToRad(camera.fov / 2)) * camera.position.distanceTo(target);

        var tween = new TWEEN.Tween(tweenValues);
        tween.to(targetValues, duration);
        tween.easing(easing);
        tween.onComplete(function () {
            resolve();
        });
        tween.onUpdate(function () {
            setCameraYawPitch(camera, target, tweenValues.yaw, tweenValues.pitch, tweenValues.radius);

            camera.fov = 2 * THREE.Math.radToDeg(Math.atan(x / tweenValues.radius));
            camera.far = tweenValues.far;
            camera.near = tweenValues.near;

            camera.updateProjectionMatrix();
        });
        tween.start();
    });
}

var transitionAnimating = false;

var removeModeChangeLoader = function () {
    $('.view-mode-wrapper .loader').remove();
};

self.viewportSetMode = function (mode) {
    var title = '';
    if (viewportMode === mode) {
        return;
    }
    if (transitionAnimating)
        return;

    transitionAnimating = true;

    viewportMode = mode;

    var farRadius = 5000;
    var camNearNormal = 1;
    var camFarNormal = renderDistanceLimit;
    var camNearAway = farRadius + camNearNormal;
    var camFarAway = farRadius + camFarNormal;
    var animationTime = 50;
    var cameraAnimation;
    if (mode === '2d') {
        cameraAnimation = new Promise(function (resolve) {
            viewcube.setView(viewcube.FACES.TOP).then(function () {
                dollyzoom(farRadius, camNearAway, camFarAway, animationTime, TWEEN.Easing.Quartic.In).then(function () {
                    resolve();
                    removeModeChangeLoader();
                });
            });
        });

        cameraAnimation.then(function () {
            title = '2D';
            viewModeControl.removeClass('active');
            camera = orthographicCamera;

            viewportModeSwitchAxes('2d');
            if (transformMode === 'scale') {
                selectionBox.material.color.set(bbox2dScaleColor);
            }

            viewModeControl.find('span').html(title);

            orbitControl.setObject(camera);
            orbitControl.enableRotate = false;
            orbitControl.enablePan = true;
            orbitControl.setZoom(zoom / 100);

            transformControl.setCamera(camera);
            scaleControl.setCamera(camera);

            viewcube.disable();

            transformControl.setRotationSnap(THREE.Math.degToRad(snapAngle2d));

            transitionAnimating = false;
            removeModeChangeLoader();
        });

    } else {
        cameraAnimation = new Promise(function (resolve) {
            var targetRadius = orbitControl.getInitialRadius() / (zoom / 100);

            // set camera and start animation
            camera = perspectiveCamera;
            orbitControl.setObject(perspectiveCamera);
            selectionBox.material.color.set(bboxDefaultColor);
            transformControl.setCamera(camera);
            scaleControl.setCamera(camera);
            viewportModeSwitchAxes('3d');

            dollyzoom(targetRadius, camNearNormal, camFarNormal, animationTime, TWEEN.Easing.Quartic.Out).then(function () {

                viewcube.enable();

                viewcube.setView(viewcube.FACES.TOP_FRONT_RIGHT_CORNER).then(function () {
                    resolve();
                });
            });
        });

        cameraAnimation.then(function () {
            title = '3D';
            viewModeControl.addClass('active');

            orbitControl.enableRotate = true;
            orbitControl.enablePan = false;
            viewcube.updateOrientation();

            viewModeControl.find('span').html(title);

            orbitControl.setZoom(zoom / 100);

            transformControl.setRotationSnap(null);

            transitionAnimating = false;
            removeModeChangeLoader();
        });
    }


};
2 Likes

Thank you for your answer

Can you give me getCameraYawPitchRadius() ,setCameraYawPitch() ?

1 Like

getCameraYawPitchRadius()

    function getCameraYawPitchRadius(camera, target) {
    var spherical = new THREE.Spherical();
    var offset = new THREE.Vector3();

    // so camera.up is the orbit axis
    var quat = new THREE.Quaternion().setFromUnitVectors(camera.up, new THREE.Vector3(0, 1, 0));
    var position = camera.position;

    offset.copy(position).sub(target);

    // rotate offset to "y-axis-is-up" space
    offset.applyQuaternion(quat);

    // angle from z-axis around y-axis
    spherical.setFromVector3(offset);

    return {yaw: spherical.theta, pitch: Math.PI / 2 - spherical.phi, radius: spherical.radius};
}

setCameraYawPitch()

    function setCameraYawPitch(camera, target, yaw, pitch, radius) {
    var spherical = new THREE.Spherical();
    var offset = new THREE.Vector3();

    // so camera.up is the orbit axis
    var quat = new THREE.Quaternion().setFromUnitVectors(camera.up, new THREE.Vector3(0, 1, 0));
    var quatInverse = quat.clone().inverse();
    var position = camera.position;

    offset.copy(position).sub(target);

    // rotate offset to "y-axis-is-up" space
    offset.applyQuaternion(quat);

    // angle from z-axis around y-axis
    spherical.setFromVector3(offset);

    var thetaDelta = yaw - spherical.theta;
    var phiDelta = (-pitch + Math.PI / 2) - spherical.phi;

    spherical.theta += thetaDelta;
    spherical.phi += phiDelta;

    if (typeof radius !== 'undefined') {
        spherical.radius = radius;
    }

    spherical.makeSafe();
    offset.setFromSpherical(spherical);

    // rotate offset back to "camera-up-vector-is-up" space
    offset.applyQuaternion(quatInverse);

    camera.position.copy(target).add(offset);
    camera.lookAt(target);
}
2 Likes

thank you for your help very much.