Orthographic Camera and pickingRay? - three.js

I tried to implement a function to select and move objects in a stage compound of an orthographic camera.
I'd like to get the same example as : http://threejs.org/examples/webgl_interactive_draggablecubes.html
but not with perspective camera.
I already replaced :
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
with :
var camera = new THREE.OrthographicCamera(window.innerWidth / -zoom, window.innerWidth / zoom, window.innerHeight / zoom, window.innerHeight / -zoom, -1000, 1000);
and replaced this :
var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
by this :
var ray = projector.pickingRay( vector, camera );
But, it does not seem to be enough to run.
So, what's it lack ?
Is it possible to use Projector and Ray with OrthographicCamera ??
Thanks.

Please try this code in your sample:
event.preventDefault();
if (event.button === 0) {
var projector = new THREE.Projector();
var vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
// use picking ray since it's an orthographic camera
var raycaster = projector.pickingRay(vector, camera);
//ditermine whether any of the supplied objects are hit by this ray
var intersertsObj = raycaster.intersectObjects(_entities);
// _entities : is array entities you put to scence
// you can store this list before you entity to scence:
// _entities.push(entity);
// scene.add(entity);
if (intersertsObj.length > 0) {
var pickedObject = intersertsObj[0];
}
}
event = null;
it is works fine in my project.

There is a function Raycaster.setFromCamera() that was introduced after this question was originally asked. It handles orthographic cameras specifically, so I think that is now the preferred way to solve this issue.

In April 2022, the following works for me to detect clicks when using an orthographic camera.
Orthographic camera:
const camera = new THREE.OrthographicCamera(
window.innerWidth / -2,
window.innerWidth / 2,
window.innerHeight / 2,
window.innerHeight / -2,
-1000,
1000
);
camera.position.y = 20;
camera.zoom = 50;
camera.updateProjectionMatrix();
Click handler:
renderer.domElement.addEventListener('pointerdown', (event) => {
const mouse = new THREE.Vector2();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
console.log("Intersects:", intersects);
});

Related

ThreeJS raycasting problem with an Orthographic camera in isometric-like view

