How to limit pan in OrbitControls for OrthographicCamera?

Hi There,

I currently working on an open project to create a station indoor map, but having difficulties to limit the pan when using orbitControls,

I tried to limit the control/camera position x/y/z but when i rotate the orbit all those coordinates suddenly changed, i know why this is happened but since i still confused. :upside_down_face:

The working example is here, http://asw.web.id/project/virtuviz

Any suggestion will be appreciated.

Thanks
Regards,
Sigit

It’s best to modify OrbitControls for this use case. So copy the class and define two new variables that you can use to control the minimum and maximum pan movement at the top of the file.

var minPan = new THREE.Vector3( - 2, - 2, - 2 );
var maxPan = new THREE.Vector3( 2, 2, 2 );

In the update() method, add the following line after panOffset was added to the target of OrbitControls.

scope.target.clamp( minPan, maxPan );

Demo: https://jsfiddle.net/t2exac47/

Panning means to offset the control’s target vector and thus the camera. If you restrict the target vector, you also restrict the camera’s final position.

4 Likes

Oh my god, its working as well, still doesn’t believe it, adding only 3 lines of code just solve this problem… :clap::clap::clap:

Thank you so much!!

2 Likes

Thanks @Mugen87, I got asked by a client to add this yesterday. Gonna steal your solution :grin:

2 Likes

My solution is not as elegant as @Mugen87 but in my case I don’t like the idea of editing OrbitControls.js

So I came out with this solution:

// Limits
const maxX = 25
const minX = -25
const maxZ = 25
const minZ = -Infinity

// State
let positionX
let positionZ
let phi
let theta

controls.addEventListener('change', e => {
    const x = controls.target.x
    const z = controls.target.z
    let shallWeUpdateAngle = false

    if (x < minX || x > maxX) {
        controls.target.setX(x < minX ? minX : maxX)
        camera.position.setX(positionX)
        shallWeUpdateAngle = true
    }
    if (z < minZ || z > maxZ) {
        controls.target.setZ(z < minZ ? minZ : maxZ)
        camera.position.setZ(positionZ)
        shallWeUpdateAngle = true
    }

    if (shallWeUpdateAngle) {
        const distance = camera.position.distanceTo(controls.target)
        camera.position.set(
            distance * Math.sin(phi) * Math.sin(theta) + controls.target.x,
            distance * Math.cos(phi) + controls.target.y,
            distance * Math.sin(phi) * Math.cos(theta) + controls.target.z
        )
    }

    // Updating state
    positionX = camera.position.x
    positionZ = camera.position.z
    phi = controls.getPolarAngle()
    theta = controls.getAzimuthalAngle()
}) 

I suppose @Mugen87’s solution could also be done with an event listener?

Tried, but did’t work. It has to be on the line he said.

You would use controls.target (assuming the controls are named controls), not scope.target, of course.

Yes. I did a package: https://github.com/ocio/three-camera-utils

I solved it:

    var minPan = new THREE.Vector3( - 2, - 2, - 2 );
    var maxPan = new THREE.Vector3( 2, 2, 2 );
    var _v = new THREE.Vector3();
    
    controls.addEventListener("change", function() {
        _v.copy(controls.target);
        controls.target.clamp(minPan, maxPan);
        _v.sub(controls.target);
        camera.position.sub(_v);
    })

https://jsfiddle.net/EliasHasle/nfswyrp4/

The problem was that camera position would drift as a consequence of clamping the padding. This is corrected by moving the camera according to the modification done by the clamping.

2 Likes

Nice solution. Very elegant.

1 Like

You are free to use it, anywhere and however you like.

this fiddle should be in three js demonstration

1 Like

I did, thanks for sharing https://github.com/ocio/three-camera-utils/blob/63a5d147075e75273ec59855887984d4849814c9/src/index.js#L38 :slight_smile:

@aswzen agree

1 Like

Good! For performance, you should create minPan and maxPan outside the update function scope, to avoid recreating them on every update. (Alone it has a negligible effect on performance, but as a habit, it will matter.) Also, defining them outside opens for modifying them (depending on how you do it. Update: Not done below.).

Something like:

export function createLimitPan({ camera, controls, THREE }) {
    const v = new THREE.Vector3();
    const minPan = new THREE.Vector3();
    const maxPan = new THREE.Vector3();
    return ({
        maxX = Infinity,
        minX = -Infinity,
        maxZ = Infinity,
        minZ = -Infinity
    }) => {
        minPan.set(minX, -Infinity, minZ)
        maxPan.set(maxX, Infinity, maxZ)
        v.copy(controls.target)
        controls.target.clamp(minPan, maxPan)
        v.sub(controls.target)
        camera.position.sub(v)
    }

Hm, why only X,Z? Pan behavior depends on the OrbitControls setting screenSpacePanning. I think it is safer to include Y too.

You are right. Forgot about the screenSpacePanning behavior.

1 Like