Trying out two-finger touch events to pinch, rotate, and zoom a THREE.Mesh
object using quaternions has been quite an interesting experience. As I delve into this new rotation method, I've noticed an intriguing behavior that puzzles me. When I rotate the object by about 90° through total touch drag, the rotation starts to jitter erratically. However, upon dragging it back under 90°, the rotation gradually smoothens out, albeit in a noticeably nonlinear manner. It's almost as if there's a hidden pattern behind this jittery rotation (or maybe it’s just a guess).
As I scratch my head over this, here's the code snippet I'm currently using to handle the rotation of the obj
:
// global variables
var
// stores the original object scale
pinchScale = new THREE.Vector3(),
// tracks the first touch
touch1 = new THREE.Vector2(),
// tracks the second touch
touch2 = new THREE.Vector2(),
// records the initial position of the first touch
touch1OnHold = new THREE.Vector2(),
// records the initial position of the second touch
touch2OnHold = new THREE.Vector2(),
// keeps track of the total rotation during the last touchmove event
angularHoldPrev = new THREE.Quaternion();
⋮
// key-value pairs within an addEventListener function
touchstart: function (event) {
event.preventDefault();
if ( event.touches.length === 1 ) {
…
} else if ( event.touches.length === 2 ) {
touch1OnHold.set(event.touches[0].pageX, event.touches[0].pageY);
touch2OnHold.set(event.touches[1].pageX, event.touches[1].pageY);
angularHoldPrev.set(0, 0, 0, 1)
}
},
touchmove: function (event) {
event.preventDefault();
if ( event.touches.length === 1 ) {
…
} else if ( event.touches.length === 2 ) {
touch1.set(event.touches[0].pageX, event.touches[0].pageY);
touch2.set(event.touches[1].pageX, event.touches[1].pageY);
var
// calculate the spread between the two touches at the current event and at the start of the hold
touchDiff = touch2.clone().sub(touch1),
touchDiffOnHold = touch2OnHold.clone().sub(touch1OnHold),
// determine the axis of the camera regardless of the object's orientation
axis1 = new THREE.Vector3(0, 0, 1).applyQuaternion(obj.quaternion.clone().inverse()),
// calculate the rotation around this axis based on the touch movement
rot1 = new THREE.Quaternion().setFromAxisAngle(axis1, (Math.atan2(touchDiffOnHold.y, touchDiffOnHold.x) - Math.atan2(touchDiff.y, touchDiff.x))).normalize(),
// calculate the barycenter of the touch at the present event and the start of the hold
touchCentre = touch1.clone().add(touch2).multiplyScalar(.5),
touchCentreOnHold = touch1OnHold.clone().add(touch2OnHold).multiplyScalar(.5),
// determine the axis of touch barycenter movement on the xy plane regardless of the object orientation
axis2 = new THREE.Vector3(touchCentre.y - touchCentreOnHold.y, touchCentre.x - touchCentreOnHold.x, 0).applyQuaternion(obj.quaternion.clone().inverse()),
// calculate a rotation proportionate to the magnitude of touch movement
rot2 = new THREE.Quaternion().setFromAxisAngle(axis2, axis2.length() * rotationSensitivity).normalize(),
// combine the two rotations
rot = rot1.multiply(rot2);
// reverse the last rotation if it's not an empty quaternion
if (!angularHoldPrev.equals(new THREE.Quaternion())) obj.quaternion.multiply(angularHoldPrev.inverse());
// apply the calculated rotation
obj.quaternion.multiply(rot);
// store this rotation for the next event
angularHoldPrev.copy(rot);
// adjust the object's scale based on the change in touch spread
obj.scale.copy(pinchScale.clone().multiplyScalar(touchDiff.length() / touchDiffOnHold.length()))
}
},
touchend: function (event) {
event.preventDefault();
// restore the original object scale
pinchScale.copy(obj.scale)
}
If anyone has insights on how I can achieve consistent and proportional rotation with all two-touch inputs, I'd be grateful for your help. Thanks!