Query Selector for Object3D

Hello everyone!

I wrote some code to perform selection queries, similar to document.querySelectorAll, on an Object3D and its children.

How to use it

scene.querySelectorAll('Mesh'); // Selects all the meshes in the scene.
scene.querySelectorAll('.even'); // Selects all Object3D that have the 'even' tag.
scene.querySelectorAll('[name=box]'); // Selects all Object3D that have 'box' as their name.
scene.querySelectorAll('[name*=box]'); // Selects all Object3D that have 'box' anywhere in their name.
scene.querySelectorAll('[name^=box]'); // Selects all Object3D that have a name starting with 'box'.
scene.querySelectorAll('[name$=box]'); // Selects all Object3D that have a name ending with 'box'.
scene.querySelectorAll('Mesh.even'); // Selects meshes with both 'Mesh' type and 'even' tag.
scene.querySelectorAll('Group .even'); // Selects all Object3D with 'even' tag that are children of a 'Group'.
scene.querySelectorAll('Group > .even'); // Selects all direct children with 'even' tag under a 'Group'.
scene.querySelectorAll('Mesh, SkinnedMesh'); // Selects all meshes and skinned meshes in the scene.

Comparison

for (const obj of scene.querySelectorAll('Mesh[name*=box]')) {
	obj.castShadow = true;
}
scene.traverse((obj) => {
	if (obj.type === 'Mesh' && obj.name.includes('box')) {
		obj.castShadow = true;
	}
});

Implementation

I currently added it in my three.ez library (if you use three.js vanilla I suggest you take a look at it), but I would like to release a package soon for anyone who wants to use it.

I just have to decide whether I will patch the Object3D to be able to use it as in three.ez, or whether there will simply be a querySelectorAll(target, query) function.

How to add tags

In three.ez each Object3D has a property tags of type Set already instantiated.

obj.tags.add('even').add('customTag');

Performance

I tried to make it as fast as possible, all 3D objects are iterated once.
Obviously the query has to be parsed and then executed on all objects.

Code

You can find code here:

Live example

Any suggestions and feedbacks are appreciated.

2 Likes

Great idea!

  • For queries like scene.querySelectorAll('.even'), where are tags stored? I’m guessing each object3D would have a tags property that’s a string array?
  • Can you write more comples queries such as scene.querySelectorAll('Group.house.green > Mesh[name=window].glass.broken')?

In terms of suggestions, the obvious thing to do is extend it to support other CSS selectors / pseudoselectors so you could do scene.querySelectorAll(':not(SkinnedMesh)'), or scene.querySelectorAll('Mesh[material=MeshBasicMaterial]') etc

1 Like

yes, but it’s not a string array. It’s a Set object.

obj.tags.add('even').add('customTag');

Yes :slight_smile:

Thanks for the suggestions :slight_smile:

To check the material I think is a bit complex, I have to think about it.

Great idea! Perhaps an optional parameter that takes a callback that performs an action on each object in the results. For example,

scene.querySelectorAll('Mesh[name*=box]', (obj) => {
  obj.castShadow = true;
})

It should be possible to declare this in JSON and possible load from a file. For example

{
  'Mesh[name*=box]' : {
   castShadow: true
  }
}

A typescript interface could make this more type safe.

1 Like

Thank you very much for your suggestions :slight_smile:

I like the callback idea, it would avoid iterating all the elements again.

For the json instead, why would you prefer it over making your own function like that?

function query(obj) {
  obj.querySelectorAll('Mesh[name*=box]', (x) => x.material.opacity = true);
}

query(mesh);

PS. Do you think it would be better to write

scene.querySelectorAll(':not(SkinnedMesh)')

or

scene.querySelectorAll('!SkinnedMesh')

and to control a type do this

scene.querySelectorAll('[material==MeshBasicMaterial]')

Loading from JSON allows minor changes to be made without recompiling the code since the file could be downloaded from a server.

To answer your questions, I think this is more friendly to a JS developer

scene.querySelectorAll('!SkinnedMesh')

As for filtering by type, my first thought would be to match against object .type. Every three object seems to have this.

scene.querySelectorAll('[material.type==MeshBasicMaterial]')

I guess your syntax is the shortened form.

Loading from JSON is a good idea.
Will you use this functionality for any of your applications? It would be a good way to test a real use.

Since you’re replicating CSS queries and document.querySelector syntax, I would expect the syntax to match CSS syntax.
scene.querySelectorAll(':not(SkinnedMesh)') has my preference, I don’t think scene.querySelectorAll('!SkinnedMesh') is friendlier towards JS devs as it’s not what most people will be used to writing. If the API uses the same name as the DOM API and the rest of its syntax is CSS like, switching to a different syntax for a single use case will create unecessary confusion.

1 Like