How to drag a point across a plane?

I am trying to let the user drag a point across a plane.

For that I project the point onto the plane every time the point is moved:

const unprojected = sphereMesh.position.clone();
const direction = unprojected.sub(camera.position).normalize();
const ray = new THREE.Ray(sphereMesh.position, direction);
ray.intersectPlane(plane, sphereMesh.position);

But when the plane is too far away, intersectPlane doesn’t hit any more.

Try it out yourself: projection test
When you drag the point to the top right corner (of the screen), the point becomes big again.

takle the math, the declarative stuff wouldn’t matter for you:

without physics https://codesandbox.io/p/sandbox/ecstatic-resonance-r36rg4?file=%2Fsrc%2FApp.js

without physics https://codesandbox.io/p/sandbox/plane-project-uexjm

with physics https://codesandbox.io/p/sandbox/drag-n-drop-with-physics-and-collisions-9pjzm6?file=%2Fsrc%2FApp.js

I don’t really understand your shared CodeSandbox.

Where can I find (in your code or somewhere else) how to project a point onto a plane along a vector?

in the source code i posted, all three examples are similar

const v = new Vector3()
const p = new Plane(new Vector3(0, 1, 0), 0)

const move = (e) => {
      e.stopPropagation()
      if (active && e.ray.intersectPlane(p, v)) onDrag(v)

I don’t really understand what you are suggesting:
You say I should use e.ray.intersectPlane instead of ray.intersectPlane?
What would I need to change in my code-example?

raycaster has a near and far plane.

https://threejs.org/docs/#api/en/core/Raycaster.far

I thinks that is the problem you see in your demo. You could make the far plane a larger number.

I am using Ray instead of Raycaster.

1 Like

A good way to visualise the problem is to create a planeGeometry as large as your camera far plane.

I don’t have any answer to the problem yet.

Any chance you can provide the relative unit you’re using to define “too far”? Why does the plane need to be that far? Also if you see the distance from camera to sphere and from sphere to plane as a ratio in the context you’re trying to find the direction of the ray, I could imagine problems being introduced where the plane is far away and the sphere too close to the camera, at some stage you’re going to start firing ambiguous rays depending on the unit sizes you’re using…

The camera position is somewhat irrelevant in the equation, it’s the position of the sphere relative to the cameras frustum eg… the projected screen space mouse position while hovering the sphere that would give the correct projection in this case maybe :thinking::wink:

I just made this toy, dragging point(s) across a plane for painting:

1 Like

That was a very good idea.

It seems like ray.intersectPlane doesn’t hit from the line where the planeMesh fades out.
(The planeMesh isn’t used for the intersection calculation, but they have the same limit)

  1. The plane itself doesn’t need to be far away for that to happen. Since the camera (rotation) is controllable, the plane can be angled such that there a points on the plane that are very far away.
  2. How is the camera position irrelevant? When projecting the point onto the plane, the point should stay under the mouse cursor. (the projection should go from the camera through the unprojected point onto the plane)

Yes this sounds like you’re on the right track of thinking, however, the code you provided previous does not mention any unprotected mouse position, it’s currently deriving the direction from the camera position to the sphere…

const cameraPosition = camera.position;
const direction = sphereMesh.position.clone().sub(cameraPosition).normalize();
const ray = new THREE.Ray(cameraPosition, direction);
ray.intersectPlane(plane, sphereMesh.position)

I think here instead of camera position you’d rather use the mouse position projected from the camera…

Since DragControls sets the sphere position to the mouse position before the drag event is called, sphereMesh.position is the unprojected mouse position before projecting.

I changed the code a bit to make it a bit more clear (should be exact same logic-wise):

const unprojected = sphereMesh.position.clone();
const direction = unprojected.sub(camera.position).normalize();
const ray = new THREE.Ray(sphereMesh.position, direction);
ray.intersectPlane(plane, sphereMesh.position);
function projectPointOntoTriangle(lineOrigin: THREE.Vector3, lineDirection: THREE.Vector3, plane_origin: THREE.Vector3, plane_normal: THREE.Vector3, epsilon: number = 1e-6): THREE.Vector3 | undefined {
    const dot = plane_normal.dot(lineDirection);
    if (Math.abs(dot) > epsilon) {
        return lineOrigin.clone().add(lineDirection.multiplyScalar(-plane_normal.dot(lineOrigin.clone().sub(plane_origin)) / dot));
    }
    return undefined;
}

Got the solution from this post.