SDK Question [SOLVED] Landed state and surface interaction

Observation

New member
Joined
Jun 13, 2019
Messages
26
Reaction score
5
Points
3
Hi everyone,

I've been trying to implement a realistic center of mass simulation, that is that it moves as fuel levels change.

Now a few problems have arisen: when I start the simulation in a landed state, it immediately jumps into active state. The same happens when I set the location with the scenario editor. Also, when I land off a base, the vessel never returns to the landed state. This problem is not directly related to moving the center of mass moving since if I comment all the ShiftCG()'s out, I still get the same behaviour.

But this leads me to the next problem: when I land on a base, if I eventually "dock" and refueling happens (which shifts the CG), the ship ends up below the surface. I found out that while a vessel is in the landed state, calls to ShiftCenterOfMass() have no effect, which means that when ShiftCG() is called, the mesh, touchdown points and thruster definitions are just sunk into the ground.

As if this wasn't enough, I have not managed to make the ship stay stable and upright while fueled above a certain limit. When it is fully fueled, it sinks down a few meters and then slowly tips over. I already use an "active suspension": calling
C++:
tdvtx[i].stiffness = weight;
tdvtx[i].damping = 2 * sqrt(tdvtx[i].stiffness * getMass());
at each timestep (I have 6 touchdown points for ground contact). I would hope this would make the vessel always sit at the same height, which is not the case. I must admit that the CG ends up pretty high up (20m) while the touchdown vertices are spread out only on a 9m diameter circle.

And finally a bonus if anyone knows: I've noticed that the ship was "sticking" to the ground at takeoff a little bit. This happens only while the touchdown vertices are still in contact with the ground. But this is not as problematic as the rest.


To sum up:
  • What makes a vessel stay in, enter and exit the landed state?
  • Is there a bypass to ShiftCenterOfMass() for when a ship is in landed state?
  • How to avoid the ship tipping over when the CG is too high up?
  • How for it not to stick to the ground?

Thank you for any insight into any of these problems!
 

jedidia

shoemaker without legs
Addon Developer
Joined
Mar 19, 2008
Messages
10,862
Reaction score
2,126
Points
203
Location
between the planets
Oh curses! I kinda solved the problem once, though that code is so closely tied up with an event propagation machine that it wouldn't be much use to you, but it's a highly finacky affair. I wanted to sum up what I did in concept, but I realise I don't know anymore... I'd have to go through my commits.

One thing I do remember is that I turned of CoG propagation completely when in landed mode.

And then there's the issue of the touchdown points, which is a whole other fun story for itself. Basically, if your landing gear works fine on empty mass, it may be way too soft for full mass, and when it's fine for full mass, it may be too hard for empty mass (and make you bounce of the ground).

The "stickiness" comes probably from the fact that your landing legs are compressed and expanding first before getting of the ground. Unfortunately its all only mathematical, there isn't a way to query that state and hook an animation to it...
 

Observation

