Three.js: Cannot identify intersected objects by raycaster (.gtlf 3D-Object's) - three.js

I'm working on a scene with three.js and imported .gtlf models via THREE.GLTFLoader(). I want to attach attributes to the objects and later work with those whenever an event occurs regarding those objects. But my problem is that the returned intersections by the raycaster do not carry the attributes added to the objects. Furthermore, I cannot identify which object was returned by the raycaster (for example by a obj.name attribute I attached beforehand, since the returned intersections are not 3DObjects anymore as they are when added to the scene).
How can I precisely identify which (gltf) object was returned by the raycaster?
For gemoetry objects it works perfectly fine and attributes are attached when returned from the raycaster, but for the loaded models (gltf) it's not working, even after using the "userData" attribute that aims for the purpose of carrying information, the attribute is empty afterwards.
gltfLoader.load('./assets/simple_shelf/scene.gltf', (gltf) => {
shelf = gltf.scene.children[0];
shelf.userData.direction = "test string";
scene.add(shelf);
});
//console.log(shelf.userData.direction) shows it correctly, but returned
//by raycaster on events (e.g. mousemove), userData is empty
function onMouseMove(event) {
event.preventDefault();
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ( ( event.clientX - rect.left ) / ( rect.width - rect.left ) ) * 2 - 1;
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
raycaster.setFromCamera(mouse,camera);
var intersects = raycaster.intersectObjects(scene.children, true);
for (let i = 0; i < intersects.length; i++){
console.log(intersects[i]);
}
}
window.addEventListener('mousemove', onMouseMove);
I want to somehow exactly identify the object the raycaster returns, i.e. trigger the events. Hence I want to add attributes and retrieve them on event occurance.
What am I doing wrong here?

Related

calling object3D children for unique styles / animations

I'm wondering how I would go about calling the individual children of cube (mesh0, mesh1) so I'm able to set different styles / animations to them both.
AFRAME.registerComponent('multi_box', {
schema: {},
update: function() {
for (var i = 0; i < 2; i++) {
var material = new THREE.MeshBasicMaterial({color: "blue"});
var geometry = new THREE.BoxGeometry(1, 1, 1);
var cube = new THREE.Mesh(geometry, material);
cube.position.x = i == 0 ? -1 : 1;
cube.position.y = 0.5;
cube.position.z = -5;
this.el.setObject3D("mesh"+i, cube); //unique name for each object
}
console.log(this.el.object3DMap)
}
});
Codepen link: https://codepen.io/ubermario/pen/wrwjVG
I can console.log them both and see that they are unique objects to each other but I'm having trouble calling them:
var meshtest = this.el.getObject3D('mesh0')
console.log(meshtest.position)
I'v tried this method but with no luck: aframe get object3d children
Any help is appreciated :)
Instancing
In your for cycle, you create
a new geometry instance
a new material instance
a new mesh that connect the previous two
Each new keyword creates a unique instance that is independent of the others. In your case mesh0 and mesh1 are fully independent of each other. If you change any property of an instance, like for example material color or position, the other(s) will not be affected by that. In fact you do that by assigning a different x position to each cube.
Storage
Your component holds a map of 3D objects. Each is identified by a unique name. You generate this name by concatenating the prefix mesh with the iteration number (value of i).
You can later access the cubes just the same way you created them. Either by name
this.el.getObject3D('mesh0')
this.el.getObject3D('mesh1')
//etc.
or by index
this.el.object3D.children[0]
this.el.object3D.children[1]
and you can manipulate them further. For example you can put his on Ln19 in your Codepen:
this.el.getObject3D('mesh0').position.y = 2;
this.el.object3D.children[1].position.z = -3;
Just for completeness: If you would omit the +i at the end, you would overwrite the same key again and again, so mesh would reference just the last cube and others would get lost.
Changing properties
Three.js has a nice API, but in javascript you always need to think what happens behind the scenes. You will see that while learning new stuff. For example:
this.el.object3D.children[1].position.z = -3;
this.el.object3D.children[1].material.color.set('#ffff00');
this.el.object3D.children[1].material.color = [1, 1, 0];
As you can see the position can be changed directly, but color needs a setter sometimes. Things get more complicated with vectors where you need to watch which methods change the current instance and which produce a new one. You could easily forget to clone it. Say you had:
var pos = new THREE.Vector3(-1, 0.5, -5)
for (var i = 0; i < 2; i++) {
var material = new THREE.MeshBasicMaterial({color: "blue"});
var geometry = new THREE.BoxGeometry(1, 1, 1);
var cube = new THREE.Mesh(geometry, material);
//wrong, since it will assign the object reference,
//i.e. always the same object.
//Thus, the cubes will be at one position in the end
cube.position = pos;
//right
cube.pos = pos.clone();
pos.x += 1;
this.el.setObject3D("mesh"+i, cube); //unique name for each object
}
The difference is a bit more moderate topic on reference and value types. You will find many tutorials for that, for example this small gist I just found.
I hope this was helpful and wish you good progress in learning!

