When I want an object to be on a separate layer, I find that I must set the layers of all its subsets to truly take effect. Why can’t I set the current object directly, and then all its subsets can inherit this layer?
In the figure, it can be seen that when the layer of the current object is not on the same layer as the camera, its child nodes will still be recursive, which forces me to set all its child nodes to be on another layer
Mugen87
January 26, 2024, 11:52am
2
This topic is discussed at GitHub:
opened 11:09PM - 20 Mar 20 UTC
Enhancement
## Description of the problem
If I have the following object hierarchy:
```
…
Scene (layer = default)
- Camera (layer = 0,1)
- Other camera (layer = default)
- Group (layer = default)
- Group (layer = default)
- Group (layer = 1) <-- this object doesn't match "Other camera"
- Object3D (layer = default) <-- but its children will still be rendered
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
...more objects...
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
(default layer, as defined by Three.js = 0)
```
I would expect "Other camera" to render any of the Object3Ds in the non-matching Group. However, unless I specifically set each of them to layer 1, they are still rendered. I would like to be able to mark the layer 1 Group to a endpoint in the layer calculation:
```
default = 0
Scene (layer = default)
- Camera (layer = 0,1)
- Other camera (layer = default)
- Group (layer = default)
- Group (layer = 1, layers.endpoint = true) <-- additional property of Layers called "endpoint"
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
...more objects...
- Object3D (layer = default)
- Object3D (layer = default)
- Object3D (layer = default)
```
As I understand it, this could be easily implemented in the recursive rendering function:
```
function renderObject(object, ..., ignoreLayers) {
if (!object.visible) return;
// Since ignoreLayers means that one of this object's parents
// was both shown and a layer endpoint, it is safe to assume
// this object should be shown too
if (ignoreLayers || object.layers.test(camera.layers)) {
...do the actual rendering...
for (const child of object.children) {
renderObject(child, ..., ignoreLayers || object.layers.endpoint)
}
}
}
```
This solution is very flexible. If the user doesn't set `Layers.endpoint`, child objects of a non-matching parent will still be rendered. However, this solution allows for layers to be managed at an arbitrarily high level. This is great for object oriented purposes, since before the change, the following hierarchy would be problematic
```javascript
// Tools.js
import Hammer from './Hammer.js';
import Shovel from './Shovel.js'
class Tools extends THREE.Group {
constructor() {
super();
this.layers.set(1);
// This would be rendered by a camera set to layer 0 only :(
this.hammer = new Hammer();
this.shovel = new Shovel();
}
}
// Hammer.js
class Hammer extends THREE.Mesh {
constructor() {
// Load complex GLTF model with many individual objects for each part and each material
super(geo, mat)
this.add(other part of mesh) // Each of these would have to have its layers set separately
this.add(other part of mesh)
this.add(other part of mesh)
}
}
// Shovel.js
class Shovel extends THREE.Mesh {
constructor() {
// Load complex GLTF model with many individual objects for each part and each material
super(geo, mat)
this.add(other part of mesh) // Each of these would have to have its layers set separately
this.add(other part of mesh)
this.add(other part of mesh)
}
}
```
It is not reasonable to expect the user to traverse through each child object of the Hammer, Shovel, and all other child classes, setting each `Layers` to a specific value when it could be done at a high level:
```javascript
// Tools.js
import Hammer from './Hammer.js';
import Shovel from './Shovel.js'
class Tools THREE.Group {
constructor() {
super();
this.layers.set(_LAYER);
this.layers.endpoint = true;
// This would **not** be rendered by a camera set to layer 0 only :)
this.hammer = new Hammer();
this.shovel = new Shovel();
}
static get layer() {
return _LAYER; // Cameras can use this.layer.enable(tools.layer) to show all tools
}
}
const _LAYER = 1;
// Hammer.js and Shovel.js don't need to know what layer was assigned in Tools.js. It would be hard for them to know even if they wanted to, because importing Tools.js would create a circular reference.
```
To explain for clarity, the `Renderer` would see the `endpoint` set to `true` and then use the `ToolBelt`'s layers object for all of its children
## Other solution
Make `Layers` behave like `object.visible` does. If a parent's layers don't match, don't render its children. This is simple but less flexible.
## Other other solution
`THREE.Object3D.layers` works differently than `THREE.Group.layers`. Children of a `THREE.Group` would not be rendered if the group's layer didn't match whereas children of a `THREE.Object3D` (or `THREE.Mesh`, etc.) would have their own layers tested.
## Note
I'm not 100% sure if the word `Layers.endpoint` is the best property name. Here are some other ideas:
- `Layers.isEndpoint` (Boolean)
- `Layers.final` (Boolean)
- `Layers.override` (Boolean)
- `Layers.mode` (Constant enum)
- `Layers.grouped` (Boolean)
- `Object3D.renderChildrenIfInvisible` (Boolean, default true)
##### Three.js version
- [ ] Dev
- [x] r114
- [ ] ...
All versions since `Layers` added, all browsers, all OSes.
let deepSetLayer=( obj, layer)=>{
obj.layer.set(layer) ;
obj.traverse(e=>e.layer.set(layer));
}