Skip to content

Commit

Permalink
Fix camera spinning. Add setter for pointer algo.
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Elliott committed Oct 14, 2020
1 parent 0fe83cd commit cf1ad2d
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 84 deletions.
51 changes: 4 additions & 47 deletions CameraSpinControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ CameraSpinControls = function ( camera, domElement ) {
lastPosition.copy( scope.object.position );
lastQuaternion.copy( scope.object.quaternion );
zoomChanged = false;

scope.spinControl.resetInputAfterCameraMovement(); // Don't let camera movement to ratchet mouse movement over sphere across frames

return true;

Expand Down Expand Up @@ -386,24 +388,6 @@ CameraSpinControls = function ( camera, domElement ) {

}

function handleMouseMoveRotate( event ) {

rotateEnd.set( event.clientX, event.clientY );

rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );

var element = scope.domElement === document ? scope.domElement.body : scope.domElement;

rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height

rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );

rotateStart.copy( rotateEnd );

scope.update();

}

function handleMouseMoveDolly( event ) {

//console.log( 'handleMouseMoveDolly' );
Expand Down Expand Up @@ -510,12 +494,6 @@ CameraSpinControls = function ( camera, domElement ) {

}

function handleTouchStartRotate( event ) {

rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );

}

function handleTouchStartDollyPan( event ) {

if ( scope.enableZoom ) {
Expand All @@ -540,30 +518,8 @@ CameraSpinControls = function ( camera, domElement ) {

}

function handleTouchMoveRotate( event ) {

//console.log( 'handleTouchMoveRotate' );

rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );

rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );

var element = scope.domElement === document ? scope.domElement.body : scope.domElement;

rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height

rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );

rotateStart.copy( rotateEnd );

scope.update();

}

