Project Making a Lua Script Vessel Car: The VW Thing

So I am trying to make a nested animation of both the steering and rolling rotations of the front wheels. The front wheels should rotate vertically left and right for steering, and should rotate about their respective axles while rolling. For the front right wheel I have the following:

Code:
function make_anim.right_front_wheel()

    right_front_wheel_steer =
    {
        type =  'rotation',
        mesh =  0,
        grp =   {82,83,84},
        ref = front_right_wheel_axle,
        axis =  {x=0,y=1,z=0},
        angle = 45*RAD
    }

    right_front_wheel_rotate =
        {
        type =  'rotation',
        mesh =  0,
        grp =   {82,83,84},
        ref = front_right_wheel_axle,
        axis =  {x=1,y=0,z=0},
        angle = 360*RAD
        }

        anim_right_front_wheel_steer = vi:create_animation(0.5)
 
        parent = vi:add_animationcomponent(anim_right_front_wheel_steer,0,1,oapi.create_animationcomponent(right_front_wheel_steer))

        anim_right_front_wheel_rotate = vi:create_animation(0.0)

        vi:add_animationcomponent(anim_right_front_wheel_rotate, 0, 1, right_front_wheel_rotate, parent)

end

I am getting the following error in the Orbiter.log related to the last line of code:

Code:
Config/Vessels/VWThingScript/make_anim.lua:140: add_animationcomponent: argument 4: invalid type (expected handle) table given

I am unsure why it is having a problem with right_front_wheel_rotate and not right_front_wheel_steer.

EDIT: It was because I forgot to create the animation component for the rotation:


Code:
vi:add_animationcomponent(anim_right_front_wheel_rotate, 0, 1, right_front_wheel_rotate, parent)

should have been:

Code:
vi:add_animationcomponent(anim_right_front_wheel_rotate, 0, 1,oapi.create_animationcomponent(right_front_wheel_rotate), parent)

That part seems to work, but the wheel isn't rotating about the axle:

Screenshot at 2024-06-23 11-16-24.png
I was able to get both animation components to work individually, but I can't get them to work at the same time.

I have the levels of each component of the animation set here (not tied to steering yet, just entering levels manually):

Code:
function set_anim.right_front_wheel()

    local rotation_speed = 0.1

    right_front_wheel_steer = 1.0

    vi:set_animation(anim_right_front_wheel_steer, right_front_wheel_steer)

    right_front_wheel_rotation= (right_front_wheel_rotation + oapi.get_simstep()*rotation_speed) % 1

    vi:set_animation(anim_right_front_wheel_rotate, right_front_wheel_rotation)

end
 
Last edited:
Code:
        vi:add_animationcomponent(anim_right_front_wheel_rotate, 0, 1, right_front_wheel_rotate, parent)
end
Aren't you missing an oapi.create_animationcomponent call (right_front_wheel_rotate is a table, not an animation component)?

There are some 'macros' (MGROUP_ROTATE, MGROUP_TRANSLATE, MGROUP_SCALE) that make the code look more like their C++ equivalents, maybe it'll help you?
For example in the Lua DG:
Code:
    local RAileronGrp = {GRP.RUAileron1,GRP.RUAileron2,GRP.RLAileron1,GRP.RLAileron2}
    local RAileron = MGROUP_ROTATE(0, RAileronGrp, _V(0,-0.4,-6.0), _V(1,0,0), 20*RAD)
    self.anim_raileron = self:create_animation (0.5)
    self:add_animationcomponent (self.anim_raileron, 0, 1, RAileron)
 
