Issues with RaycastVehicle in Cannon-es Physics Engine

Hi engineers,

Currently, I am using cannon-es as my physics engine. However, I am encountering issues when adding the RaycastVehicle car physics model.

Firstly, the angles of my car’s body and wheels are incorrect. They are in the correct positions but appear to be rotated 180 degrees.

Secondly, there is no movement when I press forward or backward, and no error messages are displayed.
I have noticed that the car body and wheels are included in a box in the cannon-es official example, but I am unsure if my realization is incorrect.


cannon-es official example:
https://schteppe.github.io/cannon.js/demos/raycastVehicle.html

my:

Looking forward to your advice and suggestions!
Thank you in advance.

1 Like

this is my code

import * as THREE from '../js/three/build/three.module.js';
import * as CANNON from '../js/cannon_es/dist/cannon-es.js';
import { OBJLoader } from "../js/three/examples/jsm/loaders/OBJLoaderLocal.js";
import { Joystick } from './joystick.js';


const fiveTone = new THREE.TextureLoader().load('../assest/gradientMaps/fiveTone.jpg')
fiveTone.minFilter = THREE.NearestFilter;
fiveTone.magFilter = THREE.NearestFilter;

export class Player {
    constructor(main) {
        this.main = main;
        // Player and chassis
        this.vehicle;
        this.playerMesh;
        // Wheels
        this.playerWheelMash;
        this.wheelMeshes = [ ];
        this.wheelMaterial = new THREE.MeshToonMaterial();
        this.wheelRadius = 0.6;
        this.wheelBodies = [];
        this.chassisSize = new CANNON.Vec3(8, 4, 4);

        this.maxSteerVal = 0.5; // Maximum steering force
        this.maxForce = 1000; // Maximum engine force
        this.brakeForce = 1000000; // Magnitude of the brake force

        this.loader = new OBJLoader();
        this.textureLoader = new THREE.TextureLoader();
        this.Texturenum = 0;

        this.Usejoystick = new Joystick(this);

        this.localVelocity = new CANNON.Vec3();
        this.moveDistance = 60;

        this.canJump = false;
        this.jumpVelocity = 15;

        this.rotationQuaternion = new CANNON.Quaternion();
    }

    init() {
        /* Physics */
        this.set_VehicleBody();
        // 01. Set up the physics for the vehicle and create a RaycastVehicle object.
        // 02. Add the wheel configuration to the RaycastVehicle and attach the wheels to the chassis. Then add the chassis to the physics world.
        // 03. Add cylindrical physics for the wheels to the world.

        /* Entities */
        this.loadPlayerTexture();
        // 04. The addmesh() function will add the player mesh and wheel meshes.
        // 05. Once all entities are added, update_WheelBodyEvListener() will be called to listen for position changes and update the meshes.

        /* Others */
        this.jump(this);
    }

    /* Physics */
    set_VehicleBody() {
        const chassisBody = new CANNON.Body({
            mass: 150,
            shape: new CANNON.Box(new CANNON.Vec3(this.chassisSize.x / 2, this.chassisSize.y / 2, this.chassisSize.z / 2)),
            material: new CANNON.Material({
                friction: 0,
                restitution: 0
            })
        })
        chassisBody.position.set(0, 10, 0);
        // Set an initial angular velocity
        chassisBody.angularVelocity.set(0, 0.5, 0);

        // Set up the vehicle using the RaycastVehicle from the CANNON library
        this.vehicle = new CANNON.RaycastVehicle({
            chassisBody,
            /* xx */
            indexForwardAxis: 0,
            indexRightAxis: 1,
            indexUpAxis: 2
        });

        this.set_WheelBody();
    }

    set_WheelBody() {
        // Set up wheel options
        const wheelOptions = {
            radius: 0.5, // Wheel radius
            directionLocal: new CANNON.Vec3(0, -1, 0), // Wheel's local direction vector
            suspensionStiffness: 30, // Affects the elasticity response of the wheel to the ground.
            suspensionRestLength: 0.3, // Initial length of the wheel suspension
            frictionSlip: 1.4, // Friction force of the wheel
            dampingRelaxation: 2.3, // Wheel's damping settings
            dampingCompression: 4.4, // Also affects elasticity
            maxSuspensionForce: 100000, // Maximum suspension force the wheel can handle
            rollInfluence: 0.01, // Influence of the wheel's rolling during steering
            axleLocal: new CANNON.Vec3(0, 0, 1), // Set the axis of rotation for the wheel
            chassisConnectionPointLocal: new CANNON.Vec3(1, 0, -1),
            maxSuspensionTravel: 0.3,
            customSlidingRotationalSpeed: -30, // Speed of wheel sliding
            useCustomSlidingRotationalSpeed: true // Whether to use custom sliding rotational speed
        };

        // Add wheels and set their connection points relative to the chassis
        wheelOptions.chassisConnectionPointLocal.set(-1.5, -2.5, 2.1);
        this.vehicle.addWheel(wheelOptions);

        wheelOptions.chassisConnectionPointLocal.set(-1.5, -2.5, -2.1);
        this.vehicle.addWheel(wheelOptions);

        wheelOptions.chassisConnectionPointLocal.set(1.5, -2.5, 2.1);
        this.vehicle.addWheel(wheelOptions);

        wheelOptions.chassisConnectionPointLocal.set(1.5, -2.5, -2.1);
        this.vehicle.addWheel(wheelOptions);

        // The physics setup is done, the vehicle can now be added to the physics world.
        this.vehicle.addToWorld(this.main.world);
        this.set_WheelGeometry();
    }

