three.js selecting children of Object3D using raycaster.intersectObject - javascript

I am trying to make a series of cubes that can be clicked to highlight them. This will enable me to change their color or add a texture or manipulate them in some way. I have looked through the source code of all the interactive examples at https://threejs.org/examples/ and it appears that each example uses a slightly different way of creating and selecting objects in the scene. I am not used to using javascript though, so maybe I'm missing something simple.
I create an Object3D class named blocks to store all of the cubes
blocks = new THREE.Object3D()
I am using a for loop to create a 9 x 9 array of cubes starting at (0,0,0) coordinates with a slight gap between them, and add() them to blocks and add() blocks to the scene. example: (cube size 2,2,2)
function stack(mx,my,mz){
for (var i = 0; i < 9; i++){
line(mx,my,mz);
mz += 3;
}
}
function line(mx,my,mz){
for (var i = 0;i<9;i++){
var block = new THREE.Mesh( Geometry, Material);
block.position.x = mx;
block.position.y = my;
block.position.z = mz;
blocks.add(block);
mx+=3;
}
}
stack(mx,my,mz)
scene.add(blocks)
When I run this code, I can see them rendered. I use raycaster to .intersectObjects() which requires an array of objects. This is where I run into the problem of selecting just one object.
function onDocumentMouseDown(event) {
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 intersects = raycaster.intersectObjects(blocks.children, true);**
if (intersects.length > 0) {
intersects[0].object.material.transparent = true;
other code stuff blah blah blah
{
This will make all children clickable but they have the same .id as the first object created. so if I try to .getObjectById() in order to change something, it doesn't work.
I have tried to generate each element and add them to the scene iteratively instead of creating an object array to hold them and it still has a similar effect. I've tried storing them in a regular array and then using true parameter to recursively search the .intersectObject() array but it selects all of the objects when I click on it.
var intersects = raycaster.intersectObjects(blocks, true);
I have considered creating 81 unique variables to hold each element and statically typing an array of 81 variables (desperate option) but I can't find a secure way to dynamically create variable names in the for loop to hold the objects. This way was posted on stackoverflow as a solution to creating different named variables but it doesn't seem to create variables at all.
for (var i=0, i<9, i++){
var window["cube" + i] = new THREE.Mesh( Geometry, Material)
{
Main Question: How can I iteratively create multiple Mesh's (enough that statically typing each variable would be ill-advised) in a controllable way that I can select them and manipulate them individually and not as a group?

I think the reason why you met this problem is you reference same Material to build your Mesh, you did intersect a single object in blocks.children, but when you change some properties of the material others mesh who use the material would change too.
function line(mx,my,mz){
for (var i = 0;i<9;i++){
material = new THREE.MeshLambertMaterial({color: 0xffffff});
var block = new THREE.Mesh( Geometry, material);
block.position.x = mx;
block.position.y = my;
block.position.z = mz;
blocks.add(block);
mx+=3;
}
}
it works for me.

Related

STL loading and incorrect world matrix access

for a three.js project I have I have run into a few problems loading vertices from an STL and adequately converting them to world coordinates. It seems the matrix isn't being applied properly and, I think, it might be related to the loading mechanism itself.
loader.load( './assets/models/trajan_print.stl', function ( geometry ) {
var mesh = new THREE.Mesh( geometry, material );
mesh.name = "target";
mesh.position.set( 0, - 300, - 400 );
mesh.rotation.set( - Math.PI / 2, 0, Math.PI );
mesh.scale.set( 5, 5, 5 );
//mesh.castShadow = true;
//mesh.receiveShadow = true;
mesh.visible = false;
SCENE.add( mesh );
model.setTargets(mesh);
} );
the important function to note is the last one. model.setTargets(mesh). I'm interested in the vertices of the object in world coordinates and that's what that function does... kinda of:
setTargets(mesh){
this.matrixWorld = mesh.matrixWorld; //THIS WORKS, PRINTING IT REVEALS VALUES TRANSLATION/SCALE/ROTATION THAT MATCH THE MODEL'S
var buffer = mesh.geometry.attributes.position.array;
for(var i = 0; i < buffer.length /3; i = i + 3){
var point = new THREE.Vector3(buffer[i], buffer[i+1],buffer[i+2]);
point.applyMatrix4(this.matrixWorld);//DOES NOT WORK
this.unassignedVertices.push(point);
}
}
Now if I do the exact same operation outside of this function it will work as intended. This one is only called if this.unassignedVertices so it was my way around the fact that I needed to wait for the asynchronous load to happen.
insertParticle(part) {
var point = this.unassignedVertices.pop();
point.applyMatrix4(this.matrixWorld); //THIS WORKS BUT HERE BUT WHY?
part.setTargetPoint(point);
this.octree.add(part);
this.particles.add(part);
}
Problem number two, relates back to setTargets(mesh) I seem to only be loading around only half of the vertices from mesh.geometry.attributes.position.array. Now this can actually be caused by other parts in the code and I think that is something that falls outside the scope of a SO question so my question is if anything on that function could be responsible for it? Am I loading it improperly, am I converting it wrong, am I skipping points?
As for further context : the model loads and displays just fine if I remove the visible = false tag.
Ok so if anyone runs into this problem. The array will have duplicate positions as not all of them refer to vertices (probably). Simple case of if(position.x == ... cleans it right up to what's expected.

Drawing a line between moving objects

I'm trying to draw a line between two moving vertices. The vertex's drawing is stored in a variable called object, which has a position, which is a THREE.Vector3.
The line is created thusly:
var Line = function(scene, source, target){
var geometry = new THREE.Geometry();
geometry.dynamic = true;
geometry.vertices.push(source.object.position);
geometry.vertices.push(target.object.position);
geometry.verticesNeedUpdate = true;
var material = new THREE.LineBasicMaterial({ color: 0x000000 });
var line = new THREE.Line( geometry, material );
scene.add(line);
return line;
};
..., where source and target are vertices and the vertices get updated via:
vertex.object.position.add(vertex.velocity);
Now, I assigned the source.object.position and target.object.position to the line.geometry.vertices[0] and line.geometry.vertices[1] because I wanted one to update with the other. But instead, the vertex positions vary wildly from the line positions. The vertices are where they are, but the lines don't connect to the vertices.
How can I make the lines move with the vertices?
In your animation loop you have to set line.geometry.verticesNeedUpdate = true. Because every time after rendering it becomes false. jsfiddle example

ThreeJS - multiple meshes from a .json 3D file

I exported a .json from the online 3D editor and I'm trying to load it and instantiate 20 versions of it, like this example. My code is flawed bc all 20 versions are actually acting like the same object. Not sure why they're not being added to the scene as separate objects in their given x,z coordinates.
var serverObject;
var allBrains = []
var xCenter;
var zCenter;
var spacing = .2;
var newServ;
var objectLoader = new THREE.ObjectLoader();
objectLoader.load("asset_src/model.json", function(obj) {
//give it a global name, so I can access it later?
serverObject = obj
//see what's inside of it
obj.traverse(function(child) {
if (child instanceof THREE.Mesh) {
console.log(child)
}
})
//was trying to add transparency this way, but ended up
//going through the online editor to apply it
// var cubeMaterial1 = new THREE.MeshBasicMaterial({
// color: 0xb7b7b7,
// refractionRatio: 0.98
// });
//Do i need to instantiate my mesh like this, if so, how do I make sure that it gets the materials from the json? The json has 6 children each with a different material
// serverObject = new THREE.Mesh( obj, cubeMaterial1 );
//make 20 versions of the file
for (var i = 0; i < 20; i++) {
xCenter = Math.cos(toRadians(i * spacing))
zCenter = Math.sin(toRadians(i * spacing))
serverObject.scale.set(.09, .09, .09)
//this amount of offset is correct for the scale of my world
//i was going to use my xCenter, zCenter but tried to simplify it till it works
serverObject.position.set((i * .1), controls.userHeight - 1, i * .1);
allBrains.push(serverObject)
//I've attempted a number of ways of adding the obj to the scene, this was just one
scene.add(allBrains[i]);
}
// see that there are 20 meshes
console.log(allBrains)
});
The return of my last console log looks like this:
At the moment, you have a single object (serverObject) which you manipulate and add multiple times, but each iteration of the loop just modifies the same object, overriding previous parameters.
You need to clone your mesh, using the... clone() method. You'll then be able to modify the settings of that object (the copy), and each of the meshes will remain independent.
Alternatively, you could run the objectLoader.load method inside the loop to create the object multiple times from the JSON file, but it's probably a waste of resources.
Thanks to #jcaor for the clone() idea, here is the working code:
var objectLoader = new THREE.ObjectLoader();
objectLoader.load("asset_src/model.json", function(obj) {
//give it a global name, so I can access it later
serverObject = obj
//see what's inside of it
obj.traverse(function(child) {
if (child instanceof THREE.Mesh) {
console.log(child)
}
})
for (var i = 0; i < 20; i++) {
var tempNew = serverObject.clone()
xCenter = Math.cos(toRadians(i * spacing))
zCenter = Math.sin(toRadians(i * spacing))
tempNew.scale.set(.05, .05, .05)
tempNew.position.set(xCenter, controls.userHeight - 1, zCenter);
allBrains.push(tempNew)
scene.add(allBrains[i]);
}
console.log(allBrains)
});
This looks like a pointer issue to me.
In JavaScript you can think of variables like pointers that is why you don't have to assign types to variables and how functions can be variables and still work in normal computer science.
In this case you are assigning the same pointer to each slot in the array.
This is a super simple version of the problem. obj2.foo was never changed but because we changed obj.foo obj2.foo changed because of the var simply pointing to the same object.
var obj = {
foo : 1
}
var obj2 = obj;
obj.foo = 2;
console.log(obj2.foo);
What I would do is create a new object and popluate it with the information of the master object in you case "serverObject"
allBrains.push(serverObject)

Move Camera to make all objects fit exactly inside the frustum - three.js

EDIT : I have rephrased my question to help users with the same problem.
I have a three.js scene on which I have added some spheres.
I want to move the camera towards a specific direction until all the objects (which are randomly positioned inside the scene) are "fitting exactly" the user's screen.
I have found the answer to my problem!
1. I move the camera (zooming to the desired direction) inside a loop, and in every repeat I create a new frustum using the camera's matrix
2. I check if any of my spheres intersects with a plane of the frustum. If it does, that means that part of one of my objects is outside the frustum so I break the loop and move the camera to its last position.
The above might also works for any object (not only spheres) because every object has a boundingSphere that can be calculated (it might not be very precise the result though).
It also works when zooming out, you 'd just have to move the camera from the object until none of the has a negative distance from all the planes (negative distance means object is "outside" the plane of the frustum).
Code (only for zooming out - r72) :
var finished = false;
var camLookingAt = /* calc. */ ;
while( finished === false ){
var toDirection= camera.position.clone().sub(camLookingAt.clone());
toDirection.setLength(vec.length() - 1); // reduce length for zooming out
camera.position.set(toDirection.x, toDirection.y, toDirection.z);
camera.updateMatrix(); // make sure camera's local matrix is updated
camera.updateMatrixWorld(); // make sure camera's world matrix is updated
var frustum = new THREE.Frustum();
frustum.setFromMatrix( new THREE.Matrix4().multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ) );
for (var j = frustum.planes.length - 1; j >= 0; j--) {
var p = frustum.planes[j];
for (var i = myMeshSpheres.length - 1; i >= 0; i--) {
var sphere = new THREE.Sphere(myMeshSpheres[0].position.clone(), myMeshSpheres[0].radius);
if( p.distanceToSphere(sphere) < 1 ){ // if is negative means part of sphere is outside plane/frustum
finished = true;
}
}
}

Three js: Transparent object adds color to DOM underneath the canvas

I'm working on a project where i would like to have objects floating around a webpage,
you can see the progress here.
I'm now using a 2d plane to float the objects around, it's the same size as the div behind it and i've set the plane's opacity to 0.
This creates the desired effect but there is one problem occurring. The objects float around the div and become invisible when behind the plane, that's good. The renderer is transparent renderer = new THREE.WebGLRenderer({alpha: true}); so i can see the DOM underneath, that's good to. But the transparent plane that hides the floating objects adds a white color to the DOM. This only happens to DOM elements behind the plane. When dom behind the canvas is not behind the plane the proper colors are shown.
This is the code to create the plane:
var plane = new THREE.Mesh(new THREE.PlaneGeometry(160, 400), new THREE.MeshNormalMaterial());
plane.overdraw = true;
plane.position.x = 0;
plane.position.y = 0;
plane.position.z = -100;
plane.material.opacity = 0;
edit:
The problem was caused by the material type. By using a THREE.MeshBasicMaterial() instead of a THREE.MeshNormalMaterial() and adding a color to the material plane.material.color = '0xffffff' the problem got solved! The final code to create the plane looks like this:
var plane = new THREE.Mesh(new THREE.PlaneGeometry(160, 400), new THREE.MeshBasicMaterial());
plane.overdraw = true;
plane.position.x = 0;
plane.position.y = 0;
plane.position.z = -100;
plane.material.color = '0xffffff';
plane.material.opacity = 0;
Hope this helps people facing the same problem.

Categories