Aren't you missing an oapi.create_animationcomponent call (right_front_wheel_rotate is a table, not an animation component)?
Yep, that was it. I was staring at it. Now both animation components are working, but they aren't working as expected. I am getting strange rotations. I am looking to have the wheel rotate about its axle, but the orientation of the axle can rotate around a vertical axis. I'm getting a strange combination that seems to rotate the axle out of the X-Z plane which is incorrect.
There are some 'macros' (MGROUP_ROTATE, MGROUP_TRANSLATE, MGROUP_SCALE) that make the code look more like their C++ equivalents, maybe it'll help you?
For example in the Lua DG:
Code:
    local RAileronGrp = {GRP.RUAileron1,GRP.RUAileron2,GRP.RLAileron1,GRP.RLAileron2}
    local RAileron = MGROUP_ROTATE(0, RAileronGrp, _V(0,-0.4,-6.0), _V(1,0,0), 20*RAD)
    self.anim_raileron = self:create_animation (0.5)
    self:add_animationcomponent (self.anim_raileron, 0, 1, RAileron)
Thanks for this, but I think I'm fine with the Lua syntax. I don't think changing to C++ syntax is going to improve my state of confusion.
 
I am having a problem with the entire wheel nutating in a rather weird fashion:

Screenshot at 2024-06-23 13-41-05.png

I am wondering if the parent/child linking of the two animation components is appropriate? Doesn't that force both rotations to happen at the same time? I basically want to rotate the wheel about the axle, THEN rotate that rotated assembly around the vertical axis.
 
Have you done the animations in the wrong order? (Yes, you have.)
 
This is what I have now. I seem to be getting both rotations of the wheel simultaneously in vessel coordinates, not sequentially.

Code:
function make_anim.right_front_wheel()

    anim_right_front_wheel_steer = vi:create_animation(0)

    right_front_wheel_steer =
    {
        type =  'rotation',
        mesh =  0,
        grp =   {82,83,84},
        ref = front_right_wheel_axle,
        axis =  {x=0,y=1,z=0},
        angle = 45*RAD
    }

    anim_right_front_wheel_rotation = vi:create_animation(0)

    right_front_wheel_rotate =
    {
        type =  'rotation',
        mesh =  0,
        grp =   {82,83,84},
        ref = front_right_wheel_axle,
        axis =  {x=1,y=0,z=0},
        angle = 360*RAD
    }
        parent = vi:add_animationcomponent(anim_right_front_wheel_steer,0,1,oapi.create_animationcomponent(right_front_wheel_steer))

        vi:add_animationcomponent(anim_right_front_wheel_rotation,0,1,oapi.create_animationcomponent(right_front_wheel_rotate), parent)
        
end

Code:
function set_anim.right_front_wheel()

    --apply a constant speed rotation to the wheel about the axle

    local rotation_speed = 0.1

    right_front_wheel_rotation= (right_front_wheel_rotation + oapi.get_simstep()*rotation_speed) % 1

    --apply an oscillating back-and-forth steering motion

    right_front_wheel_steering_angle = 0.5*(1.0 + math.sin(0.5*math.pi*oapi.get_simtime()))

    --set the animations

    vi:set_animation(anim_right_front_wheel_steer, right_front_wheel_steering_angle)

    vi:set_animation(anim_right_front_wheel_rotation, right_front_wheel_rotation)

end
 
Maybe you could take a look at GeneralVehicle's source code to see how it's done there, it must be doing exactly what you want.
 
Maybe you could take a look at GeneralVehicle's source code to see how it's done there, it must be doing exactly what you want.
Yes, and as far as I can tell I am doing what should be the Lua equivalent, but again, it's not doing the two rotating animations in sequence. It seems to be doing them simultaneously in vessel coordinates.

EDIT: I am looking harder at the GeneralVehicle's code and it seems like they had to do their own coordinate transforms for nested rotations like this. I think I have a similar problem in that I need to apply two different rotational transformations to occur in sequence to the same meshgroups. In the examples like in the API_Guide.pdf the parent and child groups are different meshgroups. I need to rotate the mesh groups, then rotate the rotated groups around another axis.
 
Last edited:
Screenshot at 2024-06-25 17-04-43.png
Now the wheel is rotating the car. That's not what I was looking for.

Maybe if I make a dummy/invisible mesh group for the steering axis, animate that, and add the wheel as child animation to that? Hmmm...
 
