Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GlobeFlightPath helper class to handle math for fly-to components. #797

Merged
merged 10 commits into from
Feb 5, 2024
Merged
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Added conversions from `std::string` to other metadata types in `MetadataConversions`. This enables the same conversions as `std::string_view`, while allowing runtime engines to use `std::string` for convenience.
- Added `applyTextureTransform` property to `TilesetOptions`, which indicates whether to preemptively apply transforms to texture coordinates for textures with the `KHR_texture_transform` extension.
- Added `loadGltf` method to `GltfReader`, making it easier to do a full, asynchronous load of a glTF.
- Added `GlobeFlightPath` class to help with calculating fly-to paths.

##### Fixes :wrench:

Expand Down
11 changes: 11 additions & 0 deletions CesiumGeospatial/include/CesiumGeospatial/Ellipsoid.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ class CESIUMGEOSPATIAL_API Ellipsoid final {
std::optional<glm::dvec3>
scaleToGeodeticSurface(const glm::dvec3& cartesian) const noexcept;

/**
* @brief Scales the provided cartesian position along the geocentric
* surface normal so that it is on the surface of this ellipsoid.
*
* @param cartesian The cartesian position to scale.
* @retun The scaled position, or the empty optional if the cartesian is at
* the center of this ellipsoid.
*/
std::optional<glm::dvec3>
scaleToGeocentricSurface(const glm::dvec3& cartesian) const noexcept;

/**
* @brief The maximum radius in any dimension.
*
Expand Down
101 changes: 101 additions & 0 deletions CesiumGeospatial/include/CesiumGeospatial/SimplePlanarEllipsoidCurve.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#pragma once

#include "Library.h"

#include <CesiumGeospatial/Ellipsoid.h>

#include <glm/vec3.hpp>

#include <optional>

namespace CesiumGeospatial {

/**
* @brief Produces points on an ellipse that lies on a plane that intersects the
csciguy8 marked this conversation as resolved.
Show resolved Hide resolved
* center of the earth and each of the input coordinates. The height above the
* surface at each point along the curve will be a linear interpolation between
* the source and destination heights.
*/
class CESIUMGEOSPATIAL_API SimplePlanarEllipsoidCurve final {
public:
/**
* @brief Creates a new instance of {@link SimplePlanarEllipsoidCurve} from a
* source and destination specified in Earth-Centered, Earth-Fixed
* coordinates.
*
* @param ellipsoid The ellipsoid that the source and destination positions
* are relative to.
* @param sourceEcef The position that the path will begin at in ECEF
* coordinates.
* @param destinationEcef The position that the path will end at in ECEF
* coordinates.
*
* @returns An optional type containing a {@link SimplePlanarEllipsoidCurve}
* object representing the generated path, if possible. If it wasn't possible
* to scale the input coordinates to geodetic surface coordinates on a WGS84
* ellipsoid, this will return {@link std::nullopt} instead.
*/
static std::optional<SimplePlanarEllipsoidCurve>
fromEarthCenteredEarthFixedCoordinates(
const Ellipsoid& ellipsoid,
const glm::dvec3& sourceEcef,
const glm::dvec3& destinationEcef);

/**
* @brief Creates a new instance of {@link SimplePlanarEllipsoidCurve} from a
* source and destination specified in cartographic coordinates (Longitude,
* Latitude, and Height).
*
* @param ellipsoid The ellipsoid that these cartographic coordinates are
* from.
* @param sourceLlh The position that the path will begin at in Longitude,
* Latitude, and Height.
* @param destinationLlh The position that the path will end at in Longitude,
* Latitude, and Height.
*
* @returns An optional type containing a {@link SimplePlanarEllipsoidCurve}
* object representing the generated path, if possible. If it wasn't possible
* to scale the input coordinates to geodetic surface coordinates on a WGS84
* ellipsoid, this will return {@link std::nullopt} instead.
*/
static std::optional<SimplePlanarEllipsoidCurve> fromLongitudeLatitudeHeight(
const Ellipsoid& ellipsoid,
const Cartographic& source,
const Cartographic& destination);

/**
* @brief Samples the curve at the given percentage of its length.
*
* @param percentage The percentage of the curve's length to sample at,
* where 0 is the beginning and 1 is the end. This value will be clamped to
* the range [0..1].
* @param additionalHeight The height above the earth at this position will be
* calculated by interpolating between the height at the beginning and end of
* the curve based on the value of \p percentage. This parameter specifies an
* additional offset to add to the height.
*
* @returns The position of the given point on this curve in Earth-Centered
* Earth-Fixed coordinates.
*/
glm::dvec3
getPosition(double percentage, double additionalHeight = 0.0) const;

private:
SimplePlanarEllipsoidCurve(
const Ellipsoid& ellipsoid,
const glm::dvec3& scaledSourceEcef,
const glm::dvec3& scaledDestinationEcef,
const glm::dvec3& originalSourceEcef,
const glm::dvec3& originalDestinationEcef);

double _totalAngle;
double _sourceHeight;
double _destinationHeight;

Ellipsoid _ellipsoid;
glm::dvec3 _sourceDirection;
glm::dvec3 _rotationAxis;
glm::dvec3 _destinationEcef;
};

} // namespace CesiumGeospatial
25 changes: 25 additions & 0 deletions CesiumGeospatial/src/Ellipsoid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,29 @@ Ellipsoid::scaleToGeodeticSurface(const glm::dvec3& cartesian) const noexcept {
positionZ * zMultiplier);
}

