2

I"m not exactly sure how I can do this. I read a lot about raycasting and that seems to be good for finding points that intersect with something, but in this case I just want it to interpolate the 2d mouse coordinates to the 3d point exactly where the mouse clicks, regardless of scale, rotation, whether or not there's an object there, etc.

One method I've thought of but not approached would be making an invisible plane that is parallel to the camera, always oriented upright and always intersecting the y axis. Then use a raycaster to hit the plane, draw as needed, then delete the plane. Seems like a silly way to do this though.

At the moment I have a method that works pretty well but it has some issues when the line gets further away from the origin, or the camera gets zoomed

In this photo I drew two lines from two different perspectives. The vertical line what it looks like when the camera is level with the x and z axis, and I draw a straight line down the y axis, while the horizontal line is what happens when i scribble with the camera facing down. https://i.stack.imgur.com/MBuhf.png

As you can see, it seems to use the distance to the camera to make this calculation, so the further the distance from the camera, the more distortion is in the calculation. How can get rid of this distortion?

source: https://github.com/AskAlice/mandala-3d-threejs live demo: https://askalice.me/mandala/

Here is the relevant code:

js/content.js@112

function get3dPointZAxis(event)
{
    camPos = camera.position;
    var mv = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY/window.innerHeight) * 2 + 1, 1).unproject(camera);
    var m2 = new THREE.Vector3(0,0,0);
    var pos = camPos.clone(); 
    pos.add(mv.sub(camPos).normalize().multiplyScalar(m2.distanceTo(camPos)));
    return pos;
}

I used information from two stackoverflow posts to come up with this, and it has the issues I've described.

firstly, this post shows how to draw and convert it to the z axis. It is flat. But I had a lot of trouble trying to make that work in three dimensions.

How to draw a line segment at run time using three.js

and then i used information in the below post to at least get it parallel to the camera on the x-z axis like such: https://i.stack.imgur.com/5mSmb.png

Moving objects parallel to projection plane in three.js

Alice
  • 422
  • 1
  • 9
  • 28
  • Instead of invisible plane mesh, you can use [`THREE.Plane()`](https://threejs.org/docs/index.html#api/math/Plane) and `.intersectPlane()` method of your [`THREE.Raycaster()`](https://threejs.org/docs/index.html#api/core/Raycaster)'s [`.ray`](https://threejs.org/docs/index.html#api/math/Ray) object. – prisoner849 Dec 06 '17 at 20:49
  • Can you try to clarify this a bit more, i'm having a hard time understanding what you need. – pailhead Dec 06 '17 at 22:27
  • Go into the demo. Hold SHIFT and drag until the camera is facing down. scribble a lot of stuff, face the camera upright. The drawing was not parallel to the orientation of the camera, rather the further away the point is from the camera, the more distorted it is, making a semisphere. Ideally it would make flat lines that do not go up in the y direction, because that is just distortion. I'll try messing with the raycaster and planes more when I'm off work – Alice Dec 06 '17 at 22:49
  • So you don't want to draw inside a sphere (radius === distanceToCamera) but rather a plane? You can also dot the vector you use for distance with the camera direction. Divide the desired distance with the dot product, and `multiplyScalar` the original vector. – pailhead Dec 06 '17 at 23:46
  • It seems I really ought to learn more college math before I try anything crazy. This didn't seem crazy. It's my first three.js project. I figured a library would help me rid of all the ridiculous math. This seems like a pretty simple concept, having the mouse click where you point, but there's a lot that goes into it, and a lot of possibilities! I've come a long way in the past 8 years of my programming experience. I now understand now why calculus is required for my CS major. I know that's a lot to say. Is there any chance you could elaborate on this math? The first time i read it was like 零零 – Alice Dec 08 '17 at 04:40
  • I'd like to try to re-word your question to see if I understand it. Please correct me if I missed the point... You're rotating the camera to an arbitrary angle and drawing a vertical line. Are you expecting that line (and its mirror lines) to all draw vertically on the screen? What is the expected behavior? (_Neat demo, btw._) – TheJim01 Dec 08 '17 at 17:03
  • Yeah, i think maybe a sketch would be in order here, I have one understanding of what you want to do but it may be waay off. There is no calculus needed here, it's all linear algebra. Try to draw some diagrams, where is your camera, where do you want to draw, what is the distance, what if the camera looks up or down, measure with a ruler etc. – pailhead Dec 09 '17 at 00:32
  • What i imagine from this description is what i'd get if i cut a vase with a plane. I'd see a line describing the contour of the vase, but it would be a 2d line, not 3d. Because you're drawing on a sphere essentially and not a plane, you get a 3d line. – pailhead Dec 09 '17 at 00:34
  • well there are two ways that I see it can be done. One is the one that is marked as a solution. it makes a plane with the same orientation as the camera and raycasts it. This is nice. But there's also another option. The plane could always be vertical, but always faces the camera, and crosses through the y axis. This would be nice, but if you were to do this and face the camera down, it would never raycast. So there are some issues with that concept, which is why I like the solution given and have applied it to the live example. (in dat.gui you can toggle it on and off with MousePointMode) – Alice Dec 09 '17 at 04:08

1 Answers1

7

That option with THREE.Plane() and THREE.Raycaster().ray.intersectPlane():

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var point = new THREE.Vector3();

function getPoint(event){
  mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
  mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
  planeNormal.copy(camera.position).normalize();
  plane.setFromNormalAndCoplanarPoint(planeNormal, scene.position);
  raycaster.setFromCamera(mouse, camera);
  raycaster.ray.intersectPlane(plane, point);
}

Run the code snippet, click the "draw" checkbox to set it as checked, move your mouse randomly (without mouse down), click the checkbox again, rotate the scene with mousedown. All points are on the same plane.

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

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

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var point = new THREE.Vector3();

document.addEventListener("mousedown", onMouseDown, false);
document.addEventListener("mousemove", onMouseMove, false);

function getPoint(event) {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  planeNormal.copy(camera.position).normalize();
  plane.setFromNormalAndCoplanarPoint(planeNormal, scene.position);
  raycaster.setFromCamera(mouse, camera);
  raycaster.ray.intersectPlane(plane, point);
}

function setPoint() {
  var sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(.125, 4, 2), new THREE.MeshBasicMaterial({
    color: "yellow",
    wireframe: true
  }));
  sphere.position.copy(point);
  scene.add(sphere);
}

function onMouseDown(event) {
  getPoint(event);
  if (draw.checked) setPoint();
}

function onMouseMove(event) {
  getPoint(event);
  if (draw.checked) setPoint();
}

render();

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
}
body {
  overflow: hidden;
  margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div style="position:absolute;">
  <input id="draw" type="checkbox">
  <label for="draw" style="color: white;">draw</label>
</div>
prisoner849
  • 16,894
  • 4
  • 34
  • 68