Discussion Why SetTouchdownPoints Sends Your Add-On into Solar Orbit and How to Fix That

Thunder Chicken

Fine Threads since 2008
Donator
Joined
Mar 22, 2008
Messages
4,565
Reaction score
3,479
Points
138
Location
Massachusetts
So I recently went through the process of deciphering how to use the "new" touchdown point model for my J-3 add-on, and I thought it might be generally useful to those who struggle with it punting their add-ons into solar orbit for no apparent reason. There are reasons for that behavior, and there are ways to avoid it and to get a well-behaved add-on. Thanks to @dgatsoulis and others who provided me with guidance to help sort out the use of this model.

The Touchdown Point Models

Originally (Orbiter 2010) you could only set three contact points to define a plane to orient the vessel on the ground something like this (I'm using Lua syntax but the C++ syntax is generally similar, see API_Reference.pdf in OrbiterSDK/Docs):

Code:
set_touchdownpoints(nose_wheel, left_wheel, right_wheel)

where each of the arguments is a vector describing the contact point. This gives a downward Z normal for tricycle gear vessels. Left and right contacts had to be specified as second and third arguments as apparently they are automatically tied to a default braking system. There is also a displacement model that provided a certain springiness to these contact points.

The "new" (Orbiter 2016+) touchdown point model gives us some rope to hang ourselves with:

Code:
set_touchdownpoints(tdvtx, ntdvtx*)

In this model, tdvtx is a list of contact vertices with their stiffness, damping, and dynamic friction coefficients, and ntdvtx is the number of vertices (*it does not seem to need to be supplied for the script method, but it is described in the API Reference for the C++ (?)). This allows a cloud of points to be defined for a general contact model so your vessel will tumble about on the surface realistically if your landings go awry.

The list of vertices tdvtx looks something like this:

Code:
nose_wheel_vtx = {pos=nose_wheel_contact, stiffness = value, damping = value, mu = value, mu_lng = value}
left_wheel_vtx = {pos=left_wheel_contact, stiffness = value, damping = value, mu = value, mu_lng = value}
right_wheel_vtx = {pos=right_wheel_contact, stiffness = value, damping = value, mu = value, mu_lng = value}
4vtx = {pos=4vtx_contact, stiffness = value, damping = value, mu = value, mu_lng = value}
...

tdvtx = {
    nose_wheel_vtx,
    left_wheel_vtx,
    right_wheel_vtx,
    4_vtx,
    ...
}

set_touchdownpoints(tdvtx)

Seems easy enough, but we need to supply the stiffness and dampening coefficients (and dynamic friction, which is another matter).

The Problem

So, what is the problem? The problem is that, for certain combinations of vessel mass, stiffness, and damping coefficients, you can make a touchdown point so "twangy" that it's vibration period is faster than the simulation time step (simdt), which means the numerical integration routines cannot resolve time well enough to accurately compute the motion of the vessel.

A typical situation would be an add-on developer making a vessel with a specified mass, and they want to make some contact points. Knowing that the touchdown points for the Atlantis and DG are available in the code base where those coefficients are available, and they are known to work, the obvious thing might be to just copy and paste them into their code. Seems innocent enough, but while those coefficients may work well for those vessels, they won't work the same for a vessel with a different mass. If your add-on mass is much greater, your add-on will sink into the ground because the springs aren't stiff enough, and if your add-on is much lighter, the oscillatory period of your excessively stiff landing struts will cause numerical stability issues that will yeet it to Alpha Centauri. So what do you do?

Keep reading, that's what you do...

Setting the Stiffness and Damping Coefficients: A Case Study

Full disclosure - I still haven't found where these are applied in the Orbiter source code, but my educated guess is that the forced mass-spring-damper model was used for each of the contact points:[math]mz'' + cz' + kz = \sum F_z[/math]where m is the mass supported by the contact, z is the displacement, z' is the rate of displacement, and z'' is the acceleration. The coefficients are defined as:

Stiffness coefficient (Applied spring force per unit displacement): [math]k = F_S/z[/math]Damping coefficient (Applied damping force per unit rate of displacement): [math]c = F_D/z'[/math]
Our goal is to apply physically appropriate values for k and c so the add-on behaves correctly and Orbiter has a hope of properly integrating the equations of motion for the vessel. The applied forces will be variable, but order-of-magnitude estimates can be made at certain limits, which is good enough for our needs. I'll use numbers for my J-3 add-on as an example, but you should be able to follow along with numbers for your add-on.

Estimating the Spring Stiffness Coefficient k

A basic requirement is that the contact points can support the stationary weight of the vessel with a sensible static displacement. The spring equation reduces to [math]kz = mg[/math] where we can solve for k: [math]k = (mg)/z[/math]In this case, the mass of the J-3 is about 450 kg, g is the local acceleration of gravity (~9.81 [imath]m/{s^2}[/imath] on Earth's surface) for a weight of about 4500 N and a sensible deflection would be maybe 10 cm. The weight is primarily on the main wheels, so we'll divide the weight by two. This gives a k = 2250 N / 0.1 m = 22,500 N/m or 2.25e4 N/m. Note that if you move a spacecraft to another planet where the surface gravitational acceleration is different from Earth, that will affect how this touchdown system behaves.

Estimating the Damping Coefficient c

Now for the damping coefficient. For the J-3, we are aiming for a slightly under-damped oscillator (the spring will have a little "bounce" with an overshoot or two before it comes to equilibrium. Critical damping is the value of the damping coefficient that just prevents any overshoot and is defined as [math]c_c=\sqrt{4mk}[/math]For the J-3, again splitting the mass across the two main gear, [imath]c_c[/imath] would be 4500 N[imath]\cdot[/imath]s/m. We will want a damping coefficient slightly lower than this value. I'll start with c = 4000 N[imath]\cdot[/imath]s/m and adjust later if needed.

Determining Numerical Stability

So something to consider is how "twangy" this system will be, more accurately what will be the period of oscillation T. If this period of oscillation comes out to be on the same order of magnitude as the simulation timestep [imath]\varDelta {t}[/imath] (simdt) or smaller, then the numerical time propagation will be unable to accurately resolve the vessel motion and this will generally lead to getting punted into solar orbit. The period of oscillation T is given by: [math]T=2m/\sqrt{4mk-c^2}[/math]
For m = 225 kg, k = 22,500 N/m, and c = 4000 N[imath]\cdot[/imath]s/m, we get [imath]T \approx[/imath] 0.2 s.

Looking into the time propagation settings in Orbiter, it's a little hard to determine if this is a resolvable period or not:

Screenshot at 2024-06-16 11-36-23.png

The Runge-Kutta routines are numerical differential equation solver routines, and I think the differing steps are limits associated with time acceleration (if your timestep is under 0.1 seconds, RK2 provides sufficient accuracy, if under 2.0 seconds RK4 is sufficient, etc..). Higher order Runge-Kutta routines are more expensive than lower order ones, but higher order routines maintain accuracy for larger time steps, so transitioning like this makes sense for computational efficiency. If someone knows what is actually going on in Orbiter please comment if you have some information on this.

Going into the simulation with the J-3 and getting the value for simdt gives a value of about 0.015 s, which is about 15 times smaller than the period of oscillation. So at least we're not the same order of magnitude, so this may work out (assuming this is all correct and this is actually what Orbiter is doing under the hood).

Results

So I apply the touchdown model for my J-3 with the following Lua script code:

Code:
        stiffness_value = 22500
        damping_value = 4000

        td_points = {{pos=tail_wheel_contact, stiffness=stiffness_value, damping=damping_value, mu=0.0, mu_lng=0.0},
        {pos=left_wheel_contact, stiffness=stiffness_value, damping=damping_value, mu=0.0, mu_lng=0.0},
        {pos=right_wheel_contact, stiffness=stiffness_value, damping=damping_value, mu=0.0, mu_lng=0.0},
        {pos=nose_tip, stiffness=stiffness_value, damping=damping_value, mu=0.2, mu_lng=0.2},
        {pos=left_wing_tip, stiffness=stiffness_value, damping=damping_value, mu=0.2, mu_lng=0.2},
        {pos=right_wing_tip, stiffness=stiffness_value, damping=damping_value, mu=0.2, mu_lng=0.2},
        {pos=left_elevator_tip, stiffness=stiffness_value, damping=damping_value, mu=0.2, mu_lng=0.2},
        {pos=right_elevator_tip, stiffness=stiffness_value, damping=damping_value, mu=0.2, mu_lng=0.2},
        {pos=rudder_tip, stiffness=stiffness_value, damping=damping_value, mu=0.2, mu_lng=0.2}}
    
        vi:set_touchdownpoints(td_points)

Empty J-3 vessel mass is 450 kg plus full fuel load of 12 gallons of gasoline, so not quite 500 kg. Firing up a simulation puts me on the runway and supports the vessel nicely:

Screenshot at 2024-06-16 14-18-41.png
I tried using the FlightData function to record some forced landing bounces but the resolution is insufficient to see centimeter displacements, but I get a light bounce that quickly stabilizes, as expected for a slightly under-damped system. And the best news is that, even taxiing around for several hours at this point, I haven't been yeeted to solar orbit even once.

Landing Status

A common issue with this model is that, even when nominally parked and stationary on the surface, it seems unwilling to let you achieve Landed status in the scenario file. That seems to be the case here as well. Here is my (Current Status).scn corresponding to the above image:

Code:
J3Script:J3Script
  STATUS Orbiting Earth
  RPOS 5072669.944 3735414.422 -951068.658
  RVEL 171.9824 -147.1474 339.3490
  AROT 75.051 20.522 72.382
  VROT 0.0012 -0.0226 -0.0012
  RCSMODE 0
  AFCMODE 7
  PRPLEVEL 0:1.000000
  NAVFREQ 94 524
  coordinate_hold false
  brake_hold true
END

I experimented with increasing the damping coefficient to see if it would ever settle down and achieve Landed status, but no luck. Practically it is stopped and isn't going anywhere, so this seems to be a numerical issue about the definition of "landed". If anyone has any information on this, please comment below.

Conclusion

I hope this helps overcome the frustrations with this contact model, and perhaps this can start a conversation where maybe the remaining issues can be addressed. Happy add-on development! -TC
 
Last edited:

nbcfrosty

Well-known member
Joined
Jun 16, 2023
Messages
183
Reaction score
216
Points
58
Location
US
This is an awesome right up. The new touchdown points have been sometimes difficult for add-on developers to work with. Is there an algorithm that takes basic parameters like mass, tail lander, 3 points in OLDER model, to get the 2 coefficients and touchdown points in newer model? It doesn't have to be perfect, even if we can replicate the older model behavior I think that'll really help.

Currently I am doing something like this:
Code:
void adjust_td(VESSEL* v) {
    double x_target = -0.5;
    double stiffness = (-1) * (v->GetMass() * 9.81) / (3 * x_target);
    double damping = 0.9 * (2 * sqrt(v->GetMass() * stiffness));

    VECTOR3 p1, p2, p3, p4;
    v->GetTouchdownPoints(p1, p2, p3);

    if (is_tail_lander(p1, p2, p3)) {
        p4 = _V(0, 0, 1);

        static const DWORD ntdvtx_geardown = 4;
        static TOUCHDOWNVTX tdvtx_geardown[ntdvtx_geardown] = {
            {p1, 1e8, 1e6, 3, 1},
            {p2, 1e8, 1e6, 3, 1},
            {p3, 1e8, 1e6, 3, 1},
            {p4, 1e8, 1e6, 3},
        };
        v->SetTouchdownPoints(tdvtx_geardown, ntdvtx_geardown);
    }
    else {
        double ylow = min(p1.y, p2.y);
        ylow = min(ylow, p3.y);
        ylow -= 0.5;
        double absy = abs(ylow);
        static const DWORD ntdvtx_geardown = 4;
        static TOUCHDOWNVTX tdvtx_geardown[ntdvtx_geardown] = {
            {_V(0, p1.y-0.5, absy * 2), stiffness, damping, 1.6, 0.1},
            {_V(-absy * 2, p2.y, -absy * 2), stiffness, damping, 3.0, 0.2},
            {_V(absy * 2, p3.y, -absy * 2), stiffness, damping, 3.0, 0.2},
            {_V(0, absy, 0), stiffness, damping, 3.0},
        };
        v->SetTouchdownPoints(tdvtx_geardown, ntdvtx_geardown);
    }
}

In here: https://www.orbiter-forum.com/resources/legacy-touchdown-points.5525/
Although this often works, it doesn't always give a good result.



I experimented with increasing the damping coefficient to see if it would ever settle down and achieve Landed status, but no luck. Practically it is stopped and isn't going anywhere, so this seems to be a numerical issue about the definition of "landed". If anyone has any information on this, please comment below.

There is a Parking Brake MFD to "solve" this problem of never truly landing.
 

Thunder Chicken

Fine Threads since 2008
Donator
Joined
Mar 22, 2008
Messages
4,565
Reaction score
3,479
Points
138
Location
Massachusetts
Is there an algorithm that takes basic parameters like mass, tail lander, 3 points in OLDER model, to get the 2 coefficients and touchdown points in newer model? It doesn't have to be perfect, even if we can replicate the older model behavior I think that'll really help.
I am not aware of a magic way to get the vertices of the point cloud and your landing points. Orbiter doesn't know what you are attempting to model until you tell it. Using Blender or some of the tools provided with Orbiter can get you some of these things from the mesh.

I think the older model takes your empty mass, does something to estimate an acceptable maximum displacement, and calculates a damping coefficient that yields a slightly underdamped system as indicated above. I don't know if it does any frequency checking to make sure that stability issues don't arise, but that stability might fall naturally out of the math so long as the weight and displacements are physically realistic. This is basically what I did in the write-up.

If you follow along with my write-up, it's not terribly difficult to estimate appropriate values for the stiffness and damping coefficients, it is just a couple lines of math. In my example I estimated most of the weight is supported by the two main gear, so I used that to estimate the weight on each contact point. In other vessels that may not be correct. You need to do what is appropriate for your vessel.
In here: https://www.orbiter-forum.com/resources/legacy-touchdown-points.5525/
Although this often works, it doesn't always give a good result.

There is a Parking Brake MFD to "solve" this problem of never truly landing.
Patching these problems with add-ons really doesn't fix the basic problem. The problem is either an issue with how the touchdown model is applied, which can be understood and fixed to do it correctly, or a problem in the Orbiter code itself, which suggests an issue should be reported to the code-keepers on GitHub. We want folks to enjoy Orbiter and be able to understand and interact with it appropriately to make satisfying add-ons.
 

Thunder Chicken

Fine Threads since 2008
Donator
Joined
Mar 22, 2008
Messages
4,565
Reaction score
3,479
Points
138
Location
Massachusetts
Hardly surprising. Lua lists know how long they are. C arrays don't.
It was a struggle to sort this out. Right now I am still reliant on the API_Reference.pdf to find out how to do things in Orbiter add-ons, and then I need to see if a corresponding Lua method exists for the C++ version in the OpenOrbiter code on GitHub. Sometimes I can just "Lua-fy" the C++ method and it works, but when there are different arguments it rather complicates that and it takes some detective work to sort that out.

I believe there is an effort to get the documentation caught up with the capabilities. I can't wait to RTFM. I'd help if I could, as I think documentation is the biggest hang-up for bringing Lua up for general use.
 

Gondos

Well-known member
Joined
Apr 18, 2022
Messages
256
Reaction score
307
Points
78
Location
On my chair
It was a struggle to sort this out. Right now I am still reliant on the API_Reference.pdf to find out how to do things in Orbiter add-ons, and then I need to see if a corresponding Lua method exists for the C++ version in the OpenOrbiter code on GitHub. Sometimes I can just "Lua-fy" the C++ method and it works, but when there are different arguments it rather complicates that and it takes some detective work to sort that out.

I believe there is an effort to get the documentation caught up with the capabilities. I can't wait to RTFM. I'd help if I could, as I think documentation is the biggest hang-up for bringing Lua up for general use.
The rule for the new vs old model is :
  • if there is only one argument and it's a table, use new model
  • else expects 3 vector arguments and use the old model
Looking at the original Lua doc, I sadly see that this is mentionned nowhere. I only updated the doc for missing functions, I guess it won't be the last surprise...
The computation for friction forces is in Vessel::AddSurfaceForces, there are several #ifdef in there, looks like Martin was not yet satisfied with this part.
 

nbcfrosty

Well-known member
Joined
Jun 16, 2023
Messages
183
Reaction score
216
Points
58
Location
US
The rule for the new vs old model is :
  • if there is only one argument and it's a table, use new model
  • else expects 3 vector arguments and use the old model
Looking at the original Lua doc, I sadly see that this is mentionned nowhere. I only updated the doc for missing functions, I guess it won't be the last surprise...
The computation for friction forces is in Vessel::AddSurfaceForces, there are several #ifdef in there, looks like Martin was not yet satisfied with this part.
Any DLL vessel currently using the old SetTouchdownpoints method is broken, and will skate on the ground like it's an ice hockey court. Example, DGIV.

I am not aware of a magic way to get the vertices of the point cloud and your landing points. Orbiter doesn't know what you are attempting to model until you tell it. Using Blender or some of the tools provided with Orbiter can get you some of these things from the mesh.

But what if we had the 3 touchdown points provided by the old model? If we know the 3 ground contact points for the old model, the mass, radius, cross section, pmi, tail lander, etc, can we then compute the 2 coefficients and a 4th point such that we can replicate the behavior of the older model?

Patching these problems with add-ons really doesn't fix the basic problem. The problem is either an issue with how the touchdown model is applied, which can be understood and fixed to do it correctly, or a problem in the Orbiter code itself, which suggests an issue should be reported to the code-keepers on GitHub. We want folks to enjoy Orbiter and be able to understand and interact with it appropriately to make satisfying add-ons.

There are add-ons like DGIV, and a bunch of spacecraftx.dll vessels that are unusable because of ground handling issues, and realistically most of these add-ons will not be updated to utilize the new model. I think orbiter should have maintained backwards combability with the old touchdown point model. In the absence of this, only option we have left is some sort of module that "patches" the TD points of vessels that are currently using older model and thus are broken in Orbiter 2016 and onwards.
 

Thunder Chicken

Fine Threads since 2008
Donator
Joined
Mar 22, 2008
Messages
4,565
Reaction score
3,479
Points
138
Location
Massachusetts
Any DLL vessel currently using the old SetTouchdownpoints method is broken, and will skate on the ground like it's an ice hockey court. Example, DGIV.
Both the old and new methods work in Lua AFAIK. Is this a problem that can be fixed in Orbiter?
But what if we had the 3 touchdown points provided by the old model? If we know the 3 ground contact points for the old model, the mass, radius, cross section, pmi, tail lander, etc, can we then compute the 2 coefficients and a 4th point such that we can replicate the behavior of the older model?
There is a get_touchdownpoints method that would let you get those points.
There are add-ons like DGIV, and a bunch of spacecraftx.dll vessels that are unusable because of ground handling issues, and realistically most of these add-ons will not be updated to utilize the new model. I think orbiter should have maintained backwards combability with the old touchdown point model. In the absence of this, only option we have left is some sort of module that "patches" the TD points of vessels that are currently using older model and thus are broken in Orbiter 2016 and onwards.
I am not even using the contact forces for the first three contact points and I have my own functions to implement ground steering using add_force.
 

BrianJ

Addon Developer
Addon Developer
Joined
Apr 19, 2008
Messages
1,702
Reaction score
932
Points
128
Location
Code 347
I experimented with increasing the damping coefficient to see if it would ever settle down and achieve Landed status, but no luck. Practically it is stopped and isn't going anywhere, so this seems to be a numerical issue about the definition of "landed". If anyone has any information on this, please comment below.
I am not even using the contact forces for the first three contact points and I have my own functions to implement ground steering using add_force.
Hi,
this might be irrelevant, not applicable to LUA (I make .dll's with VC++), or you might already be aware, but.... I think I'm right in saying...
If you call AddForce(....) it makes Orbiter set the vessel status to "Orbiting", even if the force is 0.
So if you call AddForce(...) continuously, you never get status "Landed".
This might be true for other functions e.g. SetTouchdownPoints(....), etc. I don't know.
Cheers,
Brian
 
Top