Discussion Setting Touchdown Points for Vessels on Solid Planet Surfaces I: The Mass Spring Damper Model

Thunder Chicken

Resident Lua Script Rabble-Rouser
Donator
Joined
Mar 22, 2008
Messages
5,847
Reaction score
5,509
Points
188
Location
Massachusetts
I've been doing quite a lot of tinkering with the touchdown points model to mimic bouncing balls and buoyant effects for surface vessels. I think there is some general confusion about what the touchdown point model is doing under the hood which makes setting proper contact points a challenge. Since I already was doing experimentation with a simple box mesh I figured I'd illustrate with some examples. Some of this is copy/pasted from my previous posts on the subject just to consolidate the ideas in one place.

The Touchdown Model

The touchdown model applies a simple forced mass-spring-damper model between a touchdown point on the vessel to a corresponding contact point on the planet surface:[math]my'' + cy' + ky = \sum F_y[/math]
where m is the mass supported by the contact, y is the displacement, y' is the rate of displacement, and y'' is the acceleration. The coefficients are defined as:

Stiffness coefficient (Applied spring force per unit displacement): [math]k = F_S/y[/math]
Damping coefficient (Applied damping force per unit rate of displacement): [math]c = F_D/y'[/math]


So if you have a vessel with n touchdown points all initially at y = 0 (where y is in the local vertical direction opposite gravity), and you specify a vessel weight mg and a stiffness k, the mesh will "sink" until the weight is supported by the springs at a depth y = mg/nk, which looks something like this:

Spring_Model.png

Note that there are dampers in parallel with the springs to dissipate energy, but I've omitted them for clarity to focus on the spring forces. The dampers only oppose motion and don't apply forces in static situations.

Also note that, while the touchdown points are fixed in vessel coordinates, they do not have to be coincident with any part of the mesh. As I'll show below, sometimes that is useful when trying to get a vessel to set properly on a surface.

Setting Touchdown Points

The first thing to note in the figure is that the vessel hangs under the contact points. What this means is, if you specify the contact points to coincide with points on the physical bottom of your mesh, and you have a spring stiffness k anything less than infinity, the bottom of your vessel mesh will fall through the surface until its weight is supported by the springs.

As an example, take the following mesh which is a simple 1 m cube. The origin of the vessel coordinate system is the bottom center of the box.

Screenshot at 2024-07-08 19-04-28.png
If one wanted this solid box to rest on the surface as if it were solid, one might intuitively try to put the bottom touchdown points coincident with the bottom four vertices of the mesh (the first four points in the touchdown list):

Code:
stiffness_value = (rho*g)/4
 
damping_value = math.sqrt(4.0*empty_mass*stiffness_value) --critical damping

mu_value = 0.0

