Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KHR_node_visibility Draft Proposal #2410

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

lexaknyazev
Copy link
Member

As discussed in the Interactivity DTSG, there should be a way to control node visibility through KHR_animation_pointer and/or KHR_interactivity workflows.

Copy link
Contributor

@javagl javagl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm lacking some context from the KHR_animation_pointer/KHR_interactivity, but this looks pretty straightforward.

Maybe hidden should be required, but when defaulting to false, this may not be strictly necessary.

@lexaknyazev
Copy link
Member Author

Maybe hidden should be required, but when defaulting to false, 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.

@emackey
Copy link
Member

emackey commented Jun 12, 2024

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.

@javagl
Copy link
Contributor

javagl commented Jun 12, 2024

@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?

@lexaknyazev
Copy link
Member Author

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:

  • hidden with default false
  • visible with default true

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.

@emackey
Copy link
Member

emackey commented Jun 13, 2024

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.

@javagl
Copy link
Contributor

javagl commented Jun 13, 2024

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.

@dwrodger
Copy link

dwrodger commented Jun 13, 2024 via email

@emackey
Copy link
Member

emackey commented Jun 13, 2024

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.

@lexaknyazev
Copy link
Member Author

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.

Copy link
Contributor

@javagl javagl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion for either hidden or visible. The trade off is that the latter is more "intuitive", and the former avoids that possible if (!node.visible) shouldDefaultToTrue(); bug. (Maybe there could be an 'Implementation Note' about that - but that may not be too important). So the new approach is fine for me.

@javagl
Copy link
Contributor

javagl commented Jun 26, 2024

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:undefined defaults to true, 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:

Khronos KHR visibility test

There's probably room for improvement, but maybe someone wants to give it a spin...

@aaronfranke
Copy link
Contributor

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.

@lexaknyazev
Copy link
Member Author

For content creation tools exporting glTF files, is it recommended that files using KHR_node_visibility put this in extensionsRequired?

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.

@aaronfranke
Copy link
Contributor

I have implemented this in Godot Engine here: godotengine/godot#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

"type": "boolean",
"description": "Specifies whether the node is visible.",
"default": true,
"gltf_detailedDescription": "Specifies whether the node is visible. A value of false means that the node and all its children are hidden."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick

Suggested change
"gltf_detailedDescription": "Specifies whether the node is visible. A value of false means that the node and all its children are hidden."
"gltf_detailedDescription": "Specifies whether the node is visible. A value of false means that the node and all its descendants are hidden."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also considered to bring that up, but... it transitively applies to all relevant nodes, so this does not change the meaning on a technical level. (One could argue which of them is easier to understand, conceptually, but I don't have a strong opinion there...)

|-------------|-----------|----------------------------------------|--------------------|
| **visible** | `boolean` | Specifies whether the node is visible. | No, default: `true`|

In other words, a node is visible if and only if its own `visible` property is `true` and all its parents are visible. This allows a single change of a `visible` property at a high level of the hierarchy to hide or show complex (multi-node) objects.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In other words, a node is visible if and only if its own `visible` property is `true` and all its parents are visible. This allows a single change of a `visible` property at a high level of the hierarchy to hide or show complex (multi-node) objects.
In other words, a node is visible if and only if its own `visible` property is `true` and all its ancestors are visible. This allows a single change of a `visible` property at a high level of the hierarchy to hide or show complex (multi-node) objects.


The `KHR_node_visibility` extension object is added to the objects within the `nodes` array. The extension object contains a single boolean `visible` property. This value is mutable through JSON pointers as defined in the glTF 2.0 Asset Object Model and controls visibility of the node that contains it and all its children nodes recursively. A value of `false` causes all nodes below in the hierarchy to be omitted from display, even any nodes below that have a value of `true`.

| | Type | Description | Required |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The titles here are center-aligned, but they are left-aligned in most files in this repo.


| | Type | Description | Required |
|-------------|-----------|----------------------------------------|--------------------|
| **visible** | `boolean` | Specifies whether the node is visible. | No, default: `true`|
Copy link
Contributor

@aaronfranke aaronfranke Jun 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| **visible** | `boolean` | Specifies whether the node is visible. | No, default: `true`|
| **visible** | `boolean` | Specifies whether the node is visible. | No, default: `true` |

...and modify the other lines to keep this aligned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants