Wheeled vehicle with Panda ODE

I’ve some troubles with ODE: I want to setup a wheeled (4 wheels) vehicle with ODE, so I need 1) to set steer limits on wheels and then 2) apply a torque value on them.

For the first point I’ve tried to use “setParamHiStop” and “setParamLoStop” on an “Hinge2Joint” istance (which joins a wheel with the vehicle body) but it doesn’t work.

For the second point I’m using this code:

def add_torque(self, value):
    [w.phys_body.addTorque(value) for (w, t) in self.__wheels if t]

where “w” is an istance of “Wheel” class (which has an “OdeBody” and an “OdeGeometry” istances) and “t” is a flag which says if the wheel is a traction wheel or not (oh…“self.__wheels” is, obviously, the list of connected wheels :wink:)

It seems to work but I would use “OdeAMotor” for this but I don’t understand how to set it.

Please, help me! :wink:

The PyODE vehicle sample would be helpful reference for doing this. Download a source package and look under the example folder. You’ll need PyOpenGL to get it running. I’ve also attempted to implement a wheeled vehicle with the Panda ODE but my steering isn’t sorted out yet, although I have motion using a motor. You can download it here. You will have to move the camera around manually and wait for a few seconds for the simulation to start.

Thanks alot! You are my hero! :smiley:

…and Merry Christmas! :wink:

EDIT: Your code is great and very clear but you have my same problem with wheels as described in the image:

Ok, I’m very confused, so, please, help me with this code: :slight_smile:

Wheel:

class Wheel(entity.Entity):
    def __init__(self, *args, **kwargs):
        entity.Entity.__init__(self, *args, **kwargs)
        
        # Mesh 3D.
        self.m3d = loader.loadModel(kwargs['filename'])
        self.m3d.reparentTo(kwargs['root'])

        min, max = self.m3d.getTightBounds()
        size = Point3(max - min)
        self.radius = size.getZ() * 0.5
        
        # Physics.
        self.phys_body = OdeBody(kwargs['phys_world'])
        
        mass = OdeMass()
        mass.setSphere(100, self.radius)
        self.phys_body.setMass(mass)
        
        self.phys_geometry = OdeSphereGeom(kwargs['phys_space'], self.radius)
        self.phys_geometry.setBody(self.phys_body)
        
        self.accept('physics_world.simulation.step', self.__on_simulation_step)
        
    def __on_simulation_step(self):
        pos = Point3(self.phys_body.getPosition())
        quat = Quat(self.phys_body.getQuaternion())

        self.m3d.setPosQuat(render, pos, quat)

Where entity.Entity is for now only a subclass of direct.DirectObject.

This is the generic wheeled vehicle code:

