celestia/src/celengine/observer.cpp

1602 lines
50 KiB
C++

// observer.cpp
//
// Copyright (C) 2001-2009, the Celestia Development Team
// Original version by Chris Laurel <claurel@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
#include "observer.h"
#include "simulation.h"
#include "frametree.h"
#include <celmath/mathlib.h>
#include <celmath/solve.h>
#include <celmath/geomutil.h>
static const double maximumSimTime = 730486721060.00073; // 2000000000 Jan 01 12:00:00 UTC
static const double minimumSimTime = -730498278941.99951; // -2000000000 Jan 01 12:00:00 UTC
using namespace Eigen;
using namespace std;
using namespace celmath;
#define VELOCITY_CHANGE_TIME 0.25f
static Vector3d slerp(double t, const Vector3d& v0, const Vector3d& v1)
{
double r0 = v0.norm();
double r1 = v1.norm();
Vector3d u = v0 / r0;
Vector3d n = u.cross(v1 / r1);
n.normalize();
Vector3d v = n.cross(u);
if (v.dot(v1) < 0.0)
v = -v;
double cosTheta = u.dot(v1 / r1);
double theta = acos(cosTheta);
return (cos(theta * t) * u + sin(theta * t) * v) * lerp(t, r0, r1);
}
/*! Notes on the Observer class
* The values position and orientation are in observer's reference frame. positionUniv
* and orientationUniv are the equivalent values in the universal coordinate system.
* They must be kept in sync. Generally, it's position and orientation that are modified;
* after they're changed, the method updateUniversal is called. However, when the observer
* frame is changed, positionUniv and orientationUniv are not changed, but the position
* and orientation within the frame /do/ change. Thus, a 'reverse' update is necessary.
*
* There are two types of 'automatic' updates to position and orientation that may
* occur when the observer's update method is called: updates from free travel, and
* updates due to an active goto operation.
*/
Observer::Observer() :
frame(new ObserverFrame)
{
updateUniversal();
}
/*! Copy constructor. */
Observer::Observer(const Observer& o) :
simTime(o.simTime),
position(o.position),
orientation(o.orientation),
velocity(o.velocity),
angularVelocity(o.angularVelocity),
realTime(o.realTime),
targetSpeed(o.targetSpeed),
targetVelocity(o.targetVelocity),
beginAccelTime(o.beginAccelTime),
observerMode(o.observerMode),
journey(o.journey),
trackObject(o.trackObject),
trackingOrientation(o.trackingOrientation),
fov(o.fov),
reverseFlag(o.reverseFlag),
locationFilter(o.locationFilter),
displayedSurface(o.displayedSurface)
{
setFrame(o.frame);
updateUniversal();
}
Observer& Observer::operator=(const Observer& o)
{
simTime = o.simTime;
position = o.position;
orientation = o.orientation;
velocity = o.velocity;
angularVelocity = o.angularVelocity;
frame = nullptr;
realTime = o.realTime;
targetSpeed = o.targetSpeed;
targetVelocity = o.targetVelocity;
beginAccelTime = o.beginAccelTime;
observerMode = o.observerMode;
journey = o.journey;
trackObject = o.trackObject;
trackingOrientation = o.trackingOrientation;
fov = o.fov;
reverseFlag = o.reverseFlag;
locationFilter = o.locationFilter;
displayedSurface = o.displayedSurface;
setFrame(o.frame);
updateUniversal();
return *this;
}
/*! Get the current simulation time. The time returned is a Julian date,
* and the time standard is TDB.
*/
double Observer::getTime() const
{
return simTime;
}
/*! Get the current real time. The time returned is a Julian date,
* and the time standard is TDB.
*/
double Observer::getRealTime() const
{
return realTime;
}
/*! Set the simulation time (Julian date, TDB time standard)
*/
void Observer::setTime(double jd)
{
simTime = jd;
updateUniversal();
}
/*! Return the position of the observer in universal coordinates. The origin
* The origin of this coordinate system is the Solar System Barycenter, and
* axes are defined by the J2000 ecliptic and equinox.
*/
UniversalCoord Observer::getPosition() const
{
return positionUniv;
}
#if 0
// TODO: Low-precision set position that should be removed
void Observer::setPosition(const Vector3d& p)
{
setPosition(UniversalCoord(p));
}
#endif
/*! Set the position of the observer; position is specified in the universal
* coordinate system.
*/
void Observer::setPosition(const UniversalCoord& p)
{
positionUniv = p;
position = frame->convertFromUniversal(p, getTime());
}
/*! Return the orientation of the observer in the universal coordinate
* system.
*/
Quaterniond Observer::getOrientation() const
{
return orientationUniv;
}
/*! Reduced precision version of getOrientation()
*/
Quaternionf Observer::getOrientationf() const
{
return getOrientation().cast<float>();
}
/* Set the orientation of the observer. The orientation is specified in
* the universal coordinate system.
*/
void Observer::setOrientation(const Quaternionf& q)
{
/*
RigidTransform rt = frame.toUniversal(situation, getTime());
rt.rotation = Quatd(q.w, q.x, q.y, q.z);
situation = frame.fromUniversal(rt, getTime());
*/
setOrientation(q.cast<double>());
}
/*! Set the orientation of the observer. The orientation is specified in
* the universal coordinate system.
*/
void Observer::setOrientation(const Quaterniond& q)
{
orientationUniv = q;
orientation = frame->convertFromUniversal(q, getTime());
}
/*! Get the velocity of the observer within the observer's reference frame.
*/
Vector3d Observer::getVelocity() const
{
return velocity;
}
/*! Set the velocity of the observer within the observer's reference frame.
*/
void Observer::setVelocity(const Vector3d& v)
{
velocity = v;
}
Vector3d Observer::getAngularVelocity() const
{
return angularVelocity;
}
void Observer::setAngularVelocity(const Vector3d& v)
{
angularVelocity = v;
}
double Observer::getArrivalTime() const
{
if (observerMode != Travelling)
return realTime;
return journey.startTime + journey.duration;
}
/*! Tick the simulation by dt seconds. Update the observer position
* and orientation due to an active goto command or non-zero velocity
* or angular velocity.
*/
void Observer::update(double dt, double timeScale)
{
realTime += dt;
simTime += (dt / 86400.0) * timeScale;
if (simTime >= maximumSimTime)
simTime = maximumSimTime;
if (simTime <= minimumSimTime)
simTime = minimumSimTime;
if (observerMode == Travelling)
{
// Compute the fraction of the trip that has elapsed; handle zero
// durations correctly by skipping directly to the destination.
float t = 1.0;
if (journey.duration > 0)
t = static_cast<float>(std::clamp((realTime - journey.startTime) / journey.duration, 0.0, 1.0));
Vector3d jv = journey.to.offsetFromKm(journey.from);
UniversalCoord p;
// Another interpolation method . . . accelerate exponentially,
// maintain a constant velocity for a period of time, then
// decelerate. The portion of the trip spent accelerating is
// controlled by the parameter journey.accelTime; a value of 1 means
// that the entire first half of the trip will be spent accelerating
// and there will be no coasting at constant velocity.
{
double u = t < 0.5 ? t * 2 : (1 - t) * 2;
double x;
if (u < journey.accelTime)
{
x = exp(journey.expFactor * u) - 1.0;
}
else
{
x = exp(journey.expFactor * journey.accelTime) *
(journey.expFactor * (u - journey.accelTime) + 1) - 1;
}
if (journey.traj == Linear)
{
Vector3d v = jv;
if (v.norm() == 0.0)
{
p = journey.from;
}
else
{
v.normalize();
if (t < 0.5)
p = journey.from.offsetKm(v * x);
else
p = journey.to.offsetKm(-v * x);
}
}
else if (journey.traj == GreatCircle)
{
Selection centerObj = frame->getRefObject();
if (centerObj.body() != nullptr)
{
Body* body = centerObj.body();
if (body->getSystem())
{
if (body->getSystem()->getPrimaryBody() != nullptr)
centerObj = Selection(body->getSystem()->getPrimaryBody());
else
centerObj = Selection(body->getSystem()->getStar());
}
}
UniversalCoord ufrom = frame->convertToUniversal(journey.from, simTime);
UniversalCoord uto = frame->convertToUniversal(journey.to, simTime);
UniversalCoord origin = centerObj.getPosition(simTime);
Vector3d v0 = ufrom.offsetFromKm(origin);
Vector3d v1 = uto.offsetFromKm(origin);
if (jv.norm() == 0.0)
{
p = journey.from;
}
else
{
x /= jv.norm();
Vector3d v;
if (t < 0.5)
v = slerp(x, v0, v1);
else
v = slerp(x, v1, v0);
p = frame->convertFromUniversal(origin.offsetKm(v), simTime);
}
}
else if (journey.traj == CircularOrbit)
{
Selection centerObj = frame->getRefObject();
UniversalCoord ufrom = frame->convertToUniversal(journey.from, simTime);
//UniversalCoord uto = frame->convertToUniversal(journey.to, simTime);
UniversalCoord origin = centerObj.getPosition(simTime);
Vector3d v0 = ufrom.offsetFromKm(origin);
//Vector3d v1 = uto.offsetFromKm(origin);
if (jv.norm() == 0.0)
{
p = journey.from;
}
else
{
Quaterniond q0(Quaterniond::Identity());
Quaterniond q1 = journey.rotation1;
p = origin.offsetKm(q0.slerp(t, q1).conjugate() * v0);
p = frame->convertFromUniversal(p, simTime);
}
}
}
// Spherically interpolate the orientation over the first half
// of the journey.
Quaterniond q;
if (t >= journey.startInterpolation && t < journey.endInterpolation )
{
// Smooth out the interpolation to avoid jarring changes in
// orientation
double v;
if (journey.traj == CircularOrbit)
{
// In circular orbit mode, interpolation of orientation must
// match the interpolation of position.
v = t;
}
else
{
v = pow(sin((t - journey.startInterpolation) /
(journey.endInterpolation - journey.startInterpolation) *
celestia::numbers::pi / 2.0), 2);
}
q = journey.initialOrientation.slerp(v, journey.finalOrientation);
}
else if (t < journey.startInterpolation)
{
q = journey.initialOrientation;
}
else // t >= endInterpolation
{
q = journey.finalOrientation;
}
position = p;
orientation = q;
// If the journey's complete, reset to manual control
if (t == 1.0f)
{
if (journey.traj != CircularOrbit)
{
//situation = RigidTransform(journey.to, journey.finalOrientation);
position = journey.to;
orientation = journey.finalOrientation;
}
observerMode = Free;
setVelocity(Vector3d::Zero());
}
}
if (getVelocity() != targetVelocity)
{
double t = std::clamp((realTime - beginAccelTime) / VELOCITY_CHANGE_TIME, 0.0, 1.0);
Vector3d v = getVelocity() * (1.0 - t) + targetVelocity * t;
// At some threshold, we just set the velocity to zero; otherwise,
// we'll end up with ridiculous velocities like 10^-40 m/s.
if (v.norm() < 1.0e-12)
v = Vector3d::Zero();
setVelocity(v);
}
// Update the position
position = position.offsetKm(getVelocity() * dt);
if (observerMode == Free)
{
// Update the observer's orientation
Vector3d halfAV = getAngularVelocity() * 0.5;
Quaterniond dr = Quaterniond(0.0, halfAV.x(), halfAV.y(), halfAV.z()) * orientation;
orientation = Quaterniond(orientation.coeffs() + dt * dr.coeffs());
orientation.normalize();
}
updateUniversal();
// Update orientation for tracking--must occur after updateUniversal(), as it
// relies on the universal position and orientation of the observer.
if (!trackObject.empty())
{
Vector3d up = getOrientation().conjugate() * Vector3d::UnitY();
Vector3d viewDir = trackObject.getPosition(getTime()).offsetFromKm(getPosition()).normalized();
setOrientation(LookAt<double>(Vector3d::Zero(), viewDir, up));
}
}
Selection Observer::getTrackedObject() const
{
return trackObject;
}
void Observer::setTrackedObject(const Selection& sel)
{
trackObject = sel;
}
const string& Observer::getDisplayedSurface() const
{
return displayedSurface;
}
void Observer::setDisplayedSurface(const string& surf)
{
displayedSurface = surf;
}
uint64_t Observer::getLocationFilter() const
{
return locationFilter;
}
void Observer::setLocationFilter(uint64_t _locationFilter)
{
locationFilter = _locationFilter;
}
void Observer::reverseOrientation()
{
setOrientation(getOrientation() * Quaterniond(AngleAxisd(celestia::numbers::pi, Vector3d::UnitY())));
reverseFlag = !reverseFlag;
}
struct TravelExpFunc
{
double dist, s;
TravelExpFunc(double d, double _s) : dist(d), s(_s) {};
double operator()(double x) const
{
// return (1.0 / x) * (exp(x / 2.0) - 1.0) - 0.5 - dist / 2.0;
return exp(x * s) * (x * (1 - s) + 1) - 1 - dist;
}
};
void Observer::computeGotoParameters(const Selection& destination,
JourneyParams& jparams,
double gotoTime,
double startInter,
double endInter,
double accelTime,
const Vector3d& offset,
ObserverFrame::CoordinateSystem offsetCoordSys,
const Vector3f& up,
ObserverFrame::CoordinateSystem upCoordSys)
{
if (frame->getCoordinateSystem() == ObserverFrame::PhaseLock)
{
//setFrame(FrameOfReference(astro::Ecliptical, destination));
setFrame(ObserverFrame::Ecliptical, destination);
}
else
{
setFrame(frame->getCoordinateSystem(), destination);
}
UniversalCoord targetPosition = destination.getPosition(getTime());
//Vector3d v = targetPosition.offsetFromKm(getPosition()).normalized();
jparams.traj = Linear;
jparams.duration = gotoTime;
jparams.startTime = realTime;
// Right where we are now . . .
jparams.from = getPosition();
if (offsetCoordSys == ObserverFrame::ObserverLocal)
{
jparams.to = targetPosition.offsetKm(orientationUniv.conjugate() * offset);
}
else
{
ObserverFrame offsetFrame(offsetCoordSys, destination);
jparams.to = targetPosition.offsetKm(offsetFrame.getFrame()->getOrientation(getTime()).conjugate() * offset);
}
Vector3d upd = up.cast<double>();
if (upCoordSys == ObserverFrame::ObserverLocal)
{
upd = orientationUniv.conjugate() * upd;
}
else
{
ObserverFrame upFrame(upCoordSys, destination);
upd = upFrame.getFrame()->getOrientation(getTime()).conjugate() * upd;
}
jparams.initialOrientation = getOrientation();
Vector3d focus = targetPosition.offsetFromKm(jparams.to);
jparams.finalOrientation = LookAt<double>(Vector3d::Zero(), focus, upd);
jparams.startInterpolation = min(startInter, endInter);
jparams.endInterpolation = max(startInter, endInter);
jparams.accelTime = accelTime;
double distance = jparams.from.offsetFromKm(jparams.to).norm() / 2.0;
pair<double, double> sol = solve_bisection(TravelExpFunc(distance, jparams.accelTime),
0.0001, 100.0,
1e-10);
jparams.expFactor = sol.first;
// Convert to frame coordinates
jparams.from = frame->convertFromUniversal(jparams.from, getTime());
jparams.initialOrientation = frame->convertFromUniversal(jparams.initialOrientation, getTime());
jparams.to = frame->convertFromUniversal(jparams.to, getTime());
jparams.finalOrientation = frame->convertFromUniversal(jparams.finalOrientation, getTime());
}
void Observer::computeGotoParametersGC(const Selection& destination,
JourneyParams& jparams,
double gotoTime,
const Vector3d& offset,
ObserverFrame::CoordinateSystem offsetCoordSys,
const Vector3f& up,
ObserverFrame::CoordinateSystem upCoordSys,
const Selection& centerObj)
{
setFrame(frame->getCoordinateSystem(), destination);
UniversalCoord targetPosition = destination.getPosition(getTime());
//Vector3d v = targetPosition.offsetFromKm(getPosition()).normalized();
jparams.traj = GreatCircle;
jparams.duration = gotoTime;
jparams.startTime = realTime;
jparams.centerObject = centerObj;
// Right where we are now . . .
jparams.from = getPosition();
ObserverFrame offsetFrame(offsetCoordSys, destination);
Vector3d offsetTransformed = offsetFrame.getFrame()->getOrientation(getTime()).conjugate() * offset;
jparams.to = targetPosition.offsetKm(offsetTransformed);
Vector3d upd = up.cast<double>();
if (upCoordSys == ObserverFrame::ObserverLocal)
{
upd = orientationUniv.conjugate() * upd;
}
else
{
ObserverFrame upFrame(upCoordSys, destination);
upd = upFrame.getFrame()->getOrientation(getTime()).conjugate() * upd;
}
jparams.initialOrientation = getOrientation();
Vector3d focus = targetPosition.offsetFromKm(jparams.to);
jparams.finalOrientation = LookAt<double>(Vector3d::Zero(), focus, upd);
jparams.startInterpolation = min(StartInterpolation, EndInterpolation);
jparams.endInterpolation = max(StartInterpolation, EndInterpolation);
jparams.accelTime = AccelerationTime;
double distance = jparams.from.offsetFromKm(jparams.to).norm() / 2.0;
pair<double, double> sol = solve_bisection(TravelExpFunc(distance, jparams.accelTime),
0.0001, 100.0,
1e-10);
jparams.expFactor = sol.first;
// Convert to frame coordinates
jparams.from = frame->convertFromUniversal(jparams.from, getTime());
jparams.initialOrientation = frame->convertFromUniversal(jparams.initialOrientation, getTime());
jparams.to = frame->convertFromUniversal(jparams.to, getTime());
jparams.finalOrientation = frame->convertFromUniversal(jparams.finalOrientation, getTime());
}
void Observer::computeCenterParameters(const Selection& destination,
JourneyParams& jparams,
double centerTime)
{
UniversalCoord targetPosition = destination.getPosition(getTime());
jparams.duration = centerTime;
jparams.startTime = realTime;
jparams.traj = Linear;
// Don't move through space, just rotate the camera
jparams.from = getPosition();
jparams.to = jparams.from;
Vector3d up = getOrientation().conjugate() * Vector3d::UnitY();
jparams.initialOrientation = getOrientation();
Vector3d focus = targetPosition.offsetFromKm(jparams.to);
jparams.finalOrientation = LookAt<double>(Vector3d::Zero(), focus, up);
jparams.startInterpolation = 0;
jparams.endInterpolation = 1;
jparams.accelTime = 0.5;
jparams.expFactor = 0;
// Convert to frame coordinates
jparams.from = frame->convertFromUniversal(jparams.from, getTime());
jparams.initialOrientation = frame->convertFromUniversal(jparams.initialOrientation, getTime());
jparams.to = frame->convertFromUniversal(jparams.to, getTime());
jparams.finalOrientation = frame->convertFromUniversal(jparams.finalOrientation, getTime());
}
void Observer::computeCenterCOParameters(const Selection& destination,
JourneyParams& jparams,
double centerTime)
{
jparams.duration = centerTime;
jparams.startTime = realTime;
jparams.traj = CircularOrbit;
jparams.centerObject = frame->getRefObject();
jparams.expFactor = 0.5;
Vector3d v = destination.getPosition(getTime()).offsetFromKm(getPosition()).normalized();
Vector3d w = getOrientation().conjugate() * -Vector3d::UnitZ();
Selection centerObj = frame->getRefObject();
UniversalCoord centerPos = centerObj.getPosition(getTime());
//UniversalCoord targetPosition = destination.getPosition(getTime());
Quaterniond q;
q.setFromTwoVectors(v, w);
jparams.from = getPosition();
jparams.to = centerPos.offsetKm(q.conjugate() * getPosition().offsetFromKm(centerPos));
jparams.initialOrientation = getOrientation();
jparams.finalOrientation = getOrientation() * q;
jparams.startInterpolation = 0.0;
jparams.endInterpolation = 1.0;
jparams.rotation1 = q;
// Convert to frame coordinates
jparams.from = frame->convertFromUniversal(jparams.from, getTime());
jparams.initialOrientation = frame->convertFromUniversal(jparams.initialOrientation, getTime());
jparams.to = frame->convertFromUniversal(jparams.to, getTime());
jparams.finalOrientation = frame->convertFromUniversal(jparams.finalOrientation, getTime());
}
/*! Center the selection by moving on a circular orbit arround
* the primary body (refObject).
*/
void Observer::centerSelectionCO(const Selection& selection, double centerTime)
{
if (!selection.empty() && !frame->getRefObject().empty())
{
computeCenterCOParameters(selection, journey, centerTime);
observerMode = Travelling;
}
}
Observer::ObserverMode Observer::getMode() const
{
return observerMode;
}
void Observer::setMode(Observer::ObserverMode mode)
{
observerMode = mode;
}
// Private method to convert coordinates when a new observer frame is set.
// Universal coordinates remain the same. All frame coordinates get updated, including
// the goto parameters.
void Observer::convertFrameCoordinates(const ObserverFrame::SharedConstPtr &newFrame)
{
double now = getTime();
// Universal coordinates don't change.
// Convert frame coordinates to the new frame.
position = newFrame->convertFromUniversal(positionUniv, now);
orientation = newFrame->convertFromUniversal(orientationUniv, now);
// Convert goto parameters to the new frame
journey.from = ObserverFrame::convert(frame, newFrame, journey.from, now);
journey.initialOrientation = ObserverFrame::convert(frame, newFrame, journey.initialOrientation, now);
journey.to = ObserverFrame::convert(frame, newFrame, journey.to, now);
journey.finalOrientation = ObserverFrame::convert(frame, newFrame, journey.finalOrientation, now);
}
/*! Set the observer's reference frame. The position of the observer in
* universal coordinates will not change.
*/
void Observer::setFrame(ObserverFrame::CoordinateSystem cs, const Selection& refObj, const Selection& targetObj)
{
auto newFrame = shared_ptr<ObserverFrame>(new ObserverFrame(cs, refObj, targetObj));
convertFrameCoordinates(newFrame);
frame = newFrame;
}
/*! Set the observer's reference frame. The position of the observer in
* universal coordinates will not change.
*/
void Observer::setFrame(ObserverFrame::CoordinateSystem cs, const Selection& refObj)
{
setFrame(cs, refObj, Selection());
}
/*! Set the observer's reference frame. The position of the observer in
* universal coordinates will not change.
*/
void Observer::setFrame(const ObserverFrame::SharedConstPtr& f)
{
if (frame != f)
{
if (frame)
{
convertFrameCoordinates(f);
}
frame = f;
}
}
/*! Get the current reference frame for the observer.
*/
const ObserverFrame::SharedConstPtr& Observer::getFrame() const
{
return frame;
}
/*! Rotate the observer about its center.
*/
void Observer::rotate(const Quaternionf& q)
{
orientation = q.cast<double>() * orientation;
updateUniversal();
}
/*! Orbit around the reference object (if there is one.) This involves changing
* both the observer's position and orientation. If there is no current center
* object, the specified selection will be used as the center of rotation, and
* the observer reference frame will be modified.
*/
void Observer::orbit(const Selection& selection, const Quaternionf& q)
{
Selection center = frame->getRefObject();
if (center.empty() && !selection.empty())
{
// Automatically set the center of the reference frame
center = selection;
setFrame(frame->getCoordinateSystem(), center);
}
if (!center.empty())
{
// Get the focus position (center of rotation) in frame
// coordinates; in order to make this function work in all
// frames of reference, it's important to work in frame
// coordinates.
UniversalCoord focusPosition = center.getPosition(getTime());
//focusPosition = frame.fromUniversal(RigidTransform(focusPosition), getTime()).translation;
focusPosition = frame->convertFromUniversal(focusPosition, getTime());
// v = the vector from the observer's position to the focus
//Vec3d v = situation.translation - focusPosition;
Vector3d v = position.offsetFromKm(focusPosition);
Quaterniond qd = q.cast<double>();
// To give the right feel for rotation, we want to premultiply
// the current orientation by q. However, because of the order in
// which we apply transformations later on, we can't pre-multiply.
// To get around this, we compute a rotation q2 such
// that q1 * r = r * q2.
Quaterniond qd2 = orientation.conjugate() * qd * orientation;
qd2.normalize();
// Roundoff errors will accumulate and cause the distance between
// viewer and focus to drift unless we take steps to keep the
// length of v constant.
double distance = v.norm();
v = qd2.conjugate() * v;
v = v.normalized() * distance;
orientation = orientation * qd2;
position = focusPosition.offsetKm(v);
updateUniversal();
}
}
/*! Exponential camera dolly--move toward or away from the selected object
* at a rate dependent on the observer's distance from the object.
*/
void Observer::changeOrbitDistance(const Selection& selection, float d)
{
Selection center = frame->getRefObject();
if (center.empty() && !selection.empty())
{
center = selection;
setFrame(frame->getCoordinateSystem(), center);
}
if (!center.empty())
{
UniversalCoord focusPosition = center.getPosition(getTime());
double size = center.radius();
// Somewhat arbitrary parameters to chosen to give the camera movement
// a nice feel. They should probably be function parameters.
double minOrbitDistance = size;
double naturalOrbitDistance = 4.0 * size;
// Determine distance and direction to the selected object
Vector3d v = getPosition().offsetFromKm(focusPosition);
double currentDistance = v.norm();
if (currentDistance < minOrbitDistance)
minOrbitDistance = currentDistance * 0.5;
if (currentDistance >= minOrbitDistance && naturalOrbitDistance != 0)
{
double r = (currentDistance - minOrbitDistance) / naturalOrbitDistance;
double newDistance = minOrbitDistance + naturalOrbitDistance * exp(log(r) + d);
v = v * (newDistance / currentDistance);
position = frame->convertFromUniversal(focusPosition.offsetKm(v), getTime());
updateUniversal();
}
}
}
void Observer::setTargetSpeed(float s)
{
targetSpeed = s;
Vector3d v;
if (reverseFlag)
s = -s;
if (trackObject.empty())
{
trackingOrientation = getOrientation();
// Generate vector for velocity using current orientation
// and specified speed.
v = getOrientation().conjugate() * Vector3d(0, 0, -s);
}
else
{
// Use tracking orientation vector to generate target velocity
v = trackingOrientation.conjugate() * Vector3d(0, 0, -s);
}
targetVelocity = v;
initialVelocity = getVelocity();
beginAccelTime = realTime;
}
float Observer::getTargetSpeed()
{
return (float) targetSpeed;
}
void Observer::gotoJourney(const JourneyParams& params)
{
journey = params;
double distance = journey.from.offsetFromKm(journey.to).norm() / 2.0;
pair<double, double> sol = solve_bisection(TravelExpFunc(distance, journey.accelTime),
0.0001, 100.0,
1e-10);
journey.expFactor = sol.first;
journey.startTime = realTime;
observerMode = Travelling;
}
void Observer::gotoSelection(const Selection& selection,
double gotoTime,
const Vector3f& up,
ObserverFrame::CoordinateSystem upFrame)
{
gotoSelection(selection, gotoTime, 0.0, 0.5, AccelerationTime, up, upFrame);
}
// Return the preferred distance (in kilometers) for viewing an object
static double getPreferredDistance(const Selection& selection)
{
switch (selection.getType())
{
case Selection::Type_Body:
// Handle reference points (i.e. invisible objects) specially, since the
// actual radius of the point is meaningless. Instead, use the size of
// bounding sphere of all child objects. This is useful for system
// barycenters--the normal goto command will place the observer at
// a viewpoint in which the entire system can be seen.
if (selection.body()->getClassification() == Body::Invisible)
{
double r = selection.body()->getRadius();
if (selection.body()->getFrameTree() != nullptr)
r = selection.body()->getFrameTree()->boundingSphereRadius();
return min(astro::lightYearsToKilometers(0.1), r * 5.0);
}
else
{
return 5.0 * selection.radius();
}
case Selection::Type_DeepSky:
return 5.0 * selection.radius();
case Selection::Type_Star:
if (selection.star()->getVisibility())
{
return 100.0 * selection.radius();
}
else
{
// Handle star system barycenters specially, using the same approach as
// for reference points in solar systems.
double maxOrbitRadius = 0.0;
const vector<Star*>* orbitingStars = selection.star()->getOrbitingStars();
if (orbitingStars != nullptr)
{
for (const auto star : *orbitingStars)
{
Orbit* orbit = star->getOrbit();
if (orbit != nullptr)
maxOrbitRadius = max(orbit->getBoundingRadius(), maxOrbitRadius);
}
}
return maxOrbitRadius == 0.0 ? astro::AUtoKilometers(1.0) : maxOrbitRadius * 5.0;
}
case Selection::Type_Location:
{
double maxDist = getPreferredDistance(selection.location()->getParentBody());
return max(min(selection.location()->getSize() * 50.0, maxDist),
1.0);
}
default:
return 1.0;
}
}
// Given an object and its current distance from the camera, determine how
// close we should go on the next goto.
static double getOrbitDistance(const Selection& selection,
double currentDistance)
{
// If further than 10 times the preferrred distance, goto the
// preferred distance. If closer, zoom in 10 times closer or to the
// minimum distance.
double maxDist = getPreferredDistance(selection);
double minDist = 1.01 * selection.radius();
double dist = (currentDistance > maxDist * 10.0) ? maxDist : currentDistance * 0.1;
return max(dist, minDist);
}
void Observer::gotoSelection(const Selection& selection,
double gotoTime,
double startInter,
double endInter,
double accelTime,
const Vector3f& up,
ObserverFrame::CoordinateSystem upFrame)
{
if (!selection.empty())
{
UniversalCoord pos = selection.getPosition(getTime());
Vector3d v = pos.offsetFromKm(getPosition());
double distance = v.norm();
double orbitDistance = getOrbitDistance(selection, distance);
computeGotoParameters(selection, journey, gotoTime,
startInter, endInter, accelTime,
v * -(orbitDistance / distance),
ObserverFrame::Universal,
up, upFrame);
observerMode = Travelling;
}
}
/*! Like normal goto, except we'll follow a great circle trajectory. Useful
* for travelling between surface locations, where we'd rather not go straight
* through the middle of a planet.
*/
void Observer::gotoSelectionGC(const Selection& selection,
double gotoTime,
const Vector3f& up,
ObserverFrame::CoordinateSystem upFrame)
{
if (!selection.empty())
{
Selection centerObj = selection.parent();
UniversalCoord pos = selection.getPosition(getTime());
Vector3d v = pos.offsetFromKm(centerObj.getPosition(getTime()));
double distanceToCenter = v.norm();
Vector3d viewVec = pos.offsetFromKm(getPosition());
double orbitDistance = getOrbitDistance(selection, viewVec.norm());
if (selection.location() != nullptr)
{
Selection parent = selection.parent();
double maintainDist = getPreferredDistance(parent);
Vector3d parentPos = parent.getPosition(getTime()).offsetFromKm(getPosition());
double parentDist = parentPos.norm() - parent.radius();
if (parentDist <= maintainDist && parentDist > orbitDistance)
{
orbitDistance = parentDist;
}
}
computeGotoParametersGC(selection, journey, gotoTime,
v * (orbitDistance / distanceToCenter),
ObserverFrame::Universal,
up, upFrame,
centerObj);
observerMode = Travelling;
}
}
void Observer::gotoSelection(const Selection& selection,
double gotoTime,
double distance,
const Vector3f& up,
ObserverFrame::CoordinateSystem upFrame)
{
if (!selection.empty())
{
UniversalCoord pos = selection.getPosition(getTime());
// The destination position lies along the line between the current
// position and the star
Vector3d v = pos.offsetFromKm(getPosition());
v.normalize();
computeGotoParameters(selection, journey, gotoTime,
StartInterpolation,
EndInterpolation,
AccelerationTime,
v * -distance, ObserverFrame::Universal,
up, upFrame);
observerMode = Travelling;
}
}
void Observer::gotoSelectionGC(const Selection& selection,
double gotoTime,
double distance,
const Vector3f& up,
ObserverFrame::CoordinateSystem upFrame)
{
if (!selection.empty())
{
Selection centerObj = selection.parent();
UniversalCoord pos = selection.getPosition(getTime());
Vector3d v = pos.offsetFromKm(centerObj.getPosition(getTime()));
v.normalize();
// The destination position lies along a line extended from the center
// object to the target object
computeGotoParametersGC(selection, journey, gotoTime,
v * -distance, ObserverFrame::Universal,
up, upFrame,
centerObj);
observerMode = Travelling;
}
}
/** Make the observer travel to the specified planetocentric coordinates.
* @param selection the central object
* @param gotoTime travel time in seconds of real time
* @param distance the distance from the center (in kilometers)
* @param longitude longitude in radians
* @param latitude latitude in radians
*/
void Observer::gotoSelectionLongLat(const Selection& selection,
double gotoTime,
double distance,
float longitude,
float latitude,
const Vector3f& up)
{
if (!selection.empty())
{
double phi = -latitude + celestia::numbers::pi / 2.0;
double theta = longitude;
double x = cos(theta) * sin(phi);
double y = cos(phi);
double z = -sin(theta) * sin(phi);
computeGotoParameters(selection, journey, gotoTime,
StartInterpolation,
EndInterpolation,
AccelerationTime,
Vector3d(x, y, z) * distance,
ObserverFrame::BodyFixed,
up, ObserverFrame::BodyFixed);
observerMode = Travelling;
}
}
void Observer::gotoLocation(const UniversalCoord& toPosition,
const Quaterniond& toOrientation,
double duration)
{
journey.startTime = realTime;
journey.duration = duration;
journey.from = position;
journey.initialOrientation = orientation;
journey.to = toPosition;
journey.finalOrientation = toOrientation;
journey.startInterpolation = StartInterpolation;
journey.endInterpolation = EndInterpolation;
journey.accelTime = AccelerationTime;
double distance = journey.from.offsetFromKm(journey.to).norm() / 2.0;
pair<double, double> sol = solve_bisection(TravelExpFunc(distance, journey.accelTime),
0.0001, 100.0,
1e-10);
journey.expFactor = sol.first;
observerMode = Travelling;
}
void Observer::getSelectionLongLat(const Selection& selection,
double& distance,
double& longitude,
double& latitude)
{
// Compute distance (km) and lat/long (degrees) of observer with
// respect to currently selected object.
if (!selection.empty())
{
ObserverFrame frame(ObserverFrame::BodyFixed, selection);
Vector3d bfPos = frame.convertFromUniversal(positionUniv, getTime()).offsetFromKm(UniversalCoord::Zero());
// Convert from Celestia's coordinate system
double x = bfPos.x();
double y = -bfPos.z();
double z = bfPos.y();
distance = bfPos.norm();
longitude = radToDeg(atan2(y, x));
latitude = radToDeg(celestia::numbers::pi/2.0 - acos(z / distance));
}
}
void Observer::gotoSurface(const Selection& sel, double duration)
{
Vector3d v = getPosition().offsetFromKm(sel.getPosition(getTime()));
v.normalize();
Vector3d viewDir = orientationUniv.conjugate() * -Vector3d::UnitZ();
Vector3d up = orientationUniv.conjugate() * Vector3d::UnitY();
Quaterniond q = orientationUniv;
if (v.dot(viewDir) < 0.0)
{
q = LookAt<double>(Vector3d::Zero(), up, v);
}
ObserverFrame frame(ObserverFrame::BodyFixed, sel);
UniversalCoord bfPos = frame.convertFromUniversal(positionUniv, getTime());
q = frame.convertFromUniversal(q, getTime());
double height = 1.0001 * sel.radius();
Vector3d dir = bfPos.offsetFromKm(UniversalCoord::Zero()).normalized() * height;
UniversalCoord nearSurfacePoint = UniversalCoord::Zero().offsetKm(dir);
gotoLocation(nearSurfacePoint, q, duration);
}
void Observer::cancelMotion()
{
observerMode = Free;
}
void Observer::centerSelection(const Selection& selection, double centerTime)
{
if (!selection.empty())
{
computeCenterParameters(selection, journey, centerTime);
observerMode = Travelling;
}
}
void Observer::follow(const Selection& selection)
{
setFrame(ObserverFrame::Ecliptical, selection);
}
void Observer::geosynchronousFollow(const Selection& selection)
{
if (selection.body() != nullptr ||
selection.location() != nullptr ||
selection.star() != nullptr)
{
setFrame(ObserverFrame::BodyFixed, selection);
}
}
void Observer::phaseLock(const Selection& selection)
{
Selection refObject = frame->getRefObject();
if (selection != refObject)
{
if (refObject.body() != nullptr || refObject.star() != nullptr)
{
setFrame(ObserverFrame::PhaseLock, refObject, selection);
}
}
else
{
// Selection and reference object are identical, so the frame is undefined.
// We'll instead use the object's star as the target object.
if (selection.body() != nullptr)
{
setFrame(ObserverFrame::PhaseLock, selection, Selection(selection.body()->getSystem()->getStar()));
}
}
}
void Observer::chase(const Selection& selection)
{
if (selection.body() != nullptr || selection.star() != nullptr)
{
setFrame(ObserverFrame::Chase, selection);
}
}
float Observer::getFOV() const
{
return fov;
}
void Observer::setFOV(float _fov)
{
fov = _fov;
}
Vector3f Observer::getPickRay(float x, float y) const
{
float s = 2 * (float) tan(fov / 2.0);
Vector3f pickDirection(x * s, y * s, -1.0f);
return pickDirection.normalized();
}
Vector3f Observer::getPickRayFisheye(float x, float y) const
{
float r = hypot(x, y);
float phi = celestia::numbers::pi_v<float> * r;
float sin_phi = sin(phi);
float theta = atan2(y, x);
float newX = sin_phi * cos(theta);
float newY = sin_phi * sin(theta);
float newZ = cos(phi);
Vector3f pickDirection = Vector3f(newX, newY, -newZ);
pickDirection.normalize();
return pickDirection;
}
// Internal method to update the position and orientation of the observer in
// universal coordinates.
void Observer::updateUniversal()
{
UniversalCoord newPositionUniv = frame->convertToUniversal(position, simTime);
if (newPositionUniv.isOutOfBounds())
{
// New position would take us out of range of the simulation. At this
// point the positionUniv has not been updated, so will contain a position
// within the bounds of the simulation. To make the coordinates consistent,
// we recompute the frame-local position from positionUniv.
position = frame->convertFromUniversal(positionUniv, simTime);
}
else
{
// We're in bounds of the simulation, so update the universal coordinate
// to match the frame-local position.
positionUniv = newPositionUniv;
}
orientationUniv = frame->convertToUniversal(orientation, simTime);
}
/*! Create the default 'universal' observer frame, with a center at the
* Solar System barycenter and coordinate axes of the J200Ecliptic
* reference frame.
*/
ObserverFrame::ObserverFrame() :
coordSys(Universal),
frame(nullptr)
{
frame = createFrame(Universal, Selection(), Selection());
}
/*! Create a new frame with the specified coordinate system and
* reference object. The targetObject is only needed for phase
* lock frames; the argument is ignored for other frames.
*/
ObserverFrame::ObserverFrame(CoordinateSystem _coordSys,
const Selection& _refObject,
const Selection& _targetObject) :
coordSys(_coordSys),
frame(nullptr),
targetObject(_targetObject)
{
frame = createFrame(_coordSys, _refObject, _targetObject);
}
/*! Create a new ObserverFrame with the specified reference frame.
* The coordinate system of this frame will be marked as unknown.
*/
ObserverFrame::ObserverFrame(const ReferenceFrame::SharedConstPtr &f) :
coordSys(Unknown),
frame(f)
{
}
/*! Copy constructor. */
ObserverFrame::ObserverFrame(const ObserverFrame& f) :
coordSys(f.coordSys),
frame(f.frame),
targetObject(f.targetObject)
{
}
ObserverFrame& ObserverFrame::operator=(const ObserverFrame& f)
{
coordSys = f.coordSys;
targetObject = f.targetObject;
// In case frames are the same, make sure we addref before releasing
frame = f.frame;
return *this;
}
ObserverFrame::CoordinateSystem
ObserverFrame::getCoordinateSystem() const
{
return coordSys;
}
Selection
ObserverFrame::getRefObject() const
{
return frame->getCenter();
}
Selection
ObserverFrame::getTargetObject() const
{
return targetObject;
}
const ReferenceFrame::SharedConstPtr&
ObserverFrame::getFrame() const
{
return frame;
}
UniversalCoord
ObserverFrame::convertFromUniversal(const UniversalCoord& uc, double tjd) const
{
return frame->convertFromUniversal(uc, tjd);
}
UniversalCoord
ObserverFrame::convertToUniversal(const UniversalCoord& uc, double tjd) const
{
return frame->convertToUniversal(uc, tjd);
}
Quaterniond
ObserverFrame::convertFromUniversal(const Quaterniond& q, double tjd) const
{
return frame->convertFromUniversal(q, tjd);
}
Quaterniond
ObserverFrame::convertToUniversal(const Quaterniond& q, double tjd) const
{
return frame->convertToUniversal(q, tjd);
}
/*! Convert a position from one frame to another.
*/
UniversalCoord
ObserverFrame::convert(const ObserverFrame::SharedConstPtr& fromFrame,
const ObserverFrame::SharedConstPtr& toFrame,
const UniversalCoord& uc,
double t)
{
// Perform the conversion fromFrame -> universal -> toFrame
return toFrame->convertFromUniversal(fromFrame->convertToUniversal(uc, t), t);
}
/*! Convert an orientation from one frame to another.
*/
Quaterniond
ObserverFrame::convert(const ObserverFrame::SharedConstPtr& fromFrame,
const ObserverFrame::SharedConstPtr& toFrame,
const Quaterniond& q,
double t)
{
// Perform the conversion fromFrame -> universal -> toFrame
return toFrame->convertFromUniversal(fromFrame->convertToUniversal(q, t), t);
}
// Create the ReferenceFrame for the specified observer frame parameters.
ReferenceFrame::SharedConstPtr
ObserverFrame::createFrame(CoordinateSystem _coordSys,
const Selection& _refObject,
const Selection& _targetObject)
{
switch (_coordSys)
{
case Universal:
return shared_ptr<J2000EclipticFrame>(new J2000EclipticFrame(Selection()));
case Ecliptical:
return shared_ptr<J2000EclipticFrame>(new J2000EclipticFrame(_refObject));
case Equatorial:
return shared_ptr<BodyMeanEquatorFrame>(new BodyMeanEquatorFrame(_refObject, _refObject));
case BodyFixed:
return shared_ptr<BodyFixedFrame>(new BodyFixedFrame(_refObject, _refObject));
case PhaseLock:
{
return shared_ptr<TwoVectorFrame>(new TwoVectorFrame(_refObject,
FrameVector::createRelativePositionVector(_refObject, _targetObject), 1,
FrameVector::createRelativeVelocityVector(_refObject, _targetObject), 2));
}
case Chase:
{
return shared_ptr<TwoVectorFrame>(new TwoVectorFrame(_refObject,
FrameVector::createRelativeVelocityVector(_refObject, _refObject.parent()), 1,
FrameVector::createRelativePositionVector(_refObject, _refObject.parent()), 2));
}
case PhaseLock_Old:
{
FrameVector rotAxis(FrameVector::createConstantVector(Vector3d::UnitY(),
shared_ptr<BodyMeanEquatorFrame>(new BodyMeanEquatorFrame(_refObject, _refObject))));
return shared_ptr<TwoVectorFrame>(new TwoVectorFrame(_refObject,
FrameVector::createRelativePositionVector(_refObject, _targetObject), 3,
rotAxis, 2));
}
case Chase_Old:
{
FrameVector rotAxis(FrameVector::createConstantVector(Vector3d::UnitY(),
shared_ptr<BodyMeanEquatorFrame>(new BodyMeanEquatorFrame(_refObject, _refObject))));
return shared_ptr<TwoVectorFrame>(new TwoVectorFrame(_refObject,
FrameVector::createRelativeVelocityVector(_refObject.parent(), _refObject), 3,
rotAxis, 2));
}
case ObserverLocal:
// TODO: This is only used for computing up vectors for orientation; it does
// define a proper frame for the observer position orientation.
return shared_ptr<J2000EclipticFrame>(new J2000EclipticFrame(Selection()));
default:
return shared_ptr<J2000EclipticFrame>(new J2000EclipticFrame(_refObject));
}
}