I made an open source project. It is a data visualizer for your books in 3D using Three.js. (https://github.com/AlinCiocan/BooksIn3D). And now I want to do detect if a user is in front of a book if pressed a key. Something like this:
For example, in the above image, the cursor hand it is not hitting any books.
I tried my best to actually make a function to work, but I am stuck. I searched if there any question on SO on how to do hit detection using PointerLockControls, but I found nothing. I must say that my cursor hand it is not moving, it is always on the center of the screen.
function hitDetection(targets, callback) {
var projector = new THREE.Projector();
var raycaster = new THREE.Raycaster();
var mouse = {};
// it is always the center of the screen
mouse.x = 0;
mouse.y = 0;
var vector = new THREE.Vector3(mouse.x, mouse.y, 1);
var cameraDirection = controls.getDirection(vector).clone();
projector.unprojectVector(cameraDirection,camera );
var ray = new THREE.Raycaster(controls.getObject().position, cameraDirection.sub(controls.getObject().position).normalize());
var intersects = ray.intersectObjects(targets);
// if there is one (or more) intersections
if (intersects.length > 0) {
var hitObject = intersects[0].object;
console.log("hit object: ", hitObject);
callback(hitObject);
}
}
Related
I have a plane, which is added to the scene using threejs. I am adjusting position & rotation using User Interface controls , if there is a change in position or in rotation then I am running below code to get updated plane / cube vertices & Matrix Values:
plane.updateMatrixWorld();
plane.updateMatrix();
plane.geometry.applyMatrix( plane.matrix );
plane.matrix.identity();
console.log(plane.matrix); // using this to get matrix values
console.log(plane.geometry.vertices); //using this to get plane vertices.
When I am running above code facing issue in position shift for the plane / cube / mesh in the scene.
Tried adding below code to make it dynamic updates to vertices but it did not work:
plane.verticesNeedUpdate = true;
plane.elementsNeedUpdate = true;
plane.morphTargetsNeedUpdate = true;
plane.uvsNeedUpdate = true;
plane.normalsNeedUpdate = true;
plane.colorsNeedUpdate = true;
plane.tangentsNeedUpdate = true;
Yes, Got the solution,When there is a change in position/rotation call below code.
var plane_vector;
function updateVertices(){
plane.updateMatrixWorld();
console.log("plane Vertices: ");
for(i = 0; i<=(plane.geometry.vertices.length-1); i++){
plane_vector[i] =
plane.geometry.vertices[i].clone();
plane_vector[i].applyMatrix4(
plane.matrixWorld );
}
console.log(plane.matrix);
}
I`m using three.js. To move around in my scene I am using orbit control and to select my objects I use the raycaster. The Raycaster is sent on click, as well as the orbit control. So if an object is selected and i move the camera around on mouse release, another object will be selected. Is there a way to check for camera movement in orbit control.
Or what is the common way to prevent unwanted selection?
Here is my selection handler:
function onMouseClick( event ) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
mouse.x = ( event.clientX / canvasWidth) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// update the picking ray with the camera and mouse position
raycaster.setFromCamera( mouse, camera );
// calculate objects intersecting the picking ray
var intersects = raycaster.intersectObjects( CentroLite.children, true );
if (intersects.length === 0){
intersects = raycaster.intersectObjects( millingTable.children, true );
}
SELECTED = intersects[0].object;
// DO SOMETHING
}
thanks
Flags might come handy here. You could prevent the selection from happening if the mouse is moved. Something like:
var doClickOnRelease = false;
document.onmousedown = function() {
// Get ready to see if the user wants to select something
doClickOnRelease = true
};
document.onmouseup = function() {
if (doClickOnRelease) {
// Your select function
};
document.onmousemove = function() {
// Since you're dragging, that must be because you
// didn't intend to select something in the first place
doClickOnRelease = false;
};
I wanted to make a snapping functionality to snap to my mesh vertices. I experimented with several solutions.
One solution is to add THREE.Sprite instances for all vertices in my scene and then using a rayCaster to decide whether there is a snap point in the intersects array. It works pretty well; here is a fiddle with a demo.
The idea is to hide the sprites in the final solution so they won't be rendered, but my scenes are pretty big so it would still mean adding lots of sprites to my scene (for every vertex one so possibly thousands of sprites) to detect snap points with my rayCaster.
var intersects = rayCaster.intersectObject(scene, true);
var snap = null;
if (intersects.length > 0) {
var index = 0;
var intersect = intersects[index];
while (intersect && intersect.object.name === 'snap') {
snap = sprite.localToWorld(sprite.position.clone());
index++
intersect = intersects[index];
}
if (intersect) {
var face = intersect.face;
var point = intersect.point;
var object = intersect.object;
mouse3D.copy(point);
}
}
if (snap) {
renderer.domElement.style.cursor = 'pointer';
} else {
renderer.domElement.style.cursor = 'no-drop';
}
I also thought of an alternative solution by doing the math using results from the rayCaster. That solution is demonstrated in this fiddle.
The idea here is to test all vertices from the geometry of the object (mesh) that is intersected and then check whether the distance between the intersect point and those vertices from the geometry is smaller then the snap threshold.
var intersects = rayCaster.intersectObject(mesh, true);
if (intersects.length > 0) {
var distance, intersect = intersects[0];
var face = intersects[0].face;
var point = intersects[0].point;
var object = intersects[0].object;
var snap = null;
var test = object.worldToLocal(point);
var points = object.geometry.vertices;
for (var i = 0, il = points.length; i < il; i++) {
distance = points[i].distanceTo(test);
if (distance > threshold) {
continue;
}
snap = object.localToWorld(points[i]);
}
if (snap) {
sphereHelper.position.copy(snap);
sphereHelper.visible = true;
renderer.domElement.style.cursor = 'pointer';
} else {
sphereHelper.visible = false;
renderer.domElement.style.cursor = 'no-drop';
}
}
The sad thing is that in the second solution snap will only work when the mouse is moved from the surface of the intersected object towards a vertex. In case the mouse is moved from outside the object (so there is no intersection) the snapping won't work. In that respect the first solution with sprites is much more usable...
My question, am I overcomplicating things and is there a better/simpler/more efficient way to do this? Any suggestions for alternative approaches are welcome.
I looked into #meepzh his suggestion of using an octree and made the following solution using this threeoctree repository from github. The THREE.Octree class did not solve all my problems out-of-the-box so
I added custom method findClosestVertex to the THREE.Octree class that can be used like this.
var snap = octree.findClosestVertex(position, radius);
snap is null in case no vertices within the radius of position and returns the closest point (THREE.Vector3) in world space otherwise.
I made a Pull-Request here on github for the new method.
Here is a demo in a fiddle
I'm making a 2d game, where blocks are falling down ( tetris style). I need to render alphabets on these blocks. This is how I am creating blocks:
var geometry = new THREE.BoxGeometry( this.BLOCK_WIDTH, this.BLOCK_WIDTH, 4 );
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
this.blocks = [];
for (var i = 0; i < rows * columns; i++) {
cube = new THREE.Mesh( geometry, material );
cube.visible = false;
cube.letter = letterGenerator.getNextLetter();
this.blocks[i] = cube;
scene.add( this.blocks[i] );
};
As you can see, all blocks will look exactly alike except for the fact, that they will have a different alphabet associated with them. In my update(), I move the block, left/right or down. When I do so, block position will be updated and obviously the alphabet should be rendered accordingly.
How should I go about rendering alphabets on these blocks ?
EDIT: I am using WebGLRenderer.
You can get the screen position of each block (your "cube" variable above) that you want to paint text on and use HTML to paint text at that screen location over each block. Using HTML to make a text sprite like this is discussed here:
https://github.com/mrdoob/three.js/issues/1321
You can get the screen position for your "cube" above like so:
var container = document.getElementById("idcanvas");
var containerWidth = container.clientWidth;
var containerHeight = container.clientHeight;
var widthHalf = containerWidth / 2, heightHalf = containerHeight / 2;
var locvector = new THREE.Vector3();
locvector.setFromMatrixPosition(cube.matrixWorld);
locvector.project(your_camera); //returns center of mesh
var xpos = locvector.x = (locvector.x * widthHalf) + widthHalf; //convert to screen coordinates
var ypos = locvector.y = -(locvector.y * heightHalf) + heightHalf;
You'll have to update the HTML for cube movement.
Another approach is to create specific textures with the text you want and apply each texture as a material to the appropriate cube.
I have a sphere (globe) with objects (pins) on the surface with DOM elements (labels) what are calculated from the pin position to 2d world.
My problem is that when the pins go behind the globe (with mouse dragging or animation) then I need to hide labels which are in DOM so that the text label isn’t visible without the pin.
My logic is that if I can get the pin which is in 3D world to tell me if it’s behind the globe then I can hide the label associated with the pin.
Codepen with whole the code.
The function that I have researched together:
function checkPinVisibility() {
var startPoint = camera.position.clone();
for (var i = 0; i < pins.length; i++) {
var direction = pins[i].position.clone();
var directionVector = direction.sub(startPoint);
raycaster.set(startPoint, directionVector.clone().normalize());
var intersects = raycaster.intersectObject(pins[i]);
if (intersects.length > 0) {
// ?
}
}
}
I have researched through many posts but can’t really get the result needed:
ThreeJS: How to detect if an object is rendered/visible
Three.js - How to check if an object is visible to the camera
http://soledadpenades.com/articles/three-js-tutorials/object-picking/
I have gotten it work by mouse XY position as a ray, but can’t really get a working solution with constant rendering for all the pins.
You want to know which points on the surface of a sphere are visible to the camera.
Imagine a line from the camera that is tangent to the sphere. Let L be the length of the line from the camera to the tangent point.
The camera can only see points on the sphere that are closer to the camera than L.
The formula for L is L = sqrt( D^2 - R^2 ), where D is the distance from the camera to the sphere center, and R is the sphere radius.
WestLangley's solution in code form. Please give him the accepted answer if you feel his answer the best.
function checkPinVisibility() {
var cameraToEarth = earth.position.clone().sub(camera.position);
var L = Math.sqrt(Math.pow(cameraToEarth.length(), 2) - Math.pow(earthGeometry.parameters.radius, 2));
for (var i = 0; i < pins.length; i++) {
var cameraToPin = pins[i].position.clone().sub(camera.position);
if(cameraToPin.length() > L) {
pins[i].domlabel.style.visibility = "hidden";
} else {
pins[i].domlabel.style.visibility = "visible";
}
}
}
Oddly enough it is still susceptible to that camera pan error. Very weird, but it's still better than my Projection-onto-LOOKAT solution.
MY OLD ANSWER:
I would have assumed its something like this, but this doesn't seem to work as expected.
if (intersects.length > 0) {
pins[i].domlabel.style.visibility = "visible";
} else {
pins[i].domlabel.style.visibility = "hidden";
}
I got close with this solution, but its still not perfect. What the code below does is it finds the distance along the LOOKAT direction of the camera to a pin (cameraToPinProjection) and compares it with the distance along the LOOKAT direction to the earth (cameraToEarthProjection).
If cameraToPinProjection > cameraToEarthProjection it means the pin is behind the centre of the earth along the LOOKAT direction (and then I hide the pin).
You will realise there's a "0.8" factor I multiply the cameraToEarth projection by. This is to make it slightly shorter. Experiment with it.
Its not perfect because as you rotate the Earth around you will notice that sometimes labels don't act the way you'd like them, I'm not sure how to fix.
I hope this helps.
function checkPinVisibility() {
var LOOKAT = new THREE.Vector3( 0, 0, -1 );
LOOKAT.applyQuaternion( camera.quaternion );
var cameraToEarth = earth.position.clone().sub(camera.position);
var angleToEarth = LOOKAT.angleTo(cameraToEarth);
var cameraToEarthProjection = LOOKAT.clone().normalize().multiplyScalar(0.8 * cameraToEarth.length() * Math.cos(angleToEarth));
var startPoint = camera.position.clone();
for (var i = 0; i < pins.length; i++) {
var cameraToPin = pins[i].position.clone().sub(camera.position);
var angleToPin = LOOKAT.angleTo(cameraToPin);
var cameraToPinProjection = LOOKAT.clone().normalize().multiplyScalar(cameraToPin.length() * Math.cos(angleToPin));
if(cameraToPinProjection.length() > cameraToEarthProjection.length()) {
pins[i].domlabel.style.visibility = "hidden";
} else {
pins[i].domlabel.style.visibility = "visible";
}
}
}