So, we’re working with the aforementioned starter kit as we plan to use PlayCanvas mostly for showcasing clients’ products. Unfortunately it uses hammer 1.1 in camera.js which has some problems (ie. tap doesn’t work reliably).
So we updated it to hammer 2.0.
Important:
- Update the hammer.min.js file with the latest one from here
- We don’t use panning with two fingers and we disabled it, if needed I might find the time to add it
- Of course I’m not affiliated with PlayCanvas in anyway so use it as your own risk…
- …but feel free to contribute
G.
the CODE:
var Camera = pc.createScript('camera');
Camera.attributes.add('maxElevation', {
type: 'number',
title: 'Max Elevation',
default: 70
});
// initialize code called once per entity
Camera.prototype.initialize = function() {
this.viewPos = new pc.Vec3();
this.targetViewPos = new pc.Vec3();
this.tempVec = new pc.Vec3();
this.distance = 3;
this.targetDistance = 3;
this.rotX = -180;
this.rotY = 0;
this.targetRotX = -40;
this.targetRotY = 30;
this.quatX = new pc.Quat();
this.quatY = new pc.Quat();
this.transformStarted = false;
// Disabling the context menu stops the browser disabling a menu when
// you right-click the page
this.app.mouse.disableContextMenu();
this.setBestCameraPositionForModel();
////////////////////
// Touch controls //
////////////////////
////////////////////
// Hammer 2.0 //
////////////////////
this.hammer = Hammer(this.app.graphicsDevice.canvas);
this.hammer.get('pan').set({ direction: Hammer.DIRECTION_ALL });
// Orbit (1 finger)
var cachedX, cachedY;
this.hammer.on("panstart", function (event) {
if (!this.transformStarted) {
this.dragStarted = true;
cachedX = event.center.x;
cachedY = event.center.y;
}
}.bind(this));
this.hammer.on("panend", function (event) {
if (this.dragStarted) {
this.dragStarted = false;
this.panning = false;
}
}.bind(this));
this.hammer.on("panmove", function (event) {
var dx = event.center.x - cachedX;
var dy = event.center.y - cachedY;
this.orbit(dx * 0.5, dy * 0.5);
cachedX = event.center.x;
cachedY = event.center.y;
}.bind(this));
// Zoom
this.hammer.add(new Hammer.Pinch());
this.hammer.get('pinch').set({ enable: true });
var cachedTargetDistance;
this.hammer.on("pinchstart", function (event) {
this.transformStarted = true;
cachedTargetDistance = this.targetDistance;
event.preventDefault();
this.hammer.options.drag = false;
}.bind(this));
this.hammer.on("pinchend", function (event) {
this.transformStarted = false;
this.hammer.options.drag = true;
}.bind(this));
this.hammer.on("pinchmove", function (event) {
if (this.transformStarted) {
this.targetDistance = cachedTargetDistance / event.scale;
}
}.bind(this));
this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
};
Camera.prototype.setBestCameraPositionForModel = function() {
var i, j;
// Position the camera somewhere sensible
var models = this.app.scene.getModels();
var isUnderCamera = function (mi) {
var parent = mi.node.getParent();
while (parent) {
if (parent.camera) {
return true;
}
parent = parent.getParent();
}
return false;
};
var meshInstances = [];
for (i = 0; i < models.length; i++) {
var mi = models[i].meshInstances;
for (j = 0; j < mi.length; j++) {
if (!isUnderCamera(mi[j])) {
meshInstances.push(mi[j]);
}
}
}
if (meshInstances.length > 0) {
var aabb = new pc.shape.Aabb();
aabb.copy(meshInstances[0].aabb);
for (i = 0; i < meshInstances.length; i++) {
aabb.add(meshInstances[i].aabb);
}
var focus = aabb.center;
var halfHeight = aabb.halfExtents.y;
var halfDepth = aabb.halfExtents.z;
var offset = 1.5 * halfHeight / Math.tan(0.5 * this.entity.camera.fov * Math.PI / 180.0);
this.reset(focus, offset + halfDepth);
} else {
this.reset(pc.Vec3.ZERO, 3);
}
};
Camera.prototype.reset = function(target, distance) {
this.viewPos.copy(target);
this.targetViewPos.copy(target);
this.distance = distance;
this.targetDistance = distance;
this.rotX = -180;
this.rotY = 0;
this.targetRotX = -40;
this.targetRotY = 30;
};
Camera.prototype.dolly = function (movez) {
// Dolly along the Z axis of the camera's local transform
this.targetDistance += movez;
if (this.targetDistance < 0) {
this.targetDistance = 0;
}
};
Camera.prototype.orbit = function (movex, movey) {
this.targetRotX += movex;
this.targetRotY += movey;
this.targetRotY = pc.math.clamp(this.targetRotY, -this.maxElevation, this.maxElevation);
};
Camera.prototype.onMouseWheel = function (event) {
event.event.preventDefault();
this.dolly(event.wheel * -0.25);
};
Camera.prototype.onMouseMove = function (event) {
if (event.buttons[pc.MOUSEBUTTON_LEFT]) {
this.orbit(event.dx * 0.2, event.dy * 0.2);
}
};
// update code called every frame
Camera.prototype.update = function(dt) {
if (this.app.keyboard.wasPressed(pc.KEY_SPACE)) {
this.setBestCameraPositionForModel();
}
// Implement a delay in camera controls by lerping towards a target
this.viewPos.lerp(this.viewPos, this.targetViewPos, dt / 0.1);
this.distance = pc.math.lerp(this.distance, this.targetDistance, dt / 0.2);
this.rotX = pc.math.lerp(this.rotX, this.targetRotX, dt / 0.2);
this.rotY = pc.math.lerp(this.rotY, this.targetRotY, dt / 0.2);
// Calculate the camera's rotation
this.quatX.setFromAxisAngle(pc.Vec3.RIGHT, -this.rotY);
this.quatY.setFromAxisAngle(pc.Vec3.UP, -this.rotX);
this.quatY.mul(this.quatX);
// Set the camera's current position and orientation
this.entity.setPosition(this.viewPos);
this.entity.setRotation(this.quatY);
this.entity.translateLocal(0, 0, this.distance);
};