I set up my scene as follows:
document.addEventListener('mousedown', onDocumentMouseDown, false);
var container = document.getElementById("myCanvas");
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth*0.99, window.innerHeight*0.99 );
container.appendChild( renderer.domElement );
room_material = new THREE.MeshBasicMaterial({color: 0x00ff00});
room_material.side = THREE.DoubleSide;
objects = [];
camera.position.z = 19;
camera.position.x = 5;
camera.position.y = 30;
I have an array of objects that i'm trying to detect if a click intersects with them, defined as follows:
var thing0 = new THREE.Shape();
thing0.moveTo(-12.1321728566, 35.3935535858);
thing0.lineTo(7.10021556487,35.3935535858);
thing0.lineTo(7.10021556487,19.7039735578);
thing0.lineTo(5.12636517425,19.7166264449);
thing0.lineTo(5.12636517425,33.6221493891);
thing0.lineTo(-12.1377356984,33.6439534769);
var thing0Geom = new THREE.ShapeGeometry(thing0);
var thing0Mesh = new THREE.Mesh( thing0Geom, room_material );
thing0Mesh.name = "abcd";
scene.add(thing0Mesh);
objects.push(thing0Mesh);
I then render the scene with the following code:
renderer.render(scene, camera);
requestAnimationFrame(render);
And lastly I use the following code for the mouse click event:
function onDocumentMouseDown(event) {
var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
vector = vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(objects, true);
alert("well, you clicked!");
if (intersects.length > 0) {
alert("wow, it worked");
}
}
However, no matter what I do the alert never gets called when it follows raycaster.intersectObjects(objects, true); However it does get called when it is placed anywhere before it. It seems that raycaster.intersectObjects(objects, true); is a bit of a black hole in this case?
I assume I simply have something wrong in my setup? Any help would be appreciated!
There are two things I tried that worked:
1) Make sure you're using Three.DoubleSide if your mesh face is not pointing towards origin of the ray. This is directly from the documentation:
"Note that for meshes, faces must be pointed towards the origin of the ray in order to be detected; intersections of the ray passing through the back of a face will not be detected. To raycast against both faces of an object, you'll want to set the material's side property to THREE.DoubleSide."
2) Use mesh.updateMatrixWorld() prior to raycasting. This comes from another stackoverflow post: threejs raycasting does not work
mesh.updateMatrixWorld(); // add this
raycaster.set(from, direction);
I think i have found the problem. I have to add the meshes to an extra array. An intersectObjects over the scene.children don't work, because there are other objects in there with the meshes.
So when i give the intersectObjects( mesh[] ) an mesh array than it works.
For more code detail see https://github.com/mrdoob/three.js/issues/8081
This is an old question, but I was running into a similar issue and thought what I learned might help someone else. My code setup is very similar to JohnnyDevNull's, so I won't repeat it.
The Problem
In my case, calling intersectObjects with scene.children doesn't work because it picks up other objects like ambientLighting, etc. I believe this is what the OP mentions in his own answer.
My Solution
The function below takes an empty array (intersects). It searches each child in scene for THREE Group objects and Mesh objects and adds Mesh objects to the array. For Groups, it sets recursive to true, which applies .intersectObject to each child. Once the array is built up, it calls your supplied callback function on the array.
function raycastMeshes(intersects, callback, theScene, theRaycaster) {
var scene = theScene || scene || new THREE.Scene();
var raycaster = theRaycaster || raycaster || new THREE.Raycaster();
for (var i in scene.children) {
if (scene.children[i] instanceof THREE.Group) {
intersects = raycaster.intersectObjects(scene.children[i].children, true);
} else if (scene.children[i] instanceof THREE.Mesh) {
intersects.push(raycaster.intersectObject(scene.children[i]));
}
}
if (intersects.length > 0) {
return callback(intersects);
} else {
return null;
}
}
Adapting to your Use Case
This is obviously a fairly naive and specific use case, but should serve as a launching board for you if you're having a similar problem.
Related
I want to add and remove a geometry with its wireframe together. I can use raycaster to pick object from the scene but it is hard to pick a wireframe.
One way I can think of is creating a group of object and its wireframe, when the raycaster intersect with the object (e.g. obj.geometry.type == "BoxGeometry"), find its parent and delete the parent. However, the wireframe has to be some geometry that can be added as a child. I am using a Boxhelper to create a wireframe for a cube, which should be added to the scene directly, not to be added as a child to any object. What is a good way to solve this?
Thanks.
I am not sure if i understand what you are after exactly, but maybe ou can create a clone of your object, and render that one as a wireframe. The clone wireframe object can then be added as a child to the original object. So when the original object is picked, you can remove it from the scene, and then the wireframe object will be removed aswell.
Clone your object and change its material to wireframe:
var wireframe = cube.clone();
wireframe.material = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});
cube.add( wireframe ); // adding wireframe as child to the cube
When object is picked: check whether it is a cubegeometry (if you only want it to work with cubes) and check whether its material is wireframe (if you dont want to be able to remove the wireframe without removing the cube aswell)
if (pickedObject.geometry.type == "BoxGeometry" &&
!pickedObject.material["wireframe"]){
pickedObject.parent.remove(pickedObject); //this will remove object from
// scene if it has no parents
}
Working example:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
var cube = new THREE.Mesh( geometry, material );
scene.add( cube );
var wireframe = cube.clone();
wireframe.material = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});
cube.add( wireframe );
//picking stuff
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseClick( event ) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
mouse.x = ( event.clientX / window.innerWidth ) * 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( scene.children );
for ( var i = 0; i < intersects.length; i++ ) {
if (intersects[ i ].object.geometry.type == "BoxGeometry" &&
!intersects[ i ].object.material["wireframe"]){
intersects[ i ].object.parent.remove(intersects[ i ].object);
}
}
}
camera.position.z = 5;
var render = function () {
requestAnimationFrame( render );
cube.rotation.x += 0.1;
cube.rotation.y += 0.1;
renderer.render(scene, camera);
};
window.addEventListener( 'mouseup', onMouseClick, false );
render();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.min.js"></script>
There is also a way of doing what you require with the Three.EdgesHelper instead of cloning, as exemplified in this fiddle. This shows the wireframes without the diagonals.
Even if you don't want to use the Three.EdgesHelper, I noticed that while implementing the clone solution above, it didn't completely show the wireframe, because it was slightly hidden.
To avoid this hiding I added the following code to the constructor of material, which offsets the original shape slightly so that the wireframe can be completely seen:
var material = new THREE.MeshLambertMaterial({ polygonOffset: true, polygonOffsetFactor: 1, polygonOffsetUnits: 1 })
I hope this is somehow useful.
For the human demo http://threejs.org/examples/#webgl_morphtargets_human , I wonder how to get a point on the skin of the human. For example when I click some place on the human body, what's the coordinate of that place?
I tried using the raycaster to get that but in vain.The code is like this:
var projector;
init() {
// Others
// ...
projector = new THREE.Projector();
renderer.domElement.addEventListener('mouseup', onMouseUp, false);
}
function onMouseUp(e) {
e.preventDefault();
var vector = new THREE.Vector3(
( event.clientX / window.innerWidth ) * 2 - 1,
- ( event.clientY / window.innerHeight ) * 2 + 1,
0.5
);
projector.unprojectVector( vector, camera );
var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
var intersections = raycaster.intersectObjects( character.root.children );
if (intersections.length > 0) {
debugger;
// ...
}
}
But the intersections is always empty.
Three.js is r67
Thanks in advance.
I'm new to three.js, and i've also tried to draw plots on the human. I manage to do it, but it's not on the "visible" body. In fact, you should first use the intersect method with the recursive arg :
var intersections = raycaster.intersectObjects( scene.children, true );
Thus, you 'll be able to interact with the objects composing the body, but they are not positioned under the "skin". It seems to be that they have been "moved", because you can interact with them if you aim in front of the feet of the body. Unfortunately, I don't know for the moment why, and how to interact with their "visible representation".
Well, finally I find that it's just because the human animation. It works if I comment the animation out.
I have a very simple example: a spot light pointed at a plane. I am expecting to see a cone of light whose diameter depends on the setting of the spot light angle. I cannot see any cone, the whole plane is illuminated, even for very narrow settings of angle.
Here is my jfiddle: http://jsfiddle.net/blwoodley/WLtL4/1/
I'd love to know the source code that produced this picture from https://github.com/mrdoob/three.js/pull/3291 by West Langley. It obviously is working fine in that case.
So I must be doing something obviously wrong, but I can't figure it out.
Some of the code from the jfiddle, it doesn't get much simpler than this:
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 100000);
camera.position.x = 100;
camera.position.y = 100;
camera.position.z = 200;
camera.lookAt({x: 0,y: 0,z: 0});
scene = new THREE.Scene();
var floorGeometry = new THREE.PlaneGeometry(1000, 1000, 10, 10);
var floorMaterial = new THREE.MeshLambertMaterial({ color: 0x222222, side:THREE.DoubleSide });
floor = new THREE.Mesh(new THREE.PlaneGeometry(2000,2000,10,10), floorMaterial);
floor.rotation.x = Math.PI / 2;
floor.position.y = -40;
scene.add(floor);
var light;
light = new THREE.SpotLight(0x008888);
light.position.set(0, 40, 0);
light.lookAt(floor);
light.angle = Math.PI/4;
light.intensity = 30;
light.distance=0;
scene.add(light);
// RENDERER
webglRenderer = new THREE.WebGLRenderer();
webglRenderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
webglRenderer.domElement.style.position = "relative";
container.appendChild(webglRenderer.domElement);
window.addEventListener('resize', onWindowResize, false);
}
This is subtle.
You are using MeshLambertMaterial for the plane. You need to change it to MeshPhongMaterial, so the lighting is rendered properly.
As explained here, for MeshLambertMaterial, the illumination calculation is performed only at each vertex.
For MeshPhongMaterial, the illumination calculation is performed at each texel.
So make these changes
floorGeometry = new THREE.PlaneGeometry( 1000, 1000 ); // no need to tessellate it now
var floorMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff } ); // 0x222222 is too dark
light.intensity = 1; // a reasonable value
Updated fiddle: http://jsfiddle.net/WLtL4/5/
three.js r.63
Also try to disable target for testing.
I'm getting really weird behavior from it. Sometimes it makes it not render the light at all, no idea why yet. I'll make a demo of the problem later.
I need to run raycast off mouse coordinates and check for intersections on a group of Three CSS3DObject objects.
Here is the function:
RayCastCheck = function(event, objects){
var vector = new THREE.Vector3((event.clientX / window.innerWidth)*2 - 1, -(event.clientX / window.innerHeight )*2 + 1, 0.5);
new THREE.Projector().unprojectVector( vector, camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(objects);
console.log(intersects.length);
};
The objects argument is an array of css3dobjects. I am able to use similar function to target drops on the scene to the correct mouse location so I believe my calculation of the mouse point in world space is correct. This led to believe that the Raycaster is does not check intersections on css3dobjects.
My css3dobjects are typically constructed with a div as its element.
createObject = function(){
var element = document.createElement("div");
var obj = new THREE.CSS3DObject(element);
scene.add(obj);
}
My scene is created via this function
//global
var scene;
var camera;
var renderer;
createScene = function(){
camera = new THREE.PerspectiveCamera( 75, 400 / 600, 1, 1000 );
camera.position.z = 500;
scene = new THREE.Scene();
renderer = new THREE.CSS3DRenderer();
renderer.setSize(400, 600);
$(#body).appendChild(renderer.domElement);
}
Do I have all the required elements in the scene to enable raycasting?
Is it possible to perform raycasting on css3dobjects with the css3drenderer?
Thank you for your help
You can just use the usual events with the dom elements. You can even get the relative coordinates:
var x = e.offsetX==undefined?e.layerX:e.offsetX;
var y = e.offsetY==undefined?e.layerY:e.offsetY;
Using Raycaster on css3dobjects won't work. At least this is what I figured out.
Take a look at three.js r76 line 8933. There is the definition of the "raycast" function of the css3dobject.
It is empty so it isn't implemented and won't work because of this of course. probably on a further version. would need this function too
Still isn't implemented in r78.
I've been trying for bigger parts of the night to make a export code that quickly will let me texture cubes and export them to a game i'm making, but for some reason I can't make my cube to cover the entire 128x128 width and height that I want it to have.
I have the following code:
function init(){
if( Detector.webgl ){
renderer = new THREE.WebGLRenderer({
antialias : false, // to get smoother output
preserveDrawingBuffer : true // to allow screenshot
});
renderer.setClearColorHex( 0xBBBBBB, 1 );
// uncomment if webgl is required
//}else{
// Detector.addGetWebGLMessage();
// return true;
}else{
renderer = new THREE.CanvasRenderer();
}
renderer.setSize(128,128);
document.getElementById('container').appendChild(renderer.domElement);
// add Stats.js - https://github.com/mrdoob/stats.js
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.bottom = '0px';
document.body.appendChild( stats.domElement );
var zoom = 1.0;
// create a scene
scene = new THREE.Scene();
// put a camera in the scene
camera = new THREE.OrthographicCamera(WIDTH / -zoom, HEIGHT / zoom, WIDTH / zoom, HEIGHT / -zoom, -2000, 1000);
//camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set(0.45,0.45,0.45);
camera.lookAt(scene.position);
//camera.position.set(0, 0, 5);
scene.add(camera);
// create a camera contol
//cameraControls = new THREEx.DragPanControls(camera)
// transparently support window resize
THREEx.WindowResize.bind(renderer, camera);
// allow 'p' to make screenshot
THREEx.Screenshot.bindKey(renderer);
// allow 'f' to go fullscreen where this feature is supported
//if( THREEx.FullScreen.available() ){
// THREEx.FullScreen.bindKey();
// document.getElementById('inlineDoc').innerHTML += "- <i>f</i> for fullscreen";
//}
// here you add your objects
// - you will most likely replace this part by your own
//var geometry = new THREE.TorusGeometry( 1, 0.42 );
var cubeSize = 128;
var geometry = new THREE.CubeGeometry( cubeSize, cubeSize, cubeSize );
var material = new THREE.MeshNormalMaterial();
mesh= new THREE.Mesh( geometry, material );
mesh.rotation.x = 0;
mesh.rotation.y = 0;
mesh.rotation.z = 0;
scene.add( mesh );
}
I've been trying out different "zooms" but it still ends up either too big or too small.
The point with all this is to end up with a code that can generate something like this:
https://dl.dropboxusercontent.com/u/5256694/cube_ex.png
What am I doing wrong?
Kind Regards
Hiam
Instead of thinking about the parameters of THREE.OrthographicCamera as "zoom" levels, you should think of them as coordinates of boundary planes for what the camera is able to see.
Also see the answer to Three.js - Orthographic camera for more details about using orthographic cameras in Three.js