Feature - OrbitalNavigator Setting - Orbit Around Up (#2874)

* Hack to try to get desired behavior (for testing)

* Add properties to control axes of rotation and whether rotation aorund up should be enabled

* Generalze orbit function to just use an angle instead of speed and deltatime

* Move speed scale computation to a separate function

* Refactor horizontal translation code a bit

* Refactor speed computation for idel behavior

* Refactor/clean up orbit code

* Add property docs

* Add some safety checks

* Apply suggestions from code review

Co-authored-by: Alexander Bock <alexander.bock@liu.se>

* More updates based on code review

---------

Co-authored-by: Alexander Bock <alexander.bock@liu.se>
This commit is contained in:
Emma Broman
2023-09-11 10:44:12 +02:00
committed by GitHub
parent aad8beb60c
commit 7bfedbdf12
2 changed files with 193 additions and 90 deletions

View File

@@ -233,6 +233,15 @@ private:
properties::BoolProperty _invertMouseButtons;
properties::BoolProperty _shouldRotateAroundUp;
enum class UpDirectionChoice {
XAxis = 0,
YAxis,
ZAxis
};
properties::OptionProperty _upToUseForRotation;
MouseCameraStates _mouseStates;
JoystickCameraStates _joystickStates;
WebsocketCameraStates _websocketStates;
@@ -322,6 +331,15 @@ private:
double interpolateCameraToSurfaceDistance(double deltaTime, double currentDistance,
double targetDistance);
/**
* Modify the camera position and global rotation to rotate around the up vector
* of the current anchor based on x-wise input
*
* The up-vector to rotate around is determined by the "_upToUseForRotation" property
*/
void rotateAroundAnchorUp(double deltaTime, double speedScale,
glm::dvec3& cameraPosition, glm::dquat& globalCameraRotation);
/**
* Translates the horizontal direction. If far from the anchor object, this will
* result in an orbital rotation around the object. This function does not affect the
@@ -329,8 +347,9 @@ private:
*
* \return a position vector adjusted in the horizontal direction.
*/
glm::dvec3 translateHorizontally(double deltaTime, const glm::dvec3& cameraPosition,
const glm::dvec3& objectPosition, const glm::dquat& globalCameraRotation,
glm::dvec3 translateHorizontally(double deltaTime, double speedScale,
const glm::dvec3& cameraPosition, const glm::dvec3& objectPosition,
const glm::dquat& globalCameraRotation,
const SurfacePositionHandle& positionHandle) const;
/*
@@ -410,13 +429,11 @@ private:
*
* Used for IdleBehavior::Behavior::Orbit
*
* \param deltaTime The time step to use for the motion. Controls the rotation angle
* \param angle The rotation angle to use for the motion
* \param position The position of the camera. Will be changed by the function
* \param globalRotation The camera's global rotation. Will be changed by the function
* \param speedScale A speed scale that controls the speed of the motion
*/
void orbitAnchor(double deltaTime, glm::dvec3& position,
glm::dquat& globalRotation, double speedScale);
void orbitAnchor(double angle, glm::dvec3& position, glm::dquat& globalRotation);
/**
* Orbit the current anchor node, by adding a rotation around the given axis. For
@@ -429,13 +446,15 @@ private:
* IdleBehavior::Behavior::OrbitAroundUp (axis = up = y-axis)
*
* \param axis The axis to arbit around, given in model coordinates of the anchor
* \param deltaTime The time step to use for the motion. Controls the rotation angle
* \param angle The rotation angle to use for the motion
* \param position The position of the camera. Will be changed by the function
* \param globalRotation The camera's global rotation. Will be changed by the function
* \param speedScale A speed scale that controls the speed of the motion
*/
void orbitAroundAxis(const glm::dvec3 axis, double deltaTime, glm::dvec3& position,
glm::dquat& globalRotation, double speedScale);
void orbitAroundAxis(const glm::dvec3 axis, double angle, glm::dvec3& position,
glm::dquat& globalRotation);
double rotationSpeedScaleFromCameraHeight(const glm::dvec3& cameraPosition,
const SurfacePositionHandle& positionHandle) const;
};
} // namespace openspace::interaction

View File

