# Local horizon coordinate system

#### Sword7

##### Member
I was looking for local horizon reference frame in long time. I now found a paper about local horizon coordinates.

Local Horizon Coordinates

I now noticed that formula is very similar like local 2 horizon formula in Camera.cpp but uses right-handed rule instead.

In orbiter code:

| cos(theta)sin(phi) cos(theta)cos(phi) - sin(theta) |
R = | -cos(phi) sin(ph) 0 |
| sin(theta)sin(phi) sin(theta)cos(phi) cos(theta) |

I tried that formula from Orbiter but negated Z values (sinth = -sin(go.lng)) for right-handed rule in my OpenGL program. It worked at default zero heading but did not align for orientation control and points east to screen. At equator, it rotates vertically but I tried to rotate it habitationally. At mid-latitude, it rotates diagonally... At 90-degree latitude, it rotates horizontally. I think that formula is not compatible with right-handed rule - worked best for left-handed rule.

I will try right-handed version from that paper that I was looking for and found soon and see what happens... It use ENU coordinate to convert.

In that paper:

| cos(phi)cos(theta) cos(phi)sin(theta) sin(phi) |
R = | - sin(theta) cos(theta) 0 |
| -sin(phi)cos(theta) -sin(phi)sin(theta) cos(phi) |

What is any difference between two different formulas?

Tim

#### Boxx

Donator
I would be happy if I could help you. But I'm not sure I can diagnose your bug. It would be useful to copy/paste the exact code of yours, within tags CODE (or </>). E.g., R = | -cos(phi) sin(ph) 0 | seems weird...

Code:
copy/paste sth

#### Urwumpe

##### Not funny anymore
Donator
Or use the
Code:
$...$
tag for setting regular mathematical formulas. It uses Latex syntax, which makes it easy for academics, maybe less so for mortal beings.

#### Boxx

Donator
Yes, for instance:
Code:
$R = \left[ \begin{array}{l} \cos\phi \cos\theta & \cos\phi \sin\theta & \sin\phi \\ -\sin\theta & \cos\theta & 0 \\ -\sin\phi \cos\theta & -\sin\phi \sin\theta & \cos\phi \end{array} \right]$

yields
$R = \left[ \begin{array}{c} \cos\phi \cos\theta & \cos\phi \sin\theta & \sin\phi \\ -\sin\theta & \cos\theta & 0 \\ -\sin\phi \cos\theta & -\sin\phi \sin\theta & \cos\phi \end{array} \right]$(fun... first time I use LaTeX here...)

but the code in C++ is needed, probably

#### Sword7

##### Member
Ok, I tried < code ... /code > and it worked for me. Thanks!!

I found typo in my code and corrected it by changed to clng from slng. It causes line drawing instead of picture during rendering. I will try that and see if it works.

Code:
    // Set rotation matrix for local horizon frame (right-handed rule)
double clat = cos(go.lat), slat = sin(go.lat);
double clng = cos(go.lng), slng = sin(go.lng);
go.R = { clat*clng,   clat*slng,  slat,
-slng,       clng,       0,
-slat*clng, -slat*slng,  clat };

#### Sword7

##### Member
Well I tried that but displayed land left side instead of bottom side at (0, 0) coordinate.

I tried reordering that doc explained about.

Code:
    // Set rotation matrix for local horizon frame
// for right-handed rule (OpenGL)
double clat = cos(go.lat), slat = sin(go.lat);
double clng = cos(go.lng), slng = sin(go.lng);
go.R = { -slng,       clng,       0,
-slat*clng, -slat*slng,  clat,
clat*clng,   clat*slng,  slat };
go.Q = go.R;

It now display horizon level alignment (bottom side) but points south (-Y axis). It does not follow orientation that I tried to rotate around.
However, it did not work anywhere except (0, 0) coordinate (latitude/longitude). View still follow global frame.

Code:
    // Set rotation matrix for local horizon frame
// for right-handed rule (OpenGL)
double clat = cos(go.lat), slat = sin(go.lat);
double clng = cos(go.lng), slng = sin(go.lng);
go.R = { slat*clng,  clat*clng, slng,
-clat,       slat,      0,
-slat*slng, -clat*slng, clng };
go.Q = go.R;

I quoted that code from Orbiter and converted to right-hand rule. It worked anywhere (view aligned with horizon reference frame) and points to east (-Z axis) but do not follow orientation that still follow global frame. At (90, 0) coordinate (north pole area), it correctly rotates around at horizon level. At (45, 0) coordinate (mid-latitude), it rotates diagonally. At (0, 0) coordinate (equator), it rotates vertically instead of horizon level.

Code:
  glm::dvec3 wv = go.av * 0.5;
glm::dquat dr = glm::dquat(1.0, wv.x, wv.y, wv.z) * cam.rqrot;
cam.rqrot = glm::normalize(cam.rqrot + dr);
cam.rrot = glm::mat3_cast(cam.rqrot);
cam.rpos -= glm::conjugate(cam.rqrot) * tv;

grot = go.R * cam.rrot;
gqrot = grot;

That code is in updating each frame. I tried to rotate (go.av - ground observer: angular velocity) around but it does not follow local horizon frame but only global frame. I am figuring out...

I was looking for Euler rotation matrix information through my textbooks but they do not have any Eular rotation matrix to teach. Only document (Local horizon coordinates) explains about Euler rotation.

#### Boxx