    set_WheelGeometry() {
        // Add the cylindrical physics for the wheels
        const that = this;
        const wheelMaterial = new CANNON.Material('wheel');
        this.vehicle.wheelInfos.forEach((wheel) => { // .wheelInfos is a built-in attribute of Cannon.js that holds information about the wheels
            /* Add the cylinder shape and then add the rigid body */
            const cylinderShape = new CANNON.Cylinder(wheel.radius, wheel.radius, wheel.radius, 16);
            const wheelBody = new CANNON.Body({
                mass: 0,
                material: wheelMaterial,
            });
            wheelBody.type = CANNON.Body.KINEMATIC; // KINEMATIC body is not affected by forces from the physics engine
            wheelBody.collisionFilterGroup = 0; // Disable collision

            const quaternion = new CANNON.Quaternion().setFromEuler(-Math.PI / 2, 0, 0); // Set a quaternion to rotate around the X-axis by -90 degrees
            wheelBody.addShape(cylinderShape, new CANNON.Vec3(), quaternion); /* Set the cylinder shape and change its direction */
            /* The above code sets the rotation direction for the wheel physics */

            that.wheelBodies.push(wheelBody); // Finally, add the wheel physics to an array for use in postStep.

            that.main.world.addBody(wheelBody);
        });
    }

    update_WheelBodyEvListener() {
        this.main.world.addEventListener('postStep', () => {
            for (let i = 0; i < this.vehicle.wheelInfos.length; i++) {
                this.vehicle.updateWheelTransform(i); // Update the wheel transformation information
                const transform = this.vehicle.wheelInfos[i].worldTransform; // This includes the position and rotation information of the wheel

                /* Sync the player mesh to the physical body */
                this.playerMesh.position.copy(this.vehicle.chassisBody.position);
                this.playerMesh.quaternion.copy(this.vehicle.chassisBody.quaternion);

                /* Set the position and rotation of the wheel body to match the world transformation information of the wheel */
                this.wheelBodies[i].position.copy(transform.position);
                this.wheelBodies[i].quaternion.copy(transform.quaternion);
                /* Synchronize to the mesh */
                this.wheelMeshes[i].position.copy(this.wheelBodies[i].position);
                this.wheelMeshes[i].quaternion.copy(this.wheelBodies[i].quaternion);
            }
        })
    }

    /* Entities */
    loadPlayerTexture() {
        let that = this;
        // Material and texture for the wheels
        this.mats = [
            new THREE.MeshToonMaterial({
                gradientMap: fiveTone,
                bumpMap: that.textureLoader.load('../assest/textures/mazu/mazuHead_Height.png', TextureOnLoad),
                map: that.textureLoader.load('../assest/textures/mazu/mazuHead_BaseColor.png', TextureOnLoad)
            }),
            new THREE.MeshToonMaterial({
                gradientMap: fiveTone,
                // side: THREE.DoubleSide,
                bumpMap: that.textureLoader.load('../assest/textures/mazu/mazuCar_Height.png', TextureOnLoad),
                map: that.textureLoader.load('../assest/textures/mazu/mazuCar_BaseColor.png', TextureOnLoad)
            }),
            new THREE.MeshToonMaterial({
                gradientMap: fiveTone,
                bumpMap: that.textureLoader.load('../assest/textures/mazu/mazuBody_Height.png', TextureOnLoad),
                map: that.textureLoader.load('../assest/textures/mazu/mazuBody_BaseColor.png', TextureOnLoad)
            }),
        ];
        // Material and texture for the wheels
        this.wheelMaterial = new THREE.MeshToonMaterial({
            gradientMap: fiveTone,
            bumpMap: that.textureLoader.load('../assest/textures/wheel/wheel_Normal.png', TextureOnLoad),
            map: that.textureLoader.load('../assest/textures/wheel/wheel_BaseColor.png', TextureOnLoad)
        })
        function TextureOnLoad() {
            that.main.Ui.loading();
            that.Texturenum += 1;
            if (that.Texturenum == that.mats.length * 2) {
                that.addmesh();
            }
        }
    }

