What is wrong with this 17 line script?

Well, I’m trying to make things hover. And to do this I created a simple script:

import bge

cont = bge.logic.getCurrentController()
own = cont.owner


hover_pads = [o for o in own.children if 'hover' in o]


for pad in hover_pads:
    down = pad.getAxisVect([0,0,-1])
    _, hit_point, _ = pad.rayCast(pad.worldPosition+down, pad.worldPosition, 10)
    if hit_point != None:
        height = (pad.worldPosition - hit_point).length
        
        impulse = -down/(height)
        pad.parent.applyImpulse(pad.localPosition, impulse)
        
        bge.render.drawLine(pad.worldPosition, pad.worldPosition+impulse*100, [0,1,0])

Pretty simple, right?
It gets a bunch of children designated as hoverpads by a propery, and uses their heights and locations to apply an impulse at it’s location to the main vehicle.

This method has a couple advantages of rigid bodies, as it requires less physics objects, and the rigid body joints seem to be a little ‘sloppy’ where large forces are involved. (say, a 700kg vehicle. Hang, even with ten newtons of force they flex)

So here’s the problem. The script doesn’t work properly. Check the blend:
Hovertest.blend (543 KB)

Hover-vehicle number 2 has four symmetrically arranged hover-pads. And surprise, it has a stable hover
Hover-vehicle number 1 has three pads arranged non-symmetrically. It should flip over towards the top of the screen. So why does it flip sideways? The forces should be balanced in that direction.

By pausing the scene after a couple frames (and studying the afterimage of the drawline command), I analysed what was happening. In the first frame, all the lines are the same length. So it should just travel vertically. In the second frame, it’s tilted slightly, and the lower one is applying more force than the higher one, so it should level out. In the third frame, it’s tilted more in the same direction, and the corrective force has got stronger, but it isn’t correcting.
I find this odd, because the drawline is exactly where the impulse is being applied, and of the magnitude that it will be applied.

So why are the forces not being applied correctly? Is it a problem in the script? Because I can’t find it.

Code looks solid to me but of course these kind of physics setups are prone to highlight every hickup the engine has.

Since you did the frame-by-frame analysis we can probably establish the script does run through all the instances of hoverpads every time. Also there doesn’t seem to be a problem with applying as many as 3 impulses per tick since the 4 pad setup does work.

Can it be about floating point (non-)determination? Have you tried to getReactionForce() and see what it shows?

Have you tested other cases of asymmetric setups? Or with smaller/bigger forces and coefficients?

Sorry I can’t run .blends here.

EDIT: Oh, one thing:

applyImpulse(point, impulse)
point (the point to apply the impulse to (in world coordinates)) – the point to apply the impulse to (in world coordinates)

pad.parent.applyImpulse(pad.localPosition, impulse)

?

EDIT: Ok you have another thread up about that. I thought it was pretty odd that you supply world coordinates to such function.

The reason the first vehicle flips over sideways instead of straight over does appear to be a floating point issue. Notice that if you clear its rotation using alt+R it flips over in the expected manner.

However, the equation you’re using for the impulse is a much bigger issue. Notice how even the second vehicle spins wildly out of control after a few seconds (unless you clear its rotation as well to remove any perturbations caused by floating point rounding). You’re essentially just using impulse = 1/distance which has no upper bound as the distance approaches zero. Also, just because one of the hover pads is lower to the ground does not mean that the distance you get from its ray will be less because the pad is likely firing the ray at an angle instead of straight down. The angle gets stepper as the vehicle continues to tilt, making the distance even longer.

Even after fixing both of those issues, you’ll still probably need some kind of damping to prevent the vehicle from over correcting and flipping over. If you want to read how these types of self-correcting systems work in real life, you can look up PID controllers, though you don’t have to implement anything nearly that complex since you have full knowledge of the current game state.

Hrm, seems odd to me that it would be a floating point issue. Which floating points? The distances are all small, and blender handles angles well enough. Where do you think the floating point problems are coming from?
Also, the drawLines are still in the correct place and the correct length (and use the same numbers to draw), so I’m not sure it is floating point.

@Mobius:
I am aware that this is a much simplified situation. The actual blend where I discovered this problem involves a lot more, well, everything. A total of six repellers using a somewhat more complex repulsion function, and some surfaces that, using a similar method, apply air-drag (which damps the system). And yup, it’s stable, up to ~400 in linearVelocity, whereby the slight sway either becomes too great for the repellors and it bottoms out, or it leaps too high into the air and flips (because the repellers no longer stabilize it). Given that 400m/s is ~1600kph, I’m happy enough with the stability of the system.
The problem was when I tried rotating the craft and everything went haywire.

The floating points are coming from the hover pads’ positions. When the vehicle is rotated, there is no guarantee that the hover pad’s positions will still be integer values. Print the bottom pad’s position using

print('%.47f' % own.worldPosition.x)

to see what I mean. However, once you have relatively stable function, these small errors won’t be a problem anymore.

The issue of stability has nothing to do with speed and everything to do with the pitch/rotation of the vehicle. The fact that even the tiniest rotation causes the vehicle to fly out of control indicates that your hover system is not stable.

