Orbit camera local position

Hi, can we convert the orbitcamera.js from playcanvas and works on a moving model? like converting it to localposition?

var OrbitCamera = pc.createScript('orbitCamera');

OrbitCamera.attributes.add('distanceMax', {type: 'number', default: 0, title: 'Distance Max', description: 'Setting this at 0 will give an infinite distance limit'});

OrbitCamera.attributes.add('distanceMin', {type: 'number', default: 0, title: 'Distance Min'});

OrbitCamera.attributes.add('pitchAngleMax', {type: 'number', default: 90, title: 'Pitch Angle Max (degrees)'});

OrbitCamera.attributes.add('pitchAngleMin', {type: 'number', default: -90, title: 'Pitch Angle Min (degrees)'});

OrbitCamera.attributes.add('inertiaFactor', {

    type: 'number',

    default: 0,

    title: 'Inertia Factor',

    description: 'Higher value means that the camera will continue moving after the user has stopped dragging. 0 is fully responsive.'

});

OrbitCamera.attributes.add('focusEntity', {

    type: 'entity',

    title: 'Focus Entity',

    description: 'Entity for the camera to focus on. If blank, then the camera will use the whole scene'

});

OrbitCamera.attributes.add('frameOnStart', {

    type: 'boolean',

    default: true,

    title: 'Frame on Start',

    description: 'Frames the entity or scene at the start of the application."'

});

// Property to get and set the distance between the pivot point and camera

// Clamped between this.distanceMin and this.distanceMax

Object.defineProperty(OrbitCamera.prototype, "distance", {

    get: function() {

        return this._targetDistance;

    },

    set: function(value) {

        this._targetDistance = this._clampDistance(value);

    }

});

// Property to get and set the pitch of the camera around the pivot point (degrees)

// Clamped between this.pitchAngleMin and this.pitchAngleMax

// When set at 0, the camera angle is flat, looking along the horizon

Object.defineProperty(OrbitCamera.prototype, "pitch", {

    get: function() {

        return this._targetPitch;

    },

    set: function(value) {

        this._targetPitch = this._clampPitchAngle(value);

    }

});

// Property to get and set the yaw of the camera around the pivot point (degrees)

Object.defineProperty(OrbitCamera.prototype, "yaw", {

    get: function() {

        return this._targetYaw;

    },

    set: function(value) {

        this._targetYaw = value;

        // Ensure that the yaw takes the shortest route by making sure that

        // the difference between the targetYaw and the actual is 180 degrees

        // in either direction

        var diff = this._targetYaw - this._yaw;

        var reminder = diff % 360;

        if (reminder > 180) {

            this._targetYaw = this._yaw - (360 - reminder);

        } else if (reminder < -180) {

            this._targetYaw = this._yaw + (360 + reminder);

        } else {

            this._targetYaw = this._yaw + reminder;

        }

    }

});

// Property to get and set the world position of the pivot point that the camera orbits around

Object.defineProperty(OrbitCamera.prototype, "pivotPoint", {

    get: function() {

        return this._pivotPoint;

    },

    set: function(value) {

        this._pivotPoint.copy(value);

    }

});

// Moves the camera to look at an entity and all its children so they are all in the view

OrbitCamera.prototype.focus = function (focusEntity) {

    // Calculate an bounding box that encompasses all the models to frame in the camera view

    this._buildAabb(focusEntity, 0);

    var halfExtents = this._modelsAabb.halfExtents;

    var distance = Math.max(halfExtents.x, Math.max(halfExtents.y, halfExtents.z));

    distance = (distance / Math.tan(0.5 * this.entity.camera.fov * pc.math.DEG_TO_RAD));

    distance = (distance * 2);

    this.distance = distance;

    this._removeInertia();

    this._pivotPoint.copy(this._modelsAabb.center);

};

OrbitCamera.distanceBetween = new pc.Vec3();

// Set the camera position to a world position and look at a world position

// Useful if you have multiple viewing angles to swap between in a scene

OrbitCamera.prototype.resetAndLookAtPoint = function (resetPoint, lookAtPoint) {

    this.pivotPoint.copy(lookAtPoint);

    this.entity.setPosition(resetPoint);

    this.entity.lookAt(lookAtPoint);

    var distance = OrbitCamera.distanceBetween;

    distance.sub2(lookAtPoint, resetPoint);

    this.distance = distance.length();

    this.pivotPoint.copy(lookAtPoint);

    var cameraQuat = this.entity.getRotation();

    this.yaw = this._calcYaw(cameraQuat);

    this.pitch = this._calcPitch(cameraQuat, this.yaw);

    this._removeInertia();

    this._updatePosition();

};

