API Question Euler angles and animating an ADI ball.

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,868
Reaction score
4
Points
0
Location
San Diego
As with Moach's thread here I have the FADI in my lunar module represnted by an actual 3d sphere and I need to animate it.

I've searched the forum and I've looked at Martins' ShuttleA samples but the code is rather opaque to a C++ newbie such as myself. As it stands I have my velocity and horizon vectors defined in local coordinates but I'm having trouble visualising the physical mechanics required to translate them into a form usable by MGROUP_ROTATE.

My animation definitions are below, I have 360 (+/- 180) degrees along each axis but suspect it's overkill. Using proper Euler angles I should be able to reach any desired orientation using no more than two rotation but how do I get there from here?

Code:
// ADI Ball 
static UINT ADIGroups = VC_GRP_ADI_Ball;
  static MGROUP_ROTATE ADI_X (
  4,		// mesh index
  &ADIGroups, 1,	// group list and # of groups 
  ADI_Center,	// rotation reference point 
  _V( 1, 0, 0),	// rotation axis 
  (float) 2*PI	// angular rotation range (radians)
  );

anim_ADI_Xrot = CreateAnimation(0.50);
AddAnimationComponent ( anim_ADI_Xrot, 0.0f, 1.0f, &ADI_X);

static MGROUP_ROTATE ADI_Y (
  4,		// mesh index
  &ADIGroups, 1,	// group list and # of groups 
  ADI_Center,	// rotation reference point 
  _V( 0, 1, 0),	// rotation axis 
  (float) 2*PI	// angular rotation range (converted to radians)
);

anim_ADI_Yrot = CreateAnimation(0.50);
AddAnimationComponent ( anim_ADI_Yrot, 0.0f, 1.0f, &ADI_Y);

static MGROUP_ROTATE ADI_Z (
  4,		// mesh index
  &ADIGroups, 1,	// group list and # of groups 
  ADI_Center,	// rotation reference point 
  _V( 0, 0, 1),	// rotation axis 
  (float) 2*PI	// angular rotation range (converted to radians)
);

anim_ADI_Zrot = CreateAnimation(0.50);
AddAnimationComponent ( anim_ADI_Zrot, 0.0f, 1.0f, &ADI_Z);

Code:
void LEM::UpdateADI(VECTOR3 output)
{
  double x_proc, y_proc, z_proc = 0;
  normalise(output); 
  VECTOR3 input = crossp( output, vThrust);	

  double xDelta = asin(input.x)*DEG; 
  double zDelta = asin(input.z)*DEG;
  double yDelta = asin(input.y)*DEG;
	
  x_proc = (xDelta + 180) / 360;
  y_proc = (yDelta + 180) / 360;
  z_proc = (zDelta + 180) / 360;

  sprintf(oapiDebugString(),"X delta %0.2f Y delta %0.2f Z delta %0.2f", xDelta, yDelta, zDelta);

  LEM::SetAnimation( anim_ADI_Xrot, x_proc);
  LEM::SetAnimation( anim_ADI_Yrot, y_proc);
  LEM::SetAnimation( anim_ADI_Zrot, z_proc);
}

Any help would be appreciated. :hailprobe:
 
Shuttle-A? I think you need the DeltaGlider Samples for any type of nav-ball. I cant say I fully get what youre trying to do, but I think Its similar to representing the orbit hud on a black & white version of a horizon indicator? I think I recognize that from apollo 13. Ill have a brainstorm and see what I can come up with.
 
Ok does the axis of a MGROUP_ROTATE have to be a constant?

This problem becomes really simple If I can just feed the animiation the crossproduct of my two vectors and rotate as needed.

Alternately how do I extract x y and z rotations from an arbitrary rotation matrix?

Shuttle-A? I think you need the DeltaGlider Samples for any type of nav-ball. I cant say I fully get what youre trying to do, but I think Its similar to representing the orbit hud on a black & white version of a horizon indicator? I think I recognize that from apollo 13. Ill have a brainstorm and see what I can come up with.

