Inconsistent Object/Mesh Rotations

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();

Hard to say what’s causing your issue without seeing it running, but.. you can change the “order” to (“YZX” or “ZYX” etc.. ) of Eulers to apply the rotations in different orders, and that can sometimes solve these gimbal lock problems.
Another way to solve them is to just parent them to a node that doesn’t have any rotations on it, then rotate that node instead.

1 Like

Thank you for your response. Here is a video of my issue: I uploaded it onto youtube via this link (I am sorry I don’t know how else to better illustrate my problem). You can see initially when I move my mouse, the rotation is responding, but then later on when I refresh the page and then move my mouse, the object is not rotating (not responding to my mouse movement).

I’d advise you make yourself familiar with live code publishing platforms like codepen.io or similar.

Example

As the Guys above suggested, you need to learn how to post your problems…

Go to CodePen
into the JS section paste the code below

import * as THREE from "https://esm.sh/three";
import {OrbitControls} from "https://esm.sh/three/addons/controls/OrbitControls.js";
import {GLTFLoader} from "https://esm.sh/three/addons/loaders/GLTFLoader.js";

since you can’t add your models because you don’t have the pro version most likely to the path from the model use this, because the model itself is irrelevant, but only the problem it symbolizes:

https://threejs.org/examples/models/gltf/RobotExpressive/RobotExpressive.glb
1 Like

Thank you so much everyone for the advice, I created a CodePen version of what I want, but for some reason what I have on CodePen is correct and does not exhibit the issue I have for my model…

1 Like

Please excuse me for laughing out loud :blush:

My first advice would be, to copy from your codepen and all problems would be solved. But then, I realize that your codepenn content comes from your local code already.

Which makes me guess(!), that you might have a CORS problem.

Have you checked your Javascript console of the installation which exhibits the problem?

I have never thought about this before! The objects I created within ThreeJS does not encounter this problem and only my blender mesh does. I have this error though
Screenshot 2025-05-26 at 2.00.10 PM
Is this what is causing my issues?

Remove these:

Importing once is more than enough… :slightly_smiling_face:

import * as THREE from "https://esm.sh/three";
import { GLTFLoader } from "https://esm.sh/three/examples/jsm/loaders/GLTFLoader.js";

I made the changes you mentioned. Could this be what is making my model rotating inconsistently?

No.

I still don’t understand what rotation you are talking about… Do you mean that the mouse movement should be reverse: when you move the mouse to the right, the model should rotate to the left, and vice versa?

Sorry for the confusion. The code in my CodeSandbox is what I use for my local code. For some reason what you see in my CodeSandbox is correct (it is exhibiting the behaviour I want) while what I see in my local code is different. The inconsistent part comes from this: Sometimes when I load my page, my object spins when i move the mouse (this is what i want), but when I reload the page and move the mouse, sometimes the object won’t spin at all (this is not what i want). In summary, my code works only sometimes. and the behaviour can be seen here.

1 Like

Try replace your tick() function here, in your local battlefield:

loader.load(    'https://threejs.org/examples/models/gltf/RobotExpressive/RobotExpressive.glb',
    function (gltf) {
        model = gltf.scene;
        scene.add(model);
      
        // ...  
      
        if (gltf.animations.length > 0) {
            mixer = new THREE.AnimationMixer(model);
            const action = mixer.clipAction(gltf.animations[0]);
            action.play();
        }
      tick();   // <------- move here
    },
    undefined,
    function (error) {
        console.error('Error loading GLTF model:', error);
    }
);
1 Like