object.updateWorldMatrix is not a function

As documentation of Three.js said, after changing the position of the camera, we must call the updateProjectionMatrix() method.
I'm doing the same. But it gives me an error like this:
TypeError: object.updateWorldMatrix is not a function
at Box3.expandByObject (three.module.js:6329)
at Box3.setFromObject (three.module.js:6233)
at index.js:66
at GLTFLoader.js:147
at GLTFLoader.js:1639
My goal is to put my loaded GLTF object, in the center of the screen.
And this is the code I'm using for it:
this.gltfLoader.load("/corolla.gltf", (object) => {
const box = new THREE.Box3().setFromObject(object);
const size = box.getSize(new THREE.Vector3()).length();
const center = box.getCenter(new THREE.Vector3());
// reset OrbitControl
this.controls.reset();
object.position.x += (object.position.x - center.x);
object.position.y += (object.position.y - center.y);
object.position.z += (object.position.z - center.z);
this.controls.maxDistance = size * 10;
this.camera.near = size / 100;
this.camera.far = size * 100;
this.camera.updateProjectionMatrix();
this.camera.position.copy(center);
this.camera.position.x += size / 2.0;
this.camera.position.y += size / 5.0;
this.camera.position.z += size / 2.0;
this.camera.lookAt(center);
this.gltf = object.scene;
this.scene.add(this.gltf);
},
this.manageLoading,
this.gltfLoadErr);
For the record, I'm using the last version of Three.js, it's 0.110.0;
It looks like the errors is occurring here:
const box = new THREE.Box3().setFromObject(object);
Where object is the object coming back from the call to GLTFLoader.load.
According to the docs, object is not an Object3D (which is what Box3.setFromObject expects).
Instead, object is a JSON structure containing information about the GLTF data. The three.js example suggests object.scene would be a renderable entity (if it exists).
Take a look at the code for the GLTFLoader example, here. Compare it against your implementation. You can also debug your code to see exactly what is object contains. Once you have a handle on that, if you're still having problems, come back and ask more questions!
Short solution:
var hollowCylinderGeom = new THREE.LatheBufferGeometry([
new THREE.Vector2(1, 0),
new THREE.Vector2(2, 0),
new THREE.Vector2(2, 2),
new THREE.Vector2(1, 2),
new THREE.Vector2(1, 0)
], 90).toNonIndexed();
var mesh = new THREE.Mesh(hollowCylinderGeom);
console.log(new THREE.Box3().setFromObject(mesh));

Three JS : Attaching an object to another

I'm trying to attach a cube to another cube at a specific point. I'm able to get collision and .add() is working fine. However, I want to add child cube exactly at point where collision occurs to the parent cube.
for (var vertexIndex = 0; vertexIndex < MovingCube.geometry.vertices.length; vertexIndex++) {
var localVertex = MovingCube.geometry.vertices[vertexIndex].clone();
var globalVertex = localVertex.applyMatrix4( MovingCube.matrix );
var directionVector = globalVertex.sub( MovingCube.position );
var ray = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableMeshList );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() )
console.log(" Hit and collision detected " + SELECTED);
MovingCubeBig.add(SELECTED);
// Is there way to get collision point and attach object at that point?
}
Intersections results should include a point attribute.
https://github.com/mrdoob/three.js/blob/master/src/core/Raycaster.js#L255
Maybe you can use this to attach the child at the right position ?
Also, this example may help http://threejs.org/examples/#webgl_geometry_terrain_raycast

