Issue with Character Movement Near South Pole in Three.js

Hello Three.js Community,

I’m encountering a peculiar issue in my 3D game environment where my character behaves unexpectedly when crossing over the South Pole of a spherical world. The game works perfectly at the North Pole and elsewhere, but near the South Pole, the character’s movement and gravity calculations seem to fail, and the character is locked in a sort of orbital movement around the south pole, and while I can widen or shrink the orbit, I cannot get out of it. My goal is to get character movement as close to how we move on earth as possible.

Additional libraries used: Ammo.js (not relevant to issue, when I removed it same things occurred.)
Problem Description:

The issue occurs specifically when the character approaches the South Pole.
The character’s gravity and velocity directions become locked in orbit around the South Pole, despite the up vector and right vector remaining as they should(see code).
This does not occur at the North Pole or other parts of the sphere.
Code Snippet:

javascript(no ammo.js, only 3js):
const sphereRadius = 50; // Half of the diameter
const characterSpeed = .1;

    if (character) {
        // Earth-like gravity settings
        const sphereCenter = new THREE.Vector3(0, sphereCenterY, 0);
        
        // Apply the rotation from mouse input (only if there's movement)
        if (mouse.movementX !== 0) {
            const mouseDeltaX = mouse.movementX;
            const mouseSensitivity = 0.002;
            const mouseRotationAngle = -mouseDeltaX * mouseSensitivity;
            
            // Rotate around the global Y-axis
            const yAxis = new THREE.Vector3(0, 1, 0);
            const mouseRotation = new THREE.Quaternion().setFromAxisAngle(yAxis, mouseRotationAngle);
            
            // Accumulate mouse rotation
            accumulatedMouseRotation.multiply(mouseRotation);
        }

        // Reset mouse movement
        mouse.movementX = 0;

        // Calculate the up direction based on the sphere's surface
        const characterToCenter = character.position.clone().sub(sphereCenter);
        const distanceFromCenter = characterToCenter.length();
        const upDirection = characterToCenter.normalize();

        // Adjust character position to be on the sphere's surface
        if (distanceFromCenter !== sphereRadius) {
            const correctionFactor = sphereRadius / distanceFromCenter;
            character.position.multiplyScalar(correctionFactor);
        }
    
        // Creating a quaternion to align the character's 'up' direction with upDirection
        const alignWithUpDirection = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), upDirection);

        // Combine alignment with 'up' direction with accumulated mouse rotation (if any)
        const finalOrientation = alignWithUpDirection.clone().multiply(accumulatedMouseRotation);
        character.quaternion.copy(finalOrientation);

        // Forward direction - perpendicular to upDirection and character's quaternion
        const forwardVector = new THREE.Vector3(0, 0, 1).applyQuaternion(character.quaternion).projectOnPlane(upDirection).normalize();

        // Right direction - perpendicular to both upDirection and forwardVector
        const rightVector = new THREE.Vector3(1, 0, 0).applyQuaternion(character.quaternion).projectOnPlane(upDirection).normalize();

        character.forwardVector = forwardVector;

        character.rightVector = rightVector;

        character.upVector = upDirection;
        
        // Calculate movement velocity based on character's orientation
        let velocityDirection = new THREE.Vector3(0, 0, 0);
        
        if (keys.w) {
            velocityDirection.add(forwardVector);
        }
        if (keys.s) {
            velocityDirection.sub(forwardVector);
        }
        if (keys.a) {
            velocityDirection.add(rightVector);
        }
        if (keys.d) {
            velocityDirection.sub(rightVector);
        }

        velocityDirection.projectOnPlane(upDirection);
        
        // Normalize to get direction and multiply by speed to get velocity
        velocityDirection.normalize().multiplyScalar(characterSpeed);
        
        // Update the character's position
        character.position.add(velocityDirection);

        // Ensure the character remains on the sphere's surface after movement
        const newCharacterToCenter = character.position.clone().sub(sphereCenter);
        const newDistanceFromCenter = newCharacterToCenter.length();
        if (newDistanceFromCenter !== sphereRadius) {
            const newCorrectionFactor = sphereRadius / newDistanceFromCenter;
            character.position.multiplyScalar(newCorrectionFactor);
        }

Attempts to Resolve:

I’ve tried adjusting the sphere’s position to avoid alignment issues with the Y-axis.
I’ve double-checked quaternion operations and coordinate transformations.
I’ve Tried many different movement methods, but they all see to fail and get into a lock at the bottom of the sphere.
Questions:

Has anyone experienced similar issues with character movement near the poles in a spherical environment?
Could this be related to the right-handed coordinate system of Three.js, and if so, how can I adjust my calculations to accommodate this?
Any suggestions on how to debug or resolve this issue would be greatly appreciated, as I’ve been trying to figure this out for a week now and have seen no solution online for 3js.

sounds like gimbal lock
you could forgo having poles and fake the concept visually. Poles would only be magnetic fields anyway. Visually like the other sphere walking games I assume you want free walk

yes exactly! I want free walk. There is no real sphere or collisions happening currently and it only happens at the south pole.

Well one question on walking. Why not use attractors math physics instead of plot walking?
You have a lot of rules in that bit of code instead of orbit logic. But thats me just looking at it without an example to go on

Like artificial force, and gravity vectors, I tried this as well and the same result occurred.

Yeah, that way you have a more fluid experience. I would have to see a basic example of the second one to guide you as that would be my reco.
As for the axis, you can multiply your axis to change its pole and then try walking the different pole to prove its the axis as the issue

Do you want me to send the code with ammo.js used for movement? Attached is a video of the error happening in real time: https://drive.google.com/file/d/1eDfy3Ju2gTJUhLprOTq6yJfs-gLk8xhf/view?usp=sharing

The demo you would build is a sphere and 100 test robots that spawn randomly and have random y directions to start on the sphere and then move and see which ones get pole stuck. From there read some of their values
From there someone can help

Sounds good! I’ll try to have the test up here by tonight

No I dont want a full app. You need to make a simple demo app to work out the solution you can share in glitch or jsfiddle or whatever everyone uses. I like glitch.com just cause. But its not geared for threejs

2 Likes

sounds good!

Maybe you are overcomplicating the motion.

If the pivot point is at the center of the earth, calculations are much simpler. Going forward is a rotation around the thick blue axis, turning left/right is rotation around the red axis. Both axes are actually the local axes of the moving model.

When I try it with code, there are no issues with any of the poles (and the equator too). Here is a short video:

2 Likes

This is amazing!

Do you think you could give me an example snippet of this logic implemented in code?

Here it is:

  • move the mouse (horizontally) to turn left/right
  • going forward is in lines 103-109
  • turning left/right is in lines 112-118

https://codepen.io/boytchev/pen/oNmEQjO

4 Likes

Thats pretty neat!

Im trying to round up the various ways you can do this type of movement. There are quite a few techniques used in game engines like Unity. Im prepping a math question for some of this. But for now I would say that solves christians question

2 Likes

thank you so much!

1 Like