Donator
Not sure if it helps, but I see a lot of risks of misunderstanding... just to make sure we talk the same (mathematical) language, I stress out that:
1°) your matrix R allows to compute the equatorial coordinates of a local vector (horizon frame, with Z = zenith or anti-nadir):
$\overrightarrow u_\text{ecliptic} = R. \overrightarrow u_\text{local}$assuming that your "1-x-9 vector" R is interpreted later in your code as a "3-x-3 matrix" with columns then rows as you wrote (and not rows then columns).
2°) in Orbiter, the (left-handed) coordinate system of a landed vessel is:
$\begin{matrix} \overrightarrow z &= \text{thrust vector} \\ \overrightarrow y &= \text{top} \\ \overrightarrow x &= \text{right wing}\\ \end{matrix}$3°) Orbiter does not play with local/horizon frame (as far as I know, maybe I'm wrong), instead it uses lat/lng cylindrical coorrdinates or equatorial inertial rectangular coordinates, maybe to avoid confusion and because it was useless to express rectangular horizon coordinates.

If your point is to convert "standard"(long/lat?) surface coordinates into left-handed local frame, you'd better go to a "vessel frame" as a horizon frame, wouldn't you?

Last edited:

#### Sword7

##### Member
Ok, now got it. Orbiter code does not have enough comments to trying to understand.

$\begin{matrix} \overrightarrow z &= \text{thrust vector} \\ \overrightarrow y &= \text{top} \\ \overrightarrow x &= \text{right wing}\\ \end{matrix}$
How about right-handed coordinate system for 3x3 matrix?

Tim

#### Boxx

Donator
How about right-handed coordinate system for 3x3 matrix?
$\begin{matrix} \overrightarrow z &= \text{thrust vector} \\ \overrightarrow y &= \text{top} \\ \overrightarrow x &= \text{left wing}\\ \end{matrix}$
for instance, or

$\begin{matrix} \overrightarrow z &= \text{thrust vector} \\ \overrightarrow y &= \text{bottom} \\ \overrightarrow x &= \text{right wing}\\ \end{matrix}$

#### Sword7

##### Member
Ok, I saw other L2H matrix in Vessel.cpp to convert local planet coords to local horizon.

Code:
    // rotation from planet local coords to local horizon
L2H.Set (-slng, 0, clng,
clat*clng, slat, clat*slng,
-slat*clng, clat, -slat*slng);

Code:
    go.R.Set ( clng*slat, clng*clat, -slng,   // rotate from local
-clat,      slat,       0,      // observer to local
slng*slat, slng*clat,  clng);  // planet coords

What are difference between two matrices? Is top matrix transposed? Is local observer in vessel frame? Same as local horizon frame?

I saw many code uses that L2H for local horizon frame. Yes, Orbiter uses local horizon frame for conversion from global frame/local planet coords to local horizon frame on surface.

#### Sword7

##### Member
Well, I figured them how to rotate camera view when I studied matrix rotations on Wikipedia and LearnOpenGL website I decided to try simple Z-axis and Y-axis rotation to rotate camera view (vessel frame).

Code:
// rotation matrices for right-handed rule
template <typename T>
{
return glm::dmat3(
{ 1.0,   0.0,   0.0  },
{ 0.0,   cang, -sang },
{ 0.0,   sang,  cang }
);
}
template <typename T>
{
return glm::dmat3(
{ cang,  0.0,   sang  },
{ 0.0,   1.0,   0.0   },
{-sang,  0.0,   cang  }
);
}
template <typename T>
{
return glm::dmat3(
{ cang, -sang,  0.0   },
{ sang,  cang,  0.0   },
{ 0.0 ,  0.0,   1.0   }
);
}

Code:
go.R = zRotate(go.lat) * yRotate(go.lng);

I tested (0, 0), (40, 0) and (40, 40) coordinates. It worked but it tilted at 90 degrees counter-clockwise from horizon level at all coordinates and still follows global frame (not horizon frame) when I rotate view around. I am now figuring how to display at horizon level and follow horizon frame when I rotate view around. That is supposed to rotate view rotation to horizon frame/level.

Last edited:

#### Sword7

##### Member
Well, I finally recognized that I ended up in global frame when I tried to rotate camera around. I had to swap two rotations and all problems went away.
Because it uses GLM library, that is opposite of Orbiter code that use its own vector math package.

<global> = <local> * <reference frame>

Code:
grot = cam.rrot * go.R;

Update: I corrected this code because I noticed that it was mistyped. I wonder if who wrote this terrible code?! lol

Code:
// Set rotation matrix for local horizon frame
// for right-handed rule (OpenGL). Points
// to east as origin at (0, 0).
//
//     |  slat  clat   0  | |  clng   0   slng |
// R = | -clat  slat   0  | |   0     1    0   |
//     |   0     0     1  | | -slng   0   clng |
//
double clat = cos(go.lat), slat = sin(go.lat);
double clng = cos(go.lng), slng = sin(go.lng);
go.R = { slat*clng,  clat*clng, slng,
-clat,       slat,      0,
-slat*slng, -clat*slng, clng };

I analyzed that complex matrix and was able break it into two simple matrices. I learned how to write latex code.

$R = \left[ \begin{array}{l} \sin\phi & \cos\phi & 0 \\ -\cos\phi & \sin\phi & 0 \\ 0 & 0 & 1 \end{array} \right] \left[ \begin{array}{l} \cos\theta & 0 & \sin\theta \\ 0 & 1 & 0 \\ -\sin\theta & 0 & \cos\theta \end{array} \right]$
I tried different coordinates, and everything worked now. I am now rotating camera around at horizon level.

Last edited:

Replies
14
Views
2K
Replies
0
Views
3K
Replies
5
Views
883
Replies
1
Views
1K
Replies
6
Views
4K