Rotation ring raycaster not picking up on intersection

I am trying to make a ring that, when clicked and dragged, will rotate an object that encompasses it.

I have a working demo here: Raycaster Ring Test - CodeSandbox

If you click on the yellow ring and, while holding the click, move the mouse around the green plane, it will cause the white object to rotate.

However, if you click on the yellow ring and then move the mouse without moving off of the yellow ring, it will not cause the rotation.

2023-08-04 20-37-01

Why is this occurring?

Code used in demo:

import "./styles.css";

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { AmbientLight, BoxGeometry, Mesh, MeshPhongMaterial } from "three";
import Stats from "stats.js";

const stats = new Stats();
stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
document.body.appendChild(stats.dom);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);

let sceneMeshes = [];

const geometry = new THREE.RingGeometry(4, 3.5, 30);
const material = new THREE.MeshNormalMaterial({ side: THREE.DoubleSide });
const rotationRing = new THREE.Mesh(geometry, material);
scene.add(rotationRing);

sceneMeshes.push(rotationRing);

let targetMesh = new Mesh(new BoxGeometry(1, 1, 1));

scene.add(targetMesh);

camera.position.set(1, 1, 1);
controls.update();

const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();

// These two vectors keep track of the mouse's position from one frame to another

let previousPosition = new THREE.Vector2();
let currentPosition = new THREE.Vector2();

let mouseMoveRaycast = false;
let mouseClickRaycast = false;

/**
 *
 * When the pointer is clicked, set up the pointer vector for the raycast
 * Enable the mouse move raycaster
 *
 * @param {*} event
 */
function onPointerClick(event) {
  // calculate pointer position in normalized device coordinates
  // (-1 to +1) for both components
  pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
  mouseClickRaycast = true;
  console.log("mouseClickRaycast");
  window.requestAnimationFrame(render);
}

function onPointerMove(event) {
  if (mouseMoveRaycast) {
    // calculate pointer position in normalized device coordinates
    // (-1 to +1) for both components
    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
  }
  window.requestAnimationFrame(render);
}

let rotationPlane = new THREE.Mesh(
  new THREE.PlaneGeometry(20, 20),
  new MeshPhongMaterial({ transparent: true, opacity: 0.5, color: 0x0fff0f })
);

rotationPlane.position.z -= 0.01;

scene.add(rotationPlane);

scene.add(new AmbientLight());

window.addEventListener("pointermove", onPointerMove);

function render() {
  controls.update();
  /**
   * This raycaster is used to find the initial reference point for the rotation
   */
  if (mouseClickRaycast) {
    raycaster.setFromCamera(pointer, camera);
    // Only intersect with the ring.
    const intersects = raycaster.intersectObjects([rotationRing]);
    if (intersects.length > 0) {
      // Set the previous and current reference point as the same thing.
      previousPosition = new THREE.Vector2(
        intersects[0].point.x,
        intersects[0].point.y
      );
      currentPosition = new THREE.Vector2(
        intersects[0].point.x,
        intersects[0].point.y
      );
      // Enable the mouse move raycaster
      mouseMoveRaycast = true;
      // Disable rotation of camera for click and drag
      controls.enableRotate = false;
    } else {
      // If the ring is not intersected, disable this raycaster until the next mouse click
      mouseClickRaycast = false;
    }
  }
  // If the mouse raycaster is enabled, we can rotate the target mesh
  if (mouseMoveRaycast) {
    raycaster.setFromCamera(pointer, camera);
    // User can rotate on mouse move with either the rotation ring or the green plane
    const intersects = raycaster.intersectObjects([
      rotationRing,
      rotationPlane
    ]);
    if (intersects.length > 0) {
      // Grab where the users mouse is currently
      currentPosition = new THREE.Vector2(
        intersects[0].point.x,
        intersects[0].point.y
      );
      // Calculate the change in angle between the previous intersection from mouse (or click) and this
      let angle =
        Math.atan2(currentPosition.y, currentPosition.x) -
        Math.atan2(previousPosition.y, previousPosition.x);
      if (angle) {
        // Rotate the mesh by this amount
        // console.log(angle);
        targetMesh.rotateZ(angle);
      }
      previousPosition = currentPosition.clone();
    }
  }
  renderer.render(scene, camera);
  window.requestAnimationFrame(render);
}

render();

window.addEventListener("pointermove", onPointerMove);
window.addEventListener("mousedown", onPointerClick);
window.addEventListener("mouseup", () => {
  mouseClickRaycast = false;
  mouseMoveRaycast = false;
  controls.enableRotate = true;
});

Remove line 121:

    } else { // <---remove this line
      // If the ring is not intersected, disable this raycaster until the next mouse click
      mouseClickRaycast = false;
    }

so the code:

    if (intersects.length > 0) {
      ...
    } else {
      mouseClickRaycast = false;
    }

becomes like this:

    if (intersects.length > 0) {
      ...
      mouseClickRaycast = false;
    }
1 Like