ray.interstectObjects not intersecting correctly after geometry is dynamically modified

My ray.intersectObjects works really well with the objects in my scene until I dynamically modify the geometry of the object. Although the renderer is showing the objects as being modified (vertices moved and faces changed), when an intersect is tried on the modified object, it produces strange results. I need the intersect to work even on the modified geometry!
To help debug and track how the intersect is working in my scene, I've added a function: makeMiniSphere(). This makes a new sphere object in the scene at the point where the intersection occurs. Using this, you can see that after the cube is modified, sometimes the intersect hits the cube and sometimes it goes right through (mostly the faces that have been modified). It isn't a random problem, but the more you click around the modified cube, the more you can see a pattern develop. It is almost as if the renderer for the visuals of the scene know which direction the cube was modified, but the ray.intersectObjects thinks that it has been modified in a different direction!
Here is a link to the test website: http://www.littledrop.net/html/cadiverse/HelloWorld.html
Directions to show problem:
Left click on cube to show intersect points. Mini spheres will be created wherever Three.js sees an intersect. The color of the selected object will change to yellow if not already selected.
Click on any face of the cube. This will A. Turn it yellow if it isn't already yellow. B. It will select the face of the cube, although the selected face won't look any different from the rest of the cube.
Press the "Right" arrow key to move the selected face to the right. This will dynamically change the geometry of the cube.
Now try to click on the cube--especially in the area that it has been modified. Again the Mini spheres will show where the software thinks the intersects are occurring.
Here is the intersect code:
function onDocumentMouseDown (event)
{
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
//event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
document.getElementById('message1').innerHTML = window.innerHeight;
var isinworkingarea = window.innerHeight-menubarh;
if (event.clientY<=isinworkingarea)
{
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
projector.unprojectVector( vector, camera );
//var ray = new THREE.ReusableRay();
var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
// create an array containing all objects in the scene with which the ray intersects
// use this to select anything in the scene:
var intersects = ray.intersectObjects( scene.children );
if ( intersects.length > 0 )
{
if (cadjectNow)
cadjects[cadjectNow].material.color.setHex(cadjects[cadjectNow].oldColor);
if (intersects[0].object.cadNum)
cadjectNow = intersects[0].object.cadNum;
SELECTEDface=intersects[0].face;
if (cadjectNow)
cadjects[cadjectNow].material.color.setHex( selectColor );
document.getElementById('message1').innerHTML = cadjects[cadjectNum].cadNum;
///// Information about selected /////
var facestring = intersects[0].face.a + " " + intersects[0].face.b + " " + intersects[0].face.c;
if(intersects[0].face instanceof THREE.Face4)
{
facestring=facestring + " " + intersects[0].face.d;
}
copyGeometry=cadjects[cadjectNow].geometry;
//makeCopy(copyGeometry,cadjects[cadjectNow].position.x,cadjects[cadjectNow].position.y,cadjects[cadjectNow].position.z);
makeMiniSphere(intersects[0].point.x, intersects[0].point.y, intersects[0].point.z);
document.getElementById('message1').innerHTML = facestring;
//document.getElementById('message2').innerHTML = cadjects[cadjectNow].geometry.vertices[SELECTEDface.a].x + " " + cadjects[cadjectNow].geometry.vertices[intersects[0].face.a].y + " " + cadjects[cadjectNow].geometry.vertices[intersects[0].face.a].z;
document.getElementById('message2').innerHTML = intersects[0].point.x + " " + intersects[0].point.y + " " + intersects[0].point.z;
}
}
}
Here is the modify code:
if ( keyboard.pressed("right"))
{
document.getElementById('message1').innerHTML = mouseMode;
cadjects[cadjectNow].geometry.vertices[SELECTEDface.a].x+=10;
cadjects[cadjectNow].geometry.vertices[SELECTEDface.b].x+=10;
cadjects[cadjectNow].geometry.vertices[SELECTEDface.c].x+=10;
if(SELECTEDface instanceof THREE.Face4)
{
cadjects[cadjectNow].geometry.vertices[SELECTEDface.d].x+=10;
}
cadjects[cadjectNow].geometry.verticesNeedUpdate = true;
cadjects[cadjectNow].geometry.elementsNeedUpdate = true;
cadjects[cadjectNow].geometry.normalsNeedUpdate = true;
}
Thank you to everyone who has posted past questions and given the answers. By perusing past questions, I've been able to get this far---so you guys have already been a great help. Thanks in advance for help on this one. (As this is my first question to post here, any suggestions on how to better present a question are also more than welcome.)
Update (3/21/13)--I've migrated to r57 as suggested and the updated code is shown above. I've also debugged it so that it is working at least as well as it was before. So now the geometry is still visually being changed dynamically, but the intersect is not detecting the change properly. Thanks #WestLangley for the encouraging posts so far.
Now working correctly. Here is how I did it (thanks to Westlangley for guidance).
Upgrade Three.js to latest revision (r57 from r49).
Migrate my present code to work with r57.
Remove all former code attempting to update the object.
Added the following code to "modify object" section:
cadjects[cadjectNow].geometry.verticesNeedUpdate = true;
cadjects[cadjectNow].geometry.normalsNeedUpdate = true;
cadjects[cadjectNow].geometry.computeFaceNormals();
cadjects[cadjectNow].geometry.computeVertexNormals();
cadjects[cadjectNow].geometry.computeBoundingSphere();
I was having a similar issue with a modified BufferGeometry.
I got raycasting working by calling geometry.computeBoundingSphere() and geometry.computeBoundingBox() after modifying the array with vertex positions.
Setting verticesNeedUpdate was not needed.
Running r112.

