Problem Animation Drift. Ball not returning to center

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,868
Reaction score
4
Points
0
Location
San Diego
In the following screenshot Rho, Theta, and Phi represent the calculated deflection angles (in degrees) for the ADI ball along each axis and X, Y and Z proc are the animation states. My vessel's current attitude matches my reference attitude and as such all 3 angles are less than a degree and the Proc values are either 0.5 or damn close.

picture.php


The ball is centered. (as it should be)

No this screenshot is from the same simulation session and was taken not 3 minutes later after performing a few random rotations and then returning to my reference attitude. Once again all 3 angles are within a degree and the Proc values are close to 0.5. But as you can see...

picture.php


...the Ball is not centered.

WTF?!

Why would "SetAnimation( anim_ADI_Xrot, 0.5);" not set the animation to 0.5 each time?

If 0.5 does not equal 0.5 what does it equal?

How do I make it equal 0.5 again?



ETA: here is the code...

from "DefineAnimations()"

Code:
	// ADI Ball
	static UINT ADIGroups = VC_GRP_ADI_Ball; 
	static MGROUP_ROTATE ADI_X	( 4, &ADIGroups, 1, ADI_BALL_POS, ADI_X_AXIS, (float) 2*PI); // X axis (Pitch)
		anim_ADI_Xrot = CreateAnimation(0.50);
		ah_ADIpitch = AddAnimationComponent ( anim_ADI_Xrot, 0, 1, &ADI_X);

	static MGROUP_ROTATE ADI_Y	( 4, &ADIGroups, 1, ADI_BALL_POS, ADI_Y_AXIS, (float) 2*PI); // y axis (Yaw)
		anim_ADI_Yrot = CreateAnimation(0.50);
		ah_ADIyaw = AddAnimationComponent ( anim_ADI_Yrot, 0, 1, &ADI_Y);

	static MGROUP_ROTATE ADI_Z	( 4, &ADIGroups, 1, ADI_BALL_POS, ADI_Z_AXIS, (float) 2*PI); //  z axis (Roll)
		anim_ADI_Zrot = CreateAnimation(0.50);
		ah_ADIroll = AddAnimationComponent ( anim_ADI_Zrot, 0, 1, &ADI_Z, ah_ADIpitch);

...and the ADI function itself (called every post step)

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

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

	// Ensure that process values are between 0 and 1. The function that clculates the Euler angles is restricted to +/-180 so technically we shouldn't have to do this but just to be safe...
	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(),"Rho %0.2f Theta %0.2f Phi %0.2f X proc %0.3f Y proc %0.3f Z proc %0.3f", rho, theta, phi, x_proc, y_proc, z_proc);
}
 
Last edited:
Shouldn't all those animation components form a cascade? I.e first rotate by X, then by Y, then by Z. You've registered a component for X and Y on the same mesh group without parent relationship.

I wonder what it means to have 2 animations on the same group with orthogonal rotation axis? Is one overwriting the other, or is it just defining the order of matrix multiplication?

Also maybe your default setting 0.5 is somehow messing up the start situation, like the parent relationship of X and Z starts in the wrong position.

I'd suggest to create a parent relationship like e.g. X->Z->Y with 0.0 as default values. Then see if it stays consistent after some rotations.
 
Face,
I tried your suggestions but the problem persists.

I also have some notes.

  • Rotation around a single axis does not result in an error unless it's the roll (z+) axis.
  • Error is most notable when rotating around the roll (z+) axis.
  • Exiting the simulation and coming back will cause the ball to snap to "true" orientation.

The last one in particular is interests me.

It implys that the error is some sort of cumulative factor that builds up over time and that it may be possible to "reset" it.

