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.
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;
});