I have a 3D-model that I'm loading through the JSONloader of three.js.
I want to make it possible that I can click on one of the links of the chain. The model is made in Blender and each link of the chain is a seperate node/object.
For example it should be possible when clicking on a blue link, an alert is fired with 'blue', same for green, etc...
Is that even possible in three.js or should I use other solutions?
Thanks!!
It's absolutely possible with Three.js.
There are two parts to your question:
How to pick objects on screen?
How to identify which one is picked?
Part one is easily answered in many other places. Threejs.org includes a very nice example demonstrating that. It can be found here.
The relevant portion of the code is as follows:
var raycaster, mouse = new THREE.Vector2()
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
// Do something!
}
intersects is an array so you can get the first intersect (i.e. the one in 'front') with intersects[ 0 ] or specifically the Object3D node with intersects[ 0 ].object.
What you do with that is up to you. Based on your example, I would, when creating each object, add a custom property to the object such as:
object.msg = "This is a test message!"
This could then be logged when clicking the object:
var raycaster, mouse = new THREE.Vector2()
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
if (intersects[0].object.msg) {
console.log(intersects[0].object.msg)
}
}
An alternative would be to create an object with messages and UUIDs:
var messages = {
"61f2175b-6908-48dc-9131-55ffa11c8581": "Hello world!"
};
var raycaster, mouse = new THREE.Vector2()
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
var uuid = intersects[0].object.uuid;
if (messages[uuid]) {
console.log(messages[uuid])
}
}
Hopefully this gives you some ideas about how to tackle this problem!
Related
I've dug around looking for a solve for this - but couldn't find the answer on here. I have a gltf/glb model loading into three js as such:
createScene: function() {
this.renderer = new Three.WebGLRenderer({
antialias: true,
alpha: true
});
let container = document.getElementById('container');
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setClearColor(new Three.Color('#fff'));
this.renderer.setClearAlpha( 0 );
//this.render.shadowMap.type = Three.PCFSoftShadowMap;
container.appendChild(this.renderer.domElement);
},
createCamera: function() {
this.camera = new Three.PerspectiveCamera(6, container.clientWidth/container.clientHeight, 1, 1000);
this.camera.position.set(0, 0, 40);
},
createShape: function() {
let me = this;
const loader = new GLTFLoader();
loader.load( 'model/fbf3.glb', function ( gltf ) {
me.bottle = gltf.scene
me.scene.add( gltf.scene );
gltf.scene.traverse(function(obj) { obj.frustumCulled = false; });
me.pivot = new Three.Group();
me.pivot.position.set( 0.0, 0.0, 0 );
me.bottle.add( me.pivot );
console.log(gltf.scene);
console.log(gltf.scene.children[1]);
//me.pivot.add( me.bottle );
me.animate();
}, undefined, function ( error ) {
console.error( error );
});
},
I found a post that said to loop through the scene and be sure to add frustumCulled to false - so that's added in ( and when I log the child, that objects frustumCulled is set to false ). In order to have a label on my object easy to map and also take on a different material/glossiness i've created another object in the group that is just slightly in front of my other object on the yaxis. When it is facing the camera, it works well - however, it is when the object rotates is where it disappears. Working:
Rotate enough, and gone:
Is there a setting in threejs that I need to add to be sure the render order is correct? Or is it something wrong with my object set up in Blender? Ideally I wouldn't have to UV wrap the whole bottle as one object and add the label to the bottle texture because I want the label to have less specularity ( that a word? ). Any help would be appreciated.
Solved - the tutorial is slightly old ( 2+years ) and now Blender ( the latest version 2.9+ ) has the Principled BSDF Shader baked in. Instead of using the glTF-Blender-Exporter-Master pbr appended shaders from the listed tutorial - I instead used the Principled BSDF Shader - linked my material to it's Base Color and linked that to the Material Output ( all in the node/now Shader Editor. No issues anymore.
I'have been trying to intersect only one material among 3 materials of the loaded model onclick using their ID's, however, the control continues to the entire model. Let me know if it is possible to intersec on basis of material ID's
var intersects = raycaster.intersectObjects( scene.children );
for ( var i = 0; i < intersects.length; i++ ) {
object.scene.traverse(function(child) {
if ( child instanceof THREE.Mesh ) {
if (child.material.name == "heap") {
child.material.color = new THREE.Color( 0x00ff00 );
}
}
})
}
You can update the material of a raycast object by means of accessing the material property on the object of an intersection result.
Something to keep in mind is that, when you do update the properties of a material object, you need to set needsUpdate = true so threejs knows that the material needs updating internally.
Consider the following code that achieves what you're after:
// If only interested in one intersection, you can use .intersectObject()
var intersection = raycaster.intersectObject( scene.children );
// If intersection found, update material in some way
if(intersection) {
// Extract object and material from intersection
var object = intersection.object;
var material = object.material;
/*
Manipulate your material as you need to
material.color = new THREE.Color( 1, 0, 0 );
*/
// Tell threejs that the material was changed and
// needs to be updated internally
material.needsUpdate = true;
}
I have a json model exported from Revit using RvtVa3c. I added the model to the scene using ObjectLoader.
var loader = new THREE.ObjectLoader(manager);
loader.load( 'mesa.js', function ( object ) {
scene.add(object);
}
Then I tried both...
// From Va3cViewer.js
var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
var intersects = raycaster.intersectObjects( targetList );
and...
// From three js example code
// have onMouseMove event and raycaster initialized elsewhere in the code
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children );
Both didn't find any intersect. I have read one post about calling computeFaceNormals() on geometry before trying to find intersect, but no children have geometry when I debugged the code. Is there no way to find intersect with object3d? Please help. Many thanks in advance.
Source here.
Just Found this post.
Adding the second argument of "true" seemed to do the trick.
var intersects = raycaster.intersectObjects( targetList, true );
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've tried improving rendering time on my project by creating meshes and putting them as part of a larger geometry, and having just that single geometry as the object I add to the scene. I thought that I would still be able to manage picking of objects by having an array of the original meshes, and pass those to the raycaster. I used the following code:
var vector = new THREE.Vector3( ( loc_x / window.innerWidth ) * 2 - 1, - ( loc_y / window.innerHeight ) * 2 + 1, 0.5 );
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
var objects = [];
var i = active_regions.length;
while (i--) {
objects = objects.concat(active_regions[i].mesh_entities);
}
var intersects = raycaster.intersectObjects( objects );
if ( intersects.length > 0 ) {
console.log("Intersection: " + intersects);
}
So in the above code, active_regions contains the original individual meshes, and I create an array on the fly to specify which objects I want to select from. Unfortunately intersects comes up empty.
If I modify my project slightly so that I have all those mesh_entities added to the scene individually, then the above code works and I can successfully select objects. Unfortunately, the whole scene then renders slowly.
What's a good way (or some good ways) for me to successfully check for intersection with the ray, without slowing down my rendering?
Thanks!
You need to update the Matrices for the objects not in the render scene manually as its done as part of the render process so if you are using your ghost scene, you don't need to render it, just update the matrices before doing the intersection:
scene_ghost.updateMatrixWorld(true);
I solved this by having a ghost scene. Essentially, I added all objects to the ghost scene as their individual meshes, and then when I use raycaster it works.
However, I had to use functions along these lines:
function flip_render_ghost(yes) {
if (yes == true) {
scene_ghost.add(camera);
render_ghost = true;
} else {
scene.add(camera);
render_ghost = false;
}
render();
}
function render() {
if (render_ghost == true) {
renderer.render( scene_ghost, camera );
} else {
renderer.render( scene, camera );
}
}
Whenever I am about to check for collisions, I flip to rendering ghost scene, check for hits, then flip back to normal rendering.
Edit: I have since discovered that objects cannot belong to multiple scenes (though geometries can be shared). So what I have done is created simple meshes for the picking scene. This requires more memory, but gives the option of having a simpler mesh to use for selection for faster picking. Also, it seemed to work for me to send the children of the ghost scene itself to the raycaster. You may need to, like me, add a property to each object in the ghost scene that references the main object you are trying to pick.
I am doing something similar in this and have verified that you need to render the scene to do proper raycasting. It is easy to optimize this however to just render both screens and have one clear over the other. You should be able to change your code to this, granted the second render call will clear over the first screen:
function render() {
renderer.render( scene_ghost, camera );
renderer.render( scene, camera );
}