Current code...
Code:
	static UINT ADIGroups = VC_GRP_ADI_Ball; 
	static MGROUP_ROTATE ADI_X	( 4, &ADIGroups, 1, ADI_BALL_POS, ADI_X_AXIS, (float) 2*PI); // X axis (Pitch)
		anim_ADI_Xrot = CreateAnimation(0.00);
		ah_ADIpitch = AddAnimationComponent ( anim_ADI_Xrot, 0, 1, &ADI_X);

	static MGROUP_ROTATE ADI_Y	( 4, &ADIGroups, 1, ADI_BALL_POS, ADI_Y_AXIS, (float) 2*PI); // y axis (Yaw)
		anim_ADI_Yrot = CreateAnimation(0.00);
		ah_ADIyaw = AddAnimationComponent ( anim_ADI_Yrot, 0, 1, &ADI_Y, ah_ADIroll);

	static MGROUP_ROTATE ADI_Z	( 4, &ADIGroups, 1, ADI_BALL_POS, ADI_Z_AXIS, (float) 2*PI); //  z axis (Roll)
		anim_ADI_Zrot = CreateAnimation(0.00);
		ah_ADIroll = AddAnimationComponent ( anim_ADI_Zrot, 0, 1, &ADI_Z, ah_ADIpitch);

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

	double x_proc = 0.0 + (rho / 360);
	double y_proc = 0.0 + (theta / 360);
	double z_proc = 0.0 + (phi / 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(),"Rho %0.2f Theta %0.2f Phi %0.2f X proc %0.3f Y proc %0.3f Z proc %0.3f", rho, theta, phi, x_proc, y_proc, z_proc);
}
 
It seems like you added the component with the parent relationship before adding the parent. Maybe this is confusing Orbiter.

Try reordering the call so that every parent is added before being used in a subsequent call.

The "reset" thingy is interesting indeed.
 
So I reordered the calls and something interesting happened.

The pitch and roll scales changed.

At 45 degrees roll, the Ball shows 90, and pitch seems to have been compressed by a factor of 3. (30 degrees shows 90 etc...)
 
Last edited:
You hit gimbal lock :lol:.

Im really not familiar with how this concept works, but Id imagine that its under special animation types in the programmers guide. Is it possible that the reference angles are all right on the money, its just that the reference frame for the animation has changed (ie what about the roll from the original navball state?) If your code only covers only the vessels axis angles, maybe its insensitive to rolling about an axis, and the way that would appear at any point in the VC?
 
Last edited:
all right, I've scrapped my personal attitude program and gon back to using Martins'. At least that way I can eliminate errors in the attitude program as a causal factor.

Still I'm feeling very frustrated, the more I experiment the more I'm beginning to suspect that either A: I've missed something blindingly simple. or B: the problem is something inherent to how the Orbiter core handles animations.

So going back over what I have...

LEM_Attitude class is dead, long live LEM_Attitude class. All attitude angles are being drawn from Martins' GetEulerAngles(); function

Animation Definitions
Code:
	static UINT ADIGroup = VC_GRP_ADI_Ball;

	static MGROUP_ROTATE ADI_Z	( 4, &ADIGroup, 1, ADI_BALL_POS, ADI_Z_AXIS, (float)2*PI); //  z axis (Roll)
		anim_ADI_Zrot = CreateAnimation(0.00);
		ah_ADIroll = AddAnimationComponent ( anim_ADI_Zrot, 0, 1, &ADI_Z);

	static MGROUP_ROTATE ADI_X	( 4, &ADIGroup, 1, ADI_BALL_POS, ADI_X_AXIS, (float)2*PI); // X axis (Pitch)
		anim_ADI_Xrot = CreateAnimation(0.00);
		ah_ADIpitch = AddAnimationComponent ( anim_ADI_Xrot, 0, 1, &ADI_X, ah_ADIroll);

	static MGROUP_ROTATE ADI_Y	( 4, &ADIGroup, 1, ADI_BALL_POS, ADI_Y_AXIS, (float)2*PI); // y axis (Yaw)
		anim_ADI_Yrot = CreateAnimation(0.00);
		ah_ADIyaw = AddAnimationComponent ( anim_ADI_Yrot, 0, 1, &ADI_Y, ah_ADIpitch);

