Cannon.js: Creating Gravity for a Spherical World in a Three.js Project

Hi all! I’m working on a 3D project using Three.js along with Cannon.js for physics. I’m trying to make a spherical world (imagine a mini-planet) where a car can be driven around the sphere.

I want the car to be able to drive anywhere on this sphere with the gravity keeping it attached to the sphere’s surface, regardless of where it is. In physics terms I want gravity to be spherical.

Initially, I tried this for gravity:

this.physicsworld.gravity.set(0, -10, 0)

This helps the car stay on the North Pole, but as soon as the car starts moving, it falls off.

I then tried this within my update method:

this.physicsworld.bodies.forEach(b => {
    if (b.mass !== 0) {
        const v = new CANNON.Vec3()
        v.set(-b.position.x, -b.position.y, -b.position.z).normalize()
        v.scale(98, b.force)
        console.log(v)
        b.applyLocalForce(v)  
    }
})

This makes the car stick to the sphere, but moving it around is really challenging.

Any ideas on how to make the car move smoothly?

Here is the MRE:

import * as CANNON from 'cannon-es'
import * as THREE from 'three'

export default class Physics {
    constructor() {
        // Setup
        this.setPhysicsWorld();
        this.setPhysicsFloor();

        // Time tick event
        this.time.on('tick', () => {
            this.update();
        });
    }

    setPhysicsWorld() {
        this.physicsworld = new CANNON.World();
        this.physicsworld.gravity.set(0, -10, 0); // Initially set to 0 since we'll be computing gravity dynamically
        this.physicsworld.broadphase = new CANNON.SAPBroadphase(this.physicsworld);
        this.physicsworld.solver.iterations = 10;

        // Materials
        this.defaultMaterial = new CANNON.Material('default');
        this.defaultContactMaterial = new CANNON.ContactMaterial(
            this.defaultMaterial,
            this.defaultMaterial,
            {
                friction: 0.4,
                restitution: 0.1
            }
        );
        this.physicsworld.addContactMaterial(this.defaultContactMaterial);
    }

    setPhysicsFloor() {
        const radius = this.physicsfloor.geometry.parameters.radius;

        const groundShape = new CANNON.Sphere(radius);
        this.groundBody = new CANNON.Body({
            mass: 0,
            position: new CANNON.Vec3(0, 0, 0),  // Position at the center.
            shape: groundShape
        });
        this.physicsworld.addBody(this.groundBody);
    }

    update() {
        // Updating gravity dynamically (code commented out)
        // this.physicsworld.bodies.forEach(b => {
        //     if (b.mass !== 0) {
        //         const v = new CANNON.Vec3();
        //         v.set(-b.position.x, -b.position.y, -b.position.z).normalize();
        //         v.scale(98, b.force);
        //         b.applyLocalForce(v);
        //     }
        // });

        this.physicsworld.step(1 / 60, this.time.delta, 3);

        // Code for updating positions and orientations of the dummy vehicle (simplified)
        this.bodyMesh.position.copy(this.chassisBody.position);
        this.bodyMesh.quaternion.copy(this.chassisBody.quaternion);

        // ... further code for updating vehicle's mesh ...
    }
}

This is Threejs, Cannonjs, spherical, multiplayer and you drive a car.
Use it for reference
First Car Shooter : https://fcs.sbcode.net
Demo

Source Code : GitHub - Sean-Bradley/First-Car-Shooter: Multiplayer First Car Shooter (FCS) written in Threejs with server side CannonJS physics and SocketIO

3 Likes

Hi, thanks for the response. This is exactly what I tried to use but failed. I guess need to tweak the parameters a little bit. The native gravity seems to be working smoother than the proposed solution, but I’ll keep tweaking it, thanks!