Look at the ShuttleA's 2d Panel in the most recent beta ;).
 
Ok does the axis of a MGROUP_ROTATE have to be a constant?

This problem becomes really simple If I can just feed the animiation the crossproduct of my two vectors and rotate as needed.

Alternately how do I extract x y and z rotations from an arbitrary rotation matrix?



Look at the ShuttleA's 2d Panel in the most recent beta ;).

Could you point me to said beta? I am more of a spacecraft than spaceplane person, so I always liked the Shuttle-A a little better than the DG (except for that horrible VC)
 
My animation definitions are below, I have 360 (+/- 180) degrees along each axis but suspect it's overkill. Using proper Euler angles I should be able to reach any desired orientation using no more than two rotation but how do I get there from here?
Why take two rotations when you can have three? :lol: Anyway, are you asking how to compute the Euler angles? This is detailed in the attref.cpp file of the ShuttleA sources. It's been a while since I wrote this, but looking at the code again, this is how it seems to work:

1. You need to decide which frame of reference the ADI ball should refer to, and set up the rotation matrix Rref for that system. The simplest case is an ecliptic reference, where Rref is just identity. If you want a local horizon reference, it gets a bit more tricky. The y-axis of the reference frame is then the local horizon normal, given by
[math]
y_\mathrm{ref} = || p_\mathrm{planet} - p_\mathrm{vessel}||
[/math]
the x-axis is the tangent at the local horizon pointing east:
[math]
x_\mathrm{ref} = || y_\mathrm{planet} \times y_\mathrm{ref} ||
[/math]
and
[math]
z_\mathrm{ref} = y_\mathrm{ref} \times x_\mathrm{ref}
[/math]
where
[math]
y_\mathrm{planet} = R_\mathrm{planet} \left[\begin{array}{c}0\\1\\0\end{array}\right]
[/math]
which leads to
[math]
R_\mathrm{ref} = \left[\begin{array}{ccc}x_\mathrm{ref} & y_\mathrm{ref} & z_\mathrm{ref} \end{array}\right]
[/math]
This part is computed in AttitudeReference::GetFrameRotMatrix in the ShuttleA sources.

2. Next, you compute the vessel's local axes in the global frame, and rotate them into the reference frame
[math]
x_\mathrm{vessel} = R_\mathrm{ref}^T R_\mathrm{vessel} \left[\begin{array}{c}1\\0\\0\end{array}\right],\mathrm{ etc.}
[/math]

3. Then the Euler angles I used are given by
[math]
\begin{array}{l}
\rho = atan2(x_\mathrm{vessel,2}, y_\mathrm{vessel,2})\\
\theta = asin(z_\mathrm{vessel,2})\\
\phi = atan2(z_\mathrm{vessel,1}, z_\mathrm{vessel,3})
\end{array}
[/math]
if you use an ADI ball with 360 degree yaw range, and by
[math]
\begin{array}{l}
\rho = -atan2(y_\mathrm{vessel,1}, x_\mathrm{vessel,1})\\
\theta = atan2(z_\mathrm{vessel,2}, z_\mathrm{vessel,3})\\
\phi = asin(z_\mathrm{vessel,1})
\end{array}
[/math]
if you use an ADI ball with 360 degree pitch range.
This part is implemented in AttitudeReference::GetEulerAngles

Sorry if the above syntax is a bit oblique, but maybe it gets more obvious if you look directly at the relevant ShuttleA functions.
 
Thank you for the reply Professor.

I had initially considered simple copying AttRef.h and .cpp into my own project wholesale but as I sain in my initial post your code is a bit opaque to somebody who's learning all this as he goes along. For instance I was unable to find the declaration of the "posangle" variable used in GetEulerAngles in either the AttRef or ShuttleA classes.