function handleTouchMoveDollyPan( event ) {

//console.log( 'handleTouchMoveDollyPan' );

event.stopImmediatePropagation(); //Prevent other controls from working.

if ( scope.enableZoom ) {
Expand Down Expand Up @@ -869,7 +825,8 @@ CameraSpinControls = function ( camera, domElement ) {

scope.spinControl = new SpinControls( this.targetObj, 1, camera, this.domElement );
// FIXME Camera movement moves point on sphere bug.
scope.spinControl.rotateSensitivity *= -1; //Negated it to pull camera around sphere as if sphere is fixed.
scope.spinControl.rotateSensitivity *= -1; // Negated it to pull camera around sphere as if sphere is fixed.
// scope.spinControl.rotateAlgorithm = scope.spinControl.POINTER_SPHERE_MAPPING.HOLROYD; // Only Holroyd works well for camera movement at the moment

scope.domElement.addEventListener( 'touchend', onTouchEnd, true );
scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
Expand Down
101 changes: 68 additions & 33 deletions SpinControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ var SpinControls = function ( object, trackBallRadius, camera, domElement ) {
this.dampingFactor = 5; // Increase for more friction
this.spinAxisConstraint; // Set to a THREE.Vector3 to limit spinning around an axis

// Shoemake has direct touching feel of pointer on orthographically projected sphere but jumps at sphere edge.
// Holyroyd smooths between sphere and hyperbola to avoid jump at sphere edge.
// Azimuthal is something else from Yasuhiro Fujii.
this.POINTER_SPHERE_MAPPING = { SHOEMAKE: 0, HOLROYD: 1, AZIMUTHAL: 2};
this.rotateAlgorithm = this.POINTER_SPHERE_MAPPING.HOLROYD;

// Internals

this.screen = { left: 0, top: 0, width: 0, height: 0 };
Expand All @@ -34,14 +40,18 @@ var SpinControls = function ( object, trackBallRadius, camera, domElement ) {

_mousePrev = new THREE.Vector3(),
_mouseCurr = new THREE.Vector3(),
_mouseCurrNDC = new THREE.Vector2(),
_lastMouseEventTime = 0,

// Separate touch variables as might be mousing and touching at same time on laptop?
_touchPrev = new THREE.Vector3(),
_touchCurr = new THREE.Vector3(),
_touchCurrNDC = new THREE.Vector2(),
_lastTouchEventTime = 0,

_isPointerDown = false,
_isMouseDown = false,
_isTouchDown = false,

_EPS = 0.000001;

Expand Down Expand Up @@ -91,13 +101,15 @@ var SpinControls = function ( object, trackBallRadius, camera, domElement ) {

return function updateAngularVelocity( p1, p0, timeDelta ) {

// path independent rotation from Shoemake
// q0Conj.set(p0.x, p0.y, p0.z, 0.0)
// q0Conj.normalize();
// q0Conj.conjugate();
// q1.set(p1.x, p1.y, p1.z, 0.0).multiply(q0Conj); // path independent (Shoemake)
// timeDelta *= 2.0; // divide angleDelta by 2 to keep sphere under pointer. Might break algo properties. Todo: Investigate.
// q1.set(p1.x, p1.y, p1.z, 0.0).multiply(q0Conj);
// timeDelta *= 2.0; // divide angleDelta by 2 to keep sphere under pointer. Might break algorithm properties, TODO: perhaps investigate.

q1.setFromUnitVectors(p0, p1); // path dependent
// path dependent
q1.setFromUnitVectors(p0, p1);

q0.set(p0.x, p0.y, p0.z, 1.0);
angleSpeed = q1.angleTo(q0) / timeDelta;
Expand Down Expand Up @@ -185,6 +197,19 @@ var SpinControls = function ( object, trackBallRadius, camera, domElement ) {

} );


this.resetInputAfterCameraMovement = ( function () {

if( _isMouseDown ) {
_mouseCurr.copy( getPointerInSphere( getPointerInNdc( _mouseCurrNDC.x, _mouseCurrNDC.y ) ) );
}

if( _isTouchDown ) {
_touchCurr.copy( getPointerInSphere( getPointerInNdc( _touchCurrNDC.x, _touchCurrNDC.y ) ) );
}

} );

var getPointerInNdc = ( function () {

var vector = new THREE.Vector2();
Expand All @@ -208,9 +233,7 @@ var SpinControls = function ( object, trackBallRadius, camera, domElement ) {
objPos = new THREE.Vector3(),
objEdgePos = new THREE.Vector3(),
objToPointer = new THREE.Vector2(),
cameraRot = new THREE.Quaternion(),
q = new THREE.Quaternion(),
v = new THREE.Vector3();
cameraRot = new THREE.Quaternion();

return function getPointerInSphere( ndc ) {

Expand All @@ -229,48 +252,52 @@ var SpinControls = function ( object, trackBallRadius, camera, domElement ) {
// rotate to point at screen center. TODO Investigate
// objPos.z = 0;
// if(objPos.lengthSq() > 0) {
// offset.applyAxisAngle(v.set(0, 0, -1), offset.angleTo(objPos));
// offset.applyAxisAngle(point.set(0, 0, -1), offset.angleTo(objPos));
// }
offset.applyQuaternion(q.setFromRotationMatrix(_this.camera.matrixWorld));
offset.applyQuaternion(cameraRot.setFromRotationMatrix(_this.camera.matrixWorld));
objEdgePos.add(offset);
objEdgePos.project( _this.camera ); // position in ndc/screen
objEdgePos.z = 0;
objPos.z = 0;
var objRadiusNDC = objEdgePos.distanceTo(objPos);

objToPointer.x = objToPointer.x * (1 / objRadiusNDC);
objToPointer.y = (objToPointer.y * (1 / objRadiusNDC) );
objToPointer.y = objToPointer.y * (1 / objRadiusNDC);
if(_this.camera.aspect) { // Perspective camera probably
objToPointer.y /= _this.camera.aspect;
}

// Pointer mapping code below derived from https://mimosa-pudica.net/3d-rotation/
var t = objToPointer.lengthSq();
// Shoemake pointer mapping
if (t < 1.0) {
point.set(objToPointer.x, objToPointer.y, Math.sqrt(1.0 - t));
if( _this.rotateAlgorithm == _this.POINTER_SPHERE_MAPPING.HOLROYD ) {
var t = objToPointer.lengthSq();
if (t < 0.5) {
point.set(objToPointer.x, objToPointer.y, Math.sqrt(1.0 - t));
}
else {
point.set(objToPointer.x, objToPointer.y, 1.0 / (2.0 * Math.sqrt(t)));
point.normalize();
}
}
else {
objToPointer.normalize();
point.set(objToPointer.x, objToPointer.y, 0.0);
else if( _this.rotateAlgorithm == _this.POINTER_SPHERE_MAPPING.SHOEMAKE ) {
//FIXME CameraSpinControls jumps when moving off/on sphere with Shoemake mapping
var t = objToPointer.lengthSq();
if (t < 1.0) {
point.set(objToPointer.x, objToPointer.y, Math.sqrt(1.0 - t));
}
else {
objToPointer.normalize();
point.set(objToPointer.x, objToPointer.y, 0.0);
}
}
else if( _this.rotateAlgorithm == _this.POINTER_SPHERE_MAPPING.HOLROYD ) {
var t = (Math.PI / 2.0) * objToPointer.length();
var sined = t < Number.EPSILON ? 1.0 : Math.sin(t) / t;
objToPointer.multiplyScalar((Math.PI / 2.0) * sined);
point.set(objToPointer.x, objToPointer.y, Math.cos(t));
}


// Holroyd pointer mapping
// if (t < 0.5) {
// point.set(objToPointer.x, objToPointer.y, Math.sqrt(1.0 - t));
// }
// else {
// point.set(objToPointer.x, objToPointer.y, 1.0 / (2.0 * Math.sqrt(t)));
// point.normalize();
// }

// Azimuthal equidistant pointer mapping
// t = (Math.PI / 2.0) * objToPointer.length();
// var sined = t < Number.EPSILON ? 1.0 : Math.sin(t) / t;
// objToPointer.multiplyScalar((Math.PI / 2.0) * sined);
// point.set(objToPointer.x, objToPointer.y, Math.cos(t));

point.applyQuaternion(cameraRot.setFromRotationMatrix(_this.camera.matrixWorld)); // Rotate from z axis to camera direction
point.applyQuaternion(cameraRot); // Rotate from z axis to camera direction

return point;

Expand All @@ -293,9 +320,11 @@ var SpinControls = function ( object, trackBallRadius, camera, domElement ) {
_this.domElement.focus ? _this.domElement.focus() : window.focus();

_mouseCurr.copy( getPointerInSphere( getPointerInNdc( event.pageX, event.pageY ) ) );
_mouseCurrNDC.set( event.pageX, event.pageY )
_lastMouseEventTime = performance.now();
_angularVelocity.set( 0, 0, 0 );
_isPointerDown = true;
_isMouseDown = true;

document.addEventListener( 'mousemove', onMouseMove, false );
document.addEventListener( 'mouseup', onMouseUp, false );
Expand All @@ -312,7 +341,8 @@ var SpinControls = function ( object, trackBallRadius, camera, domElement ) {

_mousePrev.copy( _mouseCurr );
_mouseCurr.copy( getPointerInSphere( getPointerInNdc( event.pageX, event.pageY ) ) );

_mouseCurrNDC.set( event.pageX, event.pageY )

var currentTime = performance.now();
var deltaTime = ( currentTime - _lastMouseEventTime ) / 1000.0;
_lastMouseEventTime = currentTime;
Expand All @@ -338,6 +368,7 @@ var SpinControls = function ( object, trackBallRadius, camera, domElement ) {
}

_isPointerDown = false;
_isMouseDown = false;

document.removeEventListener( 'mousemove', onMouseMove );
document.removeEventListener( 'mouseup', onMouseUp );
Expand All @@ -356,9 +387,11 @@ var SpinControls = function ( object, trackBallRadius, camera, domElement ) {
this.handleTouchStart = function( event ) {

_touchCurr.copy( getPointerInSphere( getPointerInNdc( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ) );
_touchCurrNDC.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY )
_lastTouchEventTime = performance.now();
_angularVelocity.set( 0, 0, 0 );
_isPointerDown = true;
_isTouchDown = true;
_this.applyVelocity(); //Todo Should not be needed here

}
Expand Down Expand Up @@ -389,6 +422,7 @@ var SpinControls = function ( object, trackBallRadius, camera, domElement ) {

_touchPrev.copy( _touchCurr );
_touchCurr.copy( getPointerInSphere( getPointerInNdc( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ) );
_touchCurrNDC.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY )

var currentTime = performance.now();
var deltaTime = ( currentTime - _lastTouchEventTime ) / 1000.0;
Expand Down Expand Up @@ -417,6 +451,7 @@ var SpinControls = function ( object, trackBallRadius, camera, domElement ) {
}

_isPointerDown = false;
_isTouchDown = false;

_this.dispatchEvent( endEvent );

Expand Down
8 changes: 4 additions & 4 deletions example_camera_spin.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
var camera, scene, renderer, trackballWidget, meshMaterial, lineMaterial;
init();
render();
animate(0);
animate( 0 );

function init() {

Expand All @@ -62,12 +62,12 @@
scene.add( light );

var geometry = new THREE.TorusKnotBufferGeometry( 100, 20, 100, 16 );
var material = new THREE.MeshNormalMaterial( );
var material = new THREE.MeshNormalMaterial();
var torusKnot = new THREE.Mesh( geometry, material );
scene.add( torusKnot );

trackballWidget = new THREE.Group();
trackballWidget.position.set(0, 0, 0);
trackballWidget.position.set( 0, 0, 0 );
var geometry = new THREE.SphereBufferGeometry( 1, 8, 8 );
meshMaterial = new THREE.MeshBasicMaterial( { color: 0xaaaaff, flatShading: true, transparent: true, opacity: 0.2 } );
lineMaterial = new THREE.LineBasicMaterial( { color: 0xffffff, transparent: true, opacity: 0.5 } );
Expand All @@ -81,7 +81,7 @@
camera2.lookAt( 0, 0, 0 );
scene.add( camera2 );
var geometry = new THREE.TorusKnotBufferGeometry( 10, 2, 100, 16 );
var material = new THREE.MeshNormalMaterial( );
var material = new THREE.MeshNormalMaterial();
var torusKnot = new THREE.Mesh( geometry, material );
camera2.add( torusKnot );

Expand Down

0 comments on commit cf1ad2d

Please sign in to comment.