Dragging along plane using raycaster


I have some code that renders an array of meshes inside a group using stored matrices. All the meshes share the same rotation and only their position changes. I would like to be able to drag the groups of meshes around by altering the x and y transform of their matrices.

My current approach is to add a large plane which shares the same matrix as the first item in the array of meshes and raycast against it to calculate how far I’ve dragged.

This is what that looks like when the plane is made visible (and slightly smaller so it fits in a screenshot - in reality the plane is much larger):

The code below stores the initial point the raycast occurred on drag start and then on drag, performs another raycast, and subtracts that point from the initial point to calculate the delta. I then apply the delta x and y to the individual matrices of the items.

This works well when the plane is facing forward in my scene but when the plane is in any other position the dragging behaviour is incorrect. For example, if the plane faces backwards then the dragging is back to front.

I feel like my approach is largely correct, I’m just missing a step to generate proper values. Any help would be appreciated!

function MyComponent(props) {

    const { items } = props;
	const groupRef = useRef();
	const dragPlane = useRef();

	const { raycaster, camera } = useThree();

	const {
	} = useMemo(() => {
		return {
			currentDragPosition: new Vector3(),
			movementMatrix: new Matrix4(),
	}, []);

	const bind = useGesture({
		onDrag: () => {
			if (!dragPlane.current || !groupRef.current) return;

			const intersection = raycaster.intersectObject(dragPlane.current);
			if (!intersection.length) return;

			const delta = currentDragPosition.sub(intersection[0].point);

			movementMatrix.makeTranslation(-delta.x, -delta.y, 0);

			groupRef.current.children.forEach((child) => {

		onDragStart: () => {
			if (dragPlane.current) {
				const intersection = raycaster.intersectObject(dragPlane.current);
				if (!intersection.length) return;


	return (
				{items.map((item) => (
				<planeGeometry args={[100, 100, 1, 1]} />
				<meshBasicMaterial visible={false} />

Not sure exactly what you want to achieve in the end, but if TextureActive can help, just though I would mention it.

There is the Scrambler too, which allows you to drag tiles for a puzzle - this can be in different arrangements like all in a single row or column too. We do a lot of word scramble type puzzles this way too.


You can use

panelBox.worldToLocal( the hit point ) to get the point in the “space” of the panelBox, so you compute your delta from That value instead and it should work regardless of the hierarchy/orientation.

1 Like

Amazing, that did it. Thanks!

1 Like