diff --git a/include/openspace/navigation/path.h b/include/openspace/navigation/path.h index e3de95defd..a376684cd4 100644 --- a/include/openspace/navigation/path.h +++ b/include/openspace/navigation/path.h @@ -87,6 +87,13 @@ public: */ bool hasReachedEnd() const; + /** + * Compute the interpolated camera pose at a certain distance along a *linear* + * path. Note that the linear path is a special case, to avoid risks of precision + * problems for long paths + */ + CameraPose linearInterpolatedPose(double distance, double displacement); + /** * Compute the interpolated camera pose at a certain distance along the path */ diff --git a/include/openspace/navigation/pathnavigator.h b/include/openspace/navigation/pathnavigator.h index 8e4801ae3f..9b30744546 100644 --- a/include/openspace/navigation/pathnavigator.h +++ b/include/openspace/navigation/pathnavigator.h @@ -59,6 +59,7 @@ public: const Path* currentPath() const; double speedScale() const; double arrivalDistanceFactor() const; + float linearRotationSpeedFactor() const; bool hasCurrentPath() const; bool hasFinished() const; @@ -103,6 +104,7 @@ private: properties::FloatProperty _speedScale; properties::BoolProperty _applyIdleBehaviorOnFinish; properties::DoubleProperty _arrivalDistanceFactor; + properties::FloatProperty _linearRotationSpeedFactor; properties::DoubleProperty _minValidBoundingSphere; properties::StringListProperty _relevantNodeTags; diff --git a/src/navigation/path.cpp b/src/navigation/path.cpp index 1c991fb2c8..9bbe6e3450 100644 --- a/src/navigation/path.cpp +++ b/src/navigation/path.cpp @@ -40,6 +40,7 @@ #include #include #include +#include namespace { constexpr const char _loggerCat[] = "Path"; @@ -131,7 +132,6 @@ Path::Path(Waypoint start, Waypoint end, Type type, // We now know how long it took to traverse the path. Use that _speedFactorFromDuration = _progressedTime / *duration; - resetPlaybackVariables(); } } @@ -161,23 +161,7 @@ CameraPose Path::traversePath(double dt, float speedScale) { if (_type == Type::Linear) { // Special handling of linear paths, so that it can be used when we are // traversing very large distances without introducing precision problems - const glm::dvec3 prevPosToEnd = _prevPose.position - _end.position(); - const double remainingDistance = glm::length(prevPosToEnd); - - // Actual displacement may not be bigger than remaining distance - if (displacement > remainingDistance) { - displacement = remainingDistance; - _traveledDistance = pathLength(); - _shouldQuit = true; - return _end.pose(); - } - - // Just move along the line from the current position to the target - newPose.position = _prevPose.position - - displacement * glm::normalize(prevPosToEnd); - - const double relativeDistance = _traveledDistance / pathLength(); - newPose.rotation = interpolateRotation(relativeDistance); + newPose = linearInterpolatedPose(_traveledDistance, displacement); } else { if (std::abs(prevDistance - _traveledDistance) < LengthEpsilon) { @@ -204,7 +188,14 @@ bool Path::hasReachedEnd() const { return true; } - return (_traveledDistance / pathLength()) >= 1.0; + bool isPositionFinished = (_traveledDistance / pathLength()) >= 1.0; + bool isRotationFinished = glm::all(glm::equal( + _prevPose.rotation, + _end.rotation(), + glm::epsilon() + )); + + return isPositionFinished && isRotationFinished; } void Path::resetPlaybackVariables() { @@ -214,6 +205,28 @@ void Path::resetPlaybackVariables() { _shouldQuit = false; } +CameraPose Path::linearInterpolatedPose(double distance, double displacement) { + ghoul_assert(_type == Type::Linear, "Path type must be linear"); + const double relativeDistance = distance / pathLength(); + const glm::dvec3 prevPosToEnd = _prevPose.position - _end.position(); + const double remainingDistance = glm::length(prevPosToEnd); + CameraPose pose; + + // Actual displacement may not be bigger than remaining distance + if (displacement > remainingDistance) { + _traveledDistance = pathLength(); + pose.position = _end.position(); + } + else { + // Just move along line from the current position to the target + const glm::dvec3 lineDir = glm::normalize(prevPosToEnd); + pose.position = _prevPose.position - displacement * lineDir; + } + + pose.rotation = linearPathRotation(relativeDistance); + return pose; +} + CameraPose Path::interpolatedPose(double distance) const { const double relativeDistance = distance / pathLength(); CameraPose cs; @@ -227,6 +240,8 @@ glm::dquat Path::interpolateRotation(double t) const { case Type::AvoidCollision: return easedSlerpRotation(t); case Type::Linear: + // @TODO (2022-03-29, emmbr) Fix so that rendering the rotation of linear + // paths works again. I.e. openspace.debugging.renderCameraPath return linearPathRotation(t); case Type::ZoomOutOverview: case Type::AvoidCollisionWithLookAt: @@ -243,45 +258,59 @@ glm::dquat Path::easedSlerpRotation(double t) const { } glm::dquat Path::linearPathRotation(double t) const { - const double tHalf = 0.5; + const glm::dvec3 a = ghoul::viewDirection(_start.rotation()); + const glm::dvec3 b = ghoul::viewDirection(_end.rotation()); + const double angle = std::acos(glm::dot(a, b)); // assumes length 1.0 for a & b - const glm::dvec3 endNodePos = _end.node()->worldPosition(); - const glm::dvec3 endUp = _end.rotation() * glm::dvec3(0.0, 1.0, 0.0); + // Seconds per pi angles. Per default, it takes 5 seconds to turn 90 degrees + double factor = 5.0 / glm::half_pi(); + factor *= global::navigationHandler->pathNavigator().linearRotationSpeedFactor(); - if (t < tHalf) { - // Interpolate to look at target - const glm::dvec3 halfWayPosition = _curve->positionAt(tHalf); - const glm::dquat q = ghoul::lookAtQuaternion(halfWayPosition, endNodePos, endUp); + double turnDuration = std::max(angle * factor, 1.0); // Always at least 1 second + const double time = glm::clamp(_progressedTime / turnDuration, 0.0, 1.0); + return easedSlerpRotation(time); - const double tScaled = ghoul::sineEaseInOut(t / tHalf); - return glm::slerp(_start.rotation(), q, tScaled); - } + // @TODO (2022-03-18, emmbr) Leaving this for now, as something similar might have to + // be implemented for navigation states. But should be removed/reimplemented - // This distance is guaranteed to be strictly decreasing for linear paths - const double distanceToEnd = glm::distance(_prevPose.position, _end.position()); + //const glm::dvec3 endNodePos = _end.node()->worldPosition(); + //const glm::dvec3 endUp = _end.rotation() * glm::dvec3(0.0, 1.0, 0.0); - // Determine the distance at which to start interpolating to the target rotation. - // The magic numbers here are just randomly picked constants, set to make the - // resulting rotation look ok-ish - double closingUpDistance = 10.0 * _end.validBoundingSphere(); - if (pathLength() < 2.0 * closingUpDistance) { - closingUpDistance = 0.2 * pathLength(); - } + //const double tHalf = 0.5; + //if (t < tHalf) { + // // Interpolate to look at target + // const glm::dvec3 halfWayPosition = _curve->positionAt(tHalf); + // const glm::dquat q = ghoul::lookAtQuaternion(halfWayPosition, endNodePos, endUp); - if (distanceToEnd < closingUpDistance) { - // Interpolate to target rotation - const double tScaled = ghoul::sineEaseInOut(1.0 - distanceToEnd / closingUpDistance); + // const double tScaled = ghoul::sineEaseInOut(t / tHalf); + // return glm::slerp(_start.rotation(), q, tScaled); + //} - // Compute a position in front of the camera at the end orientation - const double inFrontDistance = glm::distance(_end.position(), endNodePos); - const glm::dvec3 viewDir = ghoul::viewDirection(_end.rotation()); - const glm::dvec3 inFrontOfEnd = _end.position() + inFrontDistance * viewDir; - const glm::dvec3 lookAtPos = ghoul::interpolateLinear(tScaled, endNodePos, inFrontOfEnd); - return ghoul::lookAtQuaternion(_prevPose.position, lookAtPos, endUp); - } + //// This distance is guaranteed to be strictly decreasing for linear paths + //const double distanceToEnd = glm::distance(_prevPose.position, _end.position()); - // Keep looking at the end node - return ghoul::lookAtQuaternion(_prevPose.position, endNodePos, endUp); + //// Determine the distance at which to start interpolating to the target rotation. + //// The magic numbers here are just randomly picked constants, set to make the + //// resulting rotation look ok-ish + //double closingUpDistance = 10.0 * _end.validBoundingSphere(); + //if (pathLength() < 2.0 * closingUpDistance) { + // closingUpDistance = 0.2 * pathLength(); + //} + + //if (distanceToEnd < closingUpDistance) { + // // Interpolate to target rotation + // const double tScaled = ghoul::sineEaseInOut(1.0 - distanceToEnd / closingUpDistance); + + // // Compute a position in front of the camera at the end orientation + // const double inFrontDistance = glm::distance(_end.position(), endNodePos); + // const glm::dvec3 viewDir = ghoul::viewDirection(_end.rotation()); + // const glm::dvec3 inFrontOfEnd = _end.position() + inFrontDistance * viewDir; + // const glm::dvec3 lookAtPos = ghoul::interpolateLinear(tScaled, endNodePos, inFrontOfEnd); + // return ghoul::lookAtQuaternion(_prevPose.position, lookAtPos, endUp); + //} + + //// Keep looking at the end node + //return ghoul::lookAtQuaternion(_prevPose.position, endNodePos, endUp); } glm::dquat Path::lookAtTargetsRotation(double t) const { @@ -350,8 +379,17 @@ double Path::speedAlongPath(double traveledDistance) const { double startUpDistance = DampenDistanceFactor * _start.validBoundingSphere(); double closeUpDistance = DampenDistanceFactor * _end.validBoundingSphere(); + // Kind of an ugly workaround to make damping behave over very long paths, and/or + // where the target nodes might have large bounding spheres. The current max is set + // based on the order of magnitude of the solar system, which ofc is very specific to + // our space content... + // @TODO (2022-03-22, emmbr) Come up with a better more general solution + constexpr const double MaxDistance = 1E12; + startUpDistance = glm::min(MaxDistance, startUpDistance); + closeUpDistance = glm::min(MaxDistance, closeUpDistance); + if (pathLength() < startUpDistance + closeUpDistance) { - startUpDistance = 0.49 * pathLength(); // a little less than half + startUpDistance = 0.4 * pathLength(); // a little less than half closeUpDistance = startUpDistance; } @@ -619,6 +657,23 @@ Path createPathFromDictionary(const ghoul::Dictionary& dictionary, p.useTargetUpDirection.value_or(false) }; + double startToTargetCenterDistance = glm::distance( + startPoint.position(), + targetNode->worldPosition() + ); + + // Use a linear path if camera start is within the bounding sphere + const PathNavigator& navigator = global::navigationHandler->pathNavigator(); + const double bs = navigator.findValidBoundingSphere(targetNode); + bool withinTargetBoundingSphere = startToTargetCenterDistance < bs; + if (withinTargetBoundingSphere) { + LINFO( + "Camera is within the bounding sphere of the target node. " + "Using linear path" + ); + type = Path::Type::Linear; + } + waypoints = { computeWaypointFromNodeInfo(info, startPoint, type) }; break; } diff --git a/src/navigation/pathnavigator.cpp b/src/navigation/pathnavigator.cpp index 81148164b1..42005bf26a 100644 --- a/src/navigation/pathnavigator.cpp +++ b/src/navigation/pathnavigator.cpp @@ -69,7 +69,7 @@ namespace { constexpr openspace::properties::Property::PropertyInfo SpeedScaleInfo = { "SpeedScale", "Speed Scale", - "Scale factor that the speed will be mulitplied with during path traversal. " + "Scale factor that the speed will be multiplied with during path traversal. " "Can be used to speed up or slow down the camera motion, depending on if the " "value is larger than or smaller than one." }; @@ -90,6 +90,14 @@ namespace { "object." }; + constexpr openspace::properties::Property::PropertyInfo RotationSpeedFactorInfo = { + "RotationSpeedFactor", + "Rotation Speed Factor (Linear Path)", + "Affects how fast the camera rotates to the target rotation during a linear " + "path. A value of 1 means that the camera will rotate 90 degrees in about 5 " + "seconds. A value of 2 means twice that fast, and so on." + }; + constexpr const openspace::properties::Property::PropertyInfo MinBoundingSphereInfo = { "MinimalValidBoundingSphere", @@ -119,6 +127,7 @@ PathNavigator::PathNavigator() , _speedScale(SpeedScaleInfo, 1.f, 0.01f, 2.f) , _applyIdleBehaviorOnFinish(IdleBehaviorOnFinishInfo, false) , _arrivalDistanceFactor(ArrivalDistanceFactorInfo, 2.0, 0.1, 20.0) + , _linearRotationSpeedFactor(RotationSpeedFactorInfo, 1.f, 0.1f, 2.f) , _minValidBoundingSphere(MinBoundingSphereInfo, 10.0, 1.0, 3e10) , _relevantNodeTags(RelevantNodeTagsInfo) { @@ -134,6 +143,7 @@ PathNavigator::PathNavigator() addProperty(_speedScale); addProperty(_applyIdleBehaviorOnFinish); addProperty(_arrivalDistanceFactor); + addProperty(_linearRotationSpeedFactor); addProperty(_minValidBoundingSphere); _relevantNodeTags = std::vector{ @@ -166,6 +176,10 @@ double PathNavigator::arrivalDistanceFactor() const { return _arrivalDistanceFactor; } +float PathNavigator::linearRotationSpeedFactor() const { + return _linearRotationSpeedFactor; +} + bool PathNavigator::hasCurrentPath() const { return _currentPath != nullptr; }