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

Add KHR_interactivity draft #2293

Draft
wants to merge 42 commits into
base: main
Choose a base branch
from
Draft

Add KHR_interactivity draft #2293

wants to merge 42 commits into from

Conversation

lexaknyazev
Copy link
Member

@lexaknyazev lexaknyazev commented May 31, 2023

@bhouston
Copy link
Contributor

Great work! What sections are next? Also what could we release as a draft extension for SIGGRAPH 2023?

|===
| Type | `math/pi` | Ratio of a circle's circumference to its diameter
| Output value sockets | `float value` | 3.141592653589793
|===
Copy link
Contributor

@aaronfranke aaronfranke Oct 23, 2023

Choose a reason for hiding this comment

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

If we have Pi, we should also have Tau. https://tauday.com/tau-manifesto The value Tau is the circle constant (equal to 2*Pi, or rather, Pi is half of Tau). Using Tau usually results in more readable code. Tau is supported in many programming languages such as C#, Java, Python, GDScript, Rust, Unreal Blueprints, and more, so it's useful for interoperability with other languages, especially Unreal Blueprints which is conceptually similar.

===== Tau

[cols="1h,1,2"]
|===
| Type | `math/tau` | The circle constant, the circumference of the unit circle in radians.
| Output value sockets | `float value` | 6.2831853071795862
|===

Copy link
Contributor

@aaronfranke aaronfranke Jun 20, 2024

Choose a reason for hiding this comment

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

In case more justification is needed, the KHR_interactivity document currently contains Degrees-To-Radians (math/rad) and Radians-To-Degrees (math/deg). These allow converting between degrees and radians. Tau can be used for a similarly important purpose, dividing or multiplying by this number allows converting between turns and radians, where 1 turn is τ radians. Tau can be used for more than just this, but the point is, if math/rad and math/deg are justified as a part of KHR_interactivity for converting angles, then math/tau is also justified for converting angles.

@lexaknyazev lexaknyazev force-pushed the interactivity branch 2 times, most recently from dc870c5 to 29e8acc Compare March 6, 2024 23:10
| `T value` | The custom variable value
|===

This node gets a custom variable value using the variable index provided by the `variable` configuration value. The type `T` is determined by the referenced variable. The variable index **MUST** be non-negative and less than the total number of custom variables, otherwise the node is invalid.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are variables accessed by index instead of by name? It would make more sense to me to set a variable called "money" than one called 27.


Non-custom signature **MUST NOT** appear more than once.

== Events
Copy link
Contributor

@aaronfranke aaronfranke Jul 9, 2024

Choose a reason for hiding this comment

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

Can we get a high-level explanation of how this might work with a trigger volume as defined by KHR_physics_rigid_bodies? Am I reading correctly that the graph would have a script block (graph node) with type event/receive that points to an item in the document-level "events" array? And this event could have an ID of something like "trigger/bodyEntered", which is defined by the extension to emit when a body enters the trigger volume?

What would the output value type of that custom event be? It would need to be something that allows us to reference which object did the entering. A glTF node index comes to mind, but then that wouldn't work for interacting between scenes. Probably the only option here would be a runtime-determined ID for each node in the scene (could just be the memory address), which allows for many glTF files to interact with each other. However, this would then mean that in order to use this value, the KHR_interactivity graph would need a script block (graph node) that can access an object using the same runtime-provided IDs, similar to how object model properties can be accessed using JSON pointers.

For example, let's say we want to build a "teleporter". It is a trigger volume that teleports any object that enters it to another location. Assume that the trigger volume and the destination location are already known in the glTF file. How can the interactivity graph allow objects from other glTF scenes to be teleported, or even non-glTF objects provided by the game engine at runtime, including the player object itself? If a player walks into a glTF teleporter, I expect that the glTF should be able to detect that, and set the position of the player.

Is something like the below sensible? What should we put for the ????

Screenshot 2024-07-08 at 6 54 55 PM

EDIT: After writing this, I see that KHR_node_selectability is defining a new block type event/onSelect. Is this the correct way to extend KHR_interactivity? There is still the question of how to reference objects not in the glTF file.

EDIT 2: I see that OpenGL defines types like GLsizeiptr for pointing to memory addresses. Perhaps this is the appropriate type to use? https://www.khronos.org/opengl/wiki/OpenGL_Type

