Check out this video example: https://drive.google.com/file/d/18Ep4i1JMs7QvW9m-3U4oyQ4sM0CfIFzP/view
In the demonstration, I showcase how I determine the world position of a ray hitting a globe under the mouse cursor. By utilizing lookAt()
with a THREE.Group
, I am able to obtain a quaternion with the correct rotation. The red dot constantly underneath my mouse demonstrates the accuracy of this quaternion. Subsequently, starting from the quaternion representing the center of a large yellow dome, I employ rotateTowards
towards the quaternion of the mouse position (red dot) and assign this resulting quaternion as the rotation for the blue dot that follows the mouse. This method is intended to ensure the blue dot always remains attached to the dome when the mouse is further away. While it functions correctly nearer to the southern hemisphere, issues arise near the north pole where the calculation deviates from the expected great circle.
Here is the relevant code snippet:
// Using hammerjs pan events, I send an event containing the position on the sphere under the mouse to the blue sphere.
// 'event.point' value is accurate, validated by the red sphere constantly under the mouse.
this.helperGroup.lookAt(event.point); // Obtain required rotation
const p = this.helperGroup.quaternion.clone();
// 'p' represents a rotation toward the point under the mouse
const requestedDistance = dome.quaternion.angleTo(p);
if (allowedDistance >= requestedDistance) {
blueSphere.parent.lookAt(event.point);
} else {
blueSphere.parent.quaternion.copy(
dome.quaternion.clone().rotateTowards(p, allowedAngle)
);
}
// This snippet has been modified for illustrative purposes.
Update and alternative approach:
Originally, I relied on lookAt()
and rotation-based techniques to minimize mathematical complexity. However, I have now transitioned to using cartesian coordinates, normal vectors, and axis-based rotations for greater precision. Contrary to my initial belief, embracing math has proven to simplify the process significantly.
const requestedDistance = blueSphere.angleTo(event.point);
let norm = dome.position.clone().cross(event.point).normalize();
if (allowedDistance >= requestedDistance) {
blueSphere.position.set(event.point);
} else {
blueSphere.position.set(dome.position.clone()
.applyAxisAngle(norm, allowedAngle);
}