Camera path updates (issue #1919 and #1947) (#1966)

* Use linear camera path when within target bounding sphere

And do a workaround for speed computation when bounding spheres are large
Closes #1910 (by at least making it so that the camera moves in an observable speed)

* Make linear paths rotate based on time rather than distance (closes #1947)
This commit is contained in:
Emma Broman
2022-04-06 17:34:36 +02:00
committed by GitHub
parent e77754da89
commit d0d918cdae
4 changed files with 130 additions and 52 deletions

View File

@@ -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
*/

View File

@@ -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;

View File

@@ -40,6 +40,7 @@
#include <openspace/util/universalhelpers.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/interpolator.h>
#include <glm/ext/quaternion_relational.hpp>
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<double>()
));
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<double>();
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;
}

View File

@@ -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<std::string>{
@@ -166,6 +176,10 @@ double PathNavigator::arrivalDistanceFactor() const {
return _arrivalDistanceFactor;
}
float PathNavigator::linearRotationSpeedFactor() const {
return _linearRotationSpeedFactor;
}
bool PathNavigator::hasCurrentPath() const {
return _currentPath != nullptr;
}