Rotatable object with right click drag

Hi all,

I’m trying to rotate an object when right clicked and dragged.
My logic is applying the deltaX and deltaY of mouse movement per tick to the objects rotation.

const dx = self.clientX - self.lastMousePosition.x;
const dy = self.clientY - self.lastMousePosition.y;

self.dummyobject.rotation.z += -dy * 0.01;
self.dummyobject.rotation.y += dx * 0.01;

However when I rotate it more than 180 degrees horizontally, the mouse movements I apply get rotated unintuitively in inverse, because it’s applying a direction to y in its new flipped orientation.

https://paulhax.github.io/spin-controls/
The large ball example above is exactly the functionality I’m trying to replicate, but have struggled implementing myself. I tried checking if 180 degrees had been crossed with radToDeg then inverse the dx/dy value but also led to some issues. Many of the offered solutions online suggest rotating the camera instead of the object, which isn’t suitable for my case.

Any insights or solutions would be greatly appreciated!

Here is my minimal working demo (preview in the bottom right):

I am not an expert on rotations but I would avoid rotations using local axis. I would use world axis but rotated according to the camera.

const xAxis = new Vector3(1, 0, 0);
const yAxis = new Vector3(0, 1, 0);

camera.on('rotationchange', () => {
  xAxis.set(1, 0, 0).applyQuaternion(camera.quaternion);
  yAxis.set(0, 1, 0).applyQuaternion(camera.quaternion);
});

function onDrag(e) {
  e.preventDefault(); // not move obj
  e.target.rotateOnWorldAxis(xAxis, e.movementY * 0.01);
  e.target.rotateOnWorldAxis(yAxis, e.movementX * 0.01);
}

mesh.on('drag', onDrag);

Is this example good?

1 Like

Hi Agargaro,

Although rotating the camera isn’t suitable for my case, your comment about rotatingOnWorldAxis solved the issue for me.

Here is the working solution for any others out there looking to achieve the same functionality incase the glitch link expires.

<!DOCTYPE html>
<html>
  <head>
    <script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-box
        position="0 1 -3"
        scale="2 1 1"
        grabbing
        rotation="0 90 90"
        color="#F79256"
      ></a-box>
    </a-scene>
    <script>
      AFRAME.registerComponent("grabbing", {
        init: function () {
          let self = this;
          this.rightClickHeld = false;

          self.quaternion = new THREE.Quaternion();
          self.dummyobject = self.el.object3D.clone();
          self.xAxis = new THREE.Vector3(1, 0, 0);
          self.yAxis = new THREE.Vector3(0, 1, 0);

          this.mouseDown = (e) => {
            const { clientX, clientY } = e;
            if (e.button === 2) {
              self.dummyobject.rotation.setFromQuaternion(
                self.el.object3D.quaternion
              );

              self.rightClickHeld = true;
              self.lastMousePosition = { x: clientX, y: clientY };
              console.log("Right-click held down");
            }
          };

          this.mouseUp = (e) => {
            if (e.button === 2) {
              self.rightClickHeld = false;
            }
          };

          this.handleMouseMove = (event) => {
            const { clientX, clientY } = event;
            self.clientX = clientX;
            self.clientY = clientY;
          };

          // config setup
          this.disableContext = (e) => e.preventDefault();

          window.addEventListener("contextmenu", this.disableContext);
          window.addEventListener("mousedown", this.mouseDown);
          window.addEventListener("mouseup", this.mouseUp);
          document.addEventListener("mousemove", this.handleMouseMove);
        },

        tick: function () {
          let self = this;
          if (
            self.rightClickHeld &&
            self.lastMousePosition != { x: self.clientX, y: self.clientY }
          ) {
            // Calculate the change in mouse position
            const dx = self.clientX - self.lastMousePosition.x;
            const dy = self.clientY - self.lastMousePosition.y;

            self.dummyobject.rotateOnWorldAxis(self.xAxis, dy * 0.01);
	        self.dummyobject.rotateOnWorldAxis(self.yAxis, dx * 0.01);

            self.quaternion.setFromEuler(self.dummyobject.rotation);

            self.el.object3D.quaternion.copy(self.quaternion);

            // Update the last mouse position
            self.lastMousePosition.x = self.clientX;
            self.lastMousePosition.y = self.clientY;
          }
        },
      });
    </script>
  </body>
</html>

Thank you tremendously for your help :slight_smile:

1 Like

Although rotating the camera isn’t suitable for my case, your comment about rotatingOnWorldAxis solved the issue for me.

It turns out I was wrong for dismissing the camera and Agargaro was actually further ahead in my problem than I gave them credit for, applyingQuaternion to the camera is relevant as I wanted the rotation to be applied depending on the camera position relative to the target object.

In my previous reply shown in the below video the object gets rotated based on world rotation, meaning it will always go that way if it is updated. Causing it to be flipped if you rotate the camera.

and finally the working solution:

you should consider yourself an expert on rotations Agargaro :smiley:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-box
        position="-1 0.5 -3"
        scale="2 1 1"
        grabbing
        rotation="0 45 0"
        color="#F79256"
      ></a-box>
    </a-scene>
    <script>
      AFRAME.registerComponent("grabbing", {
        init: function () {
          let self = this;
          this.rightClickHeld = false;

          self.quaternion = new THREE.Quaternion();
          self.dummyobject = self.el.object3D.clone();
          self.zAxis = new THREE.Vector3(0, 0, 1);
	      self.yAxis = new THREE.Vector3(0, 1, 0);
          self.cameraEl = this.el.sceneEl.camera.el;


          this.mouseDown = (e) => {
            const { clientX, clientY } = e;
            if (e.button === 2) {
              self.dummyobject.rotation.setFromQuaternion(
                self.el.object3D.quaternion
              );
              
              self.zAxis
              .set(1, 0, 0)
              .applyQuaternion(self.cameraEl.object3D.quaternion);
            self.yAxis
              .set(0, 1, 0)
              .applyQuaternion(self.cameraEl.object3D.quaternion);

              self.rightClickHeld = true;
              self.lastMousePosition = { x: clientX, y: clientY };
              console.log("Right-click held down");
            }
          };

          this.mouseUp = (e) => {
            if (e.button === 2) {
              self.rightClickHeld = false;
            }
          };

          this.handleMouseMove = (event) => {
            const { clientX, clientY } = event;
            self.clientX = clientX;
            self.clientY = clientY;
          };

          // config setup
          this.disableContext = (e) => e.preventDefault();

          window.addEventListener("contextmenu", this.disableContext);
          window.addEventListener("mousedown", this.mouseDown);
          window.addEventListener("mouseup", this.mouseUp);
          document.addEventListener("mousemove", this.handleMouseMove);
        },

        tick: function () {
          let self = this;
          if (
            self.rightClickHeld &&
            self.lastMousePosition != { x: self.clientX, y: self.clientY }
          ) {
            // Calculate the change in mouse position
            const dx = self.clientX - self.lastMousePosition.x;
            const dy = self.clientY - self.lastMousePosition.y;

            self.dummyobject.rotation.z += -dy * 0.01;
            self.dummyobject.rotation.y += dx * 0.01;

            self.quaternion.setFromEuler(self.dummyobject.rotation);

            self.el.object3D.quaternion.copy(self.quaternion);

            // Update the last mouse position
            self.lastMousePosition.x = self.clientX;
            self.lastMousePosition.y = self.clientY;
          }
        },
      });
    </script>
  </body>
</html>

1 Like