Access A-Frame element from three.js object3D - three.js

From A-Frame, you can access an entity's object3D with .object3D or .getObject3D(), is there a way to do the reverse, when you are working with three.js objects, to get the element an object belongs to? Perhaps adding the parent element to userdata?

A-Frame attaches the entity to object3D as .el.
For example with an entity with a mesh:
document.querySelector('a-entity').getObject3D('mesh').el;
It also attaches to the group object3D:
document.querySelector('a-entity').object3D.el;
This is done during setObject3D().
For object3Ds that A-Frame does not directly manage and does not have a direct associated A-Frame entity, then we can walk up the scene graph to find the closest one with object3D.traverseAncestors.

I wrote a small function that traverses upwards using traverseAncenstors and returns an element if there is one.
function getParentEl( o ){
let p;
o.traverseAncestors( function( a ){
if( p === undefined && a.el ){
p = a;
}
});
if( p ){
return p.el
}
return;
}

Related

Checking if there is an object in the Scene THREE.JS

is there any way to know if there is an object in the scene? is there any function like isEmpty() or something ?
The THREE.Scene is the root of a scene graph, and if scene.children.length > 1 then it has at least one child / object in that scene. Whether any children are things that can be renderered depends on their type. Lights and Cameras affect the scene but are not rendered themselves. The easiest way to check for objects that could be rendered is to traverse the scene and search for Mesh instances:
let hasMesh = false;
scene.traverse(function (object) {
if (object.isMesh) hasMesh = true;
});
console.log(hasMesh ? 'Found meshes!' : 'No meshes.');
Depending on the use case you may want to check for other types, or to check properties like object.visible if you are showing/hiding objects dynamically.

raycasting objects in a group + objects in the group within it

