Skip to content

Commit f31ebd1

Browse files
authored
Add support for a second thumbstick (CelestiaProject#2358)
can be potentially used for right joystick on the game controller. unlike the yaw/pitch with existing joystick which might cause roll, this one store Euler angles and has pitch clamped between ±90 so won't cause rolls, works more like a planetarium.
1 parent 9d1f3a8 commit f31ebd1

File tree

5 files changed

+99
-25
lines changed

5 files changed

+99
-25
lines changed

src/celengine/observer.cpp

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -420,9 +420,12 @@ Observer::Observer(const Observer& o) :
420420
position(o.position),
421421
originalOrientation(o.originalOrientation),
422422
transformedOrientation(o.transformedOrientation),
423-
orientationTransform(o.orientationTransform),
423+
devicePoseQuaternion(o.devicePoseQuaternion),
424+
eulerDrivenOrientation(o.eulerDrivenOrientation),
425+
inputEulerAngles(o.inputEulerAngles),
424426
velocity(o.velocity),
425427
angularVelocity(o.angularVelocity),
428+
inputAngularVelocity(o.inputAngularVelocity),
426429
realTime(o.realTime),
427430
targetSpeed(o.targetSpeed),
428431
targetVelocity(o.targetVelocity),
@@ -447,10 +450,13 @@ Observer& Observer::operator=(const Observer& o)
447450
simTime = o.simTime;
448451
position = o.position;
449452
originalOrientation = o.originalOrientation;
450-
orientationTransform = o.orientationTransform;
453+
devicePoseQuaternion = o.devicePoseQuaternion;
454+
eulerDrivenOrientation = o.eulerDrivenOrientation;
455+
inputEulerAngles = o.inputEulerAngles;
451456
transformedOrientation = o.transformedOrientation;
452457
velocity = o.velocity;
453458
angularVelocity = o.angularVelocity;
459+
inputAngularVelocity = o.inputAngularVelocity;
454460
frame = nullptr;
455461
realTime = o.realTime;
456462
targetSpeed = o.targetSpeed;
@@ -581,13 +587,13 @@ Observer::setOriginalOrientation(const Eigen::Quaterniond& q)
581587
const Eigen::Quaterniond&
582588
Observer::getOrientationTransform() const
583589
{
584-
return orientationTransform;
590+
return devicePoseQuaternion;
585591
}
586592

587593
void
588594
Observer::setOrientationTransform(const Eigen::Quaterniond& transform)
589595
{
590-
orientationTransform = transform;
596+
devicePoseQuaternion = transform;
591597
updateOrientation();
592598
}
593599

@@ -598,7 +604,9 @@ Observer::applyCurrentTransform()
598604
{
599605
originalOrientationUniv = transformedOrientationUniv;
600606
originalOrientation = transformedOrientation;
601-
orientationTransform = Eigen::Quaterniond::Identity();
607+
devicePoseQuaternion = Eigen::Quaterniond::Identity();
608+
inputEulerAngles = Eigen::Vector3d::Identity();
609+
eulerDrivenOrientation = Eigen::Quaterniond::Identity();
602610
updateOrientation();
603611
}
604612

@@ -630,6 +638,18 @@ Observer::setAngularVelocity(const Eigen::Vector3d& v)
630638
angularVelocity = v;
631639
}
632640