Choose a reason for hiding this comment

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

This is not a response to the above, but I have an additional question.
The Behave Graph Authoring tool allows the creation of a "node/OnSelect" node, but there is no definition of this node in the Interactivity Extension Specification.
Is this simply a documentation mistake, or is it related to KHR_node_selectability?

| Configuration
| `string pointer` | The JSON pointer or JSON pointer template
| Input value sockets
| `int <segment>` | The JSON pointer template path segment to be substituted at runtime
Copy link
Contributor

@aaronfranke aaronfranke Jul 9, 2024

Choose a reason for hiding this comment

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

What does it mean to have the segment as an input integer value? Using the below example pointer "/nodes/{myId}/scale", what does 0 mean? Is it referring to /nodes, the 0th item, and therefore 1 would refer to the {myId}? Or is it referring to the first instance of {}, so if there was "/nodes/{myId}/{myProp}" then 0 refers to {myId} and 1 would refer to {myProp}? But then what does the number mean, is it saying that only one of these will be substituted? Or is it saying to substitute 0 for {myId}? But then what's the text below about "the node has the myId input value socket"? If we had "/nodes/{myId}/{myProp}" then would the node need both myId and myProp inputs? If so, then we can perform the substitution with only the template and the inputs, and I don't know why we would ever read the int <segment> value. Or is it saying that only up to one of these segments can be substituted at once? If so why? I genuinely don't understand what this means.

Copy link

@boguscoder boguscoder Jul 12, 2024

Choose a reason for hiding this comment

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

I'm not the author but the way I read is the following:
if you have path template configured as /nodes/{myId}/foo/{myProp} then your node has two input sockets
int myId and int myProp respectively, so connecting node to values 42 and 100500 gives you /nodes/42/foo/100500. int <segment> is just not most readable way to convey idea of dynamic socket number,
there is no 0s not 1s involved, curious how did you arrive at them?

Copy link
Contributor

@aaronfranke aaronfranke Jul 12, 2024

Choose a reason for hiding this comment

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

Oh! I completely missed that. So that is just a placeholder, and there is no input socket called segment. So for actual cases, there might be no segment input value sockets if it's not a template, or 3 sockets if the template has 3 slots in it. So "What if pointer is not templated, what shall socket be connected to", nothing because it won't exist.

Sorry for the confusion. We can collapse this as resolved, or maybe we can find a way to make this clearer.

Comment on lines +2879 to +2882
{
"id": "variable",
"value": [0]
}

This comment was marked as resolved.


If the variable type is custom, the `value` property is defined by the extension defining the custom type.

== Nodes

This comment was marked as resolved.


If the pointer or the pointer template with all its substitutions applied can be resolved, the `value` output value is the resolved property value and the `isValid` output value is true.

If the pointer or the pointer template with all its substitutions applied cannot be resolved, the `value` output value is the default value for its type and the `isValid` output value is false.

This comment was marked as resolved.


Intermediate output progress values **MAY** be less than zero or greater than one.

==== Animation Control Nodes

This comment was marked as resolved.

Choose a reason for hiding this comment

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

closing this one as done, this will be in the text soon

@Hackn0214
Copy link

I apologize if this has already been mentioned somewhere.
I am currently checking the Interactivity Extension Specification.
How are p1 and p2 parameters used in node pointer/interpolate?

@hybridherbst
Copy link

hybridherbst commented Aug 26, 2024

A couple of practical questions based on my experiments:

  1. For looping an animation, is the expected approach to set endTime to positive infinity?

  2. For preventing animations overlap when multiple animations target the same node, is the expected approach to just stop all animations that might be playing, or to add an internal state of which animation was last played and then stop that?

  3. For interpolating rotations, how would the most-used quaternion interpolation – slerp – be implemented? I'm not sure a bezier curve (p1/p2) can be mapped to a spherical interpolation.
    Answer: quaternion interpolation is specified as using slerp already. The bezier curve is for the easing function for the t parameter going into the interpolation.

  4. Can the same output flow socket be connected to multiple input flow sockets, or must I add a flow/sequence node inbetween? For me that's not clear from the spec. I would expect that similar to values, I can just connect those outputs to multiple inputs and they're all triggered in the order they appear in the JSON.
    Answer: one output flow socket can only be connected to one input flow socket.

  5. I find flow/sequence to be confusingly named. It's not clear to me if "all output flows are activated one by one" means that each flow output first needs to finish before the next one is invoked, or that they are all fired immediately and just the order is specified here. For reference, other flow graph implementations that I'm aware of use sequence for "sequential" execution with waiting, and "parallel" for in-order parallel execution of the connected flows.