but getting down to the math, translating your mathematical notation C++ it would look something like this correct?

[math]
y_\mathrm{ref} = || p_\mathrm{planet} - p_\mathrm{vessel}||
[/math]

Code:
VECTOR3 yRef = (rBodyPos - gPos); // position of reference body minus vessel's global position

the x-axis is the tangent at the local horizon pointing east:
[math]
x_\mathrm{ref} = || y_\mathrm{planet} \times y_\mathrm{ref} ||
[/math]

Code:
VECTOR3 xRef = rBodyPos.y * yRef; // This seems wrong, I'm worried that I may have misinterpreted this step

[math]
z_\mathrm{ref} = y_\mathrm{ref} \times x_\mathrm{ref}
[/math]

Code:
VECTOR3 zRef = crossp( xRef, yRef); // By process of elimination the Z axis must be perpendicular to X and Y

which in turn leads to...

Code:
MATRIX3 rRef = _M(xRef, yRef, zRef);

[math]
x_\mathrm{vessel} = R_\mathrm{ref}^T R_\mathrm{vessel} \left[\begin{array}{c}1\\0\\0\end{array}\right],\mathrm{ etc.}
[/math]

I'm sorry but I'm a little shaky on the notation here, you're multiplying the matrix by each axis' unit vector correct?

i.e
Code:
VECTOR3 xVessel = mul( _V(1,0,0), rRef);
VECTOR3 yVessel = mul( _V(0,1,0), rRef);
VECTOR3 zVessel = mul( _V(0,0,1), rRef);
 
Code:
VECTOR3 yRef = (rBodyPos - gPos); // position of reference body minus vessel's global position
Actually, I seem to have written the above post a bit absent-mindedly, so there are a few mistakes. What I meant was
[math]
y_\mathrm{ref} = \frac{p_\mathrm{planet}-p_\mathrm{vessel}}{|| p_\mathrm{planet}-p_\mathrm{vessel} ||}
[/math]
which translates to
Code:
VECTOR3 yref;
OBJHANDLE hRef = vessel->GetSurfaceRef();
vessel->GetRelativePos (hRef, yref);
yref = unit (yref);
and
[math]
x_\mathrm{ref} = \frac{y_\mathrm{planet} \times y_\mathrm{ref}}{|| y_\mathrm{planet} \times y_\mathrm{ref} ||}
[/math]
which translates to
Code:
MATRIX3 Rplanet;
oapiGetRotationMatrix (hRef, &Rplanet);
VECTOR3 yplanet = {Rplanet.m12, Rplanet.m22, Rplanet.m32};  // planet rotation axis in global frame
VECTOR3 xref = unit (crossp (yplanet,yref));      // direction of yaw=+90 pole in global frame
and finally
[math]
z_\mathrm{ref} = y_\mathrm{ref} \times x_\mathrm{ref}
[/math]
which translates to
Code:
VECTOR3 zref = crossp (yref,xref);

which in turn leads to...

Code:
MATRIX3 rRef = _M(xRef, yRef, zRef);
Unfortunately, constructing a matrix out of column vectors isn't supported, so you need to say
Code:
MATRIX3 Rref = _M(xref.x, yref.x, zref.x,  xref.y, yref.y, zref.y,   xref.z, yref.z, zref.z);

I'm sorry but I'm a little shaky on the notation here, you're multiplying the matrix by each axis' unit vector correct?

i.e
Code:
VECTOR3 xVessel = mul( _V(1,0,0), rRef);
VECTOR3 yVessel = mul( _V(0,1,0), rRef);
VECTOR3 zVessel = mul( _V(0,0,1), rRef);
No, it's
Code:
MATRIX3 Rvessel;
vessel->GetRotationMatrix(Rvessel);
VECTOR3 xVessel = tmul (Rref, mul (Rvessel, _V(1,0,0)));
VECTOR3 yVessel = tmul (Rref, mul (Rvessel, _V(0,1,0)));
VECTOR3 zVessel = tmul (Rref, mul (Rvessel, _V(0,0,1)));