How to dynamically add mesh from Object3D to new Object in THREE.js

I am a complete newbie to 3d programming and have been working with three.js just over a week now. I have managed to load multiple collada and obj files, and managed to get perspectives, trackballs, everything working from examples. However, now I am stuck and need some help. For reference you can see the file i'm posting about at the following url:
http://shaman-labz.appspot.com/webgl_loader_obj_mtl.html
This page is basically straight from the examples, except that i am loading up an obj file, which has all these objects as meshes. what i am working on now is after loading the obj, i am going to iterate through all the geometries in the object and extract them so that i can drop them in the scene one a time when i need them, or have them float around like bubbles or something. I was thinking to maybe try to use the fresnel example, but this is where i'm hitting up against the boundaries of my understanding, and some terminology escapes me.
My question is, when this runs, the object that returns and is added to the scene after load has all these gems in it together.
So instead of the following lines:
var loader = new THREE.OBJMTLLoader();
loader.addEventListener( 'load', function ( event ) {
var object = event.content;
object.position.y = - 100;
scene.add( object );
});
loader.load( 'obj/gems/24.obj', 'obj/gems/24.mtl' );
So what i'm doing is when the object returns, i look at the internals in debug/break mode, and i see that it's an object3D and object.children is an array of 25 meshes...and each of those meshes would be one of my 'gems' that i want to work with individually.
So here's where i get lost...when i grab the 'mesh' do i need to strip out the underlying geometry and create a new mesh?
on this page, you can see what i tried to accomplish:
http://shaman-labz.appspot.com/webgl_loader_obj_mtl2.html
the only major difference is in this section of code:
var loader = new THREE.OBJMTLLoader();
loader.addEventListener( 'load', function ( event ) {
var object = event.content;
var pos=0;
for(var i=0; i< object.children.length; i++){
var m = object.children[i];
var gem = new THREE.Object3D();
gem.name=m.name;
gem.add(m);
gem.position.x = -10;
gem.position.y = -10;
gem.position.z = pos;
scene.add( gem );
pos = pos - 10;
}
});
loader.load( 'obj/gems/24.obj', 'obj/gems/24.mtl' );
notice that only 13 of the 25 gems show up from the collection, and also note how they are scattered, indicating that they are somehow still linked to some higher-order relationships that i am unable to set with position properly (it's like each mesh is somehow offset relative to it's original positioning in the original object loaded...thinking this has to do with world matrix?

Resources