Three.js Trying to rotate object around it's center

I am trying to rotate a propeller in an drone model in three.js.

The propellers are loaded from GLTF models, in a pivot, in a group. The total loading code is here:

const loader = new THREE.GLTFLoader();


const p1Meshes = [];
const p2Meshes = [];
const mMesh = [];

const models = [


    {
        path: 'zep-main-1.gltf',
        center: new THREE.Vector3(40, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x999966,
            transparent: true,
            opacity: 0.96,
            roughness: 0.8,
            metalness: 0.4,
            side: THREE.DoubleSide,
        },
        reflection: false,
        reflectionX: 0,
        pivot: false,
    },
    {
        path: 'zep-main-2.gltf',
        center: new THREE.Vector3(40-17.5, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x99DDBB,
            transparent: true,
            opacity: 1,
            roughness: 0.6,
            metalness: 0.4,
            side: THREE.DoubleSide,
        },
        reflection: false,
        reflectionX: 0,
        pivot: false,
    },
    {
        path: 'zep-tailwing.gltf',
        center: new THREE.Vector3(40, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x22DDBB,
            transparent: true,
            opacity: 1,
            roughness: 0.6,
            metalness: 0.4,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 32,
        pivot: false,
    },
    {
        path: 'zep-mw.gltf',
        center: new THREE.Vector3(40, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x999966,
            transparent: true,
            opacity: 0.7,
            roughness: 0.1,
            metalness: 0.4,
            side: THREE.DoubleSide,
        },
        reflection: false,
        reflectionX: 0,
        pivot: false,
    },
    {
        path: 'zep-mw-wb.gltf',
        center: new THREE.Vector3(40, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x444444,
            transparent: true,
            opacity: 0.9,
            roughness: 0.7,
            metalness: 0.1,
            side: THREE.DoubleSide,
        },
        reflection: false,
        reflectionX: 0,
        pivot: false,
    },
    {
        path: 'zep-w1.gltf',
        center: new THREE.Vector3(40, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x55DD77,
            transparent: true,
            opacity: 1,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 32,
        pivot: false,
    },
    {
        path: 'zep-w2.gltf',
        center: new THREE.Vector3(40, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x55DD77,
            transparent: true,
            opacity: 1,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 32,
        pivot: false,
    },
    {
        path: 'zep-w3.gltf',
        center: new THREE.Vector3(40, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x55DD77,
            transparent: true,
            opacity: 1,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 32,
        pivot: false,
    },
    {
        path: 'zep-w4.gltf',
        center: new THREE.Vector3(40, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x55DD77,
            transparent: true,
            opacity: 1,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 32,
        pivot: false,
    },
    {
        path: 'zep-fw.gltf',
        center: new THREE.Vector3(40, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x888844,
            transparent: true,
            opacity: 0.98,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 32,
        pivot: false,
    },
    {
        path: 'zep-fw-wb.gltf',
        center: new THREE.Vector3(40-93.7, -80-2.5, 250+80),
        scale: 100,
        materialProps: {
            color: 0x343434,
            transparent: true,
            opacity: 1,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 187.4 + 32,
        pivot: false,
    },
    {
        path: 'zep-mw-ns.gltf',
        center: new THREE.Vector3(40, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x668844,
            transparent: true,
            opacity: 0.96,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 32,
        pivot: false,
    },
    {
        path: 'zep-en-mt1.gltf',
        center: new THREE.Vector3(40-1, -80+2, 250),
        scale: 100,
        materialProps: {
            color: 0xDBDBDB,
            transparent: true,
            opacity: 0.96,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 33.75,
        pivot: false,
    },
    {
        path: 'zep-en-mt3.gltf',
        center: new THREE.Vector3(40-5, -80, 250),
        scale: 100,
        materialProps: {
            color: 0xDBDBDB,
            transparent: true,
            opacity: 0.96,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 41,
        pivot: false,
    },
    {
        path: 'zep-en.gltf',
        center: new THREE.Vector3(40-5, -80, 250),
        scale: 100,
        materialProps: {
            color: 0xDBDBDB,
            transparent: true,
            opacity: 0.96,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 41,
        pivot: false,
    },
    {
        path: 'zep-en-sp.gltf',
        center: new THREE.Vector3(40-4.5, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x888844,
            transparent: true,
            opacity: 0.98,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 41,
        pivot: false,
    },
    {
        path: 'zep-p1.gltf',
        center: new THREE.Vector3(40-4.5, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x448888,
            transparent: true,
            opacity: 0.98,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 41,
        pivot: true,
    },
    /* {
        path: 'zep-p2.gltf',
        center: new THREE.Vector3(40-4.5, -80, 250),
        scale: 100,
        materialProps: {
            color: 0x448888,
            transparent: true,
            opacity: 0.98,
            roughness: 0.2,
            metalness: 0.2,
            side: THREE.DoubleSide,
        },
        reflection: true,
        reflectionX: 41,
    } */


];



const compositeModel = new THREE.Group();


function loadModel(loader, path, center, scale, materialProps, reflection = false, reflectionX = 0, pivot=false) {
    return new Promise((resolve, reject) => {
        loader.load(
            path,
            function (gltf) {
                const model = gltf.scene;

                // Optionally, center the model
                camera.lookAt(center);
                if(pivot){


                    var box = new THREE.Box3().setFromObject( model );
                    box.getCenter( model.position ); // this re-sets the mesh position
                    model.position.multiplyScalar( - 1 );
                    const pivot = new THREE.Group();
                    pivot.position.sub(center);
                    compositeModel.add(pivot);
                    pivot.add(model)
                } else {
                    model.position.sub(center);
                    compositeModel.add(model);
                }

                // Set scale
                model.scale.set(scale, scale, scale);

                // Set material properties
                model.traverse(function (child) {
                    if (child.isMesh) {
                        child.material = new THREE.MeshStandardMaterial(materialProps);

                        
                    }
                });

                if (path.includes('zep-p1')) {
                    p1Meshes.push(model);
                } else if (path.includes('zep-p2')) {
                    p2Meshes.push(model);
                }
                

                if(path.includes('zep-main-1')|| path.includes('zep-mw')) mMesh.push(model);

                                                                                // If reflection is needed, create and add the reflected model
                if (reflection) {
                    
                    if(pivot){

                        const reflectedModel = model.clone();
                        reflectedModel.scale.x = -scale;                            // Reflect along the X-axis

                        var box = new THREE.Box3().setFromObject( reflectedModel );
                        box.getCenter( reflectedModel.position ); // this re-sets the mesh position
                        reflectedModel.position.multiplyScalar( - 1 );
                        const rpivot = pivot.clone();
                        rpivot.scale.x = -scale;                            // Reflect along the X-axis
                        rpivot.position.x -= reflectionX;                   // Adjust the position if neede
                        compositeModel.add(rpivot);
                        rpivot.add(reflectedModel)
                    } else {
                        const reflectedModel = model.clone();
                        reflectedModel.scale.x = -scale;                            // Reflect along the X-axis
                        reflectedModel.position.x -= reflectionX;                   // Adjust the position if needed
                        compositeModel.add(reflectedModel);
                    }


                    
                    if (path.includes('zep-p1') ) {
                        p1Meshes.push(reflectedModel);
                    } else if (path.includes('zep-p2')) {
                        p2Meshes.push(reflectedModel);
                    }
                }

                
                resolve();
            },
            function (xhr) {
                console.log((xhr.loaded / xhr.total * 100) + '% loaded');
            },
            function (error) {
                console.error('An error happened', error);
                reject(error);
            }
        );
    });
}

I apologize for the long code, but i am confused as to where the error might be.

Then the rotation code is:

function startAirship() {
    var rotationSpeed = 0.1; // Adjust this value to change rotation speed

    

    function animateAirshipProps() {
      requestAnimationFrame(animateAirshipProps);

      mMesh.forEach( m => {
        console.log(m.position);
      })
      // Rotate p1 meshes clockwise

      const originalPositions = new Map();

      p1Meshes.forEach(mesh => {
        

        console.log("mesh position before: ", mesh.position, mesh.parent.position, mesh.parent.parent, mesh);
        
        mesh.rotation.z=rotationSpeed; 
        rotationSpeed = rotationSpeed + 0.01;

      });

      // Rotate p2 meshes counter-clockwise
      p2Meshes.forEach(mesh => {
        //mesh.position.sub(point);
        //mesh.rotation.z -= rotationSpeed;
        //mesh.position.add(point);
      });

      // Render the scene
      renderer.render(scene, camera);
    }

    animateAirshipProps();
  }

The output of this code shows me that the mesh (the prop model) is at position 0 0 0 in the pivot. But it keeps on rotating along an axis outside the object.

Attempt to solve

I followed the instructions here and here. But know change. A visual demonstration of the problem may be found in here.

You will see that the blue/gun metal props are going around an axis that goes through the parent group’s (that is parent of pivot) origin (almost). That is also the origin of the cad model that i made the GLTF from. So I also tried to manually shift the pivot, as well as the mesh (one at a time) to that origin , rotate, and shift back. This also does not work.

Help please.

Thank you

Make the propeller a separate CAD drawing, centered about its axis of rotation. Import propeller as a separate GLTF, rotate in Three.js, then move to its desired location.

OK, hence there is no way of doing this on the group itself. right?

Not that I’m aware of. I’ve found this method to work, and didn’t bother to look for other ways that might also be possible.