@warrenm
Copy link

warrenm commented Aug 26, 2024

  1. For interpolating rotations, how would the most-used quaternion interpolation – slerp – be implemented? I'm not sure a bezier curve (p1/p2) can be mapped to a spherical interpolation.

My understanding is that the control points specified by an interpolation node are to be used as the control points of a Bézier spline that is used as an easing function, à la cubic Bézier easing functions in CSS and elsewhere.

If that's correct, then the implementation would pass the normalized time parameter (on [0, 1]) to the easing function to get the eased progress fraction, then use that as the "time" parameter of the lower-level interpolation routine (whether lerp or slerp).

@hybridherbst
Copy link

Thanks! I found it now. The spec states that it's linear interpolation but also has this remark:

If the Object Model property is a quaternion, spherical linear interpolation expression SHOULD be used.

@Hackn0214
Copy link

Thank you for your response.
These parameters seem a bit challenging for designers to handle.

@Hackn0214
Copy link

Regarding the question I asked earlier, it seems it might have gotten buried and become hard to notice, so I’d like to ask again.
I couldn't find node/OnSelect(event/OnSelect) in the specification, but is it possible to handle events when an object is selected?

@lexaknyazev
Copy link
Member Author

I couldn't find node/OnSelect(event/OnSelect) in the specification, but is it possible to handle events when an object is selected?

This event is defined in the KHR_node_selectability extension, see #2422.

@Hackn0214
Copy link

I couldn't find node/OnSelect(event/OnSelect) in the specification, but is it possible to handle events when an object is selected?

This event is defined in the KHR_node_selectability extension, see #2422.

Oh, I thought this extension was an attribute to specify whether an object can be selected. I’ll read through the draft. Thanks!

@Hackn0214
Copy link

How would a connected graph with multiple output value sockets, as shown in the diagram, be represented in glTF?
gltfnode

@Hackn0214
Copy link

It's not a self-solution, but is there any issue with this kind of definition?

  "nodes": [
    {
      "type": "math/sign",
      "values": [
        {
          "id": "a",
          "node": 2,
          "socket": "1"
        }
      ]
    },
    {
      "type": "math/sign",
      "values": [
        {
          "id": "a",
          "node": 2,
          "socket": "0"
        }
      ]
    },
    {
      "type": "math/extract2",
      "values": [
        {
          "id": "a",
          "type": 1,
          "value": [
            0.0,
            0.0
          ]
        }
      ]
    }
  ],

@hybridherbst
Copy link

I believe this matches the output of https://github.com/KhronosGroup/glTF-InteractivityGraph-AuthoringTool/tree/initial-work-merge (minus the missing type declarations in your partial file).

@Hackn0214
Copy link

Thank you.
I'm comparing the behavior of my custom tool with the authoring tool as a reference.
By the way, is there a way to delete a node once it's placed on the graph in this tool? Currently, I'm restarting the tool to clear the graph.

@boguscoder
Copy link

@Hackn0214 backspace seems to remove just fine?

@Hackn0214
Copy link

@Hackn0214 backspace seems to remove just fine?
Ah... I was pressing the Delete key.

@Hackn0214
Copy link

When defining a custom event as shown below, if event/receive and event/send nodes refer to this custom event.

 "events": [
    {
      "id": "MyEvent",
      "values": [
        {
          "id": "Val_1",
          "type": 0
        },
        {
          "id": "Val_2",
          "type": 0
        }
      ]
    }
  ],

I think the generated node will have sockets as shown below, but what do you think?
gltfnode

@Hackn0214
Copy link

Is it possible to return the value pointed to by a pointer with the behavior graph?
For example, I would like to change the base color of a selected object's material.

/nodes/{nodeIdx}/mesh -> meshIdx
/meshes/{meshIdx}/primitibes/0/material -> materialIdx
/materials/{materialIdx}/pbrMetallicRoughness/baseColorFactor

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

Successfully merging this pull request may close these issues.