// Set camera position to a world position and look at an entity in the scene

// Useful if you have multiple models to swap between in a scene

OrbitCamera.prototype.resetAndLookAtEntity = function (resetPoint, entity) {

    this._buildAabb(entity, 0);

    this.resetAndLookAtPoint(resetPoint, this._modelsAabb.center);

};

// Set the camera at a specific, yaw, pitch and distance without inertia (instant cut)

OrbitCamera.prototype.reset = function (yaw, pitch, distance) {

    this.pitch = pitch;

    this.yaw = yaw;

    this.distance = distance;

    this._removeInertia();

};

/////////////////////////////////////////////////////////////////////////////////////////////

// Private methods

OrbitCamera.prototype.initialize = function () {

    var self = this;

    var onWindowResize = function () {

        self._checkAspectRatio();

    };

    window.addEventListener('resize', onWindowResize, false);

    this._checkAspectRatio();

    // Find all the models in the scene that are under the focused entity

    this._modelsAabb = new pc.BoundingBox();

    this._buildAabb(this.focusEntity || this.app.root, 0);

    this.entity.lookAt(this._modelsAabb.center);

    this._pivotPoint = new pc.Vec3();

    this._pivotPoint.copy(this._modelsAabb.center);

    // Calculate the camera euler angle rotation around x and y axes

    // This allows us to place the camera at a particular rotation to begin with in the scene

    var cameraQuat = this.entity.getRotation();

    // Preset the camera

    this._yaw = this._calcYaw(cameraQuat);

    this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));

    this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);

    this._distance = 0;

    this._targetYaw = this._yaw;

    this._targetPitch = this._pitch;

    // If we have ticked focus on start, then attempt to position the camera where it frames

    // the focused entity and move the pivot point to entity's position otherwise, set the distance

    // to be between the camera position in the scene and the pivot point

    if (this.frameOnStart) {

        this.focus(this.focusEntity || this.app.root);

    } else {

        var distanceBetween = new pc.Vec3();

        distanceBetween.sub2(this.entity.getPosition(), this._pivotPoint);

        this._distance = this._clampDistance(distanceBetween.length());

    }

    this._targetDistance = this._distance;

    // Reapply the clamps if they are changed in the editor

    this.on('attr:distanceMin', function (value, prev) {

        this._targetDistance = this._clampDistance(this._distance);

    });

    this.on('attr:distanceMax', function (value, prev) {

        this._targetDistance = this._clampDistance(this._distance);

    });

    this.on('attr:pitchAngleMin', function (value, prev) {

        this._targetPitch = this._clampPitchAngle(this._pitch);

    });

    this.on('attr:pitchAngleMax', function (value, prev) {

        this._targetPitch = this._clampPitchAngle(this._pitch);

    });

    // Focus on the entity if we change the focus entity

    this.on('attr:focusEntity', function (value, prev) {

        if (this.frameOnStart) {

            this.focus(value || this.app.root);

        } else {

            this.resetAndLookAtEntity(this.entity.getPosition(), value || this.app.root);

        }

    });

    this.on('attr:frameOnStart', function (value, prev) {

        if (value) {

            this.focus(this.focusEntity || this.app.root);

        }

    });

    this.on('destroy', function() {

        window.removeEventListener('resize', onWindowResize, false);

    });

};

OrbitCamera.prototype.update = function(dt) {

    // Add inertia, if any

    var t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);

    this._distance = pc.math.lerp(this._distance, this._targetDistance, t);

    this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);

    this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);

    this._updatePosition();

};

OrbitCamera.prototype._updatePosition = function () {

    // Work out the camera position based on the pivot point, pitch, yaw and distance

    this.entity.setLocalPosition(0,0,0);

    this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);

    var position = this.entity.getPosition();

    position.copy(this.entity.forward);

    position.scale(-this._distance);

    position.add(this.pivotPoint);

    this.entity.setPosition(position);

};

OrbitCamera.prototype._removeInertia = function () {

    this._yaw = this._targetYaw;

    this._pitch = this._targetPitch;

    this._distance = this._targetDistance;

};