in clbkPostStep
Code:
	// Get attitude data
	int attmode = 2;
	int proj = 1;
	
	if (attref->GetMode() != attmode) attref->SetMode(attmode);
	if (attref->GetProjMode() != proj) attref->SetProjMode(proj);

	DSP_ADIrot = attref->GetEulerAngles();
	AnimateADI(DSP_ADIrot);

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

	// Translate to vessel axises
	double x_proc = (theta / PI2);	// x axis = pitch 
	double y_proc = (phi / PI2);	// y axis = yaw
	double z_proc = (rho / PI2);	// z axis = roll

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

	// Debuggery
	sprintf(oapiDebugString(),"Rho %0.2f Theta %0.2f Phi %0.2f X proc %0.3f Y proc %0.3f Z proc %0.3f", rho*DEG, theta*DEG, phi*DEG, x_proc*DEG, y_proc, z_proc);

For comparison here is the pertinent code from Martins' ShuttleA 2d panel. I've been studying it in the hopes of discovering a way out of this Matrice induced hell.

Code:
bool ADIBall::Redraw2D (SURFHANDLE surf)
{
[COLOR="red"]	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();[/COLOR]

[COLOR="RoyalBlue"]	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;
	}[/COLOR][COLOR="Green"]	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;
	}[/COLOR]
	[COLOR="Orange"]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);
	}[/COLOR]

No the first part Red is exactly the same as what I've got. No issue there. The Second section (Blue) seems to be a limiter on how much the ball can move in a given Timestep. This is good to know but utterly useless if I can't make the ball work properly in the first flace.

Green is where it get interesting Martin appears to building a rotation matrix that will apply Rho Theta, and Phi to the 2D mesh.

Honestly I have no Idea how you apply a 3d rotating to a 2d mesh mesh but I there is a solution to my problem I suspect It lies there.

In the final section (Yellow) is application of the above but I don't know how applicable it is outside of the 2D panel.

If I could apply a rotation matrix to a 3d object directly I'd of been able to release this damn thing 2 Weeks ago.

ETA:
One thought that has occured to me now is to make the ADI ball a seperate vessel whos relative position would be locked to the spacecraft. I could then use "SetRotationMatrix" to define what ever attitude I wanted to display

ETAA:
NM I just remembered that as a seperate vessel the ADI ball yould be rendered on the external pass and would thus be "covered" by the rest VC of the VC. The fact that I actually thought of that as a solution (and actually started to write some code for it) though should illustrate just how desperate I've become.

Surely I can't be the only person who's ever tried to do this.

Is any one aware of any addon with a working ADI ball other than NASSP and Martins Shuttle A who's code is open source?
 
Last edited:
Is this all in your repository? If so, I'll give it a look today, maybe I can spot something...
 
I've been having the same problem with a project of mine, which is why I've semi-abandoned it.

The key problem appears to be, as you've described, the code initially starts properly aligned, then gets misaligned. Exiting and re-starting re-sets everything to aligned. I have not been able to re-set things in any less drastic way, and I have not found a combination of parent-child animations that changes this problem. I don't know what to do next.
 
From Martins via PM...

Having two rotation animations applied to the same mesh groups will lead to undefined results.
Whenever SetAnimation() is called for one of the animations, the vertices of the affected groups are rotated: x' = R1 x, where x are the current vertex positions and x' the transformed ones. If you call a second rotation, the new rotation is added to the result of the previous rotation: x'' = R2 x' = R2 R1 x. If the two rotations are coming from the same animation, the effect is as expected. In particular, the rotations commute: R1 R2 x = R2 R1 x. If the rotations are coming from different animations, this is no longer the case. This means that the end result depends on the order in which the rotations were supplied.

Ok that's good to know and helps ameliorate some of the frustration I've been feeling over this whole mess.

It also means that it is time for more drastic measures. Martins suggested modifying the ADI's vertices individually but I have no idea how to go about doing this.

