I am encountering rotation issues in THREEJS where my object (From blender, gltf.scene) totations are extremely unpredictable and inconsistent. I want my object to spin like a ballerina when the mouse moves (depending on the x coordinates of the mouse, the object will spin: anticlockwise when the mouse moves to the right and clockwise when the mouse moves to the left).
There are two points in my code where I rotated my object 1) initially when I imported my model and 2) in the tick function (so it moves like a ballerina accordingly to the position of the mouse).
This is the main part of my issue: Sometimes when I load my page it does exactly what I want. However, sometimes when I reload the page the object might not even rotate, and I can reload the page and my object will be fine again. I searched online for similar issues already and I believe it is due to Gimbal Lock. I am unsure how to fix my code so my rotations are not inconsistent. I also have a imageMesh object created in THREEJS that will spin in the same manner as my object imported from blender and this object never has the rotation issue present in gltf.scene object.
Code From Initially Loading The Object
// Load GLTF Model
gltfLoader.load(
'/Trial8.glb',
(gltf) => {
modelRef.current = gltf.scene;
gltf.scene.traverse((child) => {
if (child.isMesh) {
child.geometry.center(); // Centers the geometry relative to its local origin
child.material = bakedMaterial;
child.castShadow = true;
console.log('Model material:', child.material.constructor.name);
}
});
gltf.scene.position.set(1.5, -7.54, 3);
// Set initial rotation using Quaternion to avoid Euler issues
const initialQuaternion = new THREE.Quaternion();
initialQuaternion.setFromEuler(new THREE.Euler(0, -Math.PI / 2, Math.PI / 2, 'XYZ'));
gltf.scene.quaternion.copy(initialQuaternion);
gltf.scene.scale.set(0.5, 0.5, 0.5);
scene.add(gltf.scene);
// Add AxesHelper to visualize origin
const axesHelper = new THREE.AxesHelper(1); // 1 unit long, scaled with model (0.5)
gltf.scene.add(axesHelper);
const originMarker = new THREE.Mesh(
new THREE.SphereGeometry(0.1),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
originMarker.position.set(0, 0, 0);
modelRef.current.add(originMarker);
},
(progress) => {
console.log(`Loading: ${(progress.loaded / progress.total * 100).toFixed(2)}%`);
},
(error) => {
console.error('Error loading GLTF:', error);
}
);
Code in Tick Function
// Animation loop
const clock = new THREE.Clock();
const tick = () => {
const elapsedTime = clock.getElapsedTime();
if (cameraRef.current) {
const targetX = mouse.x * moveRange;
const targetZ = -mouse.y;
cameraRef.current.position.x = THREE.MathUtils.lerp(
cameraRef.current.position.x,
targetX,
dampingFactor
);
cameraRef.current.position.z = THREE.MathUtils.lerp(
cameraRef.current.position.z,
targetZ,
dampingFactor
);
cameraRef.current.lookAt(
cameraRef.current.position.x,
0,
cameraRef.current.position.z
);
}
// Add rotation for models based on mouse X
const targetRotationZ = mouse.x * Math.PI; // Adjust rotation range as needed
if (modelRef.current) {
// Define target rotation based on mouse.x
const targetRotationZ = mouse.x * Math.PI; // Same rotation range as before
const targetQuaternion = new THREE.Quaternion();
// Combine initial rotation with mouse-driven Y-axis rotation
targetQuaternion.setFromEuler(
new THREE.Euler(0, -Math.PI / 2 + targetRotationZ, Math.PI / 2, 'XYZ')
);
// Interpolate current quaternion to target quaternion
modelRef.current.quaternion.slerp(targetQuaternion, dampingFactor);
}
imageMesh.rotation.z = THREE.MathUtils.lerp(
imageMesh.rotation.z,
targetRotationZ,
dampingFactor
);
// Update directional light's x-position based on mouse.x
const lightTargetX = initialLightX + mouse.x * lightMoveRange;
directionalLight.position.x = THREE.MathUtils.lerp(
directionalLight.position.x,
lightTargetX,
dampingFactor
);
renderer.render(scene, camera);
requestAnimationFrame(tick);
};
tick();