td_points  =
{
     {pos = {x = 0.5, y = 0.0, z = 0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = -0.5, y = 0.0, z = 0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = 0.5, y = 0.0, z = -0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = -0.5, y = 0.0, z = -0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = 0.5, y = 1.0, z = 0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = -0.5, y = 1.0, z = 0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = 0.5, y = 1.0, z = -0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = -0.5, y = 1.0, z = -0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
 
}
vi:set_touchdownpoints(td_points)

However, once the simulation starts and you leave a landed condition, the vessel weight will be taken up by the springs until the net applied spring force balances the weight. With the vessel above, that requires a spring displacement of 0.5 m (rather soft springs meant to mimic buoyant forces in water):

Screenshot at 2024-07-08 19-03-10.png

So what this means is, if I want the bottom of the mesh to rest on the surface, I need to move the location of the touchdown points to 0.5 m below the mesh (y = -0.5 m) in vessel coordinates like this:

Code:
td_points_surface  =

{
     {pos = {x = 0.5, y = -0.5, z = 0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = -0.5, y = -0.5, z = 0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = 0.5, y = -0.5, z = -0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = -0.5, y = -0.5, z = -0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = 0.5, y = 1.0, z = 0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = -0.5, y = 1.0, z = 0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = 0.5, y = 1.0, z = -0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
     {pos = {x = -0.5, y = 1.0, z = -0.5}, stiffness = stiffness_value, damping = damping_value, mu = mu_value, mu_lng = mu_value},
 
}

At equilibrium this puts the mesh at the surface:

Screenshot at 2024-07-08 19-02-13.png

But note that I say "at equilibrium". This mesh is supported by four springs stretched 0.5 meters. It can bounce on those springs through the surface.

If you wish to model a rigid contact like a landing skid, you will need to increase the stiffness k, so less displacement is needed to support the vessel. However, the stiffer that you make the spring, the higher its vibration frequency becomes. If you don't want any bounce, you need to set the damping value at least to critical damping value or higher.

Procedure to Set Rigid Touchdown Points

So if you want a rigid vessel to sit like a rigid vessel on the surface, here's what you need to do:
  1. Determine the mass m and weight W=mg of your vessel under local acceleration of gravity g. Note that the touchdown contact model will provide different displacements on other planets with different local g, so you may need to tune constants for interplanetary operations.
  2. Determine the amount of spring displacement y under weight load that you would consider "negligible". Zero (0) is not an option here, as that would require a spring stiffness k of infinity. If you are working with a vessel 100s of meters long, fractions of a meter might be "small" to you.
  3. Determine the number of touchdown points npoints that will be supporting the vessel. Note that this isn't necessarily all of the touchdown points for general vessel collision, just the points that you expect to be touching the surface.
  4. Determine the fraction of the weight Fw that is supported by each touchdown point. For my simple box, that's pretty easy as the bottom four touchdown points support the full weight, but for vessels with more complex geometry and weight distribution this may not be true. Then you have to do some statics to figure this out for each contact.
  5. Calculate the stiffness k needed from Fw/y for each touchdown point (note that Fw and y can be different for each and every touchdown point).
  6. Calculate the critical value of damping [imath]c_c[/imath] needed to dampen any vibration without any overshoot from the critical damping equation [imath]c_c=\sqrt{4mk}[/imath]. You can make it higher, but note that the damper applies a force proportional to rate of displacement. If you crash suddenly you might get yeeted to Alpha Centauri if you make this value too high.
  7. Set the location of each touchdown point to be y meters below the corresponding contact point on the mesh.
This should put your vessel rigidly on the surface with no stability issues due to the spring-damper model. Here is my vessel very comfortably stable on the surface under 1000 X time acceleration:

Screenshot at 2024-07-08 20-50-26.png

And the status of the vessel in (Current State).scn is Landed:

Code:
BEGIN_SHIPS
Cube:Cube
  STATUS Landed Earth
  POS -80.6825943 28.5969238
  HEADING 330.02
  ALT 0.000
  AROT 66.655 34.068 9.880
  RCSMODE 0
  AFCMODE 7
  PRPLEVEL 0:1.000000
  NAVFREQ 94 524
END
END_SHIPS

Example: Setting Touchdown Points for VW Thing

So I have made a VW Thing vessel as a simple car to tinker with ground handling models. This is the mesh:

Screenshot at 2024-07-09 14-41-00.png
The wheel mesh groups were created in vessel coordinates located where they would be when the suspension is under vehicle weight. So when we establish the touchdown points, we want it to rest as shown in the above picture.

For starters, we'll (temporarily) set the touchdown points to be location of the lowest points of all four wheels in the mesh:

Code:
front_right_wheel_contact = {x=0.689 , y=-0.322, z=1.392}
front_left_wheel_contact = {x=-0.689 , y=-0.322, z=1.392}
rear_right_wheel_contact = {x=0.689 , y=-0.322 , z=-1.084}
rear_left_wheel_contact = {x=-0.689 , y=-0.322 , z=-1.084}

If we assume that the vessel weight is equally distributed on all four wheels (hmm), and assume that the suspension deflection under this weight would be 10 cm (0.1 m or about 4 inches), we could set touchdown points like this:

Code:
    travel = 0.1 --suspension travel to support weight of vessel
    stiffness = 0.25*max_weight_front/travel --weight divided over four wheels
    damping = 0.5*math.sqrt(stiffness*empty_mass) --subcritical damping

    td_points =
    {
        {pos=front_right_wheel_contact, stiffness=stiffness, damping=damping, mu=0.0, mu_lng=0.0},
        {pos=front_left_wheel_contact, stiffness=stiffness, damping=damping, mu=0.0, mu_lng=0.0},
        {pos=rear_right_wheel_contact, stiffness=stiffness, damping=damping, mu=0.0, mu_lng=0.0},
        {pos=rear_left_wheel_contact, stiffness=stiffness, damping=damping, mu=0.0, mu_lng=0.0},
        {pos=pt1, stiffness=stiffness, damping=damping, mu=0.2, mu_lng=0.2},
        {pos=pt2, stiffness=stiffness, damping=damping, mu=0.2, mu_lng=0.2},
        {pos=pt3, stiffness=stiffness, damping=damping, mu=0.2, mu_lng=0.2},
        {pos=pt4, stiffness=stiffness, damping=damping, mu=0.2, mu_lng=0.2},
        {pos=pt5, stiffness=stiffness, damping=damping, mu=0.2, mu_lng=0.2},
        {pos=pt6, stiffness=stiffness, damping=damping, mu=0.2, mu_lng=0.2},
        {pos=pt7, stiffness=stiffness, damping=damping, mu=0.2, mu_lng=0.2},
        {pos=pt8, stiffness=stiffness, damping=damping, mu=0.2, mu_lng=0.2}

    }
 
    vi:set_touchdownpoints(td_points)

We expect that when we leave a landed state that the vessel will sink into the ground by travel = 0.1 m. What actually happens is this:

Screenshot at 2024-07-09 14-44-09.png
Close, but not quite. It turns out that the vessel weight is not equally supported by the front and rear wheels as the center of mass is closer to the rear wheels. So we need to do a little statics to figure out the weight supported by the front and rear wheels, and if we want the same deflection all around, that means the rear wheel contact points will need to be a bit stiffer than the front. Here is the arrangement with the Z locations of the vessel center of mass and front and rear touchdown points:

text35753.png

For any object there are two requirements for it remain static:
  • Sum of forces on the object in all directions must sum to 0.
  • Sum of moments on the object around any point must sum to 0.
The only forces are in the Y-direction, so sum of forces in the Y-direction is:

[math]\sum F_y = 0[/math]

[math]F_f + F_r = W[/math]

We need a second equation to solve for the forces, which we get from the moment equation. In theory we could pick any point on the Y-Z plane, but the math simplifies if you pick a point where one of the unknown forces acts, since the moment of that force about that point is 0. I chose to do sum of moments around the rear wheel touchdown point to solve for [imath]F_f[/imath]. Note I am using the right hand rule for moments, but this will work with a left handed convention just as well if Orbiter has burned that system into your skull.

Sum of moments around that point is:

[math]\sum M_r = 0[/math]

Right turning moments = Left turning moments

[math]F_f (z_f - z_r) = W (-z_r)[/math]

[math]F_f = W (-z_r)/(z_f - z_r)[/math]

You can either do sum of moments around the front wheel to get [imath]F_r[/imath] in a similar fashion, or just use sum of forces to get it:

[math]F_r = W - F_f[/math]

I left this in variable form in my code, but we can check our math by putting the numbers in:

[imath] W = mg[/imath] = (1010 kg) (9.81 m/s2) = 9908 N

[imath]F_f = W (-z_r)/(z_f - z_r)[/imath] = 9908 N (+1.084 m)/(1.392 m - (-1.084 m)) = 4338 N

[imath]F_r = W - F_f[/imath] = 9908 N - 4338 N = 5570 N

All the forces are positive as drawn, and the weight supported by the rear wheels [imath]F_r[/imath] is higher than [imath]F_f[/imath], as expected. This procedure can be done as well for more complicated vessels that aren't symmetric by doing these equations in all three coordinate axes.

Since the front and rear wheels support different fractions of the vessel weight, but we want a level vehicle with each touchdown point having the same displacement travel, that means the front and rear contact points must have different stiffness:

Front touchdown point stiffness = 0.5 [imath]F_f/travel[/imath]
Rear touchdown point stiffness = 0.5 [imath]F_r/travel[/imath]

The 0.5 is to account for two wheels supporting the vehicle at front and rear.

So we update our touchdown point stiffness and damping, and I made some body touchdown points for collisions:

Code:
     travel = 0.1
     wheel_base = front_right_wheel_contact.z - rear_right_wheel_contact.z
     max_weight_front = max_weight*(-rear_right_wheel_contact.z/wheel_base)  --weight supported by two front wheels.
     max_weight_rear = max_weight - max_weight_front                         --weight supported by two rear wheels.
 
     front_stiffness = 0.5*max_weight_front/travel
     front_damping = 0.5*math.sqrt(front_stiffness*empty_mass)

     rear_stiffness = 0.5*max_weight_rear/travel
     rear_damping = 0.5*math.sqrt(rear_stiffness*empty_mass)
 
     body_stiffness = max_weight/travel
     body_damping = math.sqrt(4.0*body_stiffness*empty_mass) --critical damping
 
     td_points =
     {
         {pos=front_right_wheel_contact, stiffness=front_stiffness, damping=front_damping, mu=0.0, mu_lng=0.0},
         {pos=front_left_wheel_contact, stiffness=front_stiffness, damping=front_damping, mu=0.0, mu_lng=0.0},
         {pos=rear_right_wheel_contact, stiffness=rear_stiffness, damping=rear_damping, mu=0.0, mu_lng=0.0},
         {pos=rear_left_wheel_contact, stiffness=rear_stiffness, damping=rear_damping, mu=0.0, mu_lng=0.0},
         {pos=pt1, stiffness=body_stiffness, damping=body_damping, mu=0.2, mu_lng=0.2},
         {pos=pt2, stiffness=body_stiffness, damping=body_damping, mu=0.2, mu_lng=0.2},
         {pos=pt3, stiffness=body_stiffness, damping=body_damping, mu=0.2, mu_lng=0.2},
         {pos=pt4, stiffness=body_stiffness, damping=body_damping, mu=0.2, mu_lng=0.2},
         {pos=pt5, stiffness=body_stiffness, damping=body_damping, mu=0.2, mu_lng=0.2},
         {pos=pt6, stiffness=body_stiffness, damping=body_damping, mu=0.2, mu_lng=0.2},
         {pos=pt7, stiffness=body_stiffness, damping=body_damping, mu=0.2, mu_lng=0.2},
         {pos=pt8, stiffness=body_stiffness, damping=body_damping, mu=0.2, mu_lng=0.2}
     }
 
     vi:set_touchdownpoints(td_points)

Testing this out and calculating the depth of each touchdown point in local coordinates:

Screenshot at 2024-07-09 16-03-46.png

The car now sits level, and each touchdown point is hanging 0.1 m below the surface, exactly equal to the specified spring travel.

So now the touchdown points are taking load, but they are 0.1 m below the surface. So like I did with the box, we need to update the touchdown points by lowering the Y height by 0.1 m (-0.322 m to -0.422 m) to get the wheels to rest on the surface:

Code:
front_right_wheel_contact = {x=0.689 , y=-0.422, z=1.392}
front_left_wheel_contact = {x=-0.689 , y=-0.422, z=1.392}
rear_right_wheel_contact = {x=0.689 , y=-0.422, z=-1.084}
rear_left_wheel_contact = {x=-0.689 , y=-0.422, z=-1.084}

Screenshot at 2024-07-09 16-14-24.png

Perfect! (y) Notice that the touchdown points are still located at -0.1 m because that is the spring deflection needed to support the vessel, but the mesh is now 0.1 m higher to compensate for that.
 
Last edited:
I'll probably update this with how to model suspension on wheeled vehicles with corresponding mesh animations when I get that sorted out on my VW Thing. Stay tuned...
 
with corresponding mesh animations
That is something I've thought about for quite a while. Orbiter needs to provide the user with how much the compression is each touchdown point (what about the direction of the compression? :unsure:). Never got much pass the thinking phase... I was waiting for the next cycle of Orbiter development. Also, adding compression limits might make sense, given what a tire or landing gear does in reality.... not sure if it would help or hinder stability though.

One small thing I did was change the surface contact function to return the status of the indicated touchdown point. I then got a DG to use that data to rotate the wheels independently: when on the surface they track ground speed, if the acceleration is too much they generate smoke (like on touchdown), and if not touching the ground they slow their rotation. Also waiting for the next cycle to drop this in a PR.
 
That is something I've thought about for quite a while. Orbiter needs to provide the user with how much the compression is each touchdown point (what about the direction of the compression? :unsure:).
It is possible to determine whether a mesh vertex is below the planet surface by knowing the vessel altitude, and the pitch, bank, and yaw angles of the vessel. I wrote these Lua functions to do this for my buoyancy model:
Code:
function get_help.rotate_pitch(point, pitch)

    --function to rotate point vector around X-axis in vessel coordinates into relative coordinates

    --populate rotation matrix

    local m11 = 1.0
    local m12 = 0.0
    local m13 = 0.0

    local m21 = 0.0
    local m22 = math.cos(pitch)
    local m23 = -math.sin(pitch)
      
    local m31 = 0.0
    local m32 = math.sin(pitch)
    local m33 = math.cos(pitch)

    local rot_matrix = {m11=m11, m12=m12, m13=m13, m21=m21, m22=m22, m23=m23, m31=m31, m32=m32, m33=m33}

    rotated_point =  mat.mul(rot_matrix, point)

    return rotated_point

end

function get_help.rotate_yaw(point, yaw)

    --function to rotate point vector around Y-axis in vessel coordinates into relative coordinates

    --populate rotation matrix

    local m11 = math.cos(yaw)
    local m12 = 0.0
    local m13 = math.sin(yaw)

    local m21 = 0.0
    local m22 = 1.0
    local m23 = 0.0
      
    local m31 = -math.sin(yaw)
    local m32 = 0.0
    local m33 = math.cos(yaw)

    local rot_matrix = {m11=m11, m12=m12, m13=m13, m21=m21, m22=m22, m23=m23, m31=m31, m32=m32, m33=m33}

    rotated_point =  mat.mul(rot_matrix,point)

    return rotated_point

end

function get_help.rotate_bank(point, bank)

    --function to rotate point vector around Y-axis in vessel coordinates into relative coordinates

    --populate rotation matrix

    local m11 = math.cos(bank)
    local m12 = -math.sin(bank)
    local m13 = 0.0

    local m21 = math.sin(bank)
    local m22 = math.cos(bank)
    local m23 = 0.0
      
    local m31 = 0.0
    local m32 = 0.0
    local m33 = 1.0

    local rot_matrix = {m11=m11, m12=m12, m13=m13, m21=m21, m22=m22, m23=m23, m31=m31, m32=m32, m33=m33}

    rotated_point =  mat.mul(rot_matrix,point)

    return rotated_point

end

function get_help.rotate(point, pitch, yaw, bank)

    rotated_point = get_help.rotate_yaw(point, yaw)
    rotated_point = get_help.rotate_pitch(rotated_point, pitch)
    rotated_point = get_help.rotate_bank(rotated_point, bank)

    return rotated_point

end
Let's say you know the contact point of a wheel is at {X,Y,Z} in mesh coordinates. You can rotate that point through the three rotational transformations in relative coordinates and add the vessel altitude to get that point in local coordinates {X',Y',Z'}, and determine whether the point's Y' coordinate is positive or negative. Once you know that, you could transform the mesh to put that point on the surface.
Also, adding compression limits might make sense, given what a tire or landing gear does in reality.... not sure if it would help or hinder stability though.
I am not sure about this. This is essentially like suddenly setting the spring stiffness to infinity. Some compliance is probably necessary.
 
Last edited:
Something that I wanted to clarify - what I am calling touchdown points and contact points are not the same points. I edited my original post to clarify this and to show these points on the spring-damper-mass figure. I myself was rather sloppy and used the terms interchangeably, but clearly defining them might help understanding.

Touchdown points are fixed to the vessel.
Contact points ride on the surface of the planet above the touchdown point in the local +Y direction.

The spring and damper system connects these points when the touchdown point falls below the planet surface.
 
I was figuring what codes in Vessel.cpp for (Orbiter source codes). I now got it. Thanks for information.

I assume that info came from vehicle dynamics book. Right?

Tim
 
I was figuring what codes in Vessel.cpp for (Orbiter source codes). I now got it. Thanks for information.

I assume that info came from vehicle dynamics book. Right?

Tim
The mass-spring-damper model is used in a lot of mechanical systems, not just wheel struts. Any basic physics text would cover it in detail. The wiki page for it isn't too bad either.
 
Also, we would like to see implementing steering model for taxing to the runway from the hangar, etc. I was looking for some codes but Vessel.cpp do not have steering codes.
 
Also, we would like to see implementing steering model for taxing to the runway from the hangar, etc. I was looking for some codes but Vessel.cpp do not have steering codes.
That's on my to-do list as well. I rolled my own wheels and brakes for my J-3 addon which work nicely, but vehicles with drive axles are different matter.
 
That is something I've thought about for quite a while. Orbiter needs to provide the user with how much the compression is each touchdown point (what about the direction of the compression? :unsure:). Never got much pass the thinking phase... I was waiting for the next cycle of Orbiter development. Also, adding compression limits might make sense, given what a tire or landing gear does in reality.... not sure if it would help or hinder stability though.

One small thing I did was change the surface contact function to return the status of the indicated touchdown point. I then got a DG to use that data to rotate the wheels independently: when on the surface they track ground speed, if the acceleration is too much they generate smoke (like on touchdown), and if not touching the ground they slow their rotation. Also waiting for the next cycle to drop this in a PR.
Adding this is straight forward, you can check this for an example of how to do it
 
Adding this is straight forward, you can check this for an example of how to do it
@Gondos I've yet to look at this as I can't compile it at the moment, but I am curious to know if the depths calculated in this model are local (it's the depth under the specific contact point using the local altitude), or does reference the mean altitude of the vessel to determine those depths. I see some reference to an Elevation function that seems to get the terrain elevation at a certain lat/lng, but I don't know if that is accessed for each touchdown point.

I basically tried getting the depth of each touchdown point myself by rotating those points in vessel coordinates to relative horizon using the vessel altitude. It works to some extent, but in sloped terrain this doesn't work so well as the wheel suspension motion is tracking the clearance at the center of the vehicle, not at each wheel.
 
Last edited:
Looks like it's local to each point.
Not sure if that answers your question but I did a test with the Lua DG :
- when in landed state everything is "hardcoded" to 0 :
landed.png

During takeoff the physics kicks in and 3 TD points are a bit below ground (the first 3 so the ones corresponding to the wheels) :
rolling.png

At nose up, the front TD point leaves the ground and only the 2 back wheels are still in contact :
noseup.png

In this example, the TD points are only shown when the elevation is negative or zero
 
I think that we need ground softness depending on the runway (harder), sea, and soil (softer) applying to mass-spring-damper model.

Also, need add some codes for rolling down on slopes to the bottom valley.
 
Last edited:
Well this is Orbiter as in Orbital mechanics, having a realistic ground model is nice but I don't think we should overdo it.
That being said, I took a closer look at the Elevation API and the surface normal should be available per contact point.
I did a bit of testing but looks like I got some memory corruption somewhere... I'll investigate when I get back from holidays.
 
Back
Top