For instance I was unable to find the declaration of the "posangle" variable used in GetEulerAngles in either the AttRef or ShuttleA classes.
This simply maps any angle to the interval [0,2pi) and is defined in OrbiterAPI.h
Here's a handy tip: If you edit your code inside the Visual Studio IDE, just right-click on a function name, and select "Go to definition" or "Go to declaration", and it will take you straight there.
 
Thank you professor. I've got it working now, after fashion, so of course now I'm going to complicate things.

The ball shows the orientation of the nose (z+ axis) but what I really want is it to show the position of my axis of thrust.

How should I go about adding this third vector/frame of reference to the problem.

Right now I'm just rotating the ball by 90 degrees on the x axis (vessel is a tail sitter) but this will be inadequate once I impliment engine gimbaling.

This simply maps any angle to the interval [0,2pi) and is defined in OrbiterAPI.h
Here's a handy tip: If you edit your code inside the Visual Studio IDE, just right-click on a function name, and select "Go to definition" or "Go to declaration", and it will take you straight there.

Is this a new addition in 2011? I actually looked in OrbiterAPI.h but the version in my include directory is the 2010p1 version.
 
Last edited:
Apologies for resurecting this thread but I have more questions.

Now that I've kinda-sorta figured out how to incorperate sub-classes into my vessel I've gone back to the ADI ball and incorperated Martins "AttitudeReference" class into my VC, I've got the interface up and running (projection and mode select switches all work) but once again have been stymied by an issue with the animations.

Take this screenshot as an example.

picture.php


The ADI is running off of Martins code, it is currently in Mode 2 (Orbital Momentum) with Projection 0 and offset all set to 0.
My vessel is orientted prograde and yet the ball is displaying a deflection of close to 30 degrees. WTF?

My current animation code is a direct translation of the euler angles shown below

Code:
void SpiderLEM::AnimateADI(VECTOR3 euler)
{
	double rho = euler.x*DEG;	// roll angle
	double theta = euler.y*DEG;	// pitch angle
	double phi = euler.z*DEG;	// yaw angle

	double x_proc = (theta / 360);
	double y_proc = (phi / 360);
	double z_proc = (rho / 360);

	// Ensure that process values are between 0 and 1.
	if (x_proc > 1) x_proc -= 1;
	else if (x_proc < 0) x_proc += 1;

	if (y_proc > 1) y_proc -= 1;
	else if (y_proc < 0) y_proc += 1;

	if (z_proc > 1) z_proc -= 1;
	else if (z_proc < 0) z_proc += 1;

	// Perform Animations
	SetAnimation( anim_ADI_Xrot, x_proc);
	SetAnimation( anim_ADI_Yrot, y_proc);
	SetAnimation( anim_ADI_Zrot, z_proc);

	// Debuggery
	sprintf(oapiDebugString(),"X delta %0.2f Y delta %0.2f Z delta %0.2f X proc %0.3f Y proc %0.3f Z proc %0.3f", rho, theta, phi, x_proc, y_proc, z_proc);
}

NOTE: the "euler" variable is being fed to the AnimateADI function from Martins' AttitudeReference class via "attref->GetEulerAngles();" each poststep.

Now I looked at Martins ADI code in the ShuttleA in the hopes of figuring out what I had missed and what I found was a whole lot more complex than what I have.