641+
Eigen::Vector3d
642+
Observer::getInputAngularVelocity() const
643+
{
644+
return inputAngularVelocity;
645+
}
646+
647+
void
648+
Observer::setInputAngularVelocity(const Eigen::Vector3d& v)
649+
{
650+
inputAngularVelocity = v;
651+
}
652+
633653
double
634654
Observer::getArrivalTime() const
635655
{
@@ -687,7 +707,7 @@ Observer::update(double dt, double timeScale)
687707

688708
// At some threshold, we just set the velocity to zero; otherwise,
689709
// we'll end up with ridiculous velocities like 10^-40 m/s.
690-
if (v.norm() < 1.0e-12)
710+
if (v.norm() < celestia::engine::MIN_SIG_LINEAR_SPEED)
691711
v = Eigen::Vector3d::Zero();
692712
setVelocity(v);
693713
}
@@ -702,6 +722,19 @@ Observer::update(double dt, double timeScale)
702722
Eigen::Quaterniond dr = Eigen::Quaterniond(0.0, halfAV.x(), halfAV.y(), halfAV.z()) * transformedOrientation;
703723
Eigen::Quaterniond expectedOrientation = Eigen::Quaterniond(transformedOrientation.coeffs() + dt * dr.coeffs()).normalized();
704724
originalOrientation = undoTransform(expectedOrientation);
725+
726+
// Update the input angular orientation transform
727+
if (inputAngularVelocity.norm() > celestia::engine::MIN_SIG_ANGULAR_SPEED)
728+
{
729+
inputEulerAngles += inputAngularVelocity * dt;
730+
// Clamp pitch between -90 and +90 to avoid gimbal lock
731+
inputEulerAngles[0] = std::clamp(std::remainder(inputEulerAngles[0], 2 * celestia::numbers::pi), -0.5 * celestia::numbers::pi + std::numeric_limits<double>::epsilon(), 0.5 * celestia::numbers::pi - std::numeric_limits<double>::epsilon());
732+
inputEulerAngles[1] = std::remainder(inputEulerAngles[1], 2 * celestia::numbers::pi);
733+
inputEulerAngles[2] = std::remainder(inputEulerAngles[2], 2 * celestia::numbers::pi);
734+
eulerDrivenOrientation = Eigen::AngleAxisd(inputEulerAngles[0], Eigen::Vector3d::UnitX()) *
735+
Eigen::AngleAxisd(inputEulerAngles[1], Eigen::Vector3d::UnitY()) *
736+
Eigen::AngleAxisd(inputEulerAngles[2], Eigen::Vector3d::UnitZ());
737+
}
705738
}
706739

707740
updateUniversal();
@@ -720,14 +753,14 @@ Observer::update(double dt, double timeScale)
720753
void
721754
Observer::updateOrientation()
722755
{
723-
transformedOrientationUniv = orientationTransform * originalOrientationUniv;
756+
transformedOrientationUniv = eulerDrivenOrientation * devicePoseQuaternion * originalOrientationUniv;
724757
transformedOrientation = frame->convertFromUniversal(transformedOrientationUniv, getTime());
725758
}
726759

727760
Eigen::Quaterniond
728761
Observer::undoTransform(const Eigen::Quaterniond& transformed) const
729762
{
730-
return orientationTransform.inverse() * transformed;
763+
return devicePoseQuaternion.inverse() * eulerDrivenOrientation.inverse() * transformed;
731764
}
732765

733766
Selection
@@ -1246,7 +1279,7 @@ Observer::gotoJourney(const JourneyParams& params)
12461279
void
12471280
Observer::startTraveling()
12481281
{
1249-
journey.orientationTransformInverse = orientationTransform.inverse();
1282+
journey.orientationTransformInverse = devicePoseQuaternion.inverse() * eulerDrivenOrientation.inverse();
12501283
observerMode = ObserverMode::Travelling;
12511284
}
12521285

src/celengine/observer.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@
3131
#include "shared.h"
3232
#include "univcoord.h"
3333

34+
namespace celestia::engine
35+
{
36+
37+
constexpr inline double MIN_SIG_ANGULAR_SPEED = 1.0e-10;
38+
constexpr inline double MIN_SIG_LINEAR_SPEED = 1.0e-12;
39+
40+
}
41+
3442
class ReferenceFrame;
3543

