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. I marked where I thought the issue is with /// ISSUE IS HERE!!!!
.
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);
/// ISSUE IS HERE!!!!
// 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
/// ISSUE IS HERE!!!!
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();