I am moving a rigidbody (the character) on the surface of a sphere, like what is described here.
I am facing the following issue that I am having hard time to solve: After moving the character, it starts spinning really slightly around it's Y axis.
As a side note, I am using Ammo.js which is a javascript/webassembly port of Bullet 2.82, but this has no impact on the problem.
To "stick" the player (a dynamic rigidbody with a cylinder shape) to the sphere (a static rigid body with a sphere shape), here is the code, called on each game loop iteration:
function applyAttraction() {
let sphereOrigin = this.body.getWorldTransform().getOrigin();
let characterOrigin = this._characterEntity.body.getWorldTransform().getOrigin();
// compute and apply sphere gravity to character body
let sphereAttractionForce = new ammo.btVector3(
characterOrigin.x() - sphereOrigin.x(),
characterOrigin.y() - sphereOrigin.y(),
characterOrigin.z() - sphereOrigin.z()
);
sphereAttractionForce.normalize();
sphereAttractionForce.op_mul(gravity);
this._characterEntity.body.applyForce(sphereAttractionForce, ammo.btVector3(0, 0, 0));
// align character up with sphere origin
const gravityDirection = bt2ThreeVec3(sphereAttractionForce).normalize().multiplyScalar(-1);
// extract up axis of character transform basis
let characterUp = new THREE.Vector3();
this._characterEntity.graphic.matrixWorld.extractBasis(
new THREE.Vector3(),
characterUp,
new THREE.Vector3()
);
// apply rotation to align up with gravity vector
let verticalAlignmentRotation = new Quaternion()
.setFromUnitVectors(characterUp, gravityDirection)
.multiply(bt2ThreeQuat(this._characterEntity.body.getWorldTransform().getRotation()));
this._characterEntity.body.getWorldTransform().setRotation(three2BtQuat(verticalAlignmentRotation));
}
And to move the character on the sphere (I removed the code dealing with the mouse controlled orientation since it is irrelevant to track down the problem). This code is also called on every game loop iteration:
let scalingFactor = 0.02;
let moveX = inputController.getMoveX();
let moveZ = inputController.getMoveZ();
if (moveX === 0 && moveZ === 0) return;
let movement = new THREE.Vector3(moveX, 0, moveZ).normalize();
movement.multiplyScalar(scalingFactor);
let wantedWorldPos = this.graphic.localToWorld(movement);
this.body
.getWorldTransform()
.setOrigin(new ammo.btVector3(wantedWorldPos.x, wantedWorldPos.y, wantedWorldPos.z));
In plain (approximative) English, what I am doing:
- Get the direction from the player position to the center of the sphere
- Apply a force using that vector:
this._characterEntity.body.applyForce(sphereAttractionForce, ammo.btVector3(0, 0, 0));
- Align the player Up with the gravity direction, to do so I:
- normalize and "flip" gravity vector direction
- extract character up vector and compute the quaternion which give the rotation to align this up vector with the gravity vector
- compute the new player orientation by applying quaternion from 3.2 to it's current orientation
- Apply movement (from keyboard inputs), in details:
- get a vector resulting from the requested movement alongside x and z axis
- convert this vector from player local basis to world basis:
this.graphic.localToWorld(movement);
- set the new transform origin with this new vector
The project may be seen here (with the spinning issue I am describing). You may need to move a bit on the sphere (with WASD keys).