I think you need to reconsider the raycast direction because of the problem Mobious outlined about the angle. I didn’t even realize until now you are indeed using getAxisVect() instead of just raycasting downwards.

Seeing as you obviously think this system should be unstable, have a look at the blend ‘boeing.blend’ inside this zip:
Click here
Give it a few seconds (2-3) and you’ll see that it does stabilize. The script is a little more complex, but the lines from 96 to 111 are the ones responsible for hovering.
Pleas bear in mind that that blend is unfinished. The wing-physics-surfaces need to be set to invisible, the mag-pads need to be adjusted to actually touch the frame of the vehicle, and the camera is useless

But that is not the issue I wished to discuss in this thread.

This more complex blend the same problems as the simplified file in the first post. If you rotate the car before starting (or indeed, after starting by applying a torque) weird stuff starts at ~45 degrees, and by 90 degrees it’s so unstable it flips.

There’s definitely some funny stuff happening here. Have a look at this blend:
Hovertest 2.blend (495 KB)

Even simpler than the one at the top.
That system (a cube balancing on a single point of force) should be really really unstable, right? Then why does it right itself and point upright? I can’t see where this stabilizing force is coming from.

Just realized that the actions of hovertest 2 make sense if the applyImpulse is acting at world coordinates. But I thought I had established it was local. Arrgh.

i have checked and rechecked, then gone through it with a fine toothed comb, then checked my findings with several renowned university professors and have confirmed that the problem with your 17 line script is that it only has 12 lines…

Again, this is caused by floating point rounding. When the vehicle isn’t rotated, everything lines up on the x-axis perfectly, but when rotated, the forces no longer balance perfectly along the vehicle’s x-axis due to floating point rounding. While this system is definitely more stable than the simplified blend, it still can’t handle steeper rotations.

Also, I noticed that some of the forces corresponding to the green lines become ridiculously large when the vehicle is rolled at a fairly steep angle (> 45°). You should probably cap them at a maximum value or use a function that doesn’t grow so large.

I don’t get why you don’t just use RBJ’s and apply forces :smiley:

Check mine out

controls = Mouse and mouse wheel

Attachments

DROPPSHIPTskyb.blend (930 KB)

Green lines are air friction and have no maximum value. They will respond only to the vehicles speed through the air squared (and divided by a couple of factors). Red lines are the output of the hover-pads and are capped at 10,000 newton-seconds.

So if it’s floating-point rounding, does that mean that there is no solution?

@blueprint
I do not want to use RBJ’s because they fail at high speeds and stresses. They often break between blender version changes (like Skypilot only works for a single blender version) and a couple other reasons.

Can we build a version of the BGE closer to bullet 3.x somehow?

is it c handles the bge lacks?

The solution would be to make a more stable system. It may turn out that only using air friction isn’t enough. You can try implementing things like variable thrust depending on the current height, orientation, and speed of each hover pad. Like I mentioned earlier, if you’re wanting to simulate an actual physical system, then something along the lines of a PID controller would be the way to go (they’re not nearly as hard to implement as the Wikipedia article makes them sound).

EDIT:
Here’s an example of a PID controlled omnidirectional thruster. It simply tries to match a target position which you can set by moving the target object around with WSAD+QE, then hitting space to set the target position. The same principles can be applied to each of your hover pads.

PID_Thruster.blend (509 KB)

The drawn lines are:
Red - Proportional thrust
Green - Integral thrust
Blue - Derivative thrust

Note that 99% of the work in getting a PID contoller right is just tweaking your Kp, Ki, and Kd values to get the desired behavior. You can read more about that on the wiki page.

I kind of started having some fun with your idea and ended up making a full PID controlled hovercraft. It’s basically a virtual quad-copter. They can right themselves even if they’re upside down or fly even if they’re somewhat lopsided as you can see in the blend file. Right now they only try to maintain an upright orientation and a target height, but I may try to implement full positional movement in the future.

PID_Hovercraft.blend (687 KB)

Controls
Space: bump the vehicles
+/-: adjust the target height

Well, I think I owe you an appology. It turns out your problem had nothing to do with floating point rounding and was actually caused by the implementation of the applyImpulse method. As you noted in your other thread, the documentation is somewhat ambiguous and misleading. While the position you use to apply the impulse at is relative, it is not transformed by the object’s orientation. E.g. if you have an object rotated 90 degrees about its z-axis and you apply an impulse at [1, 0, 0], the impulse still gets applied at [1, 0 ,0] relative to the object’s world position even though you would expect it to be applied at [0, 1, 0].

The simple solution to the problem is to use obj.worldPosition - obj.parent.worldPosition instead of obj.localPosition.

Interesting. I never would have found that!

The simple solution to the problem is to use obj.worldPosition - obj.parent.worldPosition instead of obj.localPosition.

Thanks. I’m sticking this on to the other thread if you don’t mind, just in case anyone falls into a similar problem.

And nice work with those quad-copters. They’re fun to play with!

It’s likely a manifestation of Gimbal Lock, though I haven’t looked to see if we’re using Eulers.

Hmm, there is the same artifact on the getVelocity(point) function. The documentation says to use local coordianates, but it has strange behavior using obj.localPosition which fixes itself when using obj.worldPosition - obj.parent.worldPosition