Yes, and as far as I can tell I am doing what should be the Lua equivalent, but again, it's not doing the two rotating animations in sequence. It seems to be doing them simultaneously in vessel coordinates.

EDIT: I am looking harder at the GeneralVehicle's code and it seems like they had to do their own coordinate transforms for nested rotations like this. I think I have a similar problem in that I need to apply two different rotational transformations to occur in sequence to the same meshgroups. In the examples like in the API_Guide.pdf the parent and child groups are different meshgroups. I need to rotate the mesh groups, then rotate the rotated groups around another axis.
I pretty much ended up having to do the same, also within the same mesh group. Wanted to rotate a globe around the fixed x axis, and around the polar axis, which changes as the first rotation happens (orbital and Earth spin). After banging my head on the parent-child wall, I just took to editing the second animation's reference axis at each time step, by rotating that vector around the fixed axis by the amount needed. Doesn't necessarily mean it can't be done with parent-child, but it doesn't seem intuitive if it can.
 
I pretty much ended up having to do the same, also within the same mesh group. Wanted to rotate a globe around the fixed x axis, and around the polar axis, which changes as the first rotation happens (orbital and Earth spin). After banging my head on the parent-child wall, I just took to editing the second animation's reference axis at each time step, by rotating that vector around the fixed axis by the amount needed.
I was thinking of how to pass the transformed vertices from the first rotation to the second, but changing the axis is neater. I'll give that a shot.
Doesn't necessarily mean it can't be done with parent-child, but it doesn't seem intuitive if it can.
I was thinking of making a dummy mesh element, rotating that as the parent animation, and then adding the rolling wheel rotation as the child. That doesn't seem efficient or intuitive.
 
Ended up looking like:
C++:
    // correct globe e animation axis with rotation matrix around x for the initial vector for the poles in the mesh file
    double angGlobeO = 2 * PI * globe_o_proc;
    globe_e_axis.x = GLOBE_E_AXIS_INIT.x;
    globe_e_axis.y = GLOBE_E_AXIS_INIT.y * cos(angGlobeO) - GLOBE_E_AXIS_INIT.z * sin(angGlobeO);
    globe_e_axis.z = GLOBE_E_AXIS_INIT.y * sin(angGlobeO) + GLOBE_E_AXIS_INIT.z * cos(angGlobeO);
    globe_e->axis = globe_e_axis;

Adding the dummy thing would make it pretty much the API guide example. Maybe there's some condition in Orbiter's code where the parent animation has to be an array of groups including the child, or something like that. Assuming we both didn't miss some detail in the implementation. That said,

I basically want to rotate the wheel about the axle, THEN rotate that rotated assembly around the vertical axis.
Wouldn't you want the opposite? With the parent-child idea, the rotation around vertical will always be the y axis, but the rotation around the axle will vary depending on the rotation around y (reference axis somewhere on the xOz plane). So the wheel rotation would be child to the steering rotation (unless you're already doing that, between Lua and rustiness, I could totally miss it in the code).
 
Making a dummy mesh group for the parent animation and adding the wheel to it works:

Code:
function make_anim.right_front_wheel()

    anim_right_front_wheel_steer = vi:create_animation(0.5)

    right_front_wheel_steer =
    {
        type =  'rotation',
        mesh =  0,
        grp =   {82}, --dummy mesh group to take parent animation
        ref = front_right_wheel_axle,
        axis =  {x=0,y=1,z=0},
        angle = 45*RAD
    }

    parent = vi:add_animationcomponent(anim_right_front_wheel_steer,0,1,oapi.create_animationcomponent(right_front_wheel_steer))

    anim_right_front_wheel_rotation = vi:create_animation(0)

    right_front_wheel_rotate =
    {
        type =  'rotation',
        mesh =  0,
        grp =   {83,84,85}, --parts of wheel
        ref = front_right_wheel_axle,
        axis =  {x=1,y=0,z=0},
        angle = 360*RAD
    }

    vi:add_animationcomponent(anim_right_front_wheel_rotation,0,1,oapi.create_animationcomponent(right_front_wheel_rotate), parent)

