mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-07 20:21:24 -06:00
Merge remote-tracking branch 'origin/master' into thesis/2021/skybrowser
# Conflicts: # data/assets/util/webgui.asset
This commit is contained in:
@@ -1180,6 +1180,17 @@ void OpenSpaceEngine::preSynchronization() {
|
||||
setCameraFromProfile(*global::profile);
|
||||
setAdditionalScriptsFromProfile(*global::profile);
|
||||
}
|
||||
|
||||
// Handle callback(s) for change in engine mode
|
||||
if (_modeLastFrame != _currentMode) {
|
||||
using K = CallbackHandle;
|
||||
using V = ModeChangeCallback;
|
||||
for (const std::pair<K, V>& it : _modeChangeCallbacks) {
|
||||
it.second();
|
||||
}
|
||||
}
|
||||
_modeLastFrame = _currentMode;
|
||||
|
||||
LTRACE("OpenSpaceEngine::preSynchronization(end)");
|
||||
}
|
||||
|
||||
@@ -1617,6 +1628,31 @@ void OpenSpaceEngine::resetMode() {
|
||||
LDEBUG(fmt::format("Reset engine mode to {}", stringify(_currentMode)));
|
||||
}
|
||||
|
||||
OpenSpaceEngine::CallbackHandle OpenSpaceEngine::addModeChangeCallback(
|
||||
ModeChangeCallback cb)
|
||||
{
|
||||
CallbackHandle handle = _nextCallbackHandle++;
|
||||
_modeChangeCallbacks.emplace_back(handle, std::move(cb));
|
||||
return handle;
|
||||
}
|
||||
|
||||
void OpenSpaceEngine::removeModeChangeCallback(CallbackHandle handle) {
|
||||
const auto it = std::find_if(
|
||||
_modeChangeCallbacks.begin(),
|
||||
_modeChangeCallbacks.end(),
|
||||
[handle](const std::pair<CallbackHandle, ModeChangeCallback>& cb) {
|
||||
return cb.first == handle;
|
||||
}
|
||||
);
|
||||
|
||||
ghoul_assert(
|
||||
it != _modeChangeCallbacks.end(),
|
||||
"handle must be a valid callback handle"
|
||||
);
|
||||
|
||||
_modeChangeCallbacks.erase(it);
|
||||
}
|
||||
|
||||
void setCameraFromProfile(const Profile& p) {
|
||||
if (!p.camera.has_value()) {
|
||||
throw ghoul::RuntimeError("No 'camera' entry exists in the startup profile");
|
||||
|
||||
@@ -158,34 +158,6 @@ namespace {
|
||||
"meters above the surface."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo VelocityZoomControlInfo = {
|
||||
"VelocityZoomControl",
|
||||
"Velocity Zoom Control",
|
||||
"Controls the velocity of the camera motion when zooming in to the focus node "
|
||||
"on a linear flight. The higher the value the faster the camera will move "
|
||||
"towards the focus."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ApplyLinearFlightInfo = {
|
||||
"ApplyLinearFlight",
|
||||
"Apply Linear Flight",
|
||||
"This property makes the camera move to the specified distance "
|
||||
"'DestinationDistance' while facing the anchor"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo FlightDestinationDistInfo = {
|
||||
"FlightDestinationDistance",
|
||||
"Flight Destination Distance",
|
||||
"The final distance we want to fly to, with regards to the anchor node."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo FlightDestinationFactorInfo =
|
||||
{
|
||||
"FlightDestinationFactor",
|
||||
"Flight Destination Factor",
|
||||
"The minimal distance factor that we need to reach to end linear flight."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo
|
||||
StereoInterpolationTimeInfo = {
|
||||
"StereoInterpolationTime",
|
||||
@@ -299,20 +271,6 @@ OrbitalNavigator::Friction::Friction()
|
||||
addProperty(friction);
|
||||
}
|
||||
|
||||
OrbitalNavigator::LinearFlight::LinearFlight()
|
||||
: properties::PropertyOwner({ "LinearFlight" })
|
||||
, apply(ApplyLinearFlightInfo, false)
|
||||
, destinationDistance(FlightDestinationDistInfo, 2e8f, 10.f, 1e10f)
|
||||
, destinationFactor(FlightDestinationFactorInfo, 1E-4, 1E-6, 0.5, 1E-3)
|
||||
, velocitySensitivity(VelocityZoomControlInfo, 3.5f, 0.001f, 20.f)
|
||||
{
|
||||
addProperty(apply);
|
||||
addProperty(velocitySensitivity);
|
||||
destinationDistance.setExponent(5.f);
|
||||
addProperty(destinationDistance);
|
||||
addProperty(destinationFactor);
|
||||
}
|
||||
|
||||
OrbitalNavigator::IdleBehavior::IdleBehavior()
|
||||
: properties::PropertyOwner({ "IdleBehavior" })
|
||||
, apply(ApplyIdleBehaviorInfo, false)
|
||||
@@ -472,7 +430,6 @@ OrbitalNavigator::OrbitalNavigator()
|
||||
});
|
||||
|
||||
addPropertySubOwner(_friction);
|
||||
addPropertySubOwner(_linearFlight);
|
||||
addPropertySubOwner(_idleBehavior);
|
||||
|
||||
_idleBehaviorDampenInterpolator.setTransferFunction(
|
||||
@@ -593,37 +550,6 @@ void OrbitalNavigator::updateCameraStateFromStates(double deltaTime) {
|
||||
_camera->rotationQuaternion()
|
||||
};
|
||||
|
||||
if (_linearFlight.apply) {
|
||||
// Calculate a position handle based on the camera position in world space
|
||||
glm::dvec3 camPosToAnchorPosDiff = prevCameraPosition - anchorPos;
|
||||
// Use the interaction sphere to get an approximate distance to the node surface
|
||||
double nodeRadius = _anchorNode->interactionSphere();
|
||||
double distFromCameraToFocus =
|
||||
glm::distance(prevCameraPosition, anchorPos) - nodeRadius;
|
||||
|
||||
// Make the approximation delta size depending on the flight distance
|
||||
double arrivalThreshold =
|
||||
_linearFlight.destinationDistance * _linearFlight.destinationFactor;
|
||||
|
||||
const double distToDestination =
|
||||
std::fabs(distFromCameraToFocus - _linearFlight.destinationDistance);
|
||||
|
||||
// Fly towards the flight destination distance. When getting closer than
|
||||
// arrivalThreshold terminate the flight
|
||||
if (distToDestination > arrivalThreshold) {
|
||||
pose.position = moveCameraAlongVector(
|
||||
pose.position,
|
||||
distFromCameraToFocus,
|
||||
camPosToAnchorPosDiff,
|
||||
_linearFlight.destinationDistance,
|
||||
deltaTime
|
||||
);
|
||||
}
|
||||
else {
|
||||
_linearFlight.apply = false;
|
||||
}
|
||||
}
|
||||
|
||||
const bool hasPreviousPositions =
|
||||
_previousAnchorNodePosition.has_value() &&
|
||||
_previousAimNodePosition.has_value();
|
||||
@@ -1416,28 +1342,6 @@ glm::dvec3 OrbitalNavigator::translateHorizontally(double deltaTime,
|
||||
return cameraPosition + rotationDiffVec3;
|
||||
}
|
||||
|
||||
glm::dvec3 OrbitalNavigator::moveCameraAlongVector(const glm::dvec3& camPos,
|
||||
double distFromCameraToFocus,
|
||||
const glm::dvec3& camPosToAnchorPosDiff,
|
||||
double destination,
|
||||
double deltaTime) const
|
||||
{
|
||||
// This factor adapts the velocity so it slows down when getting closer
|
||||
// to our final destination
|
||||
double velocity = 0.0;
|
||||
|
||||
if (destination < distFromCameraToFocus) { // When flying towards anchor
|
||||
velocity = 1.0 - destination / distFromCameraToFocus;
|
||||
}
|
||||
else { // When flying away from anchor
|
||||
velocity = distFromCameraToFocus / destination - 1.0;
|
||||
}
|
||||
velocity *= _linearFlight.velocitySensitivity * deltaTime;
|
||||
|
||||
// Return the updated camera position
|
||||
return camPos - velocity * camPosToAnchorPosDiff;
|
||||
}
|
||||
|
||||
glm::dvec3 OrbitalNavigator::followAnchorNodeRotation(const glm::dvec3& cameraPosition,
|
||||
const glm::dvec3& objectPosition,
|
||||
const glm::dquat& focusNodeRotationDiff) const
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
namespace {
|
||||
constexpr const char _loggerCat[] = "Path";
|
||||
constexpr const float Epsilon = 1e-5f;
|
||||
constexpr const float LengthEpsilon = 1e-5f;
|
||||
|
||||
constexpr const char SunIdentifier[] = "Sun";
|
||||
|
||||
@@ -82,6 +82,15 @@ namespace {
|
||||
// A navigation state that determines the start state for the camera path
|
||||
std::optional<ghoul::Dictionary> startState
|
||||
[[codegen::reference("core_navigation_state")]];
|
||||
|
||||
enum class [[codegen::map(openspace::interaction::Path::Type)]] PathType {
|
||||
AvoidCollision,
|
||||
ZoomOutOverview,
|
||||
Linear,
|
||||
AvoidCollisionWithLookAt
|
||||
};
|
||||
// The type of the created path. Affects the shape of the resulting path
|
||||
std::optional<PathType> pathType;
|
||||
};
|
||||
#include "path_codegen.cpp"
|
||||
} // namespace
|
||||
@@ -108,6 +117,8 @@ Path::Path(Waypoint start, Waypoint end, Type type,
|
||||
throw ghoul::MissingCaseException();
|
||||
}
|
||||
|
||||
_prevPose = _start.pose();
|
||||
|
||||
// Compute speed factor to match any given duration, by traversing the path and
|
||||
// computing how much faster/slower it should be
|
||||
_speedFactorFromDuration = 1.0;
|
||||
@@ -119,11 +130,9 @@ Path::Path(Waypoint start, Waypoint end, Type type,
|
||||
|
||||
// We now know how long it took to traverse the path. Use that
|
||||
_speedFactorFromDuration = _progressedTime / *duration;
|
||||
}
|
||||
|
||||
// Reset playback variables
|
||||
_traveledDistance = 0.0;
|
||||
_progressedTime = 0.0;
|
||||
resetPlaybackVariables();
|
||||
}
|
||||
}
|
||||
|
||||
Waypoint Path::startPoint() const { return _start; }
|
||||
@@ -139,12 +148,49 @@ std::vector<glm::dvec3> Path::controlPoints() const {
|
||||
CameraPose Path::traversePath(double dt, float speedScale) {
|
||||
double speed = speedAlongPath(_traveledDistance);
|
||||
speed *= static_cast<double>(speedScale);
|
||||
const double displacement = dt * speed;
|
||||
double displacement = dt * speed;
|
||||
|
||||
const double prevDistance = _traveledDistance;
|
||||
|
||||
_progressedTime += dt;
|
||||
_traveledDistance += displacement;
|
||||
|
||||
return interpolatedPose(_traveledDistance);
|
||||
CameraPose newPose;
|
||||
|
||||
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);
|
||||
}
|
||||
else {
|
||||
if (std::abs(prevDistance - _traveledDistance) < LengthEpsilon) {
|
||||
// The distaces are too large, so we are not making progress because of
|
||||
// insufficient precision
|
||||
_shouldQuit = true;
|
||||
LWARNING("Quit camera path prematurely due to insufficient precision");
|
||||
}
|
||||
|
||||
newPose = interpolatedPose(_traveledDistance);
|
||||
}
|
||||
|
||||
_prevPose = newPose;
|
||||
return newPose;
|
||||
}
|
||||
|
||||
std::string Path::currentAnchor() const {
|
||||
@@ -153,9 +199,20 @@ std::string Path::currentAnchor() const {
|
||||
}
|
||||
|
||||
bool Path::hasReachedEnd() const {
|
||||
if (_shouldQuit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (_traveledDistance / pathLength()) >= 1.0;
|
||||
}
|
||||
|
||||
void Path::resetPlaybackVariables() {
|
||||
_prevPose = _start.pose();
|
||||
_traveledDistance = 0.0;
|
||||
_progressedTime = 0.0;
|
||||
_shouldQuit = false;
|
||||
}
|
||||
|
||||
CameraPose Path::interpolatedPose(double distance) const {
|
||||
const double relativeDistance = distance / pathLength();
|
||||
CameraPose cs;
|
||||
@@ -167,8 +224,9 @@ CameraPose Path::interpolatedPose(double distance) const {
|
||||
glm::dquat Path::interpolateRotation(double t) const {
|
||||
switch (_type) {
|
||||
case Type::AvoidCollision:
|
||||
case Type::Linear:
|
||||
return easedSlerpRotation(t);
|
||||
case Type::Linear:
|
||||
return linearPathRotation(t);
|
||||
case Type::ZoomOutOverview:
|
||||
case Type::AvoidCollisionWithLookAt:
|
||||
return lookAtTargetsRotation(t);
|
||||
@@ -183,6 +241,48 @@ glm::dquat Path::easedSlerpRotation(double t) const {
|
||||
return glm::slerp(_start.rotation(), _end.rotation(), tScaled);
|
||||
}
|
||||
|
||||
glm::dquat Path::linearPathRotation(double t) const {
|
||||
const double tHalf = 0.5;
|
||||
|
||||
const glm::dvec3 endNodePos = _end.node()->worldPosition();
|
||||
const glm::dvec3 endUp = _end.rotation() * glm::dvec3(0.0, 1.0, 0.0);
|
||||
|
||||
if (t < tHalf) {
|
||||
// Interpolate to look at target
|
||||
const glm::dvec3 halfWayPosition = _curve->positionAt(tHalf);
|
||||
const glm::dquat q = ghoul::lookAtQuaternion(halfWayPosition, endNodePos, endUp);
|
||||
|
||||
const double tScaled = ghoul::sineEaseInOut(t / tHalf);
|
||||
return glm::slerp(_start.rotation(), q, tScaled);
|
||||
}
|
||||
|
||||
// This distance is guaranteed to be strictly decreasing for linear paths
|
||||
const double distanceToEnd = glm::distance(_prevPose.position, _end.position());
|
||||
|
||||
// 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 {
|
||||
const double t1 = 0.2;
|
||||
const double t2 = 0.8;
|
||||
@@ -234,51 +334,55 @@ double Path::speedAlongPath(double traveledDistance) const {
|
||||
const glm::dvec3 endNodePos = _end.node()->worldPosition();
|
||||
const glm::dvec3 startNodePos = _start.node()->worldPosition();
|
||||
|
||||
const CameraPose prevPose = interpolatedPose(traveledDistance);
|
||||
const double distanceToEndNode = glm::distance(prevPose.position, endNodePos);
|
||||
const double distanceToStartNode = glm::distance(prevPose.position, startNodePos);
|
||||
// Set speed based on distance to closest node
|
||||
const double distanceToEndNode = glm::distance(_prevPose.position, endNodePos);
|
||||
const double distanceToStartNode = glm::distance(_prevPose.position, startNodePos);
|
||||
bool isCloserToEnd = distanceToEndNode < distanceToStartNode;
|
||||
|
||||
// Decide which is the closest node
|
||||
SceneGraphNode* closestNode = _start.node();
|
||||
glm::dvec3 closestPos = startNodePos;
|
||||
const glm::dvec3 closestPos = isCloserToEnd ? endNodePos : startNodePos;
|
||||
const double distanceToClosestNode = glm::distance(closestPos, _prevPose.position);
|
||||
|
||||
if (distanceToEndNode < distanceToStartNode) {
|
||||
closestPos = endNodePos;
|
||||
closestNode = _end.node();
|
||||
}
|
||||
|
||||
const double distanceToClosestNode = glm::distance(closestPos, prevPose.position);
|
||||
double speed = distanceToClosestNode;
|
||||
|
||||
// Dampen speed in beginning of path
|
||||
double startUpDistance = 2.0 * _start.node()->boundingSphere();
|
||||
if (startUpDistance < Epsilon) { // zero bounding sphere
|
||||
startUpDistance = glm::distance(_start.position(), startNodePos);
|
||||
const double speed = distanceToClosestNode;
|
||||
|
||||
// Dampen at the start and end
|
||||
constexpr const double DampenDistanceFactor = 3.0;
|
||||
double startUpDistance = DampenDistanceFactor * _start.validBoundingSphere();
|
||||
double closeUpDistance = DampenDistanceFactor * _end.validBoundingSphere();
|
||||
|
||||
if (pathLength() < startUpDistance + closeUpDistance) {
|
||||
startUpDistance = 0.49 * pathLength(); // a little less than half
|
||||
closeUpDistance = startUpDistance;
|
||||
}
|
||||
|
||||
double dampeningFactor = 1.0;
|
||||
if (traveledDistance < startUpDistance) {
|
||||
speed *= traveledDistance / startUpDistance + 0.01;
|
||||
dampeningFactor = traveledDistance / startUpDistance;
|
||||
}
|
||||
|
||||
// Dampen speed in end of path
|
||||
// Note: this leads to problems when the full length of the path is really big
|
||||
double closeUpDistance = 2.0 * _end.node()->boundingSphere();
|
||||
if (closeUpDistance < Epsilon) { // zero bounding sphere
|
||||
closeUpDistance = glm::distance(_end.position(), endNodePos);
|
||||
else if (_type == Type::Linear) {
|
||||
// Dampen at end of linear path is handled separately, as we can use the
|
||||
// current position to scompute the remaining distance rather than the
|
||||
// path length minus travels distance. This is more suitable for long paths
|
||||
const double remainingDistance = glm::distance(
|
||||
_prevPose.position,
|
||||
_end.position()
|
||||
);
|
||||
if (remainingDistance < closeUpDistance) {
|
||||
dampeningFactor = remainingDistance / closeUpDistance;
|
||||
}
|
||||
}
|
||||
|
||||
if (traveledDistance > (pathLength() - closeUpDistance)) {
|
||||
else if (traveledDistance > (pathLength() - closeUpDistance)) {
|
||||
const double remainingDistance = pathLength() - traveledDistance;
|
||||
speed *= remainingDistance / closeUpDistance + 0.01;
|
||||
dampeningFactor = remainingDistance / closeUpDistance;
|
||||
}
|
||||
dampeningFactor = ghoul::sineEaseOut(dampeningFactor);
|
||||
|
||||
// Prevent multiplying with 0 (and hence a speed of 0.0 => no movement)
|
||||
dampeningFactor += 0.01;
|
||||
|
||||
// TODO: also dampen speed based on curvature, or make sure the curve has a rounder
|
||||
// shape
|
||||
|
||||
// TODO: check for when path is shorter than the starUpDistance or closeUpDistance
|
||||
// variables
|
||||
|
||||
return _speedFactorFromDuration * speed;
|
||||
return _speedFactorFromDuration * speed * dampeningFactor;
|
||||
}
|
||||
|
||||
Waypoint waypointFromCamera() {
|
||||
@@ -297,7 +401,7 @@ SceneGraphNode* findNodeNearTarget(const SceneGraphNode* node) {
|
||||
bool isSame = (n->identifier() == node->identifier());
|
||||
// If the nodes are in the very same position, they are probably representing
|
||||
// the same object
|
||||
isSame |= glm::distance(n->worldPosition(), node->worldPosition()) < Epsilon;
|
||||
isSame |= glm::distance(n->worldPosition(), node->worldPosition()) < LengthEpsilon;
|
||||
|
||||
if (isSame) {
|
||||
continue;
|
||||
@@ -376,7 +480,9 @@ struct NodeInfo {
|
||||
bool useTargetUpDirection;
|
||||
};
|
||||
|
||||
Waypoint computeWaypointFromNodeInfo(const NodeInfo& info, const Waypoint& startPoint) {
|
||||
Waypoint computeWaypointFromNodeInfo(const NodeInfo& info, const Waypoint& startPoint,
|
||||
Path::Type type)
|
||||
{
|
||||
const SceneGraphNode* targetNode = sceneGraphNode(info.identifier);
|
||||
if (!targetNode) {
|
||||
LERROR(fmt::format("Could not find target node '{}'", info.identifier));
|
||||
@@ -392,12 +498,23 @@ Waypoint computeWaypointFromNodeInfo(const NodeInfo& info, const Waypoint& start
|
||||
);
|
||||
}
|
||||
else {
|
||||
const double radius = Waypoint::findValidBoundingSphere(targetNode);
|
||||
const double defaultHeight = 2.0 * radius;
|
||||
const PathNavigator& navigator = global::navigationHandler->pathNavigator();
|
||||
|
||||
const double radius = navigator.findValidBoundingSphere(targetNode);
|
||||
const double defaultHeight = radius * navigator.arrivalDistanceFactor();
|
||||
const double height = info.height.value_or(defaultHeight);
|
||||
const double distanceFromNodeCenter = radius + height;
|
||||
|
||||
const glm::dvec3 stepDir = computeGoodStepDirection(targetNode, startPoint);
|
||||
glm::dvec3 stepDir = glm::dvec3(0.0);
|
||||
if (type == Path::Type::Linear) {
|
||||
// If linear path, compute position along line form start to end point
|
||||
glm::dvec3 endNodePos = targetNode->worldPosition();
|
||||
stepDir = glm::normalize(startPoint.position() - endNodePos);
|
||||
}
|
||||
else {
|
||||
stepDir = computeGoodStepDirection(targetNode, startPoint);
|
||||
}
|
||||
|
||||
targetPos = targetNode->worldPosition() + stepDir * distanceFromNodeCenter;
|
||||
}
|
||||
|
||||
@@ -416,18 +533,29 @@ Waypoint computeWaypointFromNodeInfo(const NodeInfo& info, const Waypoint& start
|
||||
return Waypoint(targetPos, targetRot, info.identifier);
|
||||
}
|
||||
|
||||
Path createPathFromDictionary(const ghoul::Dictionary& dictionary, Path::Type type) {
|
||||
Path createPathFromDictionary(const ghoul::Dictionary& dictionary,
|
||||
std::optional<Path::Type> forceType)
|
||||
{
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
const std::optional<float> duration = p.duration;
|
||||
|
||||
Path::Type type;
|
||||
if (forceType.has_value()) {
|
||||
type = *forceType;
|
||||
}
|
||||
else if (p.pathType.has_value()) {
|
||||
type = codegen::map<Path::Type>(*p.pathType);
|
||||
}
|
||||
else {
|
||||
type = global::navigationHandler->pathNavigator().defaultPathType();
|
||||
}
|
||||
|
||||
bool hasStart = p.startState.has_value();
|
||||
const Waypoint startPoint = hasStart ?
|
||||
Waypoint(NavigationState(p.startState.value())) :
|
||||
waypointFromCamera();
|
||||
|
||||
// TODO: also handle curve type here
|
||||
|
||||
std::vector<Waypoint> waypoints;
|
||||
switch (p.targetType) {
|
||||
case Parameters::TargetType::NavigationState: {
|
||||
@@ -462,7 +590,7 @@ Path createPathFromDictionary(const ghoul::Dictionary& dictionary, Path::Type ty
|
||||
p.useTargetUpDirection.value_or(false)
|
||||
};
|
||||
|
||||
waypoints = { computeWaypointFromNodeInfo(info, startPoint) };
|
||||
waypoints = { computeWaypointFromNodeInfo(info, startPoint, type) };
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -473,7 +601,36 @@ Path createPathFromDictionary(const ghoul::Dictionary& dictionary, Path::Type ty
|
||||
// @TODO (emmbr) Allow for an instruction to represent a list of multiple waypoints
|
||||
const Waypoint waypointToAdd = waypoints[0];
|
||||
|
||||
return Path(startPoint, waypointToAdd, type, duration);
|
||||
if (glm::distance(startPoint.position(), waypointToAdd.position()) < LengthEpsilon) {
|
||||
LINFO("Already at the requested target");
|
||||
throw PathCurve::TooShortPathError("Path too short!");
|
||||
}
|
||||
|
||||
try {
|
||||
return Path(startPoint, waypointToAdd, type, duration);
|
||||
}
|
||||
catch (const PathCurve::TooShortPathError& e) {
|
||||
LINFO("Already at the requested target");
|
||||
// Rethrow e, so the pathnavigator can handle it as well
|
||||
throw e;
|
||||
}
|
||||
catch (const PathCurve::InsufficientPrecisionError&) {
|
||||
// There wasn't enough precision to represent the full curve in world
|
||||
// coordinates. For now, use a linear path instead. It uses another
|
||||
// method of interpolation that isn't as sensitive to these kinds of problems
|
||||
|
||||
LINFO(
|
||||
"Switching to a linear path, to avoid problems with precision due to "
|
||||
"immense path length."
|
||||
);
|
||||
|
||||
return createPathFromDictionary(dictionary, Path::Type::Linear);
|
||||
|
||||
// @TODO (emmbr26, 2022-02-15): later on we want to improve this case, so that
|
||||
// interpolation is not a problem. One suggestion to solve this would be using
|
||||
// two identical paths and switch the direction of interpolation halfway through.
|
||||
// That should give us sufficient precision at both ends of the path
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace openspace::interaction
|
||||
|
||||
@@ -35,11 +35,23 @@
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
constexpr const int NrSamplesPerSegment = 100;
|
||||
constexpr const char* _loggerCat = "PathCurve";
|
||||
|
||||
constexpr const double LengthEpsilon =
|
||||
100.0 * std::numeric_limits<double>::epsilon();
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace openspace::interaction {
|
||||
|
||||
PathCurve::InsufficientPrecisionError::InsufficientPrecisionError(std::string error)
|
||||
: ghoul::RuntimeError(std::move(error), "PathCurve")
|
||||
{}
|
||||
|
||||
PathCurve::TooShortPathError::TooShortPathError(std::string error)
|
||||
: ghoul::RuntimeError(std::move(error), "PathCurve")
|
||||
{}
|
||||
|
||||
PathCurve::~PathCurve() {}
|
||||
|
||||
double PathCurve::length() const {
|
||||
@@ -57,21 +69,19 @@ std::vector<glm::dvec3> PathCurve::points() const {
|
||||
|
||||
void PathCurve::initializeParameterData() {
|
||||
_nSegments = static_cast<int>(_points.size() - 3);
|
||||
|
||||
ghoul_assert(_nSegments > 0, "Cannot have a curve with zero segments!");
|
||||
|
||||
_curveParameterSteps.clear();
|
||||
_lengthSums.clear();
|
||||
_parameterSamples.clear();
|
||||
|
||||
const double max = static_cast<double>(_nSegments);
|
||||
|
||||
// Evenly space out parameter intervals
|
||||
_curveParameterSteps.reserve(_nSegments + 1);
|
||||
const double dt = 1.0 / _nSegments;
|
||||
_curveParameterSteps.push_back(0.0);
|
||||
for (unsigned int i = 1; i < _nSegments; i++) {
|
||||
_curveParameterSteps.push_back(dt * i);
|
||||
for (int i = 0; i <= _nSegments; i++) {
|
||||
_curveParameterSteps.push_back(static_cast<double>(i));
|
||||
}
|
||||
_curveParameterSteps.push_back(1.0);
|
||||
|
||||
// Arc lengths
|
||||
_lengthSums.reserve(_nSegments + 1);
|
||||
@@ -84,49 +94,82 @@ void PathCurve::initializeParameterData() {
|
||||
}
|
||||
_totalLength = _lengthSums.back();
|
||||
|
||||
if (_totalLength < LengthEpsilon) {
|
||||
throw TooShortPathError("Path too short!");
|
||||
}
|
||||
|
||||
// Compute a map of arc lengths s and curve parameters u, for reparameterization
|
||||
_parameterSamples.reserve(NrSamplesPerSegment * _nSegments + 1);
|
||||
const double uStep = 1.0 / (_nSegments * NrSamplesPerSegment);
|
||||
constexpr const int Steps = 100;
|
||||
const double uStep = 1.0 / static_cast<double>(Steps);
|
||||
_parameterSamples.reserve(Steps * _nSegments + 1);
|
||||
|
||||
for (unsigned int i = 0; i < _nSegments; i++) {
|
||||
double uStart = _curveParameterSteps[i];
|
||||
double sStart = _lengthSums[i];
|
||||
for (int j = 0; j < NrSamplesPerSegment; ++j) {
|
||||
_parameterSamples.push_back({ uStart, sStart });
|
||||
// Intermediate sampels
|
||||
for (int j = 1; j < Steps; ++j) {
|
||||
double u = uStart + j * uStep;
|
||||
double s = sStart + arcLength(uStart, u);
|
||||
// Identify samples that are indistinguishable due to precision limitations
|
||||
if (std::abs(s - _parameterSamples.back().s) < LengthEpsilon) {
|
||||
throw InsufficientPrecisionError("Insufficient precision due to path length");
|
||||
}
|
||||
_parameterSamples.push_back({ u, s });
|
||||
}
|
||||
}
|
||||
_parameterSamples.push_back({ 1.0, _totalLength });
|
||||
|
||||
// Remove the very last sample if indistinguishable from the final one
|
||||
const double diff = std::abs(_totalLength - _parameterSamples.back().s);
|
||||
if (diff < LengthEpsilon) {
|
||||
_parameterSamples.pop_back();
|
||||
}
|
||||
|
||||
_parameterSamples.push_back({ max, _totalLength });
|
||||
_parameterSamples.shrink_to_fit();
|
||||
}
|
||||
|
||||
// Compute the curve parameter from an arc length value, using a combination of
|
||||
// Newton's method and bisection. Source:
|
||||
// https://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf
|
||||
// Input s is a length value, in the range [0, _totalLength]
|
||||
// Returns curve parameter in range [0, 1]
|
||||
// Returns curve parameter in range [0, _nSegments]
|
||||
double PathCurve::curveParameter(double s) const {
|
||||
if (s <= 0.0) return 0.0;
|
||||
if (s >= _totalLength) return 1.0;
|
||||
if (s >= (_totalLength - LengthEpsilon)) return _curveParameterSteps.back();
|
||||
|
||||
unsigned int segmentIndex = 1;
|
||||
while (s > _lengthSums[segmentIndex]) {
|
||||
segmentIndex++;
|
||||
}
|
||||
|
||||
const int startIndex = (segmentIndex - 1) * NrSamplesPerSegment;
|
||||
const int endIndex = segmentIndex * NrSamplesPerSegment + 1;
|
||||
|
||||
const double segmentS = s - _lengthSums[segmentIndex - 1];
|
||||
const double uMin = _curveParameterSteps[segmentIndex - 1];
|
||||
const double uMax = _curveParameterSteps[segmentIndex];
|
||||
|
||||
// Find sample indices corresponding to the selected sample
|
||||
auto findIndexOfFirstEqualOrLarger_u = [&samples = _parameterSamples](double value) {
|
||||
auto it = std::lower_bound(
|
||||
samples.begin(),
|
||||
samples.end(),
|
||||
ParameterPair{ value, 0.0 }, // 0.0 is a dummy value for s
|
||||
[](const ParameterPair& lhs, const ParameterPair& rhs) {
|
||||
return lhs.u < rhs.u;
|
||||
}
|
||||
);
|
||||
return std::distance(samples.begin(), it);
|
||||
};
|
||||
|
||||
size_t startIndex = findIndexOfFirstEqualOrLarger_u(uMin);
|
||||
size_t endIndex = findIndexOfFirstEqualOrLarger_u(uMax);
|
||||
|
||||
// Use samples to find an initial guess for Newton's method
|
||||
// Find first sample with s larger than input s
|
||||
auto sampleIterator = std::upper_bound(
|
||||
_parameterSamples.begin() + startIndex,
|
||||
_parameterSamples.begin() + endIndex,
|
||||
ParameterPair{ 0.0 , s }, // 0.0 is a dummy value for u
|
||||
[](const ParameterPair& lhs,const ParameterPair& rhs) {
|
||||
[](const ParameterPair& lhs, const ParameterPair& rhs) {
|
||||
return lhs.s < rhs.s;
|
||||
}
|
||||
);
|
||||
@@ -148,7 +191,7 @@ double PathCurve::curveParameter(double s) const {
|
||||
double F = arcLength(uMin, u) - segmentS;
|
||||
|
||||
// The error we tolerate, in meters. Note that distances are very large
|
||||
constexpr const double tolerance = 0.005;
|
||||
constexpr const double tolerance = 0.5;
|
||||
if (std::abs(F) <= tolerance) {
|
||||
return u;
|
||||
}
|
||||
@@ -174,11 +217,12 @@ double PathCurve::curveParameter(double s) const {
|
||||
}
|
||||
|
||||
double PathCurve::approximatedDerivative(double u, double h) const {
|
||||
const double max = _curveParameterSteps.back();
|
||||
if (u <= h) {
|
||||
return (1.0 / h) * glm::length(interpolate(0.0 + h) - interpolate(0.0));
|
||||
}
|
||||
if (u >= 1.0 - h) {
|
||||
return (1.0 / h) * glm::length(interpolate(1.0) - interpolate(1.0 - h));
|
||||
if (u >= max - h) {
|
||||
return (1.0 / h) * glm::length(interpolate(max) - interpolate(max - h));
|
||||
}
|
||||
return (0.5 / h) * glm::length(interpolate(u + h) - interpolate(u - h));
|
||||
}
|
||||
@@ -196,12 +240,13 @@ double PathCurve::arcLength(double lowerLimit, double upperLimit) const {
|
||||
}
|
||||
|
||||
glm::dvec3 PathCurve::interpolate(double u) const {
|
||||
ghoul_assert(u >= 0 && u <= 1.0, "Interpolation variable must be in range [0,1]");
|
||||
const double max = _curveParameterSteps.back();
|
||||
ghoul_assert(u >= 0 && u <= max, "Interpolation variable must be in range [0,_nSegments]");
|
||||
|
||||
if (u < 0.0) {
|
||||
if (u <= 0.0) {
|
||||
return _points[1];
|
||||
}
|
||||
if (u > 1.0) {
|
||||
if (u >= max) {
|
||||
return *(_points.end() - 2);
|
||||
}
|
||||
|
||||
@@ -226,11 +271,26 @@ glm::dvec3 PathCurve::interpolate(double u) const {
|
||||
}
|
||||
|
||||
LinearCurve::LinearCurve(const Waypoint& start, const Waypoint& end) {
|
||||
_points.push_back(start.position());
|
||||
_points.push_back(start.position());
|
||||
_points.push_back(end.position());
|
||||
_points.push_back(end.position());
|
||||
initializeParameterData();
|
||||
|
||||
_totalLength = glm::distance(start.position(), end.position());
|
||||
_nSegments = 1;
|
||||
}
|
||||
|
||||
glm::dvec3 LinearCurve::positionAt(double relativeDistance) const {
|
||||
return interpolate(relativeDistance);
|
||||
}
|
||||
|
||||
glm::dvec3 LinearCurve::interpolate(double u) const {
|
||||
if (u <= 0.0) {
|
||||
return _points.front();
|
||||
}
|
||||
if (u >= 1.0) {
|
||||
return _points.back();
|
||||
}
|
||||
|
||||
return ghoul::interpolateLinear(u, _points.front(), _points.back());
|
||||
}
|
||||
|
||||
} // namespace openspace::interaction
|
||||
|
||||
@@ -70,10 +70,18 @@ AvoidCollisionCurve::AvoidCollisionCurve(const Waypoint& start, const Waypoint&
|
||||
|
||||
// Add an extra point to first go backwards if starting close to planet
|
||||
glm::dvec3 nodeToStart = start.position() - startNodeCenter;
|
||||
double distanceToStartNode = glm::length(nodeToStart);
|
||||
const double distanceToStartNode = glm::length(nodeToStart);
|
||||
|
||||
if (distanceToStartNode < CloseToNodeThresholdRadiusMultiplier * startNodeRadius) {
|
||||
double distance = startNodeRadius;
|
||||
// Note that the factor 2.0 is arbitrarily chosen to look ok.
|
||||
// @TODO: (2022-02-27, emmbr) Should be unified to a "getting close to object sphere"
|
||||
// that can be used in multiple cases when creating paths more cleverly later on
|
||||
const double closeToNodeThresholdFactor = glm::max(
|
||||
CloseToNodeThresholdRadiusMultiplier,
|
||||
2.0 * global::navigationHandler->pathNavigator().arrivalDistanceFactor()
|
||||
);
|
||||
|
||||
if (distanceToStartNode < closeToNodeThresholdFactor * startNodeRadius) {
|
||||
const double distance = startNodeRadius;
|
||||
glm::dvec3 newPos = start.position() + distance * glm::normalize(nodeToStart);
|
||||
_points.push_back(newPos);
|
||||
}
|
||||
@@ -104,8 +112,8 @@ AvoidCollisionCurve::AvoidCollisionCurve(const Waypoint& start, const Waypoint&
|
||||
const glm::dvec3 nodeToEnd = end.position() - endNodeCenter;
|
||||
const double distanceToEndNode = glm::length(nodeToEnd);
|
||||
|
||||
if (distanceToEndNode < CloseToNodeThresholdRadiusMultiplier * endNodeRadius) {
|
||||
double distance = endNodeRadius;
|
||||
if (distanceToEndNode < closeToNodeThresholdFactor * endNodeRadius) {
|
||||
const double distance = endNodeRadius;
|
||||
glm::dvec3 newPos = end.position() + distance * glm::normalize(nodeToEnd);
|
||||
_points.push_back(newPos);
|
||||
}
|
||||
|
||||
@@ -45,10 +45,9 @@ namespace {
|
||||
constexpr openspace::properties::Property::PropertyInfo DefaultCurveOptionInfo = {
|
||||
"DefaultPathType",
|
||||
"Default Path Type",
|
||||
"The defualt path type chosen when generating a path. See wiki for alternatives."
|
||||
" The shape of the generated path will be different depending on the path type."
|
||||
// TODO (2021-08-15, emmbr) right now there is no way to specify a type for a
|
||||
// single path instance, only for any created paths
|
||||
"The default path type chosen when generating a path or flying to a target. "
|
||||
"See wiki for alternatives. The shape of the generated path will be different "
|
||||
"depending on the path type."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo IncludeRollInfo = {
|
||||
@@ -72,6 +71,15 @@ namespace {
|
||||
"triggered once the path has reached its target."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ArrivalDistanceFactorInfo = {
|
||||
"ArrivalDistanceFactor",
|
||||
"Arrival Distance Factor",
|
||||
"A factor used to compute the default distance from a target scene graph node "
|
||||
"when creating a camera path. The factor will be multipled with the node's "
|
||||
"bounding sphere to compute the target height from the bounding sphere of the "
|
||||
"object."
|
||||
};
|
||||
|
||||
constexpr const openspace::properties::Property::PropertyInfo MinBoundingSphereInfo =
|
||||
{
|
||||
"MinimalValidBoundingSphere",
|
||||
@@ -102,13 +110,14 @@ PathNavigator::PathNavigator()
|
||||
, _includeRoll(IncludeRollInfo, false)
|
||||
, _speedScale(SpeedScaleInfo, 1.f, 0.01f, 2.f)
|
||||
, _applyIdleBehaviorOnFinish(IdleBehaviorOnFinishInfo, false)
|
||||
, _arrivalDistanceFactor(ArrivalDistanceFactorInfo, 2.0, 0.1, 20.0)
|
||||
, _minValidBoundingSphere(MinBoundingSphereInfo, 10.0, 1.0, 3e10)
|
||||
, _relevantNodeTags(RelevantNodeTagsInfo)
|
||||
{
|
||||
_defaultPathType.addOptions({
|
||||
{ Path::Type::AvoidCollision, "AvoidCollision" },
|
||||
{ Path::Type::Linear, "Linear" },
|
||||
{ Path::Type::ZoomOutOverview, "ZoomOutOverview"},
|
||||
{ Path::Type::Linear, "Linear" },
|
||||
{ Path::Type::AvoidCollisionWithLookAt, "AvoidCollisionWithLookAt"}
|
||||
});
|
||||
addProperty(_defaultPathType);
|
||||
@@ -116,6 +125,7 @@ PathNavigator::PathNavigator()
|
||||
addProperty(_includeRoll);
|
||||
addProperty(_speedScale);
|
||||
addProperty(_applyIdleBehaviorOnFinish);
|
||||
addProperty(_arrivalDistanceFactor);
|
||||
addProperty(_minValidBoundingSphere);
|
||||
|
||||
_relevantNodeTags = std::vector<std::string>{
|
||||
@@ -144,6 +154,10 @@ double PathNavigator::speedScale() const {
|
||||
return _speedScale;
|
||||
}
|
||||
|
||||
double PathNavigator::arrivalDistanceFactor() const {
|
||||
return _arrivalDistanceFactor;
|
||||
}
|
||||
|
||||
bool PathNavigator::hasCurrentPath() const {
|
||||
return _currentPath != nullptr;
|
||||
}
|
||||
@@ -191,7 +205,7 @@ void PathNavigator::updateCamera(double deltaTime) {
|
||||
camera()->setPose(newPose);
|
||||
|
||||
if (_currentPath->hasReachedEnd()) {
|
||||
LINFO("Reached end of path");
|
||||
LINFO("Reached target");
|
||||
handlePathEnd();
|
||||
|
||||
if (_applyIdleBehaviorOnFinish) {
|
||||
@@ -211,10 +225,6 @@ void PathNavigator::updateCamera(double deltaTime) {
|
||||
}
|
||||
|
||||
void PathNavigator::createPath(const ghoul::Dictionary& dictionary) {
|
||||
// @TODO (2021-08.16, emmbr): Improve how curve types are handled.
|
||||
// We want the user to be able to choose easily
|
||||
const int pathType = _defaultPathType;
|
||||
|
||||
OpenSpaceEngine::Mode m = global::openSpaceEngine->currentMode();
|
||||
if (m == OpenSpaceEngine::Mode::SessionRecordingPlayback) {
|
||||
// Silently ignore any paths that are being created during a session recording
|
||||
@@ -225,9 +235,7 @@ void PathNavigator::createPath(const ghoul::Dictionary& dictionary) {
|
||||
clearPath();
|
||||
|
||||
try {
|
||||
_currentPath = std::make_unique<Path>(
|
||||
createPathFromDictionary(dictionary, Path::Type(pathType))
|
||||
);
|
||||
_currentPath = std::make_unique<Path>(createPathFromDictionary(dictionary));
|
||||
}
|
||||
catch (const documentation::SpecificationError& e) {
|
||||
LERROR("Could not create camera path");
|
||||
@@ -238,16 +246,21 @@ void PathNavigator::createPath(const ghoul::Dictionary& dictionary) {
|
||||
LWARNINGC(w.offender, ghoul::to_string(w.reason));
|
||||
}
|
||||
}
|
||||
catch (const PathCurve::TooShortPathError&) {
|
||||
// Do nothing
|
||||
}
|
||||
catch (const ghoul::RuntimeError& e) {
|
||||
LERROR(fmt::format("Could not create path. Reason: ", e.message));
|
||||
return;
|
||||
}
|
||||
|
||||
LINFO("Successfully generated camera path");
|
||||
LDEBUG("Successfully generated camera path");
|
||||
}
|
||||
|
||||
void PathNavigator::clearPath() {
|
||||
LDEBUG("Clearing path");
|
||||
if (_currentPath) {
|
||||
LDEBUG("Clearing path");
|
||||
}
|
||||
_currentPath = nullptr;
|
||||
}
|
||||
|
||||
@@ -272,16 +285,14 @@ void PathNavigator::startPath() {
|
||||
);
|
||||
|
||||
_startSimulationTimeOnFinish = true;
|
||||
LINFO(
|
||||
"Pausing time simulation during path traversal. "
|
||||
"Will unpause once the camera path is finished"
|
||||
);
|
||||
LINFO("Pausing time simulation during path traversal");
|
||||
}
|
||||
|
||||
LINFO("Starting path");
|
||||
_isPlaying = true;
|
||||
|
||||
global::navigationHandler->orbitalNavigator().updateOnCameraInteraction();
|
||||
global::navigationHandler->orbitalNavigator().resetVelocities();
|
||||
}
|
||||
|
||||
void PathNavigator::abortPath() {
|
||||
@@ -323,10 +334,58 @@ void PathNavigator::continuePath() {
|
||||
_isPlaying = true;
|
||||
}
|
||||
|
||||
Path::Type PathNavigator::defaultPathType() const {
|
||||
const int pathType = _defaultPathType;
|
||||
return Path::Type(pathType);
|
||||
}
|
||||
|
||||
double PathNavigator::minValidBoundingSphere() const {
|
||||
return _minValidBoundingSphere;
|
||||
}
|
||||
|
||||
double PathNavigator::findValidBoundingSphere(const SceneGraphNode* node) const {
|
||||
ghoul_assert(node != nullptr, "Node must not be nulltpr");
|
||||
auto sphere = [](const SceneGraphNode* n) {
|
||||
// Use the biggest of the bounding sphere and interaction sphere,
|
||||
// so we don't accidentally choose a bounding sphere that is much smaller
|
||||
// than the interaction sphere of the node
|
||||
double bs = n->boundingSphere();
|
||||
double is = n->interactionSphere();
|
||||
return is > bs ? is : bs;
|
||||
};
|
||||
|
||||
double result = sphere(node);
|
||||
|
||||
if (result < _minValidBoundingSphere) {
|
||||
// If the bs of the target is too small, try to find a good value in a child node.
|
||||
// Only check the closest children, to avoid deep traversal in the scene graph.
|
||||
// Alsp. the chance to find a bounding sphere that represents the visual size of
|
||||
// the target well is higher for these nodes
|
||||
for (SceneGraphNode* child : node->children()) {
|
||||
result = sphere(child);
|
||||
if (result > _minValidBoundingSphere) {
|
||||
LDEBUG(fmt::format(
|
||||
"The scene graph node '{}' has no, or a very small, bounding sphere. "
|
||||
"Using bounding sphere of child node '{}' in computations.",
|
||||
node->identifier(),
|
||||
child->identifier()
|
||||
));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
LDEBUG(fmt::format(
|
||||
"The scene graph node '{}' has no, or a very small, bounding sphere. Using "
|
||||
"minimal value. This might lead to unexpected results.",
|
||||
node->identifier())
|
||||
);
|
||||
|
||||
result = _minValidBoundingSphere;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::vector<SceneGraphNode*>& PathNavigator::relevantNodes() {
|
||||
if (!_hasInitializedRelevantNodes) {
|
||||
findRelevantNodes();
|
||||
@@ -346,7 +405,6 @@ void PathNavigator::handlePathEnd() {
|
||||
);
|
||||
}
|
||||
_startSimulationTimeOnFinish = false;
|
||||
clearPath();
|
||||
global::openSpaceEngine->resetMode();
|
||||
}
|
||||
|
||||
@@ -438,9 +496,9 @@ scripting::LuaLibrary PathNavigator::luaLibrary() {
|
||||
&luascriptfunctions::flyTo,
|
||||
"string [, bool, double]",
|
||||
"Move the camera to the node with the specified identifier. The optional "
|
||||
"double specifies the duration of the motion. If the optional bool is "
|
||||
"set to true the target up vector for camera is set based on the target "
|
||||
"node. Either of the optional parameters can be left out."
|
||||
"double specifies the duration of the motion, in seconds. If the optional "
|
||||
"bool is set to true the target up vector for camera is set based on the "
|
||||
"target node. Either of the optional parameters can be left out."
|
||||
},
|
||||
{
|
||||
"flyToHeight",
|
||||
@@ -449,18 +507,48 @@ scripting::LuaLibrary PathNavigator::luaLibrary() {
|
||||
"Move the camera to the node with the specified identifier. The second "
|
||||
"argument is the desired target height above the target node's bounding "
|
||||
"sphere, in meters. The optional double specifies the duration of the "
|
||||
"motion. If the optional bool is set to true, the target up vector for "
|
||||
"camera is set based on the target node. Either of the optional "
|
||||
"parameters can be left out."
|
||||
"motion, in seconds. If the optional bool is set to true, the target "
|
||||
"up vector for camera is set based on the target node. Either of the "
|
||||
"optional parameters can be left out."
|
||||
},
|
||||
{
|
||||
{
|
||||
"flyToNavigationState",
|
||||
&luascriptfunctions::flyToNavigationState,
|
||||
"table, [double]",
|
||||
"Create a path to the navigation state described by the input table. "
|
||||
"The optional double specifies the target duration of the motion. Note "
|
||||
"that roll must be included for the target up direction to be taken "
|
||||
"into account."
|
||||
"The optional double specifies the target duration of the motion, "
|
||||
"in seconds. Note that roll must be included for the target up "
|
||||
"direction to be taken into account."
|
||||
},
|
||||
{
|
||||
"zoomToFocus",
|
||||
&luascriptfunctions::zoomToFocus,
|
||||
"[duration]",
|
||||
"Zoom linearly to the current focus node, using the default distance."
|
||||
"The optional input parameter specifies the duration for the motion, "
|
||||
"in seconds."
|
||||
},
|
||||
{
|
||||
"zoomToDistance",
|
||||
&luascriptfunctions::zoomToDistance,
|
||||
"distance, [duration]",
|
||||
"Fly linearly to a specific distance in relation to the focus node. "
|
||||
"The distance is given in meters above the bounding sphere of the "
|
||||
"current focus node."
|
||||
"The optional input parameter specifies the duration for the motion, "
|
||||
"in seconds."
|
||||
},
|
||||
{
|
||||
"zoomToDistanceRelative",
|
||||
&luascriptfunctions::zoomToDistanceRelative,
|
||||
"distance, [duration]",
|
||||
"Fly linearly to a specific distance in relation to the focus node. "
|
||||
"The distance is given as a multiple of the bounding sphere of the "
|
||||
"current focus node. That is, a value of 1 will result in a position "
|
||||
"at a distance of one times the size of the bounding sphere away from "
|
||||
"the object."
|
||||
"The optional input parameter specifies the duration for the motion, "
|
||||
"in seconds."
|
||||
},
|
||||
{
|
||||
"createPath",
|
||||
|
||||
@@ -37,10 +37,6 @@
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
namespace {
|
||||
constexpr const double Epsilon = 1e-5;
|
||||
} // namespace
|
||||
|
||||
namespace openspace::luascriptfunctions {
|
||||
|
||||
int isFlying(lua_State* L) {
|
||||
@@ -81,7 +77,6 @@ int flyTo(lua_State* L) {
|
||||
return ghoul::lua::luaError(L, "Duration cannot be specified twice");
|
||||
}
|
||||
|
||||
|
||||
if (!sceneGraphNode(nodeIdentifier)) {
|
||||
return ghoul::lua::luaError(L, "Unknown node name: " + nodeIdentifier);
|
||||
}
|
||||
@@ -98,7 +93,7 @@ int flyTo(lua_State* L) {
|
||||
}
|
||||
else {
|
||||
double d = std::get<double>(*useUpFromTargetOrDuration);
|
||||
if (d <= Epsilon) {
|
||||
if (d <= 0.0) {
|
||||
return ghoul::lua::luaError(L, "Duration must be larger than zero");
|
||||
}
|
||||
insDict.setValue("Duration", d);
|
||||
@@ -106,7 +101,7 @@ int flyTo(lua_State* L) {
|
||||
}
|
||||
if (duration.has_value()) {
|
||||
double d = *duration;
|
||||
if (d <= Epsilon) {
|
||||
if (d <= 0.0) {
|
||||
return ghoul::lua::luaError(L, "Duration must be larger than zero");
|
||||
}
|
||||
insDict.setValue("Duration", d);
|
||||
@@ -146,7 +141,7 @@ int flyToHeight(lua_State* L) {
|
||||
}
|
||||
else {
|
||||
double d = std::get<double>(*useUpFromTargetOrDuration);
|
||||
if (d <= Epsilon) {
|
||||
if (d <= 0.0) {
|
||||
return ghoul::lua::luaError(L, "Duration must be larger than zero");
|
||||
}
|
||||
insDict.setValue("Duration", d);
|
||||
@@ -154,7 +149,7 @@ int flyToHeight(lua_State* L) {
|
||||
}
|
||||
if (duration.has_value()) {
|
||||
double d = *duration;
|
||||
if (d <= Epsilon) {
|
||||
if (d <= 0.0) {
|
||||
return ghoul::lua::luaError(L, "Duration must be larger than zero");
|
||||
}
|
||||
insDict.setValue("Duration", d);
|
||||
@@ -194,7 +189,7 @@ int flyToNavigationState(lua_State* L) {
|
||||
|
||||
if (duration.has_value()) {
|
||||
double d = *duration;
|
||||
if (d <= Epsilon) {
|
||||
if (d <= 0.0) {
|
||||
return ghoul::lua::luaError(L, "Duration must be larger than zero");
|
||||
}
|
||||
instruction.setValue("Duration", d);
|
||||
@@ -208,6 +203,113 @@ int flyToNavigationState(lua_State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zoomToFocus(lua_State* L) {
|
||||
ghoul::lua::checkArgumentsAndThrow(L, { 0, 1 }, "lua::zoomToFocus");
|
||||
std::optional<double> duration = ghoul::lua::value<std::optional<double>>(L);
|
||||
|
||||
const SceneGraphNode* node = global::navigationHandler->anchorNode();
|
||||
if (!node) {
|
||||
return ghoul::lua::luaError(L, "Could not determine current focus node");
|
||||
}
|
||||
|
||||
ghoul::Dictionary insDict;
|
||||
insDict.setValue("TargetType", std::string("Node"));
|
||||
insDict.setValue("Target", node->identifier());
|
||||
insDict.setValue("PathType", std::string("Linear"));
|
||||
|
||||
if (duration.has_value()) {
|
||||
double d = *duration;
|
||||
if (d <= 0.0) {
|
||||
return ghoul::lua::luaError(L, "Duration must be larger than zero");
|
||||
}
|
||||
insDict.setValue("Duration", d);
|
||||
}
|
||||
|
||||
global::navigationHandler->pathNavigator().createPath(insDict);
|
||||
|
||||
if (global::navigationHandler->pathNavigator().hasCurrentPath()) {
|
||||
global::navigationHandler->pathNavigator().startPath();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zoomToDistance(lua_State* L) {
|
||||
ghoul::lua::checkArgumentsAndThrow(L, { 1, 2 }, "lua::zoomToDistance");
|
||||
auto [distance, duration] =
|
||||
ghoul::lua::values<double, std::optional<double>>(L);
|
||||
|
||||
if (distance <= 0.0) {
|
||||
return ghoul::lua::luaError(L, "The distance must be larger than zero");
|
||||
}
|
||||
|
||||
const SceneGraphNode* node = global::navigationHandler->anchorNode();
|
||||
if (!node) {
|
||||
return ghoul::lua::luaError(L, "Could not determine current focus node");
|
||||
}
|
||||
|
||||
ghoul::Dictionary insDict;
|
||||
insDict.setValue("TargetType", std::string("Node"));
|
||||
insDict.setValue("Target", node->identifier());
|
||||
insDict.setValue("Height", distance);
|
||||
insDict.setValue("PathType", std::string("Linear"));
|
||||
|
||||
if (duration.has_value()) {
|
||||
double d = *duration;
|
||||
if (d <= 0.0) {
|
||||
return ghoul::lua::luaError(L, "Duration must be larger than zero");
|
||||
}
|
||||
insDict.setValue("Duration", d);
|
||||
}
|
||||
|
||||
global::navigationHandler->pathNavigator().createPath(insDict);
|
||||
|
||||
if (global::navigationHandler->pathNavigator().hasCurrentPath()) {
|
||||
global::navigationHandler->pathNavigator().startPath();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zoomToDistanceRelative(lua_State* L) {
|
||||
ghoul::lua::checkArgumentsAndThrow(L, { 1, 2 }, "lua::zoomToDistanceRelative");
|
||||
auto [distance, duration] =
|
||||
ghoul::lua::values<double, std::optional<double>>(L);
|
||||
|
||||
if (distance <= 0.0) {
|
||||
return ghoul::lua::luaError(L, "The distance must be larger than zero");
|
||||
}
|
||||
|
||||
const SceneGraphNode* node = global::navigationHandler->anchorNode();
|
||||
if (!node) {
|
||||
return ghoul::lua::luaError(L, "Could not determine current focus node");
|
||||
}
|
||||
|
||||
distance *= node->boundingSphere();
|
||||
|
||||
ghoul::Dictionary insDict;
|
||||
insDict.setValue("TargetType", std::string("Node"));
|
||||
insDict.setValue("Target", node->identifier());
|
||||
insDict.setValue("Height", distance);
|
||||
insDict.setValue("PathType", std::string("Linear"));
|
||||
|
||||
if (duration.has_value()) {
|
||||
double d = *duration;
|
||||
if (d <= 0.0) {
|
||||
return ghoul::lua::luaError(L, "Duration must be larger than zero");
|
||||
}
|
||||
insDict.setValue("Duration", d);
|
||||
}
|
||||
|
||||
global::navigationHandler->pathNavigator().createPath(insDict);
|
||||
|
||||
if (global::navigationHandler->pathNavigator().hasCurrentPath()) {
|
||||
global::navigationHandler->pathNavigator().startPath();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int createPath(lua_State* L) {
|
||||
ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::createPath");
|
||||
ghoul::Dictionary dictionary = ghoul::lua::value<ghoul::Dictionary>(L);
|
||||
|
||||
@@ -49,7 +49,9 @@ Waypoint::Waypoint(const glm::dvec3& pos, const glm::dquat& rot, const std::stri
|
||||
LERROR(fmt::format("Could not find node '{}'", _nodeIdentifier));
|
||||
return;
|
||||
}
|
||||
_validBoundingSphere = findValidBoundingSphere(node);
|
||||
|
||||
const PathNavigator& navigator = global::navigationHandler->pathNavigator();
|
||||
_validBoundingSphere = navigator.findValidBoundingSphere(node);
|
||||
}
|
||||
|
||||
Waypoint::Waypoint(const NavigationState& ns) {
|
||||
@@ -61,7 +63,8 @@ Waypoint::Waypoint(const NavigationState& ns) {
|
||||
}
|
||||
|
||||
_nodeIdentifier = ns.anchor;
|
||||
_validBoundingSphere = findValidBoundingSphere(anchorNode);
|
||||
const PathNavigator& navigator = global::navigationHandler->pathNavigator();
|
||||
_validBoundingSphere = navigator.findValidBoundingSphere(anchorNode);
|
||||
_pose = ns.cameraPose();
|
||||
}
|
||||
|
||||
@@ -89,41 +92,4 @@ double Waypoint::validBoundingSphere() const {
|
||||
return _validBoundingSphere;
|
||||
}
|
||||
|
||||
double Waypoint::findValidBoundingSphere(const SceneGraphNode* node) {
|
||||
double bs = node->boundingSphere();
|
||||
|
||||
const double minValidBoundingSphere =
|
||||
global::navigationHandler->pathNavigator().minValidBoundingSphere();
|
||||
|
||||
if (bs < minValidBoundingSphere) {
|
||||
// If the bs of the target is too small, try to find a good value in a child node.
|
||||
// Only check the closest children, to avoid deep traversal in the scene graph.
|
||||
// Alsp. the chance to find a bounding sphere that represents the visual size of
|
||||
// the target well is higher for these nodes
|
||||
for (SceneGraphNode* child : node->children()) {
|
||||
bs = child->boundingSphere();
|
||||
if (bs > minValidBoundingSphere) {
|
||||
LWARNING(fmt::format(
|
||||
"The scene graph node '{}' has no, or a very small, bounding sphere. "
|
||||
"Using bounding sphere of child node '{}' in computations.",
|
||||
node->identifier(),
|
||||
child->identifier()
|
||||
));
|
||||
|
||||
return bs;
|
||||
}
|
||||
}
|
||||
|
||||
LWARNING(fmt::format(
|
||||
"The scene graph node '{}' has no, or a very small, bounding sphere. Using "
|
||||
"minimal value. This might lead to unexpected results.",
|
||||
node->identifier())
|
||||
);
|
||||
|
||||
bs = minValidBoundingSphere;
|
||||
}
|
||||
|
||||
return bs;
|
||||
}
|
||||
|
||||
} // namespace openspace::interaction
|
||||
|
||||
@@ -100,6 +100,13 @@ void MultiThreadedSceneInitializer::initializeNode(SceneGraphNode* node) {
|
||||
}
|
||||
|
||||
std::vector<SceneGraphNode*> MultiThreadedSceneInitializer::takeInitializedNodes() {
|
||||
// Some of the scene graph nodes might still be in the initialization queue and we
|
||||
// should wait for those to finish or we end up in some half-initialized state since
|
||||
// other parts of the application already know about their existence
|
||||
while (_threadPool.hasOutstandingTasks()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
std::lock_guard g(_mutex);
|
||||
std::vector<SceneGraphNode*> nodes = std::move(_initializedNodes);
|
||||
return nodes;
|
||||
|
||||
@@ -99,4 +99,8 @@ void ThreadPool::clearTasks() {
|
||||
} // release lock
|
||||
}
|
||||
|
||||
bool ThreadPool::hasOutstandingTasks() const {
|
||||
return !tasks.empty();
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
Reference in New Issue
Block a user