std::optional<glm::dvec3> Ellipsoid::scaleToGeocentricSurface(
const glm::dvec3& cartesian) const noexcept {

// If the input cartesian is (0, 0, 0), beta will compute to 1.0 / sqrt(0) =
// +Infinity. Let's consider this a failure.
if (Math::equalsEpsilon(glm::length(cartesian), 0, Math::Epsilon12)) {
return std::optional<glm::dvec3>();
}

const double positionX = cartesian.x;
const double positionY = cartesian.y;
const double positionZ = cartesian.z;

const double oneOverRadiiSquaredX = this->_oneOverRadiiSquared.x;
const double oneOverRadiiSquaredY = this->_oneOverRadiiSquared.y;
const double oneOverRadiiSquaredZ = this->_oneOverRadiiSquared.z;

const double beta = 1.0 / sqrt(
positionX * positionX * oneOverRadiiSquaredX +
positionY * positionY * oneOverRadiiSquaredY +
positionZ * positionZ * oneOverRadiiSquaredZ);

return glm::dvec3(positionX * beta, positionY * beta, positionZ * beta);
}

} // namespace CesiumGeospatial
102 changes: 102 additions & 0 deletions CesiumGeospatial/src/SimplePlanarEllipsoidCurve.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#include <CesiumGeospatial/Ellipsoid.h>
#include <CesiumGeospatial/GlobeTransforms.h>
#include <CesiumGeospatial/SimplePlanarEllipsoidCurve.h>
#include <CesiumUtility/Math.h>

#include <glm/gtx/quaternion.hpp>

namespace CesiumGeospatial {

std::optional<SimplePlanarEllipsoidCurve>
SimplePlanarEllipsoidCurve::fromEarthCenteredEarthFixedCoordinates(
const Ellipsoid& ellipsoid,
const glm::dvec3& sourceEcef,
const glm::dvec3& destinationEcef) {
std::optional<glm::dvec3> scaledSourceEcef =
ellipsoid.scaleToGeocentricSurface(sourceEcef);
std::optional<glm::dvec3> scaledDestinationEcef =
ellipsoid.scaleToGeocentricSurface(destinationEcef);

if (!scaledSourceEcef.has_value() || !scaledDestinationEcef.has_value()) {
// Unable to scale to geocentric surface coordinates - no curve we can
// generate
return std::optional<SimplePlanarEllipsoidCurve>();
}

return SimplePlanarEllipsoidCurve(
ellipsoid,
scaledSourceEcef.value(),
scaledDestinationEcef.value(),
sourceEcef,
destinationEcef);
}

std::optional<SimplePlanarEllipsoidCurve>
SimplePlanarEllipsoidCurve::fromLongitudeLatitudeHeight(
const Ellipsoid& ellipsoid,
const Cartographic& source,
const Cartographic& destination) {
return SimplePlanarEllipsoidCurve::fromEarthCenteredEarthFixedCoordinates(
ellipsoid,
ellipsoid.cartographicToCartesian(source),
ellipsoid.cartographicToCartesian(destination));
}

glm::dvec3 SimplePlanarEllipsoidCurve::getPosition(
double percentage,
double additionalHeight) const {
if (percentage >= 1.0) {
// We can shortcut our math here and just return the destination.
return this->_destinationEcef;
}

azrogers marked this conversation as resolved.
Show resolved Hide resolved
percentage = glm::clamp(percentage, 0.0, 1.0);

// Rotate us around the circle between points A and B by the given percentage
// of the total angle we're rotating by.
glm::dvec3 rotatedDirection =
glm::angleAxis(percentage * this->_totalAngle, this->_rotationAxis) *
this->_sourceDirection;

// It's safe for us to assume here that scaleToGeocentricSurface will return a
// value, since rotatedDirection should never be (0, 0, 0)
glm::dvec3 geocentricPosition =
this->_ellipsoid.scaleToGeocentricSurface(rotatedDirection)
.value_or(glm::dvec3(0, 0, 0));

glm::dvec3 geocentricUp = glm::normalize(geocentricPosition);

double altitudeOffset =
glm::mix(this->_sourceHeight, this->_destinationHeight, percentage) +
additionalHeight;

return geocentricPosition + geocentricUp * altitudeOffset;
}

SimplePlanarEllipsoidCurve::SimplePlanarEllipsoidCurve(
const Ellipsoid& ellipsoid,
const glm::dvec3& scaledSourceEcef,
const glm::dvec3& scaledDestinationEcef,
const glm::dvec3& originalSourceEcef,
const glm::dvec3& originalDestinationEcef)
: _ellipsoid(ellipsoid), _destinationEcef(originalDestinationEcef) {
// Here we find the center of a circle that passes through both the source and
// destination points, and then calculate the angle that we need to move along
// that circle to get from point A to B.

glm::dquat flyQuat = glm::rotation(
glm::normalize(scaledSourceEcef),
glm::normalize(scaledDestinationEcef));

this->_rotationAxis = glm::axis(flyQuat);
this->_totalAngle = glm::angle(flyQuat);

this->_sourceHeight = glm::length(originalSourceEcef - scaledSourceEcef);
this->_destinationHeight =
glm::length(originalDestinationEcef - scaledDestinationEcef);

this->_sourceDirection =
glm::normalize(originalSourceEcef - scaledSourceEcef);
}

} // namespace CesiumGeospatial
Loading
Loading