what I'm trying to achieve: at first, you're only able to select objects within the group "interactable". when you click on an "interactable" object, you can now select any object within group "master". (you're picking up an interactable then selecting an object to place it on.)
"master" contains the group "interactable". right now I'm only able to switch from selecting within "interactable" to selecting within "master" without "interactable".
here's my code:
function render() {
raycaster.setFromCamera(mouse, camera);
if (obj_selected) {intersects = raycaster.intersectObjects(master.children)}
else {intersects = raycaster.intersectObjects(interactable.children)}
if (intersects.length > 0) {
if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
INTERSECTED = intersects[0].object;
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
INTERSECTED.material.emissive.setHex( 0xf4425f );
} else {
if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
INTERSECTED = null;
}
renderer.render(scene, camera);
}
I hope my explanation wasn't too confusing. thanks in advance.
never mind, I figured it out!
so instead of putting things in groups, I added meshes to an array. I have three arrays: master, static, and interactable. if I get the intersections for master, then I can access all the objects in my scene without messing with groups.
conclusion: I don't think I should use groups when I'm simply categorizing things.

Control multiple cameras with the same controls

I have two different threejs scenes and each has its own camera. I can control each camera individually with a corresponding TrackballControls instance.
Is there a reliable way to 'lock' or 'bind' these controls together, so that manipulating one causes the same camera repositioning in the other? My current approach is to add change listeners to the controls and update both cameras to either's change, but this isn't very neat as, for one, both controls can be changing at once (due to dampening).
I believe it should work if you set the matrices of the second camera to the values of the first and disable automatic matrix-updates of both cameras:
camera2.matrix = camera1.matrix;
camera2.projectionMatrix = camera1.projectionMatrix;
camera1.matrixAutoUpdate = false;
camera2.matrixAutoUpdate = false;
But now you need to update the matrix manually in your renderloop:
camera1.updateMatrix();
That call will take the values for position, rotation and scale (that have been updated by the controls) and compose them into camera1.matrix, which per assignment before is also used as the matrix for the second camera.
However, this feels a bit hacky and can lead to all sorts of weird problems. I personally would probably prefer the more explicit approach you have already implemented.
Question is why are you even using two camera- and controls-instances? As long as the camera isn't added to the scene you can just render both scenes using the same camera.
Is it possible to use the Observer or Publisher design patterns to control these objects?
It seems that you are manipulating the cameras with a control. You might create an object that has the same control interface, but when you pass a command to the object, it repeats that same command to each of the subscribed or registered cameras.
/* psuedo code : es6 */
class MasterControl {
constructor(){
this.camera_bindings = [];
}
control_action1(){
for( var camera of this.camera_bindings ){
camera.control_action1();
}
}
control_action2( arg1, arg2 ){
for( var camera of this.camera_bindings ){
camera.control_action2( arg1, arg2 );
}
}
bindCamera( camera ){
if( this.camera_bindings.indexOf( camera ) === -1 ){
this.camera_bindings.push( camera );
}
}
}
var master = new MasterControl();
master.bindCamera( camera1 );
master.bindCamera( camera2 );
master.bindCamera( camera3 );
let STEP_X = -5;
let STEP_Y = 10;
//the following command will send the command to all three cameras
master.control_action2( STEP_X, STEP_Y );
This binding is self created rather than using native three.js features, but it is easy to implement and can get you functional quickly.
Note: I wrote my psuedocode in es6, because it is simpler and easy to communicate. You can write it in es5 or older, but you must change the class definition into a series of functional object definitions that create the master object and its functionality.

Selecting an object in threejs?

I think the problem might be because it's a loaded object and not a "created" geometry, but I want to be able to select an object and show that it is selected, like in this example.
I load multiple objects in and I keep track of an index for each object (so the first object loaded has objIndex = 0, etc.) and I know that my code recognizes when I "mouse down" from the console. However, it says that intersects.length = 0 so the rest of the function is skipped.
I'm also not sure what "intersects.length" is actually taking the length of.
So I suppose my questions are:
What is "intersects.length" taking the length of?
Why do my objects have an "intersects.length" of 0?
What are some things I could do so that the objects are recognized?
Here's the relevant code:
function onDocumentMouseDown(event) {
event.preventDefault();
// default action of event will not be triggered
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5).unproject(camera);
// used to pass 3D positions and directions
var raycaster = new THREE.Raycaster(camera.position,
vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(objects);
console.log('Mouse is down.');
console.log(intersects.length);
if (intersects.length > 0) {
controls.enabled = false;
SELECTED = intersects[0].object;
var intersects = raycaster.intersectObjects(plane);
offset.copy(intersects[0].point).sub(plane.position);
container.style.cursor = 'move';
console.log('Clicked object: ' + object.name);
}
}
If you need to see more, let me know! Thank you. :)
Answering your question:
Here is an interesting read on raycast to help clarify raycasting:
http://soledadpenades.com/articles/three-js-tutorials/object-picking/
So what raycasting basically do is to (really) cast an imaginary ray. When you "mouse down" your program will, so called, cast a straight-line array into the scene. Now to answer your question:
For question one(1) and two(2) : Now when you cast a ray, it may or may not intersect an object. For example if you click into an empty space, the ray will not catch an object. However, as the name suggest, a raycast cast a ray and it may also hits multiple objects along the way. For example if you have one object and another object positioned directly behind the first object, casting a ray into the first object will also intersects the second object. The ray cast will "catch" both objects and put in the two objects into into the array "intersects". Therefore, the command
if (intersects.length > 0)
stated that if the raycast "catches" and object, do these. Moreover, it keeps on calling intersects[0] to refer to the first objects in the intersects array. In this case the most front object that the raycast catches. You can test my answer by creating hundreds of objects and state that, for every member in the intersects array, you change the color to red(for example). You will see that if the raycast catches multiple objects, all the objects will turn into that color. Hope for the first two question!
I am not sure of what you are asking for the third question. Can you clarify further? One way for the object to be recognized is to put them into the scene and put a raycast on the scene. However, I am not too sure of what you are asking to give you answer that I think you want.

How to select a root Object3D using Raycaster

I have a parent Object3D that has child meshes. How can I use Raycaster to select only the root parent object?
my example
If you have a parent Object3D that has multiple child meshes, and you want to select the parent by raycasting, you can do the following:
Add the parent object to the array of objects:
var objects = [];
...
objects.push( root_parent_object );
Add to each child object a pointer to the root parent object:
child.userData.parent = root_parent_object;
Pass in the recursive flag to intersectObjects().
var intersects = raycaster.intersectObjects( objects, true );
Now when the raycaster intersects a child object, you can obtain the root object.
three.js r.68
I had similar need using collada objects exported from Sketchup. Meshes have a trail of multiple parents ahead of them so all I did was this:
if (intersects.length >0){
par = intersects[ 0 ].object.parent;
while(par.type !== "Group"){
par = par.parent;
}
groupName = par.name;
}

Resources