OrbitCamera.prototype._checkAspectRatio = function () {

    var height = this.app.graphicsDevice.height;

    var width = this.app.graphicsDevice.width;

    // Match the axis of FOV to match the aspect ratio of the canvas so

    // the focused entities is always in frame

    this.entity.camera.horizontalFov = height > width;

};

OrbitCamera.prototype._buildAabb = function (entity, modelsAdded) {

    var i = 0, j = 0, meshInstances;

   

    if (entity instanceof pc.Entity) {

        var allMeshInstances = [];

        var renders = entity.findComponents('render');

        for (i = 0; i < renders.length; ++i) {

            meshInstances = renders[i].meshInstances;

            for (j = 0; j < meshInstances.length; j++) {

                allMeshInstances.push(meshInstances[j]);

            }

        }  

        var models = entity.findComponents('model');

        for (i = 0; i < models.length; ++i) {

            meshInstances = models[i].meshInstances;

            for (j = 0; j < meshInstances.length; j++) {

                allMeshInstances.push(meshInstances[j]);

            }

        }  

        for (i = 0; i < allMeshInstances.length; i++) {

            if (modelsAdded === 0) {

                this._modelsAabb.copy(allMeshInstances[i].aabb);

            } else {

                this._modelsAabb.add(allMeshInstances[i].aabb);

            }

            modelsAdded += 1;

        }

    }

    for (i = 0; i < entity.children.length; ++i) {

        modelsAdded += this._buildAabb(entity.children[i], modelsAdded);

    }

    return modelsAdded;

};

OrbitCamera.prototype._calcYaw = function (quat) {

    var transformedForward = new pc.Vec3();

    quat.transformVector(pc.Vec3.FORWARD, transformedForward);

    return Math.atan2(-transformedForward.x, -transformedForward.z) * pc.math.RAD_TO_DEG;

};

OrbitCamera.prototype._clampDistance = function (distance) {

    if (this.distanceMax > 0) {

        return pc.math.clamp(distance, this.distanceMin, this.distanceMax);

    } else {

        return Math.max(distance, this.distanceMin);

    }

};

OrbitCamera.prototype._clampPitchAngle = function (pitch) {

    // Negative due as the pitch is inversed since the camera is orbiting the entity

    return pc.math.clamp(pitch, -this.pitchAngleMax, -this.pitchAngleMin);

};

OrbitCamera.quatWithoutYaw = new pc.Quat();

OrbitCamera.yawOffset = new pc.Quat();

OrbitCamera.prototype._calcPitch = function(quat, yaw) {

    var quatWithoutYaw = OrbitCamera.quatWithoutYaw;

    var yawOffset = OrbitCamera.yawOffset;

    yawOffset.setFromEulerAngles(0, -yaw, 0);

    quatWithoutYaw.mul2(yawOffset, quat);

    var transformedForward = new pc.Vec3();

    quatWithoutYaw.transformVector(pc.Vec3.FORWARD, transformedForward);

    return Math.atan2(transformedForward.y, -transformedForward.z) * pc.math.RAD_TO_DEG;

};

Do you want to always orbit around a moving entity? If so, you will need to update the Orbit Camera’s pivot point property (pivotPoint, line 131) every frame to the position of the moving entity.

yep, That’s my goal, can I ask how can I update the pivot line?

orbitCameraEntity.script.orbitCamera.pivotPoint = someNewPosition;
1 Like

okay, sorry I’m new to this one, so I need to create a new script “orbitcamerEntity” ? to call the new position? thank you

orbitcamerEntity is just an example variable name. This would be the entity that has the orbitCamera script on it

1 Like

got it, then I’ll add your line to this script: line 131

Object.defineProperty(OrbitCamera.prototype, "pivotPoint", {
orbitCameraEntity.script.orbitCamera.pivotPoint = someNewPosition;
    get: function() {

        return this._pivotPoint;

    },

    set: function(value) {

        this._pivotPoint.copy(value);

    }

});

Er, no that’s not the way to go :sweat_smile: Do you have an example project to work from to show you how?

1 Like

haha, my apologize. will send a public link

https://playcanvas.com/editor/scene/1459846

Actually, it probably be easier to give you an example: https://playcanvas.com/project/954420/overview/moving-target-example

Look for the follow.js script and where it’s used in the project (it’s attached to the Camera entity)

got it, thank you so much again for your help