@@ -393,6 +393,24 @@ namespace {
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo ShouldRotateAroundUpInfo = {
"ShouldRotateAroundUp",
"Should Rotate Around Up",
"When set to true, global rotation interactions in the X-direction will lead to "
"a rotation around the specified up vector instead of just around the object. "
"The up vector is the local coordinate axis, and can be set to either the X-, Y- "
"or Z-axis through the 'UpToUseForRotation' property",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo UpToUseForRotationInfo = {
"UpToUseForRotation",
"Up To Use For Rotation",
"Specifies the local coordinate axis of the anchor node to use as up direction "
"when the camera is set to orbit around up. In general, the Z-axis is a good "
"choice for globes, and the Y-axis is a good choice for models",
openspace::properties::Property::Visibility::AdvancedUser
};
} // namespace
namespace openspace::interaction {
@@ -498,6 +516,8 @@ OrbitalNavigator::OrbitalNavigator()
, _websocketStates(_websocketSensitivity, 1 / (_friction.friction + 0.0000001))
, _disableZoom(DisableZoomInfo, false)
, _disableRoll(DisableRollInfo, false)
, _shouldRotateAroundUp(ShouldRotateAroundUpInfo, false)
, _upToUseForRotation(UpToUseForRotationInfo)
{
_anchor.onChange([this]() {
if (_anchor.value().empty()) {
@@ -698,6 +718,15 @@ OrbitalNavigator::OrbitalNavigator()
LINFO("Camera roll has been enabled");
}
});
addProperty(_shouldRotateAroundUp);
_upToUseForRotation.addOptions({
{ static_cast<int>(UpDirectionChoice::XAxis), "Local X" },
{ static_cast<int>(UpDirectionChoice::YAxis), "Local Y" },
{ static_cast<int>(UpDirectionChoice::ZAxis), "Local Z" }
});
_upToUseForRotation = static_cast<int>(UpDirectionChoice::ZAxis);
addProperty(_upToUseForRotation);
}
glm::dvec3 OrbitalNavigator::anchorNodeToCameraVector() const {
@@ -864,9 +893,25 @@ void OrbitalNavigator::updateCameraStateFromStates(double deltaTime) {
camRot.localRotation = interpolateLocalRotation(deltaTime, camRot.localRotation);
camRot.localRotation = rotateLocally(deltaTime, camRot.localRotation);
double horizontalTranslationSpeedScale =
rotationSpeedScaleFromCameraHeight(pose.position, posHandle);
// Rotation around target object's up vector based on user input
// (one kind of horizontal translation)
// Affects the position and global rotation
if (_shouldRotateAroundUp) {
rotateAroundAnchorUp(
deltaTime,
horizontalTranslationSpeedScale,
pose.position,
camRot.globalRotation
);
}
// Horizontal translation based on user input
pose.position = translateHorizontally(
deltaTime,
horizontalTranslationSpeedScale,
pose.position,
anchorPos,
camRot.globalRotation,
@@ -1583,72 +1628,68 @@ double OrbitalNavigator::interpolateCameraToSurfaceDistance(double deltaTime,
return result;
}
glm::dvec3 OrbitalNavigator::translateHorizontally(double deltaTime,
void OrbitalNavigator::rotateAroundAnchorUp(double deltaTime, double speedScale,
glm::dvec3& cameraPosition,
glm::dquat& globalCameraRotation)
{
const glm::dvec3 axis = [](UpDirectionChoice upAxis) {
switch (upAxis) {
case UpDirectionChoice::XAxis:
return glm::dvec3(1.0, 0.0, 0.0);
case UpDirectionChoice::YAxis:
return glm::dvec3(0.0, 1.0, 0.0);
case UpDirectionChoice::ZAxis:
return glm::dvec3(0.0, 0.0, 1.0);
default:
throw ghoul::MissingCaseException();
}
}(UpDirectionChoice(_upToUseForRotation.value()));
double combinedXInput = _mouseStates.globalRotationVelocity().x +
_joystickStates.globalRotationVelocity().x +
_websocketStates.globalRotationVelocity().x +
_scriptStates.globalRotationVelocity().x;
double angle = combinedXInput * deltaTime * speedScale;
orbitAroundAxis(axis, angle, cameraPosition, globalCameraRotation);
}
glm::dvec3 OrbitalNavigator::translateHorizontally(double deltaTime, double speedScale,
const glm::dvec3& cameraPosition,
const glm::dvec3& objectPosition,
const glm::dquat& globalCameraRotation,
const SurfacePositionHandle& positionHandle) const
{
const glm::dmat4 modelTransform = _anchorNode->modelTransform();
// If we are orbiting around an up vector, we only want to allow verical
// movement and not use the x velocity
bool useX = !_shouldRotateAroundUp;
const glm::dvec3 outDirection = glm::normalize(glm::dmat3(modelTransform) *
positionHandle.referenceSurfaceOutDirection);
// Vector logic
const glm::dvec3 posDiff = cameraPosition - objectPosition;
const glm::dvec3 centerToActualSurfaceModelSpace =
positionHandle.centerToReferenceSurface +
positionHandle.referenceSurfaceOutDirection * positionHandle.heightToSurface;
const glm::dvec3 centerToActualSurface =
glm::dmat3(modelTransform) * centerToActualSurfaceModelSpace;
const glm::dvec3 actualSurfaceToCamera = posDiff - centerToActualSurface;
const double distFromSurfaceToCamera = [&]() {
if (_constantVelocityFlight) {
const glm::dvec3 centerToRefSurface =
glm::dmat3(modelTransform) * positionHandle.centerToReferenceSurface;
const glm::dvec3 refSurfaceToCamera = posDiff - centerToRefSurface;
return glm::length(refSurfaceToCamera);
}
else {
return glm::length(actualSurfaceToCamera);
}
}();
// Final values to be used
const double distFromCenterToSurface = glm::length(centerToActualSurface);
const double distFromCenterToCamera = glm::length(posDiff);
double speedScale =
distFromCenterToSurface > 0.0 ?
glm::clamp(distFromSurfaceToCamera / distFromCenterToSurface, 0.0, 1.0) :
1.0;
double angleScale = deltaTime * speedScale;
// Get rotation in camera space
const glm::dquat mouseRotationDiffCamSpace = glm::dquat(glm::dvec3(
-_mouseStates.globalRotationVelocity().y * deltaTime,
-_mouseStates.globalRotationVelocity().x * deltaTime,
0.0) * speedScale);
-_mouseStates.globalRotationVelocity().y,
useX ? -_mouseStates.globalRotationVelocity().x : 0.0,
0.0
) * angleScale);
const glm::dquat joystickRotationDiffCamSpace = glm::dquat(glm::dvec3(
-_joystickStates.globalRotationVelocity().y * deltaTime,
-_joystickStates.globalRotationVelocity().x * deltaTime,
0.0) * speedScale
);
-_joystickStates.globalRotationVelocity().y,
useX ? -_joystickStates.globalRotationVelocity().x : 0.0,
0.0
) * angleScale);
const glm::dquat scriptRotationDiffCamSpace = glm::dquat(glm::dvec3(
-_scriptStates.globalRotationVelocity().y * deltaTime,
-_scriptStates.globalRotationVelocity().x * deltaTime,
0.0) * speedScale
);
-_scriptStates.globalRotationVelocity().y,
useX ? -_scriptStates.globalRotationVelocity().x : 0.0,
0.0
) * angleScale);
const glm::dquat websocketRotationDiffCamSpace = glm::dquat(glm::dvec3(
-_websocketStates.globalRotationVelocity().y * deltaTime,
-_websocketStates.globalRotationVelocity().x * deltaTime,
0.0) * speedScale
);
-_websocketStates.globalRotationVelocity().y,
useX ? -_websocketStates.globalRotationVelocity().x : 0.0,
0.0
) * angleScale);
// Transform to world space
const glm::dquat rotationDiffWorldSpace = globalCameraRotation *
@@ -1656,10 +1697,18 @@ glm::dvec3 OrbitalNavigator::translateHorizontally(double deltaTime,
websocketRotationDiffCamSpace * scriptRotationDiffCamSpace *
glm::inverse(globalCameraRotation);
const glm::dmat4 modelTransform = _anchorNode->modelTransform();
const glm::dvec3 outDirection = glm::normalize(
glm::dmat3(modelTransform) *
positionHandle.referenceSurfaceOutDirection
);
// Compute the vector to rotate to find the new position
const double distFromCenterToCamera = glm::length(cameraPosition - objectPosition);
const glm::dvec3 outVector = distFromCenterToCamera * outDirection;
// Rotate and find the difference vector
const glm::dvec3 rotationDiffVec3 =
(distFromCenterToCamera * outDirection) * rotationDiffWorldSpace -
(distFromCenterToCamera * outDirection);
const glm::dvec3 rotationDiffVec3 = outVector * rotationDiffWorldSpace - outVector;
// Add difference to position
return cameraPosition + rotationDiffVec3;
@@ -1908,22 +1957,8 @@ void OrbitalNavigator::applyIdleBehavior(double deltaTime, glm::dvec3& position,
SurfacePositionHandle posHandle =
calculateSurfacePositionHandle(*_anchorNode, position);
const glm::dvec3 centerToActualSurfaceModelSpace =
posHandle.centerToReferenceSurface +
posHandle.referenceSurfaceOutDirection * posHandle.heightToSurface;
const glm::dvec3 centerToActualSurface = glm::dmat3(_anchorNode->modelTransform()) *
centerToActualSurfaceModelSpace;
const glm::dvec3 centerToCamera = position - _anchorNode->worldPosition();
const glm::dvec3 actualSurfaceToCamera = centerToCamera - centerToActualSurface;
const double distFromSurfaceToCamera = glm::length(actualSurfaceToCamera);
const double distFromCenterToSurface = glm::length(centerToActualSurface);
double speedScale =
distFromCenterToSurface > 0.0 ?
glm::clamp(distFromSurfaceToCamera / distFromCenterToSurface, 0.0, 1.0) :
1.0; // same as horizontal translation
// Same speed scale as horizontal translation
double speedScale = rotationSpeedScaleFromCameraHeight(position, posHandle);
speedScale *= _idleBehavior.speedScaleFactor;
speedScale *= 0.05; // without this scaling, the motion is way too fast
@@ -1936,6 +1971,8 @@ void OrbitalNavigator::applyIdleBehavior(double deltaTime, glm::dvec3& position,
double s = _idleBehaviorDampenInterpolator.value();
speedScale *= _invertIdleBehaviorInterpolation ? (1.0 - s) : s;
double angle = deltaTime * speedScale;
// Apply the chosen behavior
const IdleBehavior::Behavior choice = _idleBehavior.chosenBehavior.value_or(
static_cast<IdleBehavior::Behavior>(_idleBehavior.defaultBehavior.value())
@@ -1943,7 +1980,7 @@ void OrbitalNavigator::applyIdleBehavior(double deltaTime, glm::dvec3& position,
switch (choice) {
case IdleBehavior::Behavior::Orbit:
orbitAnchor(deltaTime, position, globalRotation, speedScale);
orbitAnchor(angle, position, globalRotation);
break;
case IdleBehavior::Behavior::OrbitAtConstantLat: {
// Assume that "north" coincides with the local z-direction
@@ -1951,13 +1988,13 @@ void OrbitalNavigator::applyIdleBehavior(double deltaTime, glm::dvec3& position,
// north/up, so that we can query this information rather than assuming it.
// The we could also combine this idle behavior with the next
const glm::dvec3 north = glm::dvec3(0.0, 0.0, 1.0);
orbitAroundAxis(north, deltaTime, position, globalRotation, speedScale);
orbitAroundAxis(north, angle, position, globalRotation);
break;
}
case IdleBehavior::Behavior::OrbitAroundUp: {
// Assume that "up" coincides with the local y-direction
const glm::dvec3 up = glm::dvec3(0.0, 1.0, 0.0);
orbitAroundAxis(up, deltaTime, position, globalRotation, speedScale);
orbitAroundAxis(up, angle, position, globalRotation);
break;
}
default:
@@ -1965,15 +2002,15 @@ void OrbitalNavigator::applyIdleBehavior(double deltaTime, glm::dvec3& position,
}
}
void OrbitalNavigator::orbitAnchor(double deltaTime, glm::dvec3& position,
glm::dquat& globalRotation, double speedScale)
void OrbitalNavigator::orbitAnchor(double angle, glm::dvec3& position,
glm::dquat& globalRotation)
{
ghoul_assert(_anchorNode != nullptr, "Node to orbit must be set");
// Apply a rotation to the right, in camera space
// (Maybe we should also let the user decide which direction to rotate?
// Or provide a few different orbit options)
const glm::dvec3 eulerAngles = glm::dvec3(0.0, -1.0, 0.0) * deltaTime * speedScale;
const glm::dvec3 eulerAngles = glm::dvec3(0.0, -1.0, 0.0) * angle;
const glm::dquat rotationDiffCameraSpace = glm::dquat(eulerAngles);
const glm::dquat rotationDiffWorldSpace = globalRotation *
@@ -1988,18 +2025,20 @@ void OrbitalNavigator::orbitAnchor(double deltaTime, glm::dvec3& position,
position += rotationDiffVec3;
}
void OrbitalNavigator::orbitAroundAxis(const glm::dvec3 axis, double deltaTime,
glm::dvec3& position, glm::dquat& globalRotation,
double speedScale)
void OrbitalNavigator::orbitAroundAxis(const glm::dvec3 axis, double angle,
glm::dvec3& position, glm::dquat& globalRotation)
{
ghoul_assert(_anchorNode != nullptr, "Node to orbit must be set");
if (glm::abs(angle) < AngleEpsilon) {
return;
}
const glm::dmat4 modelTransform = _anchorNode->modelTransform();
const glm::dvec3 axisInWorldSpace =
glm::normalize(glm::dmat3(modelTransform) * glm::normalize(axis));
// Compute rotation to be applied around the axis
double angle = deltaTime * speedScale;
const glm::dquat spinRotation = glm::angleAxis(angle, axisInWorldSpace);
// Rotate the position vector from the center to camera and update position
@@ -2007,6 +2046,10 @@ void OrbitalNavigator::orbitAroundAxis(const glm::dvec3 axis, double deltaTime,
const glm::dvec3 rotationDiffVec3 =
spinRotation * anchorCenterToCamera - anchorCenterToCamera;
if (glm::length(rotationDiffVec3) == 0.0) {
return;
}
position += rotationDiffVec3;
// Also apply the rotation to the global rotation, so the camera up vector is
@@ -2014,6 +2057,47 @@ void OrbitalNavigator::orbitAroundAxis(const glm::dvec3 axis, double deltaTime,
globalRotation = spinRotation * globalRotation;
}
double OrbitalNavigator::rotationSpeedScaleFromCameraHeight(
const glm::dvec3& cameraPosition,
const SurfacePositionHandle& positionHandle) const
{
const glm::dmat4 modelTransform = _anchorNode->modelTransform();
const glm::dvec3 anchorPos = _anchorNode->worldPosition();
const glm::dvec3 outDirection = glm::normalize(
glm::dmat3(modelTransform) *
positionHandle.referenceSurfaceOutDirection
);
const glm::dvec3 posDiff = cameraPosition - anchorPos;
const glm::dvec3 centerToActualSurfaceModelSpace =
positionHandle.centerToReferenceSurface +
positionHandle.referenceSurfaceOutDirection * positionHandle.heightToSurface;
const glm::dvec3 centerToActualSurface =
glm::dmat3(modelTransform) * centerToActualSurfaceModelSpace;
const glm::dvec3 actualSurfaceToCamera = posDiff - centerToActualSurface;
const double distFromSurfaceToCamera = [&]() {
if (_constantVelocityFlight) {
const glm::dvec3 centerToRefSurface =
glm::dmat3(modelTransform) * positionHandle.centerToReferenceSurface;
const glm::dvec3 refSurfaceToCamera = posDiff - centerToRefSurface;
return glm::length(refSurfaceToCamera);
}
else {
return glm::length(actualSurfaceToCamera);
}
}();
const double distFromCenterToSurface = glm::length(centerToActualSurface);
return distFromCenterToSurface > 0.0 ?
glm::clamp(distFromSurfaceToCamera / distFromCenterToSurface, 0.0, 1.0) :
1.0;
}
scripting::LuaLibrary OrbitalNavigator::luaLibrary() {
return {
"orbitalnavigation",