KHR_node_visibility Draft Proposal
As discussed in the Interactivity DTSG, there should be a way to control node visibility through KHR_animation_pointer and/or KHR_interactivity workflows.
Maybe
hiddenshould berequired, but whendefaulting tofalse, this may not be strictly necessary.
Having a default value is exactly why it's not required. So to make node's visibility controllable, an empty extension object is all that's needed.
This sort of "Inverted Boolean" is a design pitfall that I usually lobby against, for the sake of non-programmer users who are sometimes confused by double-negatives.
Please take a moment and really think through the implications of changing this to parameter visible, default true, instead of parameter hidden, default false. Think about the Boolean flag in isolation, where its value may appear in code or data separated from the input parameter name that appears here. Normally a value of true appearing in code or data means YES, do it, show it, use it, go for it. Normally a value of false means no, don't go, don't show, get that thing out of here.
Currently this draft's hidden parameter is asking users to accept a value of true as indicating "Don't show that now", and a value of false as "Yes, put it in there please." That's backwards, and causes confusion wherever the value appears independently from the parameter being manipulated.
Apologies if this is just a pet peeve of mine. I've been opposed to this kind of Boolean use since the late 90's when some software I work on sported a checkbox on the UI that looked like this:
- [ ] Don't overwrite the file
There was a similarly named DontOverwriteFile Boolean in the corresponding API. How many users got confused by turning the checkbox off thinking it would keep their file safe from being overwritten, we'll never know, but we know a few were quite vocal to tech support about it. I've been opposed to inverted Boolean design ever since.
@emackey That was also the first thing that I thought. And I fully agree that this is an antipattern: Nobody wants things like boolean doNotIgnoreNonPositiveExclusions 🤪 . But I (have to) assume that this was an intentional and conscious choice, most likely related to a form of backward compatibility for systems that do not know the extension. But I'd also be curious about the exact reasoning for not using a visible with default:true. In which cases could this cause trouble or have undesired effects?
Consider the following:
- All nodes are visible in unextended glTF.
- An empty extension object must not change the default visibility.
- Subject to engine design, it may be an open question whether "hiding a node (tree)" is a positive or negative action.
Out of all possible combinations of the property direction (positive/negative) and the default value (true/false), only two options remain:
-
hiddenwith defaultfalse -
visiblewith defaulttrue
An optional boolean JSON property has three states:
-
undefined -
true -
false
If undefined and true are supposed to be semantically the same, then distinguishing between them may require special error-prone handling not needed for other glTF boolean properties found in the core spec. For example in JS:
let ext1 = {visible: false}
let ext2 = {visible: true}
let ext3 = {};
// Naive check
if (ext1.visible) {} // ok
if (ext2.visible) {} // ok
if (ext3.visible) {} // wrong
// Inverted naive check
if (!ext1.visible) {} // ok
if (!ext2.visible) {} // ok
if (!ext3.visible) {} // wrong
// Accurate check
if (ext1.visible !== false) {} // ok
if (ext2.visible !== false) {} // ok
if (ext3.visible !== false) {} // ok
//
let ext4 = {hidden: false}
let ext5 = {hidden: true}
let ext6 = {};
// Naive check
if (ext4.hidden) {} // ok
if (ext5.hidden) {} // ok
if (ext6.hidden) {} // ok
// Inverted naive check
if (!ext4.hidden) {} // ok
if (!ext5.hidden) {} // ok
if (!ext6.hidden) {} // ok
The same logic applies to other languages that may not even have a concept of an undefined boolean. DCC tools do not have to present the flag literally so the double-negative UI could be easily avoided in user-facing tools.
Yes. This is the one I'm advocating for here:
- Property name
visiblewith default valuetrue
Yes, JavaScript and JSON have some interesting wrinkles when distinguishing between undefined, null, false, 0, and the empty string. My asking for a default value of true will feed into this problem, but it's well-known by JS developers (and I have experience with how these issues were routinely handled from my time on the early Cesium team and other WebGL projects). We do already have a case where undefined defaults to infinity (attenuation distance if I recall), and another case where undefined defaults to 0.5 (which happens in alphaMask I believe). So, this should not place any undue burden on developers. It will be immediately apparent if some client application defaults visibility to false by accident.
it may be an open question whether "hiding a node (tree)" is a positive or negative action.
In your code samples, sure. But I'm talking simple human understanding here. I'm talking about people trying to learn foreign design details of unfamiliar standards. There's no wiggle room here to say that the default behavior of displaying ordinary glTF nodes is somehow a negative action, and that hiding them from view is positive. It's clearly inverted. The cleaner design is to use true as the default state here for displaying visible nodes.
JavaScript ... interesting wrinkles
That's an interesting euphemism for "irrecoverably broken clusterf..." 🙂
(There are probably hundreds and thousands of if (!something) throw "Something is undefined!!!111"); out there...)
I already suspected that the reasoning was in some form of "backward compatibility". And this indeed is the reason here - namely, via the statement
An empty extension object must not change the default visibility.
which causes a problem (only) in combination with the case of
let ext3 = {};
if (!ext3.visible) {} // wrong
Now... one could argue about that. One could call this "an implementation error". And something like this is less likely to happen for experienced developers who know the ... wrinkles... of JS. However, there's certainly that trade-off between the more intuitive approach of visible with default:true, which is tailored for humans, and the less error-prone approach of hidden with default:false, which is tailored for developers. And I do understand the reasoning for both sides.
If we chose to use the visible/default:true approach, then it might be possible to alleviate the problem by emphasizing the special case in the spec text. Maybe as an "Implementation Note" containing the relevant part of the example code shown above. But I don't have a really strong opinion here.
The important thing from a functionality perspective is that it must be possible to hide a subtree of the nodes with a single value change, since we don’t provide constructs for iterating an entire tree, it would inefficient to do so, and it could lose important state at lower levels (such as where a diorama is to be made visible and the animation within the diorama involves flipping the visibility state of some diorama elements – we must be able to toggle on the visibility of the diorama as a whole while allowing the animation track to have unfettered control of visibility within the diorama).
So, if you have “visible” with default “true”, then the logic for whether any node should display must be “display node if and only if node and all parents have visible=true (including by default)”.
This means that a node X may be hidden despite having “visible=true”. To me, it feels slightly more intuitive to reason that “node X is visible even though hidden=false, because its parent is hidden” than it is to reason that “node X is invisible even though visible=true, because its parent is invisible”, but I don’t have a strong opinion here. Mostly I want to point out that either way people will have to learn the design detail of how the effect of the property propagates in the hierarchy, which is the most important thing.
Dwight
From: Ed Mackey @.> Date: Thursday, June 13, 2024 at 8:49 AM To: KhronosGroup/glTF @.> Cc: Dwight Rodgers @.>, Review requested @.> Subject: Re: [KhronosGroup/glTF] KHR_node_visibility Draft Proposal (PR #2410)
EXTERNAL: Use caution when clicking on links or opening attachments.
Yes. This is the one I'm advocating for here:
- Property name visible with default value true
Yes, JavaScript and JSON have some interesting wrinkles when distinguishing between undefined, null, false, 0, and the empty string. My asking for a default value of true will feed into this problem, but it's well-known by JS developers (and I have experience with how these issues were routinely handled from my time on the early Cesium team and other WebGL projects). We do already have a case where undefined defaults to infinity (attenuation distance if I recall), and another case where undefined defaults to 0.5 (which happens in alphaMask I believe). So, this should not place any undue burden on developers. It will be immediately apparent if some client application defaults visibility to false by accident.
it may be an open question whether "hiding a node (tree)" is a positive or negative action.
In your code samples, sure. But I'm talking simple human understanding here. I'm talking about people trying to learn foreign design details of unfamiliar standards. There's no wiggle room here to say that the default behavior of displaying ordinary glTF nodes is somehow a negative action, and that hiding them from view is positive. It's clearly inverted. The cleaner design is to use true as the default state here for displaying visible nodes.
— Reply to this email directly, view it on GitHubhttps://github.com/KhronosGroup/glTF/pull/2410#issuecomment-2166064622, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AM7FVTK5RDH7AUGJIYYYWZ3ZHG5PHAVCNFSM6AAAAABJGJVKIWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNRWGA3DINRSGI. You are receiving this because your review was requested.Message ID: @.***>
All good points. And @lexaknyazev does show the correct code for checking this:
// Accurate check
if (ext1.visible !== false) {} // ok
if (ext2.visible !== false) {} // ok
if (ext3.visible !== false) {} // ok
Yes, implementers will still need to pay attention to the details and the hierarchy. But we can easily supply test asset(s) that will make it very plain to see if an entire node has gone missing or inappropriately shown.
The folks I'm concerned about are the adopters: The users, the artists, the designers. They use this spec too, and they can't be kept in line with a simple unit test like programmers can be. And this is an instance of a known design antipattern for them that seems easy to avoid.
I don't want to stand in the way of progress here, so if the consensus is to dismiss my objection, I can accept that. But so far in this thread I have only seen, pardon my expression, programmer reasoning here, coder's logic, which is not always along the same lines as how the artists and creators like to think.
Agreed within the Interactivity DTSG to use the "positive" property name provided that test assets for this extension will include multi-node objects relying on the implicit (default) property value.
A quick attempt to create a test asset:
KHR_node_visibility test 0001 - 2024-06-26.zip
- When the extension is not supported, then it shows "The extension is not supported" (and nothing else).
- When
visible:undefineddefaults totrue, then it shows "Does handle defaults properly" (and if it doesn't, it shows "Does not handle defaults properly"). - When the constraint that a node is visible if itself and all its ancestors are visible is not met, then it shows "Does not properly handle hierarchy" (otherwise, it does not display this)
The behavior is "emulated" in BabylonJS here:
There's probably room for improvement, but maybe someone wants to give it a spin...
For content creation tools exporting glTF files, is it recommended that files using KHR_node_visibility put this in extensionsRequired? It would make sense considering that the model does not render correctly without it, and it is likely that the asset author explicitly needed it, but I wanted to ask here.
For content creation tools exporting glTF files, is it recommended that files using
KHR_node_visibilityput this inextensionsRequired?
That should depend on the intended usage. Couple examples:
- Visibility flag is actively used (toggled) by the animation pointer and/or interactivity extensions. The extension(s) should likely be required.
- Visibility flag is used just to temporarily hide something during DCC interchange. The extension should not be required and likely should not be included in the final asset at all.
I have implemented this in Godot Engine here: https://github.com/godotengine/godot/pull/93722. Both import and export are supported, so Godot Engine can be used for both authoring and consuming glTF assets with KHR_node_visibility. We decided to make the handling of visibility configurable on export, so that users can choose what they prefer.
Test file: cube_visibility_example.zip
This extension is great! It's quite simple yet very useful.
It has been implemented in both Godot Engine and BabylonJS, is there a timeline for when this will be merged and ratified?
I have a question regarding this proposal, mostly with how other upcoming extensions would/could use it:
The current system deals with mesh/object visibility within the scene and assumes it will be a part of the systems scenegraph.
ThreeJS for example has .visible that keeps the mesh in the graph and its children as well. Thus in memory.
GLTFLoader (I believe) will also generate materials for these objects if not referenced.
The problem is, not all nodes will ever be visible and shouldn't be treated as such. For example, Colliders for physics systems. Convex Hulls, Trimeshes, even plain shapes from what I understand are being tracked as nodes. These "invisible" items would still be processed as meshes, especially as some extensions propose putting them as children of parents to keep things like world space intact.
I'm looking at LOD as well potentially using visibility/availability flags on nodes/assets.
If a node/asset exists for other purposes (physics, interaction, other processing, lod, etc) but never renders, and still exists in the node tree, I think it may be helpful to mark the asset as such, and there's a difference between not CURRENTLY visible, and NEVER will be visible. No materials will be generated, UUIDS, etc etc.
I can't think of a good term, but isReference or something would work.
Without a flag will have to do checks/process them out some other way.
Sidenote re: falsy, I agree the hidden: true setup way better.
if (!node.hidden) resultingMesh.visible = false;
Is a better setup than being visible: true as it wouldn't break existing gltfs/loaders and keeps json (ever so slightly) smaller.
@DennisSmolek Nodes that will never be visible will not be affected by this property. A physics shape is not expected to be visible at runtime. Whether the visible property is true or false will not change that. What gives you the impression that they "would still be processed as meshes"? A physics shape is not the same as a visible mesh.
@DennisSmolek Nodes that will never be visible will not be affected by this property. A physics shape is not expected to be visible at runtime. Whether the visible property is true or false will not change that. What gives you the impression that they "would still be processed as meshes"? A physics shape is not the same as a visible mesh.
Consider:
{
"children": [1],
"mesh": 1,
"extensions": {
"KHR_node_visibility": {
"visible": false
}
},
}
Lets say a extension uses this node to hold a collider shape. A Trimesh or a convexHull. It is a mesh. It has mesh data.
Currently (From what I understand), when threeJS process the node in GLTFLoader it would load it as a regular mesh and put it into the active scenegraph, albeit with the visible: false. property.
It will get a material, a world position Matrix, it's buffer copied, etc etc.
If this had a prop like nonRenderable this would be mitigated.
I'm going to be trying to get the physics extension working with three so saw some mention of this.
It's fine, I just have to add more logical processing for the meshes to check if they are LODs, Physics, etc.
@DennisSmolek Indeed, that is a concern with the recent changes to the KHR physics extension. However, note that this is not a problem with the old version of the KHR physics extension, or with the OMI physics extension. Document-level:
{
"extensions": {
"OMI_physics_shape": {
"shapes": [
{
"type": "trimesh",
"trimesh": { "mesh": 1 }
}
]
}
}
}
On a node:
{
"children": [1],
"extensions": {
"KHR_node_visibility": {
"visible": false // Does nothing to this node but would hide any child meshes.
},
"OMI_physics_body": {
"collider": {
"shape": 0
}
}
}
}
The same structure used to be possible in KHR_collision_shapes before it was refactored into KHR_implicit_shapes.
I ran into an issue with selectable nodes, where visibility is set to false. Should theses invisible nodes still be selectable? I saw other implementations (like Godot and BabylonJS), where only the visibility will be set on the nodes, but the nodes are still enabled (e.g. for physics). That would mean invisible nodes could block raycasts for other selectable nodes which are visible. So does the term "visible" means pure visibility of a mesh or a general "enabled" state of the node?
@pfcDorn Visible is strictly for render visibility (meshes/particles/etc), it is not a general disable nodes feature.
As for selectability, if the application allows for selecting things outside of clicking on the visible mesh, then that should not be impacted by invisibility. Also if an editor has a scene hierarchy editor, the invisible nodes should still be shown in there.
A quick attempt to create a test asset:
KHR_node_visibility test 0001 - 2024-06-26.zip
- When the extension is not supported, then it shows "The extension is not supported" (and nothing else).
- When
visible:undefineddefaults totrue, then it shows "Does handle defaults properly" (and if it doesn't, it shows "Does not handle defaults properly").- When the constraint that a node is visible if itself and all its ancestors are visible is not met, then it shows "Does not properly handle hierarchy" (otherwise, it does not display this)
The behavior is "emulated" in BabylonJS here:
There's probably room for improvement, but maybe someone wants to give it a spin...
Hi @javagl, we are using this asset to test in Babylon.js. I think it would be great to make this test available in the glTF sample assets. There were a couple of small things we talked about in the WG to improve this asset:
- Add green check marks / red Xs to the textures to make it obvious whether it is working or not.
- Add one more level to the parent hierarchy to test ancestors and not just direct parents.
Is this something you would be interested in changing?
@bghgary
Is this something you would be interested in changing?
Can do (I might not be able to do this in the next few days, but will try to carve out some time)
@bghgary An updated test is attached here.
Add one more level to the parent hierarchy to test ancestors and not just direct parents.
I wasn't entirely sure about the exact shape for this. The previous asset had the two cases
- node (visible:true)
- child with mesh (visible:false)
and
- node (visible:false)
- child with mesh (visible:true)
with the mesh showing the message "Does not properly handle hierarchy", and being expected to not be displayed (because either the node containing it or its parent had 'visible:false').
I now just inserted another node in-between
- node (visible:false)
- child (visble:false)
- grandchild with mesh (visible:true)
and
- node (visible:true)
- child (visible:true)
- grandchild with mesh (visible:false)
But I wondered whether there should also be "true-false-true" and "false-true-false" chains (or even more of the 8 possible combinations)...? (I'd probably have to think more closely about what exactly is supposed to be tested there in a rendering engine...)
KHR_node_visibility test 0002 - 2025-10-30.zip
It's only the "test 0002", so we can iterate on that 😁
@bghgary Depending on whether there already are specific points about that "0002" version that should be fixed, just drop me a note. Otherwise, I can allocate some time to create a "cleaned up" version of that and create a PR into glTF-Sample-Assets.
Thanks @javagl!
But I wondered whether there should also be "true-false-true" and "false-true-false" chains (or even more of the 8 possible combinations)...?
I think testing all 8 combinations is overkill. I would just test "false-true-true" to make sure "parent of a parent" is working. @lexaknyazev You made this suggestion during the WG call. Do you have any thoughts?
Otherwise, I can allocate some time to create a "cleaned up" version of that and create a PR into glTF-Sample-Assets.
Since this PR is already merged, I think it would be good to open a new PR in glTF-Sample-Assets with the current state (even without any cleanup) so that we can continue the discussion there?
@bghgary Opened https://github.com/KhronosGroup/glTF-Sample-Assets/pull/244 with the current state from here.
Whether the hierarchy test cases should only cover the false-true-true case can be discussed there.
Thanks @javagl!