class WheeledVehicle(entity.Entity):
    def __init__(self, *args, **kwargs):
        entity.Entity.__init__(self, *args, **kwargs)
        self.__phys_world = kwargs['phys_world']
        
        self.__wheels = []
        self.__joints = []

        self.__throttle = 0.0
        self.__brake = 0.0
        self.__steer = 0.0

        self.__engine_fmax = 475
        self.__brake_fmax = 1000
        self.__steer_fmax = 100
        
        self.__air_friction_coefficient = 0.3
        self.__gear_box_efficiency = 0.7
        self.__diff_ratio = 3.42
        self.__gear_ratios = [2.66, -1.78, 1.30]

        self.__gear = 0

        # Car body AABB size.
        self.__size = kwargs['vehicle_size']
        
        # Physics.
        self.phys_body = OdeBody(self.__phys_world)
        
        mass = OdeMass()
        mass.setBox(10, self.__size)
        self.phys_body.setMass(mass)
        
        self.phys_geometry = OdeBoxGeom(kwargs['phys_space'], self.__size)
        self.phys_geometry.setCollideBits(BitMask32(0x00000002))
        self.phys_geometry.setCategoryBits(BitMask32(0x00000001))
        self.phys_geometry.setBody(self.phys_body)

        taskMgr.add(self.__on_update_simulation, "Wheeled Vehicle Physics Simulation")
        self.accept('physics_world.simulation.step', self.__on_simulation_step)
        
    def add_wheel(self, wheel, traction=False, steer_limits=Vec2.zero(), susp_softness=0.001, susp_damping=0.1):
        if wheel in self.__wheels:
            id = self.__wheels.index_of(wheel)
            self.__wheels.remove(id)
            del self.__joints[id]

        final_pos = self.phys_body.getPosition() + wheel.phys_body.getPosition()
        wheel.phys_body.setPosition(final_pos)

        # Suspension joint.
        susp = OdeHinge2Joint(self.__phys_world)
        susp.attach(self.phys_body, wheel.phys_body)
        susp.setAnchor(final_pos)
        susp.setAxis1(Vec3(0, 0, 1))
        susp.setAxis2(Vec3(1, 0, 0))
        
        susp.setParamFMax(0, self.__steer_fmax)
        susp.setParamLoStop(0, steer_limits.getY())
        susp.setParamHiStop(0, steer_limits.getX())

        susp.setParamSuspensionCFM(0, susp_softness)
        susp.setParamSuspensionERP(0, susp_damping)
        
        if traction:
            susp.setParamFMax(1, self.__engine_fmax)
            susp.setParamFudgeFactor(1, 0.1)
            
        self.__joints.append((susp, traction))
        self.__wheels.append(wheel)
        
    def __on_update_simulation(self, task):
        engine_torque = self.__throttle * self.__engine_fmax
        
        area = self.__size.getX() * self.__size.getY()
        speed = self.phys_body.getLinearVel().length()
        air_force = speed * speed * 0.5 * self.__air_friction_coefficient * area * 1.29

        brake_force = self.__brake * self.__brake_fmax
                
        ratio = self.__gear_ratios[self.__gear] * self.__diff_ratio * self.__gear_box_efficiency
        k = engine_torque * ratio
        sign = -1
        if k < 0:
            sign = 1
        
        for n, (j, t) in enumerate(self.__joints):
            
            if t is True:
                wheel = self.__wheels[n]
                drive_torque = k / wheel.radius
                wheel_torque = drive_torque
                if wheel_torque != 0:
                    wheel_torque += sign * (brake_force + air_force)
                j.setParamVel(1, wheel_torque)

            wheel_angle = j.getAngle1()
            wheel_steer = self.__steer - wheel_angle
            j.setParamVel(0, wheel_steer)
        
        return task.cont
        
    def accelerate(self, delta):
        self.__throttle = delta
        
    def brake(self, delta):
        self.__brake = delta
 
    def steer(self, delta):
        self.__steer = delta
        
    def prev_gear(self):
        if self.__gear > 0:
            self.__gear -= 1
            
    def next_gear(self):
        if self.__gear < (len(self.__gear_ratios)-1):
            self.__gear += 1

    def __on_simulation_step(self):
        pos = Point3(self.phys_body.getPosition())
        quat = Quat(self.phys_body.getQuaternion())

        self.m3d.setPosQuat(render, pos, quat)

The simulation is very unstable and i don’t understand why…Besides i don’t know how i can implement a realistic wheel turn simulation, something more accurate than:

wheel_angle = j.getAngle1()
wheel_steer = self.__steer - wheel_angle
j.setParamVel(0, wheel_steer)

Thanks!

Ok, i’ve found the error in your code (in the Wheel class constructor):

Original:

self.joint.attach(self.body, carBody)

Correct:

self.joint.attach(carBody, self.body)

Now steering works! :wink:

Nicely caught! I think I came across that eccentricity of ODE before but never thought that was the cause of my steering problem.

Ok, one more simple question about wheel torque.

For each motion wheel i apply this formula:

wheel_torque = sign * (drive_torque - resistance_force)

where:

wheel_torque = final torque applied to each motion wheel.
drive_torque = (throttle * engine_maxforce * gear_ratio * diff_ratio) / wheel_radius
resistance_force = brake_force + air_force (without friction one because I think it’s simulated by ODE).

In the code i’ve wrote this:

engine_torque = self.__throttle * self.__engine_fmax

brake_force = self.__brake * self.__brake_fmax

air_force = 0.5 * velocity.lengthSquared() * 1.29 * self.__front_area * self.__drag_coefficient

for id, wheel in enumerate(self.__wheels):
    susp, steering, traction = self.__joints[id]

    if traction:
        k = engine_torque * ratio
        drive_torque = k / wheel.get_radius()
        resistance_force = brake_force + air_force
        final_torque = drive_torque - resistance_force
        wheel_torque = sign * final_torque
        susp.addTorques(0, wheel_torque)

    if steering:
        susp.setParamVel(0, self.__steering)

It seems to work quite well but I don’t understand if I must add friction force in the formula or not (because I think ODE calculates friction into “addTorques” method).

Another issue is that with real values the simulation is very unstable. For example:

engine_fmax = 472 (N * m)
ratio = 2.77 (gear_ratio) * 3.4 (diff_ratio)
wheel_mass = 12 (Kg)
car_body_mass = 2000 (Kg)
drag_coefficient = 0.5

I think ODE doesn’t calculate total mass of “car system” (car_body_mass + 4 * wheel_body_mass) for me…