end

I just copied a triangle from each front wheel and declared that the "axle" mesh group for the parent rotation and added the wheel as the child. I set the visibility flag for these triangles off.
 
Last edited:
Added an aileron to drive each of the front wheel steering animations and to set the steering controls (left and right arrow). The area of the aileron is 1 square meter and the change in lift coefficient was set to 0, so this will just animate the wheel steering, not apply any forces. It also permits a delay in going from animation level 0 to 1, as it takes time to turn the wheel stop to stop.

Code:
function make_anim.right_front_wheel()

    anim_right_front_wheel_steer = vi:create_animation(0.5)

    right_front_wheel_steer =
    {
        type =  'rotation',
        mesh =  0,
        grp =   {82},
        ref = front_right_wheel_axle,
        axis =  {x=0,y=1,z=0},
        angle = 45*RAD
    }

    local parent = vi:add_animationcomponent(anim_right_front_wheel_steer,0,1,oapi.create_animationcomponent(right_front_wheel_steer))

    vi:create_controlsurface(AIRCTRL.AILERON, 1, 0, front_right_wheel_axle, AIRCTRL_AXIS.YNEG, 1.0, anim_right_front_wheel_steer)

    anim_right_front_wheel_rotation = vi:create_animation(0)

    right_front_wheel_rotate =
    {
        type =  'rotation',
        mesh =  0,
        grp =   {83,84,85},
        ref = front_right_wheel_axle,
        axis =  {x=1,y=0,z=0},
        angle = 360*RAD
    }

    vi:add_animationcomponent(anim_right_front_wheel_rotation,0,1,oapi.create_animationcomponent(right_front_wheel_rotate), parent)

end
Screenshot at 2024-06-26 16-42-26.png
 
Can't steer yet, but I have all the contact points in and put in a 10000 N rocket for some movement, so it behaves a bit like Rocket League when it goes off road:

Screenshot at 2024-06-26 17-57-01.png
 
Sorted out the animations. All wheels rotate with forward speed, all wheels can travel up and down depending on their touchdown point depth to keep the tire on the surface (mostly), and the front wheels have left/right steering animations. Car behaves like it has suspension.
Here are some shot of the car with front steering, side view at neutral suspension stance, and a 2000N load downward on the hood compressing the front suspension:

Screenshot at 2024-07-10 19-09-56.png
Screenshot at 2024-07-10 19-18-37.png
Screenshot at 2024-07-10 19-18-59.png
Tried to put a steerable thruster between the front wheels to drive around a bit, which kinda worked, but it was like driving on black ice:
Screenshot at 2024-07-10 19-04-19.png
I can probably lower the range of suspension travel a bit (+/- 8 inches / 20 cm is kind of a lot for a car like this) but it seems to be doing the appropriate things mostly. The wheel suspension animations are a bit weird because they are based on the altitude of the vessel at [0,0,0] relative to the vessel, but the wheels are ahead or behind that point so the altitude at those parts of the vehicle are different. I somehow need a local altitude for the different wheels for those animations to work appropriately on rougher terrain.
 
Last edited:
Implemented Ackermann steering geometry:

Screenshot at 2024-10-13 20-50-58.png

Now the bits of the car move appropriately.

Next step is to make it generally move about on its wheels like a car, preferably in a general fashion. I have to think about that a bit more. I think I can determine what the lateral forces would need to be on all four wheels (in their local coordinate systems) in order to get the car to turn in a circle according to the radius of the Ackermann geometry, and then see if those forces exceed the static friction force of the tire on the surface. If they do, I would apply the dynamic friction force; if not, I apply those forces. This should permit proper handling yet allow side skid (I want to do donuts in the VAB parking lot!). Drive and braking forces could be applied in the longitudinal direction of all four wheels (again, in their local coordinate systems). I will need to calculate the normal contact force on all four wheels while turning and accelerating as well.
 
Back
Top