The current plan is to break the ADI ball off into it's own mesh and then add it back into the VC using the Addmesh function.

I assume that this will simplify the maths involved as all coordinates will be calculated relative to the origin rather than an arbitrary point in 3D space (AKA the ADI's position in the panel)

I've skimmed through the documentation and found the description of the DEVMESH handle but no explanation of how to use it

I am in uncharted territory and could really use a guide.
 
From Martins via PM...



Ok that's good to know and helps ameliorate some of the frustration I've been feeling over this whole mess.

It also means that it is time for more drastic measures. Martins suggested modifying the ADI's vertices individually but I have no idea how to go about doing this.

The current plan is to break the ADI ball off into it's own mesh and then add it back into the VC using the Addmesh function.

I assume that this will simplify the maths involved as all coordinates will be calculated relative to the origin rather than an arbitrary point in 3D space (AKA the ADI's position in the panel)

I've skimmed through the documentation and found the description of the DEVMESH handle but no explanation of how to use it

I am in uncharted territory and could really use a guide.

If I follow the issue correctly, why not tear out the old ADI ball, add a blank one, then add the axis scales for each deflection angle as individual wireframe meshes with ambient lighting. The only downside would be that you would lose the Black/White portion of the ADI ball and have to go with only a solid colour. That would work no?
 
Actually, I don't see how AddMesh helps you. You can change its position, but not its orientation, that way.
My ADI ball is already a separate mesh, and I don't see that it actually helps.

I see two possible approaches:

1. Make the ADI ball a separate vessel entirely. Set its orientation, and possibly position, using a SetStatus. If the ball wanders a bit around the panel due to lag (likely), fix it in place with a floppy attach point.
This is inelegant and wasteful, but could almost certainly be made to work.

2. Use the various surface blitting commands to draw the texture on the ball; set the starting point in X and Y to plot the ball with the yaw and pitch, respectively, that you want. Then roll the ball with an animation.
I also think this should be very possible, though I don't understand the blitting commands very well and would have to study quite a bit.
 
What about simply making a first rotation that operates on a list of dummy vertexes (Vertex list as animation target) and make the second rotation a child of the first? That one works well for me.
 
Urwumpe,
Perhaps I misunderstood, but that sounds like another way of calculating how to position the ball. That's not the problem here; the problem is that, once desired rotation is calculated, we need to apply it to an ADI ball which is a mesh or mesh group.
 
Actually, I don't see how AddMesh helps you. You can change its position, but not its orientation, that way.
My ADI ball is already a separate mesh, and I don't see that it actually helps.

I see two possible approaches:

1. Make the ADI ball a separate vessel entirely. Set its orientation, and possibly position, using a SetStatus. If the ball wanders a bit around the panel due to lag (likely), fix it in place with a floppy attach point. This is inelegant and wasteful, but could almost certainly be made to work.

2. Use the various surface blitting commands to draw the texture on the ball; set the starting point in X and Y to plot the ball with the yaw and pitch, respectively, that you want. Then roll the ball with an animation.
I also think this should be very possible, though I don't understand the blitting commands very well and would have to study quite a bit.

I considered option 1 for a bit (and even started to write some code for it) but waved off because the way Orbiter handles cockpit renders made it impractical. This was in addition to the to the inelegance and waste you already mentioned.

Option 2 is probably workable but could be a major frame-rate killer if I wanted the ADI ball to be updated every timestep (which I do) as blitting functions are memory hogs.

No, I think Martins is correct and the best way to do it is through vertex manipulation.

The only problem is that I haven't been able to find any documentation on (or SDK samples using) the oapiEditMeshGroup() and DEVMESHHANDLE functions that I would need to use for this.


What about simply making a first rotation that operates on a list of dummy vertexes (Vertex list as animation target) and make the second rotation a child of the first? That one works well for me.

As Sputnik said, Maybe I'm misinterpretting but that doesn't sound like it'd fix the issue.


oapiEditMeshGroup()! how does it work?
 
Back
Top