Do some restructuring

This commit is contained in:
Emma Broman
2021-06-08 09:47:45 +02:00
parent ff7301fbc7
commit 3881f1f2fc
7 changed files with 209 additions and 230 deletions

View File

@@ -29,8 +29,6 @@
#include <openspace/engine/globals.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/interaction/navigationhandler.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scene.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/query/query.h>
#include <openspace/util/camera.h>
@@ -70,13 +68,6 @@ namespace {
"no path is playing."
};
constexpr openspace::properties::Property::PropertyInfo RelevantNodeTagsInfo = {
"RelevantNodeTags",
"Relevant Node Tags",
"List of tags for the nodes that are relevant for path creation, for example "
"when avoiding collisions."
};
constexpr openspace::properties::Property::PropertyInfo SpeedScaleInfo = {
"SpeedScale",
"Speed Scale",
@@ -99,7 +90,6 @@ AutoNavigationHandler::AutoNavigationHandler()
properties::OptionProperty::DisplayType::Dropdown
)
, _applyStopBehaviorWhenIdle(ApplyStopBehaviorWhenIdleInfo, false)
, _relevantNodeTags(RelevantNodeTagsInfo)
, _speedScale(SpeedScaleInfo, 1.f, 0.01f, 2.f)
{
addPropertySubOwner(_atNodeNavigator);
@@ -128,14 +118,6 @@ AutoNavigationHandler::AutoNavigationHandler()
);
});
addProperty(_stopBehavior);
// Add the relevant tags
_relevantNodeTags = std::vector<std::string>{
"planet_solarSystem",
"moon_solarSystem"
};;
_relevantNodeTags.onChange([this]() { _relevantNodes = findRelevantNodes(); });
addProperty(_relevantNodeTags);
}
AutoNavigationHandler::~AutoNavigationHandler() {} // NOLINT
@@ -148,10 +130,6 @@ const SceneGraphNode* AutoNavigationHandler::anchor() const {
return global::navigationHandler->anchorNode();
}
const std::vector<SceneGraphNode*>& AutoNavigationHandler::relevantNodes() const {
return _relevantNodes;
}
double AutoNavigationHandler::speedScale() const {
return _speedScale;
}
@@ -218,9 +196,6 @@ void AutoNavigationHandler::updateCamera(double deltaTime) {
void AutoNavigationHandler::createPath(PathInstruction& instruction) {
clearPath();
// TODO: do this in some initialize function instead
_relevantNodes = findRelevantNodes();
// TODO: Improve how curve types are handled
const int curveType = _defaultCurveOption;
@@ -228,26 +203,16 @@ void AutoNavigationHandler::createPath(PathInstruction& instruction) {
Waypoint waypointToAdd;
if (waypoints.empty()) {
if (instruction.type == PathInstruction::Type::Node) {
// TODO: allow curves to compute default waypoint instead
waypointToAdd = computeDefaultWaypoint(instruction);
}
else {
LWARNING("No path was created from instruction. Failed creating waypoints");
return;
}
LWARNING("No path was created from instruction. Failed creating waypoints");
return;
}
else {
// TODO: allow for an instruction to represent a list of waypoints
waypointToAdd = waypoints[0];
}
bool hasStartState = instruction.startState.has_value();
Waypoint startState = hasStartState ? Waypoint(instruction.startState.value())
: waypointFromCamera();
_currentPath = std::make_unique<Path>(
startState,
instruction.startPoint,
waypointToAdd,
CurveType(curveType),
instruction.duration
@@ -304,14 +269,14 @@ void AutoNavigationHandler::abortPath() {
// TODO: remove when not needed
// Created for debugging
std::vector<glm::dvec3> AutoNavigationHandler::curvePositions(int nPerSegment) {
std::vector<glm::dvec3> AutoNavigationHandler::curvePositions(int nSteps) const {
if (noCurrentPath()) {
LERROR("There is no current path to sample points from.");
return {};
}
std::vector<glm::dvec3> positions;
const double du = 1.0 / nPerSegment;
const double du = 1.0 / nSteps;
const double length = _currentPath->pathLength();
for (double u = 0.0; u < 1.0; u += du) {
glm::dvec3 position = _currentPath->interpolatedPose(u * length).position;
@@ -324,14 +289,14 @@ std::vector<glm::dvec3> AutoNavigationHandler::curvePositions(int nPerSegment) {
// TODO: remove when not needed
// Created for debugging
std::vector<glm::dquat> AutoNavigationHandler::curveOrientations(int nPerSegment) {
std::vector<glm::dquat> AutoNavigationHandler::curveOrientations(int nSteps) const {
if (noCurrentPath()) {
LERROR("There is no current path to sample points from.");
return {};
}
std::vector<glm::dquat> orientations;
const double du = 1.0 / nPerSegment;
const double du = 1.0 / nSteps;
const double length = _currentPath->pathLength();
for (double u = 0.0; u <= 1.0; u += du) {
const glm::dquat orientation =
@@ -346,14 +311,14 @@ std::vector<glm::dquat> AutoNavigationHandler::curveOrientations(int nPerSegment
// TODO: remove when not needed or combined into pose version
// Created for debugging
std::vector<glm::dvec3> AutoNavigationHandler::curveViewDirections(int nPerSegment) {
std::vector<glm::dvec3> AutoNavigationHandler::curveViewDirections(int nSteps) const {
if (noCurrentPath()) {
LERROR("There is no current path to sample points from.");
return {};
}
std::vector<glm::dvec3> viewDirections;
const double du = 1.0 / nPerSegment;
const double du = 1.0 / nSteps;
for (double u = 0.0; u < 1.0; u += du) {
const glm::dquat orientation = _currentPath->interpolatedPose(u).rotation;
const glm::dvec3 direction = glm::normalize(
@@ -373,7 +338,7 @@ std::vector<glm::dvec3> AutoNavigationHandler::curveViewDirections(int nPerSegme
// TODO: remove when not needed
// Created for debugging
std::vector<glm::dvec3> AutoNavigationHandler::controlPoints() {
std::vector<glm::dvec3> AutoNavigationHandler::controlPoints() const {
if (noCurrentPath()) {
LERROR("There is no current path to sample points from.");
return {};
@@ -386,13 +351,6 @@ std::vector<glm::dvec3> AutoNavigationHandler::controlPoints() {
return points;
}
Waypoint AutoNavigationHandler::waypointFromCamera() {
const glm::dvec3 pos = camera()->positionVec3();
const glm::dquat rot = camera()->rotationQuaternion();
const std::string node = global::navigationHandler->anchorNode()->identifier();
return Waypoint{ pos, rot, node };
}
void AutoNavigationHandler::removeRollRotation(CameraPose& pose, double deltaTime) {
const glm::dvec3 anchorPos = anchor()->worldPosition();
const glm::dvec3 cameraDir = glm::normalize(
@@ -409,136 +367,4 @@ void AutoNavigationHandler::removeRollRotation(CameraPose& pose, double deltaTim
pose.rotation = rollFreeRotation;
}
// Test if the node lies within a given proximity radius of any relevant node in the scene
SceneGraphNode* AutoNavigationHandler::findNodeNearTarget(const SceneGraphNode* node) {
const glm::dvec3 nodePos = node->worldPosition();
const std::string nodeId = node->identifier();
const float proximityRadiusFactor = 3.f;
for (SceneGraphNode* n : _relevantNodes) {
if (n->identifier() == nodeId) {
continue;
}
float bs = static_cast<float>(n->boundingSphere());
float proximityRadius = proximityRadiusFactor * bs;
const glm::dmat4 invModelTransform = glm::inverse(n->modelTransform());
const glm::dvec3 posInModelCoords = invModelTransform * glm::dvec4(nodePos, 1.0);
bool isClose = helpers::isPointInsideSphere(
posInModelCoords,
glm::dvec3(0.0, 0.0, 0.0),
proximityRadius
);
if (isClose) {
return n;
}
}
return nullptr;
}
// OBS! The desired default waypoint may vary between curve types.
// TODO: let the curves update the default position if no exact position is required
Waypoint AutoNavigationHandler::computeDefaultWaypoint(const PathInstruction& ins) {
const SceneGraphNode* targetNode = sceneGraphNode(ins.nodeIdentifier);
if (!targetNode) {
LERROR(fmt::format("Could not find target node '{}'", ins.nodeIdentifier));
return Waypoint();
}
const float epsilon = 1e-5f;
const glm::dvec3 nodePos = targetNode->worldPosition();
const glm::dvec3 sunPos = glm::dvec3(0.0, 0.0, 0.0);
const SceneGraphNode* closeNode = findNodeNearTarget(targetNode);
const glm::dvec3 prevPos = waypointFromCamera().position();
// Position
glm::dvec3 stepDirection;
if (closeNode) {
// If the node is close to another node in the scene, make sure that the
// position is set to minimize risk of collision
stepDirection = glm::normalize(nodePos - closeNode->worldPosition());
}
else if (glm::length(sunPos - nodePos) < epsilon) {
// Special case for when the target is the Sun. Assumption: want an overview of
// the solar system, and not stay in the orbital plane
stepDirection = glm::dvec3(0.0, 0.0, 1.0);
}
else {
// Go to a point that is being lit up by the sun, slightly offsetted from sun
// direction
const glm::dvec3 targetToPrev = prevPos - nodePos;
const glm::dvec3 targetToSun = sunPos - nodePos;
constexpr const float defaultPositionOffsetAngle = -30.f; // degrees
constexpr const float angle = glm::radians(defaultPositionOffsetAngle);
const glm::dvec3 axis = glm::normalize(glm::cross(targetToPrev, targetToSun));
const glm::dquat offsetRotation = angleAxis(static_cast<double>(angle), axis);
stepDirection = glm::normalize(offsetRotation * targetToSun);
}
const double radius = Waypoint::findValidBoundingSphere(targetNode);
const double defaultHeight = 2.0 * radius;
const double height = ins.height.has_value() ? ins.height.value() : defaultHeight;
const glm::dvec3 targetPos = nodePos + stepDirection * (radius + height);
// Up direction
glm::dvec3 up = camera()->lookUpVectorWorldSpace();
if (ins.useTargetUpDirection) {
// @TODO (emmbr 2020-11-17) For now, this is hardcoded to look good for Earth,
// which is where it matters the most. A better solution would be to make each
// sgn aware of its own 'up' and query
up = targetNode->worldRotationMatrix() * glm::dvec3(0.0, 0.0, 1.0);
}
// Rotation
const glm::dvec3 lookAtPos = targetNode->worldPosition();
const glm::dquat targetRot = helpers::lookAtQuaternion(targetPos, lookAtPos, up);
return Waypoint(targetPos, targetRot, ins.nodeIdentifier);
}
std::vector<SceneGraphNode*> AutoNavigationHandler::findRelevantNodes() {
const std::vector<SceneGraphNode*>& allNodes =
global::renderEngine->scene()->allSceneGraphNodes();
const std::vector<std::string> relevantTags = _relevantNodeTags;
if (allNodes.empty() || relevantTags.empty()) {
return std::vector<SceneGraphNode*>();
}
auto isRelevant = [&](const SceneGraphNode* node) {
const std::vector<std::string> tags = node->tags();
auto result = std::find_first_of(
relevantTags.begin(),
relevantTags.end(),
tags.begin(),
tags.end()
);
// does not match any tags => not interesting
if (result == relevantTags.end()) {
return false;
}
return node->renderable() && (node->boundingSphere() > 0.0);
};
std::vector<SceneGraphNode*> resultingNodes;
std::copy_if(
allNodes.begin(),
allNodes.end(),
std::back_inserter(resultingNodes),
isRelevant
);
return resultingNodes;
}
} // namespace openspace::autonavigation

View File

@@ -38,7 +38,7 @@ namespace openspace {
namespace openspace::autonavigation {
struct Waypoint;
//struct Waypoint;
struct PathInstruction;
class AutoNavigationHandler : public properties::PropertyOwner {
@@ -49,7 +49,6 @@ public:
// Accessors
Camera* camera() const;
const SceneGraphNode* anchor() const;
const std::vector<SceneGraphNode*>& relevantNodes() const;
double speedScale() const;
bool noCurrentPath() const;
@@ -65,27 +64,19 @@ public:
//void continuePath();
// TODO: remove functions for debugging
std::vector<glm::dvec3> curvePositions(int nPerSegment);
std::vector<glm::dquat> curveOrientations(int nPerSegment);
std::vector<glm::dvec3> curveViewDirections(int nPerSegment);
std::vector<glm::dvec3> controlPoints();
std::vector<glm::dvec3> curvePositions(int nSteps) const;
std::vector<glm::dquat> curveOrientations(int nSteps) const;
std::vector<glm::dvec3> curveViewDirections(int nSteps) const;
std::vector<glm::dvec3> controlPoints() const;
private:
Waypoint waypointFromCamera();
void removeRollRotation(CameraPose& pose, double deltaTime);
SceneGraphNode* findNodeNearTarget(const SceneGraphNode* node);
Waypoint computeDefaultWaypoint(const PathInstruction& ins);
std::vector<SceneGraphNode*> findRelevantNodes();
std::unique_ptr<Path> _currentPath = nullptr;
AtNodeNavigator _atNodeNavigator;
bool _isPlaying = false;
std::vector<SceneGraphNode*> _relevantNodes;
properties::OptionProperty _defaultCurveOption;
properties::BoolProperty _includeRoll;
properties::FloatProperty _speedScale;
@@ -94,8 +85,6 @@ private:
// TODO: remove later, if it causes problems with regular navigation
properties::BoolProperty _applyStopBehaviorWhenIdle;
properties::OptionProperty _stopBehavior;
properties::StringListProperty _relevantNodeTags;
};
} // namespace openspace::autonavigation

View File

@@ -28,6 +28,8 @@
#include <openspace/engine/globals.h>
#include <openspace/engine/globalscallbacks.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scene.h>
#include <ghoul/logging/logmanager.h>
namespace {
@@ -38,16 +40,33 @@ namespace {
"computation of target positions and path generation, to avoid issues when "
"there is no bounding sphere."
};
constexpr openspace::properties::Property::PropertyInfo RelevantNodeTagsInfo = {
"RelevantNodeTags",
"Relevant Node Tags",
"List of tags for the nodes that are relevant for path creation, for example "
"when avoiding collisions."
};
} // namespace
namespace openspace {
AutoNavigationModule::AutoNavigationModule()
: OpenSpaceModule(Name),
_minValidBoundingSphere(MinBoundingSphereInfo, 10.0, 1.0, 3e10)
: OpenSpaceModule(Name)
, _minValidBoundingSphere(MinBoundingSphereInfo, 10.0, 1.0, 3e10)
, _relevantNodeTags(RelevantNodeTagsInfo)
{
addPropertySubOwner(_autoNavigationHandler);
addProperty(_minValidBoundingSphere);
// Add the relevant tags
_relevantNodeTags = std::vector<std::string>{
"planet_solarSystem",
"moon_solarSystem"
};;
_relevantNodeTags.onChange([this]() { findRelevantNodes(); });
addProperty(_relevantNodeTags);
}
autonavigation::AutoNavigationHandler& AutoNavigationModule::AutoNavigationHandler() {
@@ -58,6 +77,50 @@ double AutoNavigationModule::minValidBoundingSphere() const {
return _minValidBoundingSphere;
}
const std::vector<SceneGraphNode*>& AutoNavigationModule::relevantNodes() const {
return _relevantNodes;
}
void AutoNavigationModule::findRelevantNodes() {
const std::vector<SceneGraphNode*>& allNodes =
global::renderEngine->scene()->allSceneGraphNodes();
const std::vector<std::string> relevantTags = _relevantNodeTags;
if (allNodes.empty() || relevantTags.empty()) {
_relevantNodes = std::vector<SceneGraphNode*>();
return;
}
auto isRelevant = [&](const SceneGraphNode* node) {
const std::vector<std::string> tags = node->tags();
auto result = std::find_first_of(
relevantTags.begin(),
relevantTags.end(),
tags.begin(),
tags.end()
);
// does not match any tags => not interesting
if (result == relevantTags.end()) {
return false;
}
return node->renderable() && (node->boundingSphere() > 0.0);
};
std::vector<SceneGraphNode*> resultingNodes;
std::copy_if(
allNodes.begin(),
allNodes.end(),
std::back_inserter(resultingNodes),
isRelevant
);
_relevantNodes = resultingNodes;
}
std::vector<documentation::Documentation> AutoNavigationModule::documentations() const {
return {
autonavigation::PathInstruction::Documentation()

View File

@@ -41,16 +41,21 @@ public:
autonavigation::AutoNavigationHandler& AutoNavigationHandler();
double minValidBoundingSphere() const;
const std::vector<SceneGraphNode*>& relevantNodes() const;
std::vector<documentation::Documentation> documentations() const override;
scripting::LuaLibrary luaLibrary() const override;
private:
void internalInitialize(const ghoul::Dictionary&) override;
void findRelevantNodes();
autonavigation::AutoNavigationHandler _autoNavigationHandler;
properties::DoubleProperty _minValidBoundingSphere;
properties::StringListProperty _relevantNodeTags;
std::vector<SceneGraphNode*> _relevantNodes;
};
} // namespace openspace

View File

@@ -49,8 +49,8 @@ namespace {
namespace openspace::autonavigation {
AvoidCollisionCurve::AvoidCollisionCurve(const Waypoint& start, const Waypoint& end) {
AutoNavigationModule* module = global::moduleEngine->module<AutoNavigationModule>();
_relevantNodes = module->AutoNavigationHandler().relevantNodes();
_relevantNodes =
global::moduleEngine->module<AutoNavigationModule>()->relevantNodes();
const glm::dvec3 startNodeCenter = start.node()->worldPosition();
const glm::dvec3 endNodeCenter = end.node()->worldPosition();

View File

@@ -24,9 +24,11 @@
#include <modules/autonavigation/pathinstruction.h>
#include <modules/autonavigation/autonavigationmodule.h>
#include <modules/autonavigation/helperfunctions.h>
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/interaction/navigationhandler.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/query/query.h>
@@ -35,6 +37,8 @@
namespace {
constexpr const char* _loggerCat = "PathInstruction";
constexpr const float Epsilon = 1e-5f;
struct [[codegen::Dictionary(Instruction)]] Parameters {
enum class Type {
@@ -83,6 +87,9 @@ PathInstruction::PathInstruction(const ghoul::Dictionary& dictionary) {
duration = p.duration;
bool hasStart = p.startState.has_value();
startPoint = hasStart ? Waypoint(p.startState.value()) : waypointFromCamera();
switch (p.type) {
case Parameters::Type::NavigationState: {
type = Type::NavigationState;
@@ -114,29 +121,7 @@ PathInstruction::PathInstruction(const ghoul::Dictionary& dictionary) {
position = p.position;
height = p.height;
useTargetUpDirection = p.useTargetUpDirection.value_or(false);
if (position.has_value()) {
// Note that the anchor and reference frame is our targetnode.
// The position in instruction is given is relative coordinates.
glm::dvec3 targetPos = targetNode->worldPosition() +
targetNode->worldRotationMatrix() * position.value();
Camera* camera = global::navigationHandler->camera();
glm::dvec3 up = camera->lookUpVectorWorldSpace();
if (useTargetUpDirection) {
// @TODO (emmbr 2020-11-17) For now, this is hardcoded to look good for Earth,
// which is where it matters the most. A better solution would be to make each
// sgn aware of its own 'up' and query
up = targetNode->worldRotationMatrix() * glm::dvec3(0.0, 0.0, 1.0);
}
const glm::dvec3 lookAtPos = targetNode->worldPosition();
const glm::dquat targetRot = helpers::lookAtQuaternion(targetPos, lookAtPos, up);
Waypoint wp{ targetPos, targetRot, nodeIdentifier };
waypoints = { wp };
}
waypoints = { computeDefaultWaypoint() };
break;
}
default: {
@@ -147,5 +132,113 @@ PathInstruction::PathInstruction(const ghoul::Dictionary& dictionary) {
}
}
Waypoint PathInstruction::waypointFromCamera() const {
Camera* camera = global::navigationHandler->camera();
const glm::dvec3 pos = camera->positionVec3();
const glm::dquat rot = camera->rotationQuaternion();
const std::string node = global::navigationHandler->anchorNode()->identifier();
return Waypoint{ pos, rot, node };
}
// OBS! The desired default waypoint may vary between curve types.
// TODO: let the curves update the default position if no exact position is required
Waypoint PathInstruction::computeDefaultWaypoint() const {
const SceneGraphNode* targetNode = sceneGraphNode(nodeIdentifier);
if (!targetNode) {
LERROR(fmt::format("Could not find target node '{}'", nodeIdentifier));
return Waypoint();
}
glm::dvec3 targetPos;
if (position.has_value()) {
// Note that the anchor and reference frame is our targetnode.
// The position in instruction is given is relative coordinates
targetPos = targetNode->worldPosition() +
targetNode->worldRotationMatrix() * position.value();
}
else {
const glm::dvec3 nodePos = targetNode->worldPosition();
const glm::dvec3 sunPos = glm::dvec3(0.0, 0.0, 0.0);
const SceneGraphNode* closeNode = findNodeNearTarget(targetNode);
glm::dvec3 stepDirection;
if (closeNode) {
// If the node is close to another node in the scene, make sure that the
// position is set to minimize risk of collision
stepDirection = glm::normalize(nodePos - closeNode->worldPosition());
}
else if (glm::length(sunPos - nodePos) < Epsilon) {
// Special case for when the target is the Sun. Assumption: want an overview of
// the solar system, and not stay in the orbital plane
stepDirection = glm::dvec3(0.0, 0.0, 1.0);
}
else {
// Go to a point that is being lit up by the sun, slightly offsetted from sun
// direction
const glm::dvec3 prevPos = startPoint.position();
const glm::dvec3 targetToPrev = prevPos - nodePos;
const glm::dvec3 targetToSun = sunPos - nodePos;
constexpr const float defaultPositionOffsetAngle = -30.f; // degrees
constexpr const float angle = glm::radians(defaultPositionOffsetAngle);
const glm::dvec3 axis = glm::normalize(glm::cross(targetToPrev, targetToSun));
const glm::dquat offsetRotation = angleAxis(static_cast<double>(angle), axis);
stepDirection = glm::normalize(offsetRotation * targetToSun);
}
const double radius = Waypoint::findValidBoundingSphere(targetNode);
const double defaultHeight = 2.0 * radius;
const double targetHeight = height.value_or(defaultHeight);
targetPos = nodePos + stepDirection * (radius + targetHeight);
}
// Up direction
glm::dvec3 up = global::navigationHandler->camera()->lookUpVectorWorldSpace();
if (useTargetUpDirection) {
// @TODO (emmbr 2020-11-17) For now, this is hardcoded to look good for Earth,
// which is where it matters the most. A better solution would be to make each
// sgn aware of its own 'up' and query
up = targetNode->worldRotationMatrix() * glm::dvec3(0.0, 0.0, 1.0);
}
// Rotation
const glm::dvec3 lookAtPos = targetNode->worldPosition();
const glm::dquat targetRot = helpers::lookAtQuaternion(targetPos, lookAtPos, up);
return Waypoint(targetPos, targetRot, nodeIdentifier);
}
// Test if the node lies within a given proximity radius of any relevant node in the scene
SceneGraphNode* PathInstruction::findNodeNearTarget(const SceneGraphNode* node) const {
const std::vector<SceneGraphNode*>& relevantNodes =
global::moduleEngine->module<AutoNavigationModule>()->relevantNodes();
for (SceneGraphNode* n : relevantNodes) {
if (n->identifier() == node->identifier()) {
continue;
}
constexpr const float proximityRadiusFactor = 3.f;
const float bs = static_cast<float>(n->boundingSphere());
const float proximityRadius = proximityRadiusFactor * bs;
const glm::dvec3 posInModelCoords =
glm::inverse(n->modelTransform()) * glm::dvec4(node->worldPosition(), 1.0);
bool isClose = helpers::isPointInsideSphere(
posInModelCoords,
glm::dvec3(0.0, 0.0, 0.0),
proximityRadius
);
if (isClose) {
return n;
}
}
return nullptr;
}
} // namespace openspace::autonavigation

View File

@@ -43,10 +43,11 @@ struct PathInstruction {
static documentation::Documentation Documentation();
Type type;
Waypoint waypointFromCamera() const;
Waypoint computeDefaultWaypoint() const;
SceneGraphNode* findNodeNearTarget(const SceneGraphNode* node) const;
std::optional<double> duration;
std::optional<NavigationState> startState;
Type type;
// Node details
std::string nodeIdentifier;
@@ -57,6 +58,8 @@ struct PathInstruction {
// Navigation state details
NavigationState navigationState;
std::optional<double> duration;
Waypoint startPoint;
std::vector<Waypoint> waypoints;
};