Cannon RigidVehicle and wheel rotations

I have a RigidVehicle setup (based off cannon’s own example, except I define the car as facing positive z) and it does work as expected. But occasionally, the back wheels will be incorrectly rotated along the z axis, this matches what the underlying sphere body is doing too, so it doesn’t appear to be related to mesh issues.

Occasionally the wheel will correct itself, or slide back into an awkwardly slightly tilted rotation.

What could be the issue? Is this something that just comes with the territory?

let rotation = new Quaternion().setFromEuler(...incomingRotation)
let material = new Material("wheelMaterial")
let contactMaterial = new ContactMaterial(material, world.defaultMaterial, {
    friction: 100,
    restitution: .1,
    contactEquationStiffness: 1000
})
let chassisBody = new Body({
    mass,
    position: position ? new Vec3(...position) : undefined,
    quaternion: rotation
})
let emptyOffset = new Vec3()
let emptyQuaternion = new Quaternion()

for (let [shape, offset = emptyOffset, quaternion = emptyQuaternion] of chassis) {
    chassisBody.addShape(
        shape,
        offset.vadd(centerOfMassAdjust),
        quaternion
    )
}

let vehicle = new RigidVehicle({ chassisBody })
let direction = new Vec3(0, -1, 0) // down
let axis = [-1, -1, 1, 1]

for (let [index, { position, radius }] of wheels.entries()) {
    let shape = new Sphere(radius)
    let body = new Body({
        shape,
        mass,
        material,
        angularDamping: .99,
        quaternion: rotation,
    })

    vehicle.addWheel({
        body,
        position: new Vec3(...position).vadd(centerOfMassAdjust),
        axis: new Vec3(axis[index], 0, 0),
        direction
    })
}


// wheels like this: 
const wheels: Wheel[] = [
    {
        position: [wheelX, wheelY, .7],
        radius
    },
    {
        position: [-wheelX, wheelY, .7],
        radius
    },
    {
        position: [wheelX, wheelY, -.7],
        radius
    },
    {
        position: [-wheelX, wheelY, -.7],
        radius
    }
]

<group ref={wheelsRef}>
    <mesh
        castShadow
        receiveShadow
        geometry={nodes["wheel-front-left"].geometry}
    >
        <primitive
            attach="material"
            object={materials.colormap}
            wireframe={Config.DEBUG}
        />
    </mesh>
    <mesh
        castShadow
        receiveShadow
        geometry={nodes["wheel-front-right"].geometry}
    >
        <primitive
            attach="material"
            object={materials.colormap}
            wireframe={Config.DEBUG}
        />
    </mesh>
    <mesh
        castShadow
        receiveShadow
        geometry={nodes["wheel-back-left"].geometry}
    >
        <primitive
            attach="material"
            object={materials.colormap}
            wireframe={Config.DEBUG}
        />
    </mesh>
    <mesh
        castShadow
        receiveShadow
        geometry={nodes["wheel-back-right"].geometry}
    >
        <primitive
            attach="material"
            object={materials.colormap}
            wireframe={Config.DEBUG}
        />
    </mesh>
</group>
1 Like

Eulers have a semi hidden 4th parameter called “order” what can be “XYZ” ZXY YZX and controls how the rotations are concatenated. Often in situations like this you have to specify some non default order to match how the physics engine is creating them.

1 Like

Thanks for the feedback man :slight_smile:
How would i do that with a quaternion, don’t they have order implied? I have not had issues with that before when going from Cannon to Three, but is this because it’s a joint? I also tried going from quaternion => euler with various different orders but they don’t seem to make any difference

The strange thing is that the rotation appears correct most of the time (and always for the front wheels), but the back wheels will sometimes/often be angled at a very specific -/+ z rotation (and always the same amount, but negated). occasionally this disappears and the wheels snaps back into its proper rotation, often if the wheel/vehicle is nudged by something else

(the origin of the wheel is a little off center along the x axis)

1 Like

I was also thinking that if i parent the wheels to the chassis visually, then i could just apply local x and y rotation, chatgpt suggested this:

for (let [index, wheel] of vehicle.wheelBodies.entries()) {
    let wheelMesh = wheelsRef.current?.children[index] as Mesh

    // get wheel position relative to chassis
    wheel.position.vsub(vehicle.chassisBody.position, rel)
    vehicle.chassisBody.quaternion.conjugate().vmult(rel, local)
    wheelMesh.position.copy(local)

    // get wheel rotation relative to chassis
    vehicle.chassisBody.quaternion.conjugate().mult(wheel.quaternion, qRel)

    const e = new Euler().setFromQuaternion(new TQuaternion(...qRel.toArray()))

    e.z = 0

    wheelMesh.rotation.set(e.x, e.y, 0) // keep spin and steer only
    /*
    let quat = new TQuaternion(...wheel.quaternion.toArray())
    let eul = new Euler().setFromQuaternion(quat, "YZX")

    wheelMesh?.position.copy(wheel.position)
    wheelMesh?.quaternion.setFromEuler(eul)
    */
} 

i’m not on top of the math here, but this does work, except now the wheels with the tilt exhibit a wobble

When you do quaternion.fromEuler , that’s where the order of the quaternion comes into play. You pass x,y,z into the Euler.. you may need to pass “ZXY” as the 4th parameter to the constructor. Or YZX or some other variant… Basically wherever the euler is created.. or right after, it’s .order may need to be changed. I know this sounds vague but it’s just a fact sometimes. Usually I avoid doing the euler->quaternion dance and just use quaternions.. which are unambiguous. I don’t recall if cannon lets u just do that or they have to be tweaked in translation…

1 Like

so i tried both: copying the quaternion directly and converting it to an euler first. the direct quaternion had the original problem first described, and going by euler with different orders had no obvious difference but all had the same issue.

i did find a (stupid) workaround though: parenting the back wheels to the chassis, and converting to euler and only applying the x rotation. that seems to work! but annoying that it doesn’t fix the underlying issue. would be very interesting to find out why

I would suggest not using sphere instead use cylinder and reduce the side to minimum like three sides only, the sphere has too many geometry faces for cannon to calculate, this will be heavy on when you have numbers of vehicles.

Checkout Cannon.js overdrive implementation, cannon.js handling massive terrain bodies and vehicles.