    addmesh() { // Executed in loadPlayerTexture
        let that = this; // Change the context of 'this' first
        this.add_VehicleMesh(this);
        this.add_WheeleMesh(this);
    }

    add_VehicleMesh(that) {
        let materialIndex = 0; // Index variable to track the current material to use
        this.loader.load('../assest/models/mazu/mazuAll_L.obj', (object) => {
            that.main.Ui.loading();
            that.playerMesh = object;
            that.playerMesh.scale.set(0.3, 0.3, 0.3);
            that.playerMesh.traverse(function (child) {
                if (child instanceof THREE.Mesh) {
                    child.material = that.mats[materialIndex];
                    child.castShadow = true;
                    child.receiveShadow = true;
                    materialIndex = (materialIndex + 1) % that.mats.length; // Increment the index to loop through the materials within the range
                }
            });
            that.main.scene.add(that.playerMesh);
            that.main.Camera.CameraSwitch("init"); // Create the camera
        });
    }

    add_WheeleMesh(that) {
        this.loader.load('../assest/models/mazu/wheel.obj', (object) => {
            that.main.Ui.loading();
            that.playerWheelMash = object;
            that.playerWheelMash.scale.set(0.3, 0.3, 0.3);
            that.playerWheelMash.traverse(function (child) {
                if (child instanceof THREE.Mesh) {
                    child.material = that.wheelMaterial;
                    child.castShadow = true;
                    child.receiveShadow = true;
                }
            });

            this.wheelBodies.forEach((wheelBody, index) => {
                const wheelMesh = that.playerWheelMash.clone();
                that.main.scene.add(wheelMesh);
                that.wheelMeshes.push(wheelMesh); // Add the wheel meshes to the array
            });

            // Call update_WheelBodyEvListener() here as well
            this.update_WheelBodyEvListener();
        });
    }

    /* Other methods */

    jump(that) {
        /* This segment is used to detect whether it encounters a surface from which it can jump */
        // let contactNormal = new CANNON.Vec3();
        // let upAxis = new CANNON.Vec3(0, 1, 0);
        // this.playerBody.addEventListener("collide", function (e) {
        //     let contact = e.contact;
        //     if (contact.bi.id == that.playerBody.id) {
        //         contact.ni.negate(contactNormal);
        //     }
        //     else {
        //         contactNormal.copy(contact.ni);
        //     }
        //     if (contactNormal.dot(upAxis) > 0) {
        //         that.canJump = true;
        //     }
        // });
    }

    movement() {
        // Handle movement based on input keys or joystick direction
        if (
            this.main.keys.includes("w") ||
            this.main.keys.includes('arrowup') ||
            this.main.direction.includes('top')
        ) {
            this.vehicle.applyEngineForce(-this.maxForce, 2);
            this.vehicle.applyEngineForce(-this.maxForce, 3);
        }

        if (
            this.main.keys.includes("s") ||
            this.main.keys.includes('arrowdown') ||
            this.main.direction.includes('bottom')
        ) {
            this.vehicle.applyEngineForce(this.maxForce, 2);
            this.vehicle.applyEngineForce(this.maxForce, 3);
        }

        if (
            this.main.keys.includes("a") ||
            this.main.keys.includes('arrowleft') ||
            this.main.direction.includes('left')
        ) {
            this.vehicle.setSteeringValue(this.maxSteerVal, 0);
            this.vehicle.setSteeringValue(this.maxSteerVal, 1);
        }
        if (
            this.main.keys.includes("d") ||
            this.main.keys.includes('arrowright') ||
            this.main.direction.includes('right')
        ) {
            this.vehicle.setSteeringValue(-this.maxSteerVal, 0);
            this.vehicle.setSteeringValue(-this.maxSteerVal, 1);
        }
    }

    setState() {
        // Your code for setting state goes here
    }
}

Oh! I found that when I removed this section, there was no problem with the movement.

 // Set up the vehicle using the RaycastVehicle from the CANNON library
        this.vehicle = new CANNON.RaycastVehicle({
            chassisBody,
            /* xx */
            // indexForwardAxis: 0,
            // indexRightAxis: 1,
            // indexUpAxis: 2
        });

1 Like

the coding is ok, u just need to rotate the models in ur 3d design platform 90 degrees and then load again, if its backward then rotate the opposite -90 degrees.

2 Likes

Oh my god, yeah! I did it! :flushed:
Thank you so much!!

I was so determined to modify the code that I forgot I could directly change the model​:rofl::rofl:

1 Like

excellent, i did the same before, i did everything on the code but then when i also ask here, they told me to just rotate the model in 3ds max, :rofl:

2 Likes