I'm pulling my hair off this one. I have to work with a specific camera angle for a 3D projet with the constraint of using an Orthographic Camera. I need to be able to precisely click on the floor for gameplay purposes. The ThreeJS Raycast doesn't seem to work properly (or maybe I set something the wrong way?). In a top-down view like angle, it works better.
Here is a fiddle that explains the kind of situation I'm in: https://jsfiddle.net/p6td5oak/42/
const sceneWidth = window.innerWidth;
const sceneHeight = window.innerHeight;
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera( -sceneWidth / 2, sceneWidth / 2, sceneHeight / 2, -sceneHeight / 2, -1000, 1000 );
camera.rotation.set(
-Math.PI / 12,
Math.PI / 12,
Math.PI / 24
);
camera.position.set(0, 1, 0);
camera.zoom = 2;
camera.updateProjectionMatrix();
const renderer = new THREE.WebGLRenderer();
renderer.setSize( sceneWidth, sceneHeight );
document.body.appendChild( renderer.domElement );
const whiteMaterial = new THREE.MeshBasicMaterial({});
const redMaterial = new THREE.MeshBasicMaterial({
color: 0xFF0000
});
const size = 100;
const geometry = new THREE.PlaneGeometry(size, size, 10, 10);
for (var x = 0; x < 2; x++)
{
for (var z = 0; z < 2; z++)
{
let mesh = new THREE.Mesh(geometry, ((x + z) % 2 ? whiteMaterial : redMaterial));
mesh.rotation.set(
-Math.PI / 2,
0,
0
);
mesh.position.set(
x*size,
0,
z*size
)
scene.add(mesh);
}
}
var raycaster = new THREE.Raycaster();
window.addEventListener("pointerup", function(e)
{
var screenPos = new THREE.Vector2();
screenPos.x = (e.clientX / window.innerWidth) * 2 - 1;
screenPos.y = - (e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(screenPos, camera);
var rays = raycaster.intersectObjects(scene.children, true);
for (var i = 0; i < rays.length; i++)
{
scene.remove(rays[i].object);
}
}
.bind(this));
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
animate();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
In the example, I try to remove the planes as soon as they are clicked. As you can see, the top two planes can be removed if you click around their top-left corner. The other twos cannot even be triggered.
If someone have an idea what's going on, you'll be my hero.
Thanks!
PS: I have basic knowledge of ThreeJS but I'm far from being expert
Raycaster only detects objects in front of the camera, and your camera is located near the origin. Move the camera back.
Also, the near value of your orthographic camera is invalid. From the documentation:
The valid range is between 0 and the current value of the far plane.
Negative values are not supported.
const sceneWidth = window.innerWidth;
const sceneHeight = window.innerHeight;
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-sceneWidth / 2, sceneWidth / 2, sceneHeight / 2, -sceneHeight / 2, 0.1, 1000);
camera.rotation.set(
-Math.PI / 12,
Math.PI / 12,
Math.PI / 24
);
camera.position.set(100, 100, 500);
camera.zoom = 2;
camera.updateProjectionMatrix();
const renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(sceneWidth, sceneHeight);
document.body.appendChild(renderer.domElement);
const whiteMaterial = new THREE.MeshBasicMaterial();
const redMaterial = new THREE.MeshBasicMaterial({
color: 0xFF0000
});
const size = 100;
const geometry = new THREE.PlaneGeometry(size, size, 10, 10);
for (let x = 0; x < 2; x++) {
for (let z = 0; z < 2; z++) {
let mesh = new THREE.Mesh(geometry, ((x + z) % 2 ? whiteMaterial : redMaterial));
mesh.rotation.set(
-Math.PI / 2,
0,
0
);
mesh.position.set(
x * size,
0,
z * size
)
scene.add(mesh);
}
}
const raycaster = new THREE.Raycaster();
const screenPos = new THREE.Vector2();
renderer.domElement.addEventListener("pointerup", function(e) {
screenPos.x = (e.clientX / window.innerWidth) * 2 - 1;
screenPos.y = -(e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(screenPos, camera);
const intersections = raycaster.intersectObject(scene, true);
for (let i = 0; i < intersections.length; i++) {
scene.remove(intersections[i].object);
}
});
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
body {
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.129/build/three.js"></script>

Can't rotate each mesh on animation update

I am quite new to Three.js and have been experimenting to get familiar with it.
I am making this exercise where I add to the scene 35 icosahedrons. I would like for each one of them to rotate when calling requestAnimationFrame.
I thought that by looping into each group children element (which is each mesh) and adding value to x and y rotation I could make the meshes rotate. Why is not so? Any help is very appreciated. Thank you.
This my approach:
var camera, scene, renderer;
var geometry, material, mesh;
var edgesGeometry, edgesMaterial, edges;
var group;
var mouseX = 0, mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
init()
animate()
function init() {
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 1500;
scene = new THREE.Scene();
group = new THREE.Group();
for ( var i = 0; i < 35; i ++ ) {
var randomSize = Math.floor( Math.random() * (150 - 20) + 20 )
geometry = new THREE.IcosahedronGeometry( randomSize, 1 );
material = new THREE.MeshBasicMaterial({ color: 0x000000 });
mesh = new THREE.Mesh( geometry, material );
mesh.position.x = Math.random() * 2000 - 1000;
mesh.position.y = Math.random() * 2000 - 1000;
mesh.position.z = Math.random() * 2000 - 1000;
mesh.rotation.x = Math.random() * 2 * Math.PI;
mesh.rotation.y = Math.random() * 2 * Math.PI;
edgesGeometry = new THREE.EdgesGeometry( mesh.geometry )
edgesMaterial = new THREE.LineBasicMaterial( { color: 0x63E260, linewidth: 2 } )
edges = new THREE.LineSegments( edgesGeometry, edgesMaterial )
mesh.add( edges )
mesh.matrixAutoUpdate = false;
mesh.updateMatrix();
group.add( mesh );
}
scene.add( group );
//
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
//
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseMove( event ) {
mouseX = ( event.clientX - windowHalfX ) * 0.25;
mouseY = ( event.clientY - windowHalfY ) * 0.25;
}
function animate() {
requestAnimationFrame( animate );
for ( var i = 0; i < group.children.length; i ++ ) {
group.children[i].rotation.x += 0.001;
group.children[i].rotation.y += 0.001;
}
render();
}
function render() {
camera.position.x += ( mouseX - camera.position.x ) * 0.05;
camera.position.y += ( - mouseY - camera.position.y ) * 0.05
camera.lookAt( scene.position );
renderer.render( scene, camera );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/93/three.js"></script>
To expand on #prisoner849's comment:
When three.js renders a scene, it parses the entire scene looking for renderable items (visible, within the view frustum, etc.). Part of that process involves multiplying out the transformation matrices to populate the world matrix (matrixWorld) of each renderable item is up-to-date. As you can imagine, this can potentially be a process hog, so you also have the ability to turn off that auto-update.
It looks like you understand that, because your line of code: mesh.matrixAutoUpdate = false; does exactly that, then you follow it up by manually updating the mesh's matrix. This is mostly correct, but you also need to do this for each frame.
For a simple/shallow scene like yours, #prisoner849's approach is correct--just let three.js auto-update the matrices by removing the lines mentioned. But if your scene is more complex, and you want finer control over it, you'll need to exert that control for each frame you want to render.
In the example below, I took your original code and made it so that only every second icosahedron rotates. This is accomplished by collecting them into an array, and then only updating the matrices for objects in that array. (Also note I turned off matrix auto-updating for the entire scene, rather than individual objects.)
var camera, scene, renderer;
var geometry, material, mesh;
var edgesGeometry, edgesMaterial, edges;
var group;
var mouseX = 0,
mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
var updatableObjects = [];
init()
animate()
function init() {
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 1500;
scene = new THREE.Scene();
scene.autoUpdateMatrix = false; // turn off automatic matrix computation
group = new THREE.Group();
for (var i = 0; i < 35; i++) {
var randomSize = Math.floor(Math.random() * (150 - 20) + 20)
geometry = new THREE.IcosahedronGeometry(randomSize, 1);
material = new THREE.MeshBasicMaterial({
color: 0x000000
});
mesh = new THREE.Mesh(geometry, material);
mesh.position.x = Math.random() * 2000 - 1000;
mesh.position.y = Math.random() * 2000 - 1000;
mesh.position.z = Math.random() * 2000 - 1000;
mesh.rotation.x = Math.random() * 2 * Math.PI;
mesh.rotation.y = Math.random() * 2 * Math.PI;
edgesGeometry = new THREE.EdgesGeometry(mesh.geometry)
edgesMaterial = new THREE.LineBasicMaterial({
color: 0x63E260,
linewidth: 2
})
edges = new THREE.LineSegments(edgesGeometry, edgesMaterial)
mesh.add(edges)
if (i % 2) {
updatableObjects.push(mesh);
}
group.add(mesh);
}
scene.add(group);
//
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//
document.addEventListener('mousemove', onDocumentMouseMove, false);
//
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDocumentMouseMove(event) {
mouseX = (event.clientX - windowHalfX) * 0.25;
mouseY = (event.clientY - windowHalfY) * 0.25;
}
function updateMeshes(mesh) {
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.01;
mesh.updateMatrix();
}
function animate() {
requestAnimationFrame(animate);
updatableObjects.forEach(updateMeshes);
render();
}
function render() {
camera.position.x += (mouseX - camera.position.x) * 0.05;
camera.position.y += (-mouseY - camera.position.y) * 0.05
camera.lookAt(scene.position);
renderer.render(scene, camera);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/93/three.js"></script>

Three.js: not accurate to detect faceIndex when clicking the 2*2*2 geometry cube

I have created a 2*2*2 geometry cube via Three.js. Now I want to detect the click event when clicking the faces (24 faces in total).
Please check my current implementation at https://jsfiddle.net/agongdai/pdwg3myr/17/. When clicking on the faces, I want to console.log the current face index. But the index is not always accurate. For example, clicking on the top-left gray cell should show 0, but actually clicking the bottom part of it shows 2.
Please help me to check the mouse click event handler:
var raycaster = new THREE.Raycaster();
function onDocumentMouseDown( event ) {
var vector = new THREE.Vector3(
( event.clientX / window.innerWidth ) * 2 - 1,
- ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
vector.unproject( camera );
raycaster.setFromCamera( vector, camera );
raycaster.set( camera.position, vector.sub( camera.position ).normalize() );
var intersects = raycaster.intersectObject( cube );
if ( intersects.length > 0 ) {
var index = Math.floor( intersects[0].faceIndex / 2 );
console.log(index);
}
}
Could anybody please help?
Em, after googling a lot, I found this page and applied the approach. It's working properly https://jsfiddle.net/agongdai/pdwg3myr/19/:
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onDocumentMouseDown(event) {
mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
mouse.y = -(event.clientY / renderer.domElement.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObject(cube);
if (intersects.length > 0) {
var index = Math.floor(intersects[0].faceIndex / 2);
console.log(index);
}
}
Update
To adjust to the left/top shift and scrolling, update it to https://jsfiddle.net/agongdai/pdwg3myr/24/:
function onDocumentMouseDown(event) {
const holder = renderer.domElement;
const rect = holder.getBoundingClientRect();
mouse.x = ((event.pageX - rect.left - window.scrollX) / holder.clientWidth) * 2 - 1;
mouse.y = -((event.pageY - rect.top - window.scrollY) / holder.clientHeight) * 2 + 1;
...
}

three.js orthographic camera object picking

i am trying to pick objects in a scene where i use an orthographic camera.
my code fragment already works, but it is not precise.
i already found some answers on stackoverflow, but those are deprecated or won't work anymore at all.
here is my code onMouseDown
function onDocumentMouseUp( event ) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
var pos = camera.position;
var ray = new THREE.Raycaster(pos, vector.unproject(camera).sub(camera.position).normalize());
var intersects = ray.intersectObjects(objects);
if (intersects.length > 0) {
console.log("touched:" + intersects[0]);
}
else {
console.log("not touched");
}
}
please see http://jsfiddle.net/ujzpe07t/1/
if you click some pixels away left/right/above/below the cube, it still tells me that the object was touched.
i am using three.js r69.
any hints would be very much appreciated.
thanks, cheers!
Here is the pattern to use when raycasting (picking) with either an orthographic camera or a perspective camera:
var raycaster = new THREE.Raycaster(); // create once
var mouse = new THREE.Vector2(); // create once
...
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( objects, recursiveFlag );
three.js r.84

Three.js Raycasting with Camera as Origin

I'm trying to determine whether a point in space is visible to the camera or hidden behind other objects in the scene. I'm doing this by casting a ray from the position of the camera to that point in space and testing if that ray is intersected by an set of intersectable objects.
My problem is no intersections occur until the camera position itself intersects one of the objects in the set of intersectable objects.
I've created a jsfiddle in which, if an intersection is detected, a line is drawn from the camera position to the position in space i'm testing for visibility. Currently I believe, the line is only draw at specific points where the camera position intersects the set of intersectable objects.
How do I get the intersections to be registered as they should be, without having to have the camera position intersect objects in the set of intersectable objects?
the code:
var container;
var camera, controls, scene, renderer;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 1000;
controls = new THREE.OrbitControls(camera);
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
controls.keys = [65, 83, 68];
controls.addEventListener('change', render);
// world
scene = new THREE.Scene();
var testObject_G = new THREE.CubeGeometry(100, 100, 5);
var testObject_M = new THREE.MeshBasicMaterial({
color: 0xBBBBBB
});
var testObject_Mesh = new THREE.Mesh(testObject_G, testObject_M);
testObject_Mesh.position.x = -150;
scene.add(testObject_Mesh);
var testObject_Mesh2 = new THREE.Mesh(testObject_G, testObject_M);
testObject_Mesh2.position.x = 0;
scene.add(testObject_Mesh2);
var testObject_Mesh3 = new THREE.Mesh(testObject_G, testObject_M);
testObject_Mesh3.position.x = 150;
scene.add(testObject_Mesh3);
scene2 = new THREE.Object3D();
// renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 1);
container = document.getElementById('container');
container.appendChild(renderer.domElement);
//
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
controls.handleResize();
render();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
}
function render() {
renderer.render(scene, camera);
castRays();
}
function castRays() {
// rays
var direction = new THREE.Vector3(0, 200, -200);
var startPoint = camera.position.clone();
var ray = new THREE.Raycaster(startPoint, direction);
scene.updateMatrixWorld(); // required, since you haven't rendered yet
var rayIntersects = ray.intersectObjects(scene.children, true);
if (rayIntersects[0]) {
console.log(rayIntersects[0]);
var material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(ray.ray.origin.x, ray.ray.origin.y, ray.ray.origin.z));
geometry.vertices.push(new THREE.Vector3(ray.ray.direction.x, ray.ray.direction.y, ray.ray.direction.z));
var line = new THREE.Line(geometry, material);
scene2.add( line );
}
scene.add(scene2);
}
Thank you
For anyone currently seeing this thread, THREE.Projector has been replaced.
Three.js THREE.Projector has been moved to
The code below handles a 3D vector. If you go to the link above, the first commenter provided the code for a 2D vector.
var vector = new THREE.Vector3();
var raycaster = new THREE.Raycaster();
var dir = new THREE.Vector3();
...
if ( camera instanceof THREE.OrthographicCamera ) {
vector.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, - 1 ); // z = - 1 important!
vector.unproject( camera );
dir.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );
raycaster.set( vector, dir );
} else if ( camera instanceof THREE.PerspectiveCamera ) {
vector.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 ); // z = 0.5 important!
vector.unproject( camera );
raycaster.set( camera.position, vector.sub( camera.position ).normalize());
}
var intersects = raycaster.intersectObjects( objects, recursiveFlag );`
Your idea of casting a ray is good, however raycasting in three.js already does this :
mouse.x = ( ( event.clientX - renderer.domElement.offsetLeft ) / renderer.domElement.width ) * 2 - 1;
mouse.y = - ( ( event.clientY - renderer.domElement.offsetTop ) / renderer.domElement.height ) * 2 + 1;
this formula maps a pixel coordinate from Screen Space to a point in Normalized Device Coordinate ( NDC ) Space.
projector.unprojectVector( vector, camera );
maps a point from NDC Space to a point in World space
Raycaster then creates a ray from the camera position through that world point.
Here is your working Fiddle in which I changed the way of raycasting in your scene and this works, all you have to do now is creating a ray with the right coordinates that I provided you.
r.68

Resources