New member
Joined
Jun 13, 2019
Messages
26
Reaction score
5
Points
3
Alright thank you! I could already improve it a little bit: no CG updating while the vessel is in landed state. But when it refuels, it reenters active flight (still don't know why). To keep it from tipping over when full, I set the CG to zero (height) right when it comes back into active state but is still on the ground (using GroundContact()), and resume CG propagation after it leaves the ground. But this does not resolve the issue of landing an almost full vessel for example.

By setting the stiffness and the damping of the touchdown points at each timestep, I actually don't have a too jumpy vessel anymore (the parameters are always optimal), but it still tips over. My best guess is that it has something to do with the base width versus the CG height, but maybe there's a workaround that doesn't involve killing physics.

I have figured that the stickiness actually comes from the damping of the touchdown points, which is also effective above the equilibrium position mg/k, simply because it's effective as soon as you have any contact at all. So now I decided that if we're firing our main thruster group and going upwards, damping was no longer necessary and could be set to zero. It's not very clean but works well.

By the way on the animation question, you just gotta redo the math to find out where your legs are:
C++:
VECTOR3 surfaceNormalHorizon = GetSurfaceNormal();
VECTOR3 surfaceNormalLocal;
HorizonInvRot(surfaceNormalHorizon, surfaceNormalLocal);
double surfaceDistance = GetAltitude(ALTMODE_GROUND) * surfaceNormalHorizon.y / surfaceNormalLocal.z;       // distance in a straight path from the CG to the surface in your landing direction (mine is a tailsitter)
if (surfaceNormalLocal.z > 0)     // only works correctly when the vessel is about in the good direction
{
     surfaceNormalLocal /= surfaceNormalLocal.z;
     for (int i = 0; i < LANDINGLEGS_COUNT; i++)
     {
          SetAnimation(landingLegsAnimation[i], min(landingLegsPosition[i], (surfaceDistance + tdvtx[i].pos.x * surfaceNormalLocal.x + tdvtx[i].pos.y * surfaceNormalLocal.y + tdvtx[i].pos.z + LANDINGLEGS_RANGE) / LANDINGLEGS RANGE));
     }
}
else
{
     for (int i = 0; i < LANDINGLEGS_COUNT; i++)
     {
          SetAnimation(landingLegsAnimation[i], landingLegsPosition[i]);
     }
}
It did take a little amount of work to figure it out, and it requires some changes to the rest of the implementation, like multiple animation definitions, but it is very much worth it. The result is just so cool when you can watch the legs compress one at a time when it crashes down asymetrically. An implementation using just surfaceDistance is also a great start as you don't have anything underground anymore and it is enough in most scenarios.

That leaves us with CG propagation in the landed state and stickiness solved. Two to go!
 

n72.75

Move slow and try not to break too much.
Orbiter Contributor
Addon Developer
Tutorial Publisher
Donator
Joined
Mar 21, 2008
Messages
2,694
Reaction score
1,352
Points
128
Location
Saco, ME
Website
mwhume.space
Preferred Pronouns
he/him
This may be of some use:

 

jedidia

shoemaker without legs
Addon Developer
Joined
Mar 19, 2008
Messages
10,862
Reaction score
2,126
Points
203
Location
between the planets
By the way on the animation question, you just gotta redo the math to find out where your legs are:

Oh, sure... The fun starts when the craft is not level with the ground, or the ground is uneven... ?‍♂️

Looking through my code, I see that I had isolated the issue with Scenario Editor. Basically, scenario editor goes on the old orbiter convention and only cares about the first three touchdown points in the array, assuming they are the only landing legs present. So I had kind of a compatibility option that could be turned on that would modify the stiffness of these three points to hold the entire vessel at the proper position, otherwise the vessel might end up with the gear heavily compressed on the ground and take a jump as soon as it released.
 

Observation

New member
Joined
Jun 13, 2019
Messages
26
Reaction score
5
Points
3
This may be of some use:

This doesn't go into much detail regarding landing a vessel, which is the problem at hand, but thank you!
Oh, sure... The fun starts when the craft is not level with the ground, or the ground is uneven... ?‍♂️
That's why I play around with the surface normals. Now if the ground is not following the same surface normal as where you are, you indeed have a problem, but that's rare.
Looking through my code, I see that I had isolated the issue with Scenario Editor. Basically, scenario editor goes on the old orbiter convention and only cares about the first three touchdown points in the array, assuming they are the only landing legs present. So I had kind of a compatibility option that could be turned on that would modify the stiffness of these three points to hold the entire vessel at the proper position, otherwise the vessel might end up with the gear heavily compressed on the ground and take a jump as soon as it released.
Hmmmm you might be onto something. Maybe when a vessel goes into landed state, it verifies if it is in a stable state using the first three touchdown points and thinks it is not because they are not in their equilibrium state by themselves. Or it computes a stable position from the first three which is thus not stable and it reenters the active state? Maybe we could try lifting the other touchdown points when a vessel isn't moving anymore?
 

Observation

New member
Joined
Jun 13, 2019
Messages
26
Reaction score
5
Points
3
So I tried that but it still doesn't really work. When in landed position. The vessels enters landed state every five seconds for just one frame and then returns to freeflight right after it. What can cause this to happen? Any function calls that are not compatible with landed state?
 

Observation

New member
Joined
Jun 13, 2019
Messages
26
Reaction score
5
Points
3
Sooo, I found two functions that were causing problems:
- SetTouchdownPoints()
- AddForce()
When you call any of those two functions, you exit the landed state at the end of the frame. Unfortunately it was hard to find them because they don't do it right away on the call. Especially AddForce() was causing trouble because it wasn't at all connected to CG propagation, but a custom drag model.
Oddly, you can call ShiftCG(), but ShiftCenterOfMass() has no effect. Thus the only result is a shift of the meshes, thruster definitions and docking ports, which in turn can cause trouble for refueling.
That solves the issue with the landed state and such.
Only thing left is the vessel tipping over. It does not bounce around like crazy, but only tips over for no reason when the CG is too high. Any solutions?
 

n72.75

Move slow and try not to break too much.
Orbiter Contributor
Addon Developer
Tutorial Publisher
Donator
Joined
Mar 21, 2008
Messages
2,694
Reaction score
1,352
Points
128
Location
Saco, ME
Website
mwhume.space
Preferred Pronouns
he/him
Only thing left is the vessel tipping over. It does not bounce around like crazy, but only tips over for no reason when the CG is too high. Any solutions?

You're sure the weight vector is pointing through the touchdown point triangle at all times?
 

jedidia

shoemaker without legs
Addon Developer
Joined
Mar 19, 2008
Messages
10,862
Reaction score
2,126
Points
203
Location
between the planets
Sooo, I found two functions that were causing problems:
  • SetTouchdownPoints()
  • AddForce()
When you call any of those two functions, you exit the landed state at the end of the frame.

Indeed, now that you mention it, I remember...

Unfortunately it was hard to find them because they don't do it right away on the call.

Most state changes are applied in either the next preStep or postStep callbacks. You could call it an event queue, though I guess it's just the order of processing steps in the frame. It can be tricky and sometimes crucial to figure out when exactly it happens. I remember pulling my hair over updates to attachment points. At first I had to do a lot of state managment by setting variables that I updated something this frame, because I had to wait one frame for the state to be applied before doing the next step in the modification. I eventually got rid of that silliness by writing my own event messaging system where I could just shove in an event and give it a delay until it fired, and distribute events to whatever parts of the code I wanted so I didn't have to have any undue interdependancies.

Trouble is, I didn't really know anything about event messaging systems back then, I pretty much came up with the entire concept on the spot to solve my rising spaghetti tide. What I had was pretty good for somebody that reinvents the wheel without having seen one, but now that I worked with a couple of them there's definitely things I would do differently...
 
Last edited:

kuddel

Donator
Donator
Joined
Apr 1, 2008
Messages
2,064
Reaction score
507
Points
113
@jedidia : Do you have some code to share? Even if it's ugly or spaghetti, it might give us some hints.
I've had a similar problem and used to make it by heavy use of lambdas (C++11 feature I think).

It looks something like this (just to get the idea):

https://ideone.com/ldBmLW

C++:
#include <iostream>
#include <queue>
#include <functional>
#include <utility>
//using namespace std;

using Offset = int; // next frame, or "after previous Entry was done", ...
using DeferredFn = std::function<void()>;

// Queue entry
struct Entry {
    Entry (Offset o, DeferredFn f) : offset(o), func {std::move(f)} {}
    Offset offset;
    DeferredFn func;
};

// Queue handler (singleton maybe?)
class MsgQ {
public:
    MsgQ () : _q() {}
    ~MsgQ () {}
    void push (Offset offset, DeferredFn function) {
        _q.push(Entry{offset, std::move(function)});
    }
    bool process () {
        // example! In reality it reacts on e.offs somehow!
        if (!_q.empty()) {
            auto e = _q.front();
            e.func(); // execute
            _q.pop();
            return true;
        }
        return false; // Q empty!
    }
private:
    std::queue<Entry> _q;
};

// Declare some Orbiter OAI functions (as test)
int oapiFoo(int a, int b) { auto res = a+b; std::cout << "done " << res; return res; };
void oapiBar(int x) { std::cout << "done " << x; };

int main() {
    int x = 1;
    int y = 2;
    int z = 0;
    MsgQ q;
    q.push(1, [x,y,&z](){ z = oapiFoo(x, y); });
    // ...
    y++; // the lambda captured 'y'-state before, so try to avoid it ;)
    // ...
    q.push(12, [](){ oapiBar(42); });
    // ...
    q.process(); // these would be done in  postStep...
    q.process();
    return 0;
}

The basic idea was to be able to call any oapi-function with just a very little extra "wrapper" code (lambda that is).

As you see the "boilerplate code" is only something like "q.push(12, [](){" and "});" compared to a direct call.

The captures however do not work satisfactorily, although I never had to use them. The 'this' context was the only thing I needed in some lambdas.
 
Last edited:

jedidia

shoemaker without legs
Addon Developer
Joined
Mar 19, 2008
Messages
10,862
Reaction score
2,126
Points
203
Location
between the planets
Do you have some code to share? Even if it's ugly or spaghetti, it might give us some hints.

Of the event system? It's tied up in this huge thing: https://github.com/TheNewBob/IMS2
The project is decently structured in Visual studio, but just a folder of files on the disk, I'm afraid. The best way to find anything is to check out the whole thing and open it in VS.
Looks like the API reference is still around, though: https://thenewbob.github.io/IMS2ApiRef/
And here's the header file with the documentation for the EventHandler interface: https://github.com/TheNewBob/IMS2/blob/master/EventHandler.h


There's a couple of other cool things in there, like a graphical state machine, a high-level abstraction for animations and a decently complete UI-framework with toolkit. Unfortunately I started way too late to wrap independant parts out into their own libraries, so it's all stuck in there, and at some point I didn't have the time to continue the project anymore.
 
Last edited:

kuddel

Donator
Donator
Joined
Apr 1, 2008
Messages
2,064
Reaction score
507
Points
113
@jedidia : Thanks for sharing!
The general idea is something like the "addEventListener" concept from JavaScript.
A simple (non-async) version could look something like this https://ideone.com/k8CJZ0

C++:
#include <iostream>
#include <functional>
#include <map>
#include <vector>

using Listener = std::function<void()>;
using ListenerInt = std::function<void(int)>;
using ListenerLong = std::function<void(long)>;
using ListenerDouble = std::function<void(double)>;
// ... etc. pp.

// Abstract base class for all classes that want to fire or react on events.
class Event {
public:
    Event () {}

    void fireEvent(const char *name);              // "trigger" Event (no data)
    void fireEvent(const char *name, int data);    // DataEvent
    void fireEvent(const char *name, long data);   // DataEvent
    void fireEvent(const char *name, double data); // DataEvent
    // ...

    void addListener(const char *name, Listener callback);
    void addListener(const char *name, ListenerInt callback);
    void addListener(const char *name, ListenerLong callback);
    void addListener(const char *name, ListenerDouble callback);
    // ...

private:
    std::map<std::string, std::vector<ListenerInt>> _queues;   
};

class Counter : public Event {
public:
    Counter () : Event(), _value(0) {}
    void Inc (unsigned int by = 1) {
        _value += by; fireEvent("changeValue", _value);
    }
    void Dec (unsigned int by = 1) {
        _value -= by; fireEvent("changeValue", _value);
    }
    void Reset () { setValue(0); }

    int getValue () const { return _value; }
    void setValue (int value) {
        _value = value; fireEvent("changeValue", _value);
    }
private:
    int _value;
};



int main() {
    Counter cnt;

    // "10th" listener
    cnt.addListener("changeValue", ListenerInt{[](int v) {
        if (v && (v%10)==0) {
            std::cout << v << " is a multiple of ten\n";
        }
    }});

    // "3rd" listener
    cnt.addListener("changeValue", ListenerInt{[](int v) {
        if ((v%3) == 0) {
            std::cout << v << " is multiple of three\n";
        }
    }});

    for (int i = 0; i < 42; ++i) {
        cnt.Inc();
    }
    return 0;
}


void Event::fireEvent(const char *name, int data) {
    auto q = _queues.find(name);
    if (q != _queues.end()) {
        for (ListenerInt callback : _queues[name]) {
            callback(data);
        }
    }
}

void Event::addListener(const char *name, ListenerInt callback) {
    _queues[name].push_back(callback);
}

...and by they way: Don't you think that the C++ syntax highlighting color-scheme of orbiter-forum is not the best? At least I think so ;) I am using a dark theme, that might not be perfect for it.

Anyway, thanks again for sharing your code (-ideas)!
 

Observation

New member
Joined
Jun 13, 2019
Messages
26
Reaction score
5
Points
3
You're sure the weight vector is pointing through the touchdown point triangle at all times?
Yes, the only thing that changes is the z coordinate of the touchdown points. I have made sure the triangle is isometric and perfectly centered. Since I have 6 touchdown points, the first three describe a triangle, and the last three complete the hexagon.
 

Observation

New member
Joined
Jun 13, 2019
Messages
26
Reaction score
5
Points
3
I found something interesting. The issue appears when the z-coordinate exceeds exactly 20m. After that, the vessel drops to an altitude of 20m (touchdown points underground) and begins acting weird (not locking into landed state, doing micro-jumps, tipping over. This is not related to other stability since it happens regardless of the leg radius.
The direction to which it tips is not always the same (in relation to the vessel), so I didn't forget a touchdown point somewhere that causes the plane not to be flat.
But I tried it on another vessel and found that limit to be 7m. Now there is a constant that you have to set and that is logically different for each vessel, and that is size. I checked and found that for this second vessel I had set it to 3.5m and for the first to 10m. So I set it to 12 instead, and miracle, it works perfectly!
So whatever you pass to SetSize() make it at least half the maximum distance between the CG and gouchdown points.
Thank you everyone for helping me on this!
 
Top