mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-02-22 04:49:12 -06:00
* 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:
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user