Code:
bool ADIBall::Redraw2D (SURFHANDLE surf)
{
	VECTOR3 euler = aref->GetEulerAngles ();
	double rho = euler.x; // roll angle
	double tht = euler.y; // pitch angle
	double phi = euler.z; // yaw angle
	double dt = oapiGetSimStep();

	const double ballspeed = 3.0;
	double dangle_max = ballspeed*dt;

	double drho = rho-rho_curr;
	if (drho > PI)       drho -= PI2;
	else if (drho < -PI) drho += PI2;
	double dtht = tht-tht_curr;
	if (dtht > PI)       dtht -= PI2;
	else if (dtht < -PI) dtht += PI2;
	double dphi = phi-phi_curr;
	if (dphi > PI)       dphi -= PI2;
	else if (dphi < -PI) dphi += PI2;
	double dangle = max (fabs(drho), max (fabs(dtht), fabs(dphi)));
	if (dangle > dangle_max) {
		double scale = dangle_max/dangle;
		rho = rho_curr + drho*scale;
		tht = tht_curr + dtht*scale;
		phi = phi_curr + dphi*scale;
	}
	rho_curr = rho;
	tht_curr = tht;
	phi_curr = phi;

	DWORD i;

	double sinp = sin(phi), cosp = cos(phi);
	double sint = sin(tht), cost = cos(tht);
	double sinr = sin(rho), cosr = cos(rho);

	// Ball transformation
	double a1, b1, c1, a2, b2, c2, a3, b3, c3;

	if (layout == 0) {
		// below are the coefficients of the rows of the rotation matrix M for the ball,
		// given by M = RTP, with
		// MATRIX3 P = {cosp,0,-sinp,  0,1,0,  sinp,0,cosp};
		// MATRIX3 T = {1,0,0,  0,cost,-sint,  0,sint,cost};
		// MATRIX3 R = {cosr,sinr,0,  -sinr,cosr,0,  0,0,1};

		a1 =  cosr*cosp - sinr*sint*sinp;
		b1 =  sinr*cost;
		c1 = -cosr*sinp - sinr*sint*cosp;
		a2 = -sinr*cosp - cosr*sint*sinp;
		b2 =  cosr*cost;
		c2 =  sinr*sinp - cosr*sint*cosp;
		a3 =  cost*sinp;
		b3 =  sint;
		c3 =  cost*cosp;

	} else {
		// below are the coefficients of the rows of the rotation matrix M for the ball,
		// given by M = ZRPT, with
		// MATRIX3 Z = {0,1,0,  -1,0,0,  0,0,1};
		// MATRIX3 P = {1,0,0,  0,cosp,-sinp,  0,sinp,cosp};  // yaw
		// MATRIX3 T = {cost,0,sint,  0,1,0,  -sint,0,cost};  // pitch
		// MATRIX3 R = {cosr,sinr,0,  -sinr,cosr,0,  0,0,1};  // bank

		a1 = -sinr*cost + cosr*sinp*sint;
		b1 =  cosr*cosp;
		c1 = -sinr*sint - cosr*sinp*cost;
		a2 = -cosr*cost - sinr*sinp*sint;
		b2 = -sinr*cosp;
		c2 = -cosr*sint + sinr*sinp*cost;
		a3 = -cosp*sint;
		b3 =  sinp;
		c3 =  cosp*cost;
	}

	for (i = 0; i < nballvtx; i++) {
		ballgrp->Vtx[ballofs+i].x = bb_cntx + (float)(a1*ballvtx0[i].x + b1*ballvtx0[i].y + c1*ballvtx0[i].z);
		ballgrp->Vtx[ballofs+i].y = bb_cnty - (float)(a2*ballvtx0[i].x + b2*ballvtx0[i].y + c2*ballvtx0[i].z);
	}

I'm pretty sure the answer to my problem is in that matrix att the end but I don't know how to go about decomposing it. Once again any assistance would be greatly appreciated.
 
Ummm :huh:

Youre sure the reference is right? Maybe the autopilot is wrong? How consistently does it hold 30 degrees?
 
Ummm :huh:

Youre sure the reference is right? Maybe the autopilot is wrong? How consistently does it hold 30 degrees?

Not consistantly at all.

ETA: for instance, after orienting retro and then rotating back, the error was ten degrees to in the opposite direction.
 
Last edited:
Back
Top