3644
/*! ObserverFrame is a wrapper class for ReferenceFrame which adds some
@@ -141,6 +149,8 @@ class Observer
141149
void setVelocity(const Eigen::Vector3d&);
142150
Eigen::Vector3d getAngularVelocity() const;
143151
void setAngularVelocity(const Eigen::Vector3d&);
152+
Eigen::Vector3d getInputAngularVelocity() const;
153+
void setInputAngularVelocity(const Eigen::Vector3d&);
144154

145155
float getFOV() const;
146156
void setFOV(float);
@@ -303,9 +313,15 @@ class Observer
303313
UniversalCoord position{ 0.0, 0.0, 0.0 };
304314
Eigen::Quaterniond originalOrientation{ Eigen::Quaterniond::Identity() };
305315
Eigen::Quaterniond transformedOrientation{ Eigen::Quaterniond::Identity() };
306-
Eigen::Quaterniond orientationTransform{ Eigen::Quaterniond::Identity() };
316+
// Flexible, full 6DoF observer pose, e.g., AR/VR headset
317+
Eigen::Quaterniond devicePoseQuaternion{ Eigen::Quaterniond::Identity() };
318+
// Orientation driven by input (Euler-based, like joystick)
319+
Eigen::Quaterniond eulerDrivenOrientation{ Eigen::Quaterniond::Identity() };
320+
Eigen::Vector3d inputEulerAngles{ Eigen::Vector3d::Zero() };
321+
307322
Eigen::Vector3d velocity{ Eigen::Vector3d::Zero() };
308323
Eigen::Vector3d angularVelocity{ Eigen::Vector3d::Zero() };
324+
Eigen::Vector3d inputAngularVelocity{ Eigen::Vector3d::Zero() };
309325

310326
// Position and orientation in universal coordinates, derived from the
311327
// equivalent quantities in the observer reference frame.

src/celestia/celestiacore.cpp

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ void CelestiaCore::mouseMove(float dx, float dy, int modifiers)
716716
}
717717

718718

719-
void CelestiaCore::joystickAxis(int axis, float amount)
719+
void CelestiaCore::joystickAxis(JoyAxis axis, float amount)
720720
{
721721
float deadZone = 0.25f;
722722

@@ -727,10 +727,23 @@ void CelestiaCore::joystickAxis(int axis, float amount)
727727

728728
amount = math::sign(amount) * math::square(amount);
729729

730-
if (axis == Joy_XAxis)
730+
switch (axis)
731+
{
732+
case CelestiaCore::JoyAxis::X:
731733
joystickRotation.y() = amount;
732-
else if (axis == Joy_YAxis)
734+
break;
735+
case JoyAxis::Y:
733736
joystickRotation.x() = -amount;
737+
break;
738+
case JoyAxis::RX:
739+
joystickRightRotation.y() = amount;
740+
break;
741+
case JoyAxis::RY:
742+
joystickRightRotation.x() = -amount;
743+
break;
744+
default:
745+
break;
746+
}
734747
}
735748

736749

@@ -1792,8 +1805,11 @@ void CelestiaCore::tick(double dt)
17921805

17931806
// Keyboard rotate
17941807
Vector3d av = sim->getObserver().getAngularVelocity();
1808+
Vector3d inputAV = sim->getObserver().getInputAngularVelocity();
17951809

1796-
av = av * exp(-dt * RotationDecay);
1810+
double decayFactor = std::exp(-dt * static_cast<double>(RotationDecay));
1811+
av = av * decayFactor;
1812+
inputAV = inputAV * decayFactor;
17971813

17981814
float fov = sim->getActiveObserver()->getFOV() / stdFOV;
17991815
Selection refObject = sim->getFrame()->getRefObject();
@@ -1864,6 +1880,11 @@ void CelestiaCore::tick(double dt)
18641880

18651881
sim->getObserver().setAngularVelocity(av);
18661882

1883+
if (joystickRightRotation != Vector3f::Zero())
1884+
inputAV += (dt * KeyRotationAccel) * joystickRightRotation.cast<double>();
1885+
1886+
sim->getObserver().setInputAngularVelocity(inputAV);
1887+
18671888
if (keysPressed[static_cast<int>('A')] || joyButtonsPressed[JoyButton2])
18681889
{
18691890
bSetTargetSpeed = true;
@@ -1884,7 +1905,7 @@ void CelestiaCore::tick(double dt)
18841905
sim->setTargetSpeed(currentSpeed / std::exp(static_cast<float>(dt) * 3.0f * decelerationCoefficient));
18851906
}
18861907
}
1887-
if (!bSetTargetSpeed && av.norm() > 1.0e-10)
1908+
if (!bSetTargetSpeed && av.norm() > MIN_SIG_ANGULAR_SPEED)
18881909
{
18891910
// Force observer velocity vector to align with observer direction if an observer
18901911
// angular velocity still exists.

src/celestia/celestiacore.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,14 @@ class CelestiaCore // : public Watchable<CelestiaCore>
100100
WhatsThisCursor = 16,
101101
};
102102

103-
enum
103+
enum class JoyAxis
104104
{
105-
Joy_XAxis = 0,
106-
Joy_YAxis = 1,
107-
Joy_ZAxis = 2,
105+
X = 0,
106+
Y = 1,
107+
Z = 2,
108+
RX = 3,
109+
RY = 4,
110+
RZ = 5,
108111
};
109112

110113
enum
@@ -223,7 +226,7 @@ class CelestiaCore // : public Watchable<CelestiaCore>
223226
void mouseButtonUp(float, float, int);
224227
void mouseMove(float, float, int);
225228
void mouseMove(float, float);
226-
void joystickAxis(int axis, float amount);
229+
void joystickAxis(JoyAxis axis, float amount);
227230
void joystickButton(int button, bool down);
228231
void pinchUpdate(float focusX, float focusY, float scale, bool zoomFOV);
229232
void resize(GLsizei w, GLsizei h);
@@ -481,6 +484,7 @@ class CelestiaCore // : public Watchable<CelestiaCore>
481484
double sysTime{ 0.0 };
482485

483486
Eigen::Vector3f joystickRotation{ Eigen::Vector3f::Zero() };
487+
Eigen::Vector3f joystickRightRotation{ Eigen::Vector3f::Zero() };
484488
bool joyButtonsPressed[JoyButtonCount];
485489
bool keysPressed[KeyCount];
486490
bool shiftKeysPressed[KeyCount];

src/celestia/win32/winmainwindow.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -569,9 +569,9 @@ MainWindow::handleKey(WPARAM wParam, bool down)
569569
case VK_F8:
570570
if (joystickAvailable && down)
571571
{
572-
appCore->joystickAxis(CelestiaCore::Joy_XAxis, 0);
573-
appCore->joystickAxis(CelestiaCore::Joy_YAxis, 0);
574-
appCore->joystickAxis(CelestiaCore::Joy_ZAxis, 0);
572+
appCore->joystickAxis(CelestiaCore::JoyAxis::X, 0);
573+
appCore->joystickAxis(CelestiaCore::JoyAxis::Y, 0);
574+
appCore->joystickAxis(CelestiaCore::JoyAxis::Z, 0);
575575
useJoystick = !useJoystick;
576576
}
577577
break;
@@ -1211,8 +1211,8 @@ MainWindow::handleJoystick()
12111211
float x = static_cast<float>(info.dwXpos) / 32768.0f - 1.0f;
12121212
float y = static_cast<float>(info.dwYpos) / 32768.0f - 1.0f;
12131213

1214-
appCore->joystickAxis(CelestiaCore::Joy_XAxis, x);
1215-
appCore->joystickAxis(CelestiaCore::Joy_YAxis, y);
1214+
appCore->joystickAxis(CelestiaCore::JoyAxis::X, x);
1215+
appCore->joystickAxis(CelestiaCore::JoyAxis::Y, y);
12161216
appCore->joystickButton(CelestiaCore::JoyButton1,
12171217
(info.dwButtons & 0x1) != 0);
12181218
appCore->joystickButton(CelestiaCore::JoyButton2,

0 commit comments

Comments
 (0)