I want to rotate an object3D with hammerjs gestures.
Basically the rotation work, but with two issues I can't figure out.
The direction of the rotation changes randomly. It turns left. I stop and than move my fingers in the same diction again and suddenly instead of continuing to rotate left its going right. Happens sometimes but not always.
Once the rotation is started it only rotates to the same direction, despite me moving my fingers to different directions.
Here is how I handle the rotation:
public rotateObject3D (e: HammerInput): void {
if (rotationEnabled) {
const translation = new THREE.Vector3();
const rotation = new THREE.Quaternion();
const scale = new THREE.Vector3();
const rotateMatrix = new THREE.Matrix4();
const deltaRotationQuaternion = new THREE.Quaternion();
this._myObject.matrix.decompose(translation, rotation, scale);
this._deltaRot = (e.rotation * 0.01);
deltaRotationQuaternion.setFromEuler(new THREE.Euler(
0,
this._deltaRot * (Math.PI / 180),
0,
'XYZ'
));
deltaRotationQuaternion.multiplyQuaternions(deltaRotationQuaternion, rotation);
this._myObject.matrix = rotateMatrix.compose(translation, deltaRotationQuaternion, scale);
}
}
And this is the call of it:
this._hammerManager.on('rotate', (e) => {
this._arTools.rotateObject3D(e);
});
Is there anything I am missing?
It looks like you're using the absolute rotation value, instead of the change in rotation. For instance, consider the following scenario:
Rotation1: 12°
Rotation2: 10°
Rotation3: 5°
By multiplying your quaternions, your object is being rotated to 12°, then 22°, then finally 27°, even though you were turning back towards 0. This is because you're adding the new rotation to the last rotation on each event.
What you should do is save the previous rotation value, and subtract it from the new one, to get the rotation delta:
var previousRot = 0;
var newRot = 0;
rotateObject3D(e) {
newRot = e.rotation - previousRot;
// You could use newRot for your quaternion calculations
// but modifying .rotation.y is simpler
myObject.rotation.y += newRot
// Save value to be used on next event
previousRot = newRot;
}
With this method, the scenario above will give you the change in rotation. Your object will first rotate by +12°, then -2°, then -5°, for a more natural behavior.
Just make sure to reset previousRot = 0 on HammerJS' rotateend event so you don't use the value from previous gestures when a new one begins.
Related
I have a run into a problem with FPS camera controls in a three.js scene. I'm using a Raycaster to determine the camera group position based on it's intersection with the scene along the Y axis (a poor man's gravity if you will =) and then apply user input to move around. The camera group position keeps getting reset to the intersection location on every frame, essentially gluing you to the spot.
I'm assuming this is either an updateMatrix() problem or a that a Vector3 is getting passed by reference somewhere, but for the life of me I can't seem to put my finger on it. I need some help... I hope this code is clear enough to help understand the problem :
renderer.setAnimationLoop((event) => {
if (clock.running) {
update();
renderer.render(scene,character.view);
}
});
clock.start();
//
const update = () => {
// velocity
const velocity = new THREE.Vector3();
velocity.x = input.controller.direction.x;
velocity.z = input.controller.direction.y;
velocity.clampLength(0,1);
if (velocity.z < 0) {
velocity.z *= 1.4;
}
// gravity
if (scene.gravity.length() > 0) {
const origin = new THREE.Vector3().copy(character.view.position);
const direction = new THREE.Vector3().copy(scene.gravity).normalize();
const intersection = new THREE.Raycaster(origin,direction).intersectObjects(scene.collision).shift();
if (intersection) {
character.group.position.copy(intersection.point);
character.group.updateMatrix();
}
}
// rotation
const rotation = new THREE.Euler();
rotation.x = input.controller.rotation.y;
rotation.y = input.controller.rotation.x;
character.group.rotation.set(0,rotation.y,0);
character.view.rotation.set(rotation.x,0,0);
// velocity.applyEuler(rotation);
const quaternion = new THREE.Quaternion();
character.group.getWorldQuaternion(quaternion);
velocity.applyQuaternion(quaternion);
// collision
const origin = new THREE.Vector3().setFromMatrixPosition(character.view.matrixWorld);
const direction = new THREE.Vector3().copy(velocity).normalize();
const raycaster = new THREE.Raycaster(origin,direction);
for (const intersection of raycaster.intersectObjects(scene.collision)) {
if (intersection.distance < 0.5) {
// face normals ignore object quaternions
const normal = new THREE.Vector3().copy(intersection.face.normal);
const matrix = new THREE.Matrix4().extractRotation(intersection.object.matrixWorld);
normal.applyMatrix4(matrix);
// normal
normal.multiplyScalar(velocity.clone().dot(normal));
velocity.sub(normal);
}
}
// step
const delta = 0.001 / clock.getDelta();
velocity.multiplyScalar(delta);
// apply
character.group.position.add(velocity);
}
The camera setup is a lot like the PointerLockControls helper, the camera being a child of a Group Object for yaw and pitch. The controller input is defined elsewhere as it can come from the mouse or a gamepad, but it returns normalized values.
To be more precise, the part that is causing the problem is here :
// gravity
if (scene.gravity.length() > 0) {
const origin = new THREE.Vector3().copy(character.view.position);
const direction = new THREE.Vector3().copy(scene.gravity).normalize();
const intersection = new THREE.Raycaster(origin,direction).intersectObjects(scene.collision).shift();
if (intersection) {
character.group.position.copy(intersection.point);
character.group.updateMatrix();
}
}
if I comment out character.group.position.copy(intersection.point);, for example, the camera moves like it's supposed to (except of course it's flying), but otherwise it moves a frame's worth of distance and then gets reset back to the intersection point on the next frame.
I have tried all manner of updateMatrix(), updateMatrixWorld(), updateProjectionMatrix(), and Object.matrixWorldNeedsUpdate = true, but alas no joy.
I apologise for using a copy/paste of my code rather than a testable case scenario. Thank you for your time.
Holy cow, I feel dumb... const origin = new THREE.Vector3().copy(character.view.position); returns local space coordinates, of course it gets reset to the origin!
replacing it with const origin = new THREE.Vector3().setFromMatrixPosition(character.view.matrixWorld); gives me the proper result
There's a lesson in there somewhere about staring blankly et your code for too long. I hope at least that this question helps someone out there one day.
I'm using three.js on a separate canvas on top of a openlayers map. the way I synchronize the view of the map and the three.js camera (which looks straight down onto the map) works fine, and looks like this:
function updateThreeCam(
group, // three.js group — has camera in it
view, // ol3 view
map // ol3 map
) {
// get visible part of map
const extent = getViewExtent(view, map);
const h = ol.extent.getHeight(extent);
const mapCenter = ol.extent.getCenter(extent);
// calculates how for away from the ground the camera has to
// be to match the currently visible part of the map
const camDist = h / (2 * Math.tan(constants.CAM_V_FOV_RAD / 2));
camera.updateProjectionMatrix(); // needed?
group.position.set(...mapCenter, camDist);
group.updateMatrixWorld();
}
however, when I am animating the flight of an object from in front of the camera down to the ground, the movement is not smooth: the vertical movement is jumping a lot. https://jsbin.com/qohutabofe/2/edit?js,output
this is the animation code:
// construct a spline to animate along
const waypoints = [
objStartPosition,
objEndPosition,
];
const pathSpline = new THREE.CatmullRomCurve3(waypoints);
// const pathSpline = new THREE.LineCurve3(...waypoints);
startTime = Date.now();
// [...]
function animate() {
const diff = Date.now() - startTime;
const t = diff / aniDuration;
// sample curve at `t`
const pos = pathSpline.getPointAt(t);
// update position
obj.position.copy(pos);
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
what is weird is that I don't really see the jumps in the sampled positions:
as you can see the map coordinates tend to be very big numbers. that's why I thought it might be a float precision issue. — dividing everything by 1000 indeed seems to help. there is just two new problems with that:
the foreshortening effect is way less drastic / not realistic anymore
panning does not work as expected anymore: the rectangle on the ground does not stay in its position
https://jsbin.com/bujepediro/1/edit?js,output
I have been developing a basic game engine just to learn the process and I have hit a issue with my rotation function.
It works fine except that the object shrinks and appears to invert.
Here is a jsfiddle that illustrates my point.
I think the problem would be in the rotation code its self but i'm not positive.
function Rotation(vec, rot){
if(Math.acos((vec.x + vec.y + vec.z -1)/2) === 0) { return vec; }
var qVec = new Quaternion(vec.x, vec.y, vec.z, 0);
qVec = Quaternions.multiply(qVec, rot);
qVec = Quaternions.multiply(qVec, rot.conjugate());
return new Vector3(qVec.x, qVec.y, qVec.z);
}
Couple things:
First, the rotation quaternion is not normalized, so it's inverse is not the same as its conjugate. Rotation by a quaternion is defined by:
Where q is the vector you're rotating around, p is the vector you're rotating, and p' is the final rotated vector.
So this is defined using the inverse of q, which is defined as conjugate(q) / magnitude(q)^2. In the case where q is normalized, magnitude(q)^2 == 1, so it's the same as just multiplying by the conjugate.
Also note the order of operations here. Quat multiplications are non-commutative so their order matters.
You can fix this by normalizing your rotation quaternion, and fixing the order of operations:
var qVec = new Quaternion(vec.x, vec.y, vec.z, 0);
qVec = Quaternions.multiply(rot.normalize(), qVec);
qVec = Quaternions.multiply(qVec, rot.conjugate());
return new Vector3(qVec.x, qVec.y, qVec.z);
Second, you want to define your rotation quat as normal to the plane you want to rotate around. In this case, you want to rotate around the x-y plane. The z-axis is normal to this plane, so we want to define the rotation vector along the z-axis:
function update(){
for(var i = 0; i < gameObjects.length; i++){
gameObjects[i].rotation = euler(new Vector3(0, 0, frames/100));
}
}
With these changes, I was able to get the box rotating correctly.
(In terms of why it was scaling up/down, I'm not 100% sure. Still trying to figure that out.)
I am trying to rotate the camera smoothly and without altering the y-vector of the camera direction, i can use look at, and it changes the camera direction in a flash, but this is not working for me, I would like a smooth transition as the direction of the camera changes. I have been reading up, and not understanding everything, but it seems to me that quaternions are the solution to this problem.
I have this.object (my camera) moving along a set path (this.spline.points). The location of the camera at any one time is (thisx,thisy, thisz)
I have cc[i] the direction vector for the direction I would like the camera to face (formerly I was using lookat(cc[i]) which changes the direction correctly, but too quickly/instantaneously)
Using info I have read, I have tried this below, and it just resulted in the screen going black at the point when the camera is due to move.
Could anyone please explain if I am on the right track, how to correct my code.
Thanks
var thisx = this.object.matrixWorld.getPosition().x.toPrecision(3);
var thisy = this.object.matrixWorld.getPosition().y.toPrecision(3);
var thisz = this.object.matrixWorld.getPosition().z.toPrecision(3);
var i = 0;
do {
var pathx = this.spline.points[i].x.toPrecision(3);
var pathz = this.spline.points[i].z.toPrecision(3);
if (thisx == pathx && thisz == pathz){
this.object.useQuaternion = true;
this.object.quaternion = new THREE.Quaternion(thisx, thisy, thisz, 1);
var newvect;
newvect.useQuaternion = true;
newvect.quaternion = new THREE.Quaternion(thisx+cc[i].x, thisy+cc[i].y, thisz+cc[i].z, 1);
var newQuaternion = new THREE.Quaternion();
THREE.Quaternion.slerp(this.object.quaternion, newvect.quaternion, newQuaternion, 0.5);
this.object.quaternion = newQuaternion;
//this.object.lookAt( cc[i]);
i = cc.length;
} else i++;
} while(i < cc.length);
There is no need to call this.object.useQuaternion = true. That is default behavior.
Also, this.object.quaternion contains the current rotation, so no need to generate that either.
You might want to try a different approach - construct the rotation matrix from the spline position, lookAt and up vectors, creating a path of quaternions as a preprocessing step:
var eye = this.spline.points[i].clone().normalize();
var center = cc[i].normalize();
var up = this.object.up.normalize();
var rotMatrix = new THREE.Matrix4().lookAt(eye, center, up);
You could then create the quaternions from the rotation matrix:
var quaternionAtSplineCoordinates = [];
quaternionAtSplineCoordinates.push(new THREE.Quaternion().setFromRotationMatrix(rotMatrix));
Once you have that path, you could apply the quaternion to the camera in your animation loop - provided you have a large enough number of samples. Otherwise, you could consider using slerp to generate the intermediate points.
I'm using Trackball controls in a scene and I want to implement a function to rotate the camera just like the way dragging the mouse in the canvas does it. How can I accomplish something like that? I've been looking the code of the Trackball control module, but I can't find something to start.
EDIT: I've been looking several pages, the THREE documentation and whatnot, but still can't reproduce the Trackball style rotation. I've been using quaternions too but they can't reproduce the behavior(or I'm missing something, most probably). Any help?
EDIT 2 : What I'm looking for is a way to do something like this:
function rotateCam(angle) { // code }
var angle = 0.01; //some value
rotateCam(angle);
$('#button').addEventListener('mousedown', function() { rotateCam(angle); } );
Where button is an HTML element representing a button.
I noticed that the Trackball controls, apart of rotate via quaternion, do a zoom to correct some distances. I tried to read the code, and got this:
function rotate(L) {
var vector = controls.target.clone();
var l = (new THREE.Vector3()).subVectors(camera.position, vector).length();
var up = camera.up.clone();
var quaternion = new THREE.Quaternion();
// Zoom correction
camera.translateZ(L - l);
quaternion.setFromAxisAngle(up, 0.015);
camera.position.applyQuaternion(quaternion);
camera.lookAt(vector);
renderer.render(scene, camera);
}
Works like a charm...hope someone find this useful too. ;)
function cameraRotate(distance, angle){
camera.position.x = distance * Math.cos( angle );
camera.position.z = distance * Math.sin( angle );
}
This would rotate your camera at the specified distance and angle around the scene. If you want a smooth rotation you could call this with a small angle increase from the animate-loop, depending on input for example.
I'm on the right way I think...I did this:
function rotate() {
if (this.showCase) {
var vector = controls.target.clone(); // controls is a TrackballControls
var up = camera.up.clone();
var quaternion = new THREE.Quaternion();
quaternion.setFromAxisAngle(up, 0.015);
camera.position.applyQuaternion(quaternion);
camera.lookAt(vector);
renderer.render(this.scene, this.camera);
}
},
Still, it doesn't look right right like the TrackballControls rotation.