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

OMI_physics_shape and OMI_physics_body #2258

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

Conversation

aaronfranke
Copy link
Contributor

@aaronfranke aaronfranke commented Feb 17, 2023

This pull request adds OMI's OMI_physics_shape and OMI_physics_body glTF extensions to this repo. These extensions allow specifying physics shapes and behavior for objects in a glTF file. Additionally, these extensions work together with OMI_link and OMI_seat to define trigger volumes for interacting with URIs and seats.

This extension has been discussed in the OMI group extensively over the past year, with many design decisions made to improve the spec to be highly portable between Unity, Unreal, Godot, and more. For example, there was extensive discussion about if cylinders should be specified, and if so, what guidelines to give to approximate them for engines that do not support cylinders.

A complete import and export pipeline for both content creation and consumption is available in Godot here: godotengine/godot#69266. OMI_collider is also implemented in Third Room. Also, I made a glTF Validator implementation here: KhronosGroup/glTF-Validator#202 and this PR includes example files.

These extensions are a competing standard to Microsoft's physics #2257. OMI's concerns about this spec have been listed as separate issues on their issue tracker here. Here are some differences between the specs:

  • OMI_physics_body defines the type of motion using a string for the type, while Microsoft's spec uses an isKinematic boolean. The boolean is less flexible, and in my opinion, is less readable even if there were only two options. The definition of "kinematics" is the motion of bodies, so should a non-kinematic body be static and not move? No, it means that a body should be simulated with rigid body dynamics. In addition to kinematic and dynamic, OMI allows defining explicit static bodies.
  • OMI has joints on a separate spec, and does not yet define physics materials, while the MSFT spec defines these as part of the rigid body spec. OMI prefers to leave these to separate specs that combine together into a larger standard, to avoid burdening implementations with a large base spec to support. For example, an implementation which uses rigid bodies but not joints can simply skip the joints extension, instead of half-implementing a spec. It's also possible that someone may wish to compose together with a different spec for more specialized joints, but keep the rigid bodies defined in the official spec.
  • MSFT_CollisionPrimitives defines collision filtering with collision systems. OMI's specs do not define this, we are concerned that it would be difficult to implement in many engines and therefore not portable. Not every engine implements filtering in the same way, and there have already been serious concerns about the portability and composability of the filtering MSFT proposes.

Our hope is to hammer out the differences between the specs to find a common ground that will make everyone happy. Since Microsoft's physics now has a PR open, we wanted to put OMI's design on the table too.

@jmousseau
Copy link

OMI's specs define the type of body using a string for the type, while Microsoft's spec uses an isKinematic boolean.

I agree completely and made a similar comment #2257 (comment)

MSFT_CollisionPrimitives defines collision filtering with collision systems. OMI's specs do not define this, we are concerned that it would be difficult to implement in many engines and therefore not portable.

In my opinion, collision filtering is a critical feature. Is the concern collision filtering engine support or the implementation complexities? For the former, I see support in Bullet, Jolt, PhysX, and Unity Physics.

@aaronfranke
Copy link
Contributor Author

aaronfranke commented Feb 19, 2023

@jmousseau Most engines with collision filtering use bitwise layers and masks, usually with a 32-bit integer, but there are several concerns here:

  • The obvious one is that not all engines may have collision filtering.
  • What kind of standards can we have for assets to agree on which layers do what?
    • Should we try to standardize the numbers? What about values already used by games for their own logic?
    • Since standardizing numbers is hard, what about strings?
      • What happens if the models have more collision system strings than there are bits in a 32-bit integer? Then it can't be represented in engines that use 32-bit integers.

EDIT: And just to clarify, these are not insurmountable challenges, but any spec including collision filtering needs to explain in detail what's supposed to happen in these situations.

@jmousseau
Copy link

@aaronfranke You raise valid points, for which I've replied inline below. I certainly want to avoid a scenario where collision filtering is the reason why implementations don't adopt the extension or consensus can not be reached. This outcome defeats the purpose of a standard. Although I believe filtering is a critical feature, I'm open to pursuing it as a future extension.


  • The obvious one is that not all engines may have collision filtering.
In my opinion, ... ...collision filtering is a critical feature for many applications. For example, imagine a glTF authored with two overlapping colliders that approximate the model's shape. How could I prevent them from continuously producing collision events?

A feature support table with the proposed extension features (such as filtering) as rows and physics/game engines as columns may allow us to make informed decisions on each of the proposed features. Thoughts?


  • What kind of standards can we have for assets to agree on which layers do what?

The specification doesn't assign semantic meaning to any string, that's left to the glTF author. Let's assume filtering is part of the extension, do you think there needs to be a set of "standard" system strings? Seems related to the number of systems concern.


  • Should we try to standardize the numbers? What about values already used by games for their own logic?

Wether the specification uses numbers or strings, I think implementations should always map to their own collision system domain.

For example, let's say my experience already uses the first N bit masks for collision filtering and I load a glTF with systems "a" and "b". These systems would be mapped to 1 << (N + 1) and 1 << (N + 2), respectively so they don't interfere with existing bit masks.


  • What happens if the models have more collision system strings than there are bits in a 32-bit integer? Then it can't be represented in engines that use 32-bit integers.

I had this same thought, and I don't think a perfect solution exists for this problem.

There's a similar problem with the number of texture coordinates (or other primitive attributes) supported per mesh primitive, especially when texture transform extensions are used extensively. The glTF specification makes a recommendation:

Client implementations SHOULD support at least two texture coordinate sets, one vertex color, and one joints/weights set.

The physics extension could make a similar recommendation for the number of supported systems (or, more extreme, enforce a max system count per glTF).

I'm also under the impression that collision systems are scoped to each glTF, meaning collisions between two different glTF is undefined behavior. Asked for clarification in #2257 (comment)

@eoineoineoin
Copy link
Member

eoineoineoin commented Feb 20, 2023

I'll refrain on commenting on the style here, but from a technical perspective, in addition what Jack mentioned regarding the lack of filtering, I think that excluding joints is a lost opportunity. Without them, you're limited to objects which just fall in a pile. The real world genuinely does contain objects which can be joined together in all sorts of ways. In particular, physics simulations used by the robotics research community exclusively work with constraints - it's not uncommon to see a simulation which doesn't even do collision detection.

Similarly, it's important to include some kind of "physics material" property - otherwise, every object appears to be composed of the same material. The USD Physics spec includes a very similar material definition to what I've proposed in MSFT_rigid_bodies: https://graphics.pixar.com/usd/release/api/class_usd_physics_material_a_p_i.html

It's also a serious mistake to exclude inertia - with the exception of 1D simulations, inertia is an intrinsic property of a moving body in exactly the same manner as mass; the two are fundamentally related. We chose a 3x3 matrix because that's the actual mathematical representation of it and the type system used by glTF makes it easier to specify a single optional property rather than a pair of properties which must either both be provided or not-provided (it's not impossible though, and if people prefer the principal axis representation, I'm not opposed to modifying MSFT_rigid_bodies to represent the inertia tensor that way). While it's true that, thanks to symmetries, you can decompose the 3x3 inertia tensor into a principal axis, it looks like Godot does not expose the inertia orientation and always assumes the non-diagonal components are zero. I can't speak for why they've done it this way (especially since the underlying physics simulation supports it), but it's possible to get the same effect by adding additional transforms to the hierarchy which would effectively convert that 3vector to a 3x3 tensor.

@aaronfranke
Copy link
Contributor Author

aaronfranke commented Feb 20, 2023

@eoineoineoin I agree with you that joints and physics materials are important, but I disagree that they should be in the same spec as rigid bodies. I would prefer them to be in a different spec that is composed to on top of them. There are many physics simulations that do not require joints or physics materials, so requiring importing them in order to support importing physics bodies seems like a mistake to me.

As for inertia, I think it does deserve to be a part of the rigid body spec, we just need to ensure we do it right, and to provide conversions to/from other representations of inertia. I don't know the formula to convert a 3x3 matrix inertia to a Vector3 inertia, so I don't know how to implement this in Godot Engine. EDIT: Opened a PR on OMI's repo. EDIT 2: OMI's physics body spec now includes 3x3 inertia and information of how to convert to Vector3 inertia.

@aaronfranke
Copy link
Contributor Author

aaronfranke commented Apr 18, 2023

OMI has merged a change that adds an inertia tensor 3x3 matrix and I have updated this PR. The spec includes information on how to convert it for engines that implement inertia differently.

We are also working on physics materials and joints. For both physics materials and joints we are having them be separate specs that can be composed together. For physics materials we have done research and decided to go with the same properties as MSFT with minor changes like renames. Joints will come later. Overall we're converging these specs together which is great.

@aaronfranke
Copy link
Contributor Author

aaronfranke commented May 17, 2023

Added physics joints to this PR from the PR on OMI's side: omigroup/gltf-extensions#147

They are similar to Microsoft's joints but with a few differences:

  • Joints are a separate spec instead of part of the rigid body spec.
  • Joints are their own nodes. This allows the joint's position to be defined by the transform of the glTF node it's on, which is much more intuitive. This is how Godot, Unreal, Roblox, and Blender work, but Unity has joints as part of a RigidBody node. It looks like Unity is the odd one out here, and Unity's way is less intuitive.
  • Some renames, min -> lowerLimit, max -> upperLimit, springConstant -> stiffness, springDamping -> damping.
  • Included in the spec are detailed descriptions and charts of how to implement particular joint types like pin or weld.
  • There are example files, a validator implementation, and a Godot implementation, all in compliance with each other.

@mrmetaverse
Copy link

Can we please prioritize merging the two competing standards before pressing on? OMI has made efforts to tweak our proposed standard to incorporate MSFT suggestions. I'd like to see that flow both ways. The OMI protocol suggestion is the result of a highly collaborative discussion and effort. I am not sure how many people are contributing to the MSFT standard proposed, hopefully, more than one person, but I'd rather see us come out of this with a single shared collaborative standard than two competing ones.

It comes down to this: Do we want to see the Metaverse be interoperable, or do we want to bicker over who gets credit for the standard? I'd rather merge efforts, and see this as an opportunity to garner more collaborative standards in the future. This is what the MSF is all about.

@eoineoineoin
Copy link
Member

There are a number of issues I have with the OMI rigid body specifications. Some of these are major issues which limit flexibility and correctness, while others are more minor. It seems to be that the OMI rigid body specs were derived from how the Godot 3.4 physics UI presentation layer shows users the options, rather than actually representing what physics engines are doing under the hood.

In all of OMI_collider, OMI_physics_body and OMI_joint, the new extension objects have an unreasonable restriction that a node can only be a collider, rigid body, joint or a shape and cannot store any other properties simultaneously. i.e. here is a node that I might want to create; it's a node which has a mesh, physics body and a geometry:

{
    "extensions": {
        "OMI_collider": {
            "collider": 0
        },
        "OMI_physics_body": {
            "type": "rigid"
        }
    },
    "name": "MyNode",
    "mesh": 0
}

This is a node which has some graphical representation along with a physical one, and is a perfectly reasonable object to want to create. This is explicitly forbidden by the OMI physics specifications. Instead, users must add additional nodes:

{
    "extensions": {
        "OMI_physics_body": {
            "type": "rigid"
        }
    },
    "name": "MyNode",
    "mesh": 0,
    "children": [1, 2]
},
{
    "name": "MyNode_GraphicalMesh",
    "mesh": 0
},
{
    "extensions": {
        "OMI_collider": {
            "collider": 0
        },
    },
    "name": "MyNode_Collider"
}

I don't know why this restriction is in the spec; the only reason I can think of is "because that's what the Godot scene tree looks like in the editor." This puts an unreasonable burden on all other users of glTF, even those not using Godot. The same restriction does not occur anywhere else in glTF - a node can be a simultaneous mesh+camera+light. Why would a rigid body be any different?

In order to be associated with a OMI_physics_body glTF node, OMI_collider glTF nodes must be direct children, not indirect children.

Again, this is a totally arbitrary and unnecessary restriction which seems to only exist because that's the way the Godot UI presentation layer works, is that right? This is not how people make content - here's another perfectly reasonable setup:

image

So here, there's several nodes in a hierarchy and I want to configure it for physics simulation; I'd like to be able to put a collision shape on the Forklift_left_fork. However, OMI_rigid_body forbids this, as it's not a direct child of Forklift. So, I either need to:

  1. Break apart my whole hierarchy and flatten it out
  2. Duplicate the Forklift_left_fork as an immediate child of Forklift and configure it just for the collider (no visual mesh)

Both of these options are objectively bad - it's a whole ton of extra work for an artist and it deliberately throws away any of the benefits you get from using a scene tree - i.e., in both cases, I can now no longer update the local transform of Forklift_Mast and have the Forklift_left_fork's transform be updated, as the restrictions imposed by OMI_physics_body have forced me to throw away the actual structure of the object.

Both of the above decisions are major design flaws and those restrictions do not exist in MSFT_rigid_bodies - a collider can associated with any node in the tree and we impose no restrictions on what properties a node can have (i.e. a node can be a mesh and have a rigid_body extension simultaneously):

"nodes" = [
{
    "extensions":{
        "MSFT_RigidBodies":{
            "rigidBody":{
                "mass":1
            },
                "collider":3,
                "physicsMaterial":3
        }
    },
        "mesh":8,
        "name":"AnObjectWhichHasAMeshAndRigidBodyAndCollider"
},
"nodes":[
{
    "children":[ 1 ],
    "extensions":{
        "MSFT_RigidBodies":{
            "rigidBody":{ "mass":1 }
        }
    },
    "mesh":1,
    "name":"RootObject"
},
{
    "children":[ 2 ],
    "name":"IntermediateTransform",
    "translation": [1, 1, 1]
},
{
    "extensions":{
        "MSFT_RigidBodies":{
            "collider":0,
            "physicsMaterial":0
        }
    },
    "mesh":0,
    "name":"ChildGeometry"
}

Both of these constructs are not only reasonable, but also how artists actually create content in practice. However, these constructs have been forbidden by OMI_collider and OMI_physics_body.

OMI_collider issues

As above, a node with an OMI_collider cannot also be a mesh, camera, rigid body, etc.

The OMI_collider object has an isTrigger property:

isTrigger | boolean | If true, the shape is a trigger and is not solid to other objects. | false | Always valid |

isTrigger is is a physical property, not a geometric one. It should not be in the specification which defines geometry; this suggests that the design of OMI_physics_body is leaking into OMI_collider, which should be independent.

The OMI_collider's type string is inflexible and prevents users from extending the set of shapes that are supported. Users are permanently limited to the six collider types defined in the spec. In a 1:1 meeting, I was asked how a particular engine could expose their signed distance field shapes. In MSFT_geometric_primitives, you simply create a collision primitive which does not have one of the "out-of-the-box" types and add an extension to it:

"extensions":{
    "MSFT_CollisionPrimitives":{
        "colliders":[
        {
            "extensions":{
                "MY_SDF_Shape_Extension": {
                    "sdf_data": ...
                }
            }
        }
        ]
    }
}

This can't be done in OMI_collider.

 The glTF mesh in the array MUST be a trimesh to work, and should be made of only one glTF mesh primitive (one surface)

Why does this restriction exist? There's no reason the physics shape can't concatenate all the meshes together.

 The final hull shape should have no more than 255 points in total.

Why does this restriction exist? This breaks the "zero one infinity" rule. While I appreciate in all software (not just physics) that there are practical limits to the number of vertices in a hull (or triangles in a mesh, or children of a node or any number of things) there is no reason to write this into the spec. By example, an older version of Unity could not import glTF meshes with >=65000 triangles - the correct solution was to fix/workaround the limitation in Unity. It was not to edit the spec to say "meshes should have less than 65000 triangles in total."

 the default size is [1, 1, 1], representing a cube with a volume of one cubic meter, edges/diameters one meter long, and extents/radius of half a meter.

Probably should read "half-extents," rather than "extents" here? Also, a cube with extents of [1, 1, 1] does not have a radius of 0.5.

Finally, there's no options for collision filtering; in practice, any non-trivial usage of colliders will require collision filtering. While it's common for applications to use a dynamic collision filter which ties into the business logic of the application, it's important to have some sort of filtering which covers non-dynamic filtering use-cases.

OMI_physics_body

As above, a node with OMI_physics_body is forbidden from also being a mesh, camera, etc. and the restriction that colliders must be immediate children is overly restrictive and prevents people from making content effectively. I'm aware that in the case of the latter, the default Godot UI does not allow you to setup content like this (which is far too limiting IMO) but I was able to trivially workaround it in the MSFT_rigid_body Godot importer - obviously, that's 18 lines of code I'd have preferred not to write, but demonstrates that there's no technical limitation here, and the Godot UI should be improved.

|| Type | Description | Default value
mass | number | The mass of the physics body in kilograms. | 1.0
inertiaTensor | number[9] | The inertia tensor 3x3 matrix in kilogram meter squared (kg⋅m²). | [0.0, ..., 0.0]

The default mass is 1.0, but the default inertia is [0]? Is this to be interpreted that, by default, "rigid" types can't rotate? This will cause no end of end-user confusion.

 Physics bodies without collision shapes on them will not have any function.

This not true. A physics body, even without collision shapes adds an additional six degrees of freedom to a simulated system.

Some engines only support specifying the inertia around the principle axes as a Vector3. In those engines, when importing a glTF file and reading the inertia matrix, only the diagonal principle axis values should be used, and the non-diagonal coupling values should be discarded. Similarly, when exporting a glTF file while writing the inertia matrix, write the Vector3 values to the matrix diagonal principle axis values, and set the non-diagonal coupling values to zero.

This is absolutely the wrong thing to do. On an earlier iteration of this spec which did not include inertia, I tried to make it clear that inertia was critical to the realistic movement of a body

By saying "you can ignore the orientation of the inertia tensor and when writing a glTF file, set that value to the identity rotation" you're going to get the wrong result and objects will not behave realistically. The above quote from OMI_physics_body is equivalent to writing something like:

Some engines do not support vertex normals. In those engines, a vertex normal should be considered to always be (1,0,0) and when writing a glTF file, vertex normals should be set to (1,0,0)

I don't think anyone would agree that this sort behaviour should be permitted by a specification. Like the mesh limitations above, the correct thing to do is to fix those engines which do not support this functionality; the solution is not to limit all glTF users who are not using those engines. I'll also note that Godot does actually support inertia orientation -- it's just not exposed in the UI. Are there any other engines which do not "support" inertia orientation?

The rigid_body's type parameter has a number of issues:

Body Type | Unity | Godot 3 | Godot 4 | Unreal
Vehicle | Rigidbody | VehicleBody | VehicleBody3D | Vehicle, Simulate Physics = true

What does it mean to be a vehicle? At the very minimum, there needs to be a mathematical basis for saying what effect "vehicle" has on a simulated body, which is probably impossible, given the totally diverse set of use-cases for vehicles in different domains -- an arcade-style driving game is going to have a totally different vehicle model to a serious aerodynamic simulation. It is not good enough to say "do what these engines do." For example, Godot's "VehicleBody3D" does two things:

  1. It adds some additional options for a simple engine model, to apply forces outside the physics step
  2. It changes the default mass of the rigid body to 40kg

I don't know why #2 makes the object suitable for vehicle simulation, but I suspect most people would not agree that this is appropriate.

Given the fact that neither of these items are suitable for a generic vehicle simulation and this type mandates no difference in how a rigid body is handled, it should not be in the spec.

Body Type | Unity | Godot 3 | Godot 4 | Unreal
Character | Rigidbody.isKinematic | KinematicBody | CharacterBody3D | Pawn, Simulate Physics = false

Same objections to vehicle; what does it mean mathematically for an object to be a character? Again, the use-cases are so varied (even moreso than vehicles) that I'd suggest this is impossible. It's not sufficient to say "do what Godot does." A simple example of this is in this prescribed mapping. Unreal has a "Character" prefab - why is that not suitable for OMI_rigid_body.type=character objects, and instead, you mandate "Pawn, Simulate Physics = false"? Similarly, Unity has a "CharacterController" component; why is that not suitable? Why is Godot's "CharacterRigidBody3D" more suitable for characters than other engine's out-of-the-box character functionality?

Kinematic

Kinematic bodies collide with other bodies, and can be moved using scripts or animations. They can be used for moving platforms.
Character

Character bodies are like kinematic bodies, except are designed for characters. If an engine does not have a dedicated character type, treat this as kinematic instead.
    
Rigid

Rigid bodies collide with other bodies, and move around on their own in the physics simulation. They are affected by gravity. They can be used for props that move around in the world.

Character bodies are kinematic, so they do not have gravity applied? They can also move through static objects, due to the kinematic object's infinite mass and push dynamic objects out of the way with infinite force?

Body Type | Unity | Godot 3 | Godot 4 | Unreal
Trigger | Collider.isTrigger | Area | Area3D | Generate Overlap Events = true

Again, same issue; what is a "trigger" and how does it differ from the other options? The description of this is particularly confusing:

Trigger

Trigger bodies do not collide with other objects, but can generate events when another physics body "enters" them. For example, a "goal" area which triggers whenever a ball gets thrown into it.

If an OMI_collider's "isTrigger" setting does not match the body it's a part of, implementations should ensure the per-collider setting is preserved.

So, a "trigger" body is composed of multiple colliders which also have an "isTrigger" property, but the body should ensure that the collider's setting is used? This sure seems like the OMI_rigid_body.type=trigger actually has no effect? It's exactly equivalent to an OMI_rigid_body.type=rigid?

Body Type | Unity | Godot 3 | Godot 4 | Unreal
Static | Collider | StaticBody | StaticBody3D | WorldStatic, Simulate Physics = false

"Static" should not be a body type; all the motion properties which come with an OMI_physics_body will be ignored, which is surely breeding grounds for user confusion -- the OMI_physics_body does not have any properties which a static body makes use of. In MSFT_rigid_bodies, static bodies are implicit, which is much more flexible and avoids addition of unnecessary extension objects.

OMI_physics_joint

A joint should be on its own glTF node, it should not be on the same node as a mesh, camera, light, collider, physics body, etc.

As with the other objects, this is overly restrictive and does not align with the rest of glTF.

 A joint cannot be connected to a trigger body, and cannot be connected to a non-OMI_physics_body glTF node.

Why not? Again, both of these requirements are overly-restrictive.

While I appreciate that you took the bulk of the joint spec from MSFT_rigid_bodies, there was a modification made which fundamentally broke it:

You replaced the two pivots with a single one. A constraint, by definition links reference frames in two distinct nodes. A single transform for the joint is not sufficient to represent a joint. I am aware that this is how a joint is configured in the Godot UI, but this is another case where the specification was derived from the options in Godot's presentation layer rather than what the physics engine actually uses -- Godot's joint implementation really does represent the constraint space as a relative transform from each body: https://github.com/godotengine/godot/blob/1d14c054a12dacdc193b589e4afb0ef319ee2aae/servers/physics_3d/joints/godot_generic_6dof_joint_3d.h#L155

As an example for why this is the wrong thing to do, consider this scene, adapted from the OMI "simple_joint.gltf" example:

{
    "asset": {
        "version": "2.0"
    },
    "extensionsUsed": [
        "OMI_collider",
        "OMI_physics_body",
        "OMI_physics_joint"
    ],
    "extensions": {
        "OMI_collider": {
            "colliders": [
                {
                    "size": [ 1.0, 2.0, 0.05 ],
                    "type": "box"
                },
                {
                    "size": [ 10.0, 0.05, 10.0 ],
                    "type": "box"
                }
            ]
        },
        "OMI_physics_joint": {
            "constraints": [
                { "linearAxes": [0, 1, 2] },
                { "angularAxes": [1] }
            ]
        }
    },
    "nodes": [
        {
            "extensions": {
                "OMI_physics_joint": {
                    "constraints": [0,1],
                    "nodeA": 1,
                    "nodeB": 2
                }
            },
            "name": "HingeJoint",
            "translation": [-0.5, 0.5, 0.0]
        },
        {
            "extensions": {
                "OMI_physics_body": {
                    "type": "static"
                }
            },
            "name": "DoorFrame",
            "translation": [0.0, 0.0, 0.0]
        },
        {
            "extensions": {
                "OMI_physics_body": {
                    "type": "rigid"
                }
            },
            "name": "DoorBody",
            "translation": [0.0, 0.5, 0.0]
            "children": [3]
        },
        {
            "name": "DoorBody_Collider",
            "extensions": {
                "OMI_collider": { "collider": 0 }
            }
        },
        {
            "name": "ForceSolverErrorTerm",
            "extensions": {
                "OMI_physics_body": {
                    "type": "static"
                }
            },
            children: [5]
        },
        {
            "name": "ForceSolverErrorTerm_Collider",
            "extensions": {
                "OMI_collider": { "collider": 1 }
            }
        }
    ]
}

The nodes "DoorFrame" and "DoorBody" model a door on a hinge, while the node "ForceSolverErrorTerm" is used to force an error in the constraint solver. If you were to simulate this scene and attempt to save it as a glTF, no matter what you do, the joint will be incorrect. Either the position or the axis (or both) will be incorrect and can never be fixed, so the door will rotate around the wrong position and in the wrong direction - that solver error is now permanently encoded into your saved glTF file.

This is a pathological case, obviously, but it's also going to happen in the general case; the solver can always have an error term (though it's usually more subtle) the exact error depends on the solver behaviour, but it's especially apparent in PGS solvers, which are commonly used for real-time simulation, including by Godot.

Node Property Summary
Type | Description | Default value
constraints | number[] | Array of indices in the document-level constraint array. Must be integers. | Required, no default

Why is this changed? In MSFT_rigid_bodies, a "joint" object indexes into a single object in the document-level array, and that object contains the set of limits. Not sure what this adds, as those "separated" limit sets can't generally be shared and with the strategy suggested here, you end up with duplication. Consider:

{
    "name": "NodeToRepresentAJoint",
    "extensions": {
        "OMI_physics_joint": {
            "constraints": [0, 1, 2],
            "nodeA": ...,
            "nodeB": ...
        }
    }
},
{
    "name": "NodeToRepresentAJointWithTheSameLimitsAsAbove",
    "extensions": {
        "OMI_physics_joint": {
            "constraints": [0, 1, 2],
            "nodeA": ...,
            "nodeB": ...
        }
    }
}

Here, the constraints array has to be duplicated amongst every node, which is pretty strange. The constraint at index 1 is unlikely to be used by any other limit. In MSFT_rigid_bodies, a joint is defined by the set of limits, so you have:

{
    "extensions":{
        "MSFT_RigidBodies":{
            "joint":{
                "connectedNode": ...,
                "jointLimits":0
            }
        }
    },
    "name": "NodeToRepresentAJoint"
}
{
    "extensions":{
        "MSFT_RigidBodies":{
            "joint":{
                "connectedNode": ...,
                "jointLimits":0
            }
        }
    },
    "name": "NodeToRepresentAJointWithTheSameLimitsAsAbove",
}
When physics body nodes are joined, they should not collide with each other. 

This should be optional. Collision geometry can still impose limits which can't be described with joints.

@eoineoineoin
Copy link
Member

Also, unrelated to the spec discussion, but Aaron, all of your PRs have been force-pushed. This makes it much harder for a reviewer to see what the actual changes are and causes any inline-comments on the changes to be marked as "outdated" in the GitHub review, even if they havent' been modified. If you could refrain from force-pushing in the future, that would be great.

@aaronfranke
Copy link
Contributor Author

aaronfranke commented May 29, 2023

the new extension objects have an unreasonable restriction that a node can only be a collider, rigid body, joint or a shape and cannot store any other properties simultaneously

I don't know why this restriction is in the spec; the only reason I can think of is "because that's what the Godot scene tree looks like in the editor."

The reason is simple: We want to support multiple collider shapes per body, which requires child nodes. We want to support transforming those collider shapes, which requires child nodes. Therefore we make it the only option to have child nodes, instead of having 2 different ways of adding a collider shape: on the node and on child nodes. Unity allows offsetting the center on the shape itself, but this seems to add unnecessary complexity and reinvents transforms when it's not necessary since you can just use child nodes.

Similarly, meshes may want to be transformed from the physics body (since the physics body origin is the center of mass), so it makes sense to have those on child nodes. However, I won't die on this hill, if we think it is very important to allow this then we can.

To be clear, I have already written the ability for the Godot implementation to import colliders placed on the same node as the body since it happened to be easy, but I don't know if that's worth standardizing when we can instead standardize having less possibilities for the implementer to worry about (just child nodes).

must be direct children, not indirect children.

Again, this is a totally arbitrary and unnecessary restriction

This is another hill I will not die on. If we allow indirect children, we can do that, we just need to be aware that it adds an additional possibility that implementations need to handle.

isTrigger is is a physical property, not a geometric one. It should not be in the specification which defines geometry; this suggests that the design of OMI_physics_body is leaking into OMI_collider, which should be independent.

I agree with you, but that's not what we decided as consensus in the OMI meetings. If I was just making an extension for myself, I would replace OMI_collider with a theoretical OMI_physics_shape which does not define isTrigger but keeps everything else from OMI_collider.

However, the reason we decided on this course of action is that we wanted to support the use case of importers only supporting OMI_collider and not OMI_physics_body, for importing simple level geometry. The problem would arise when an importer accidentally turns a trigger shape solid, so what the content creator thought was a harmless trigger volume for detection is now blocking the ability of objects to move.

If you want to remove isTrigger from OMI_collider, then we would likely want to rename it to OMI_physics_shape, and we would either have to accept 1) That users of OMI_physics_shape but not OMI_physics_body would end up with broken blocking triggers (bad), or 2) That users of OMI_physics_shape would be required to also implement OMI_physics_body or at least be aware of it to look for whether or not it's a trigger (this is not as bad, but it does create a 2-way dependency between the specs).

The OMI_collider's type string is inflexible and prevents users from extending the set of shapes that are supported. Users are permanently limited to the six collider types defined in the spec. In a 1:1 meeting, I was asked how a particular engine could expose their signed distance field shapes. ... This can't be done in OMI_collider.

Sorry, but that's just straight up wrong. There is already precedent here for what to do if you want to add more but avoid touching the type string enum. Take a look at EXT_texture_webp:

"textures": [
    {
        "source": 0,
        "extensions": {
            "EXT_texture_webp": {
                "source": 1
            }
        }
    }
],

EXT_texture_webp defines a PNG/JPG source image as a fallback, and a WebP image to use instead for implementations that support EXT_texture_webp. Similarly, you could define a fallback OMI_collider shape and provide an extension MY_SDF_Shape_Extension that overrides it for implementations that support it.

With your example JSON, you are only providing an extension with no fallback, which means that the shape has nothing to load as a fallback. This breaks implementations that follow MSFT_CollisionPrimitives. Since you are already breaking compatibility with that, you could argue that it's no less bad to just add a new string to OMI_collider, but having an extension + fallback as EXT_texture_webp does is better.

Keep in mind that this was designed with the precedent of KHR_lights_punctual in mind. If you wanted to add on a new light type, you would have the exact same situation as trying to add a new collider type: you could break the standard by adding something to the type enum, or provide an extension + fallback.

Why does this restriction exist? This breaks the "zero one infinity" rule. ... the correct solution was to fix/workaround the limitation in Unity. It was not to edit the spec to say "meshes should have less than 65000 triangles in total."

I agree, but this was requested in the OMI meetings by Unity users to improve compatibility with Unity. If we want to change this, it would be worth defining what implementations are supposed to do. Simplify the mesh? Split it up into multiple meshes? If so, do we specify the algorithms to use for simplifying and splitting? This was all discussed in the meeting, the short-term solution pending a consensus of what else to do is to put a limit.

We can change this, we just need to decide exactly what that will look like. Something like "If an engine does not support convex hull shapes with the number of vertices in the shape, it should do... (what)".

The glTF mesh in the array MUST be a trimesh to work, and should be made of only one glTF mesh primitive (one surface)

Why does this restriction exist? There's no reason the physics shape can't concatenate all the meshes together.

This is another case of "we weren't sure what to do so we kept things simple and portable". If we define what's supposed to happen in this case (combining the meshes together), that's perfectly valid.

Probably should read "half-extents," rather than "extents" here? Also, a cube with extents of [1, 1, 1] does not have a radius of 0.5.

Extents are already half of the size. I assumed radius was understandable but maybe it's not. This may be a terminology problem, we can reword this if it's confusing. Maybe "a cube with a size of [1, 1, 1] has a distance of 0.5 between the center of the cube and the center of each face".

Finally, there's no options for collision filtering; in practice, any non-trivial usage of colliders will require collision filtering. While it's common for applications to use a dynamic collision filter which ties into the business logic of the application, it's important to have some sort of filtering which covers non-dynamic filtering use-cases.

I agree that collision filtering is important to have somewhere, but:

  • I disagree that this is vital for non-trivial uses. Maybe it is vital in an implementation to change filtering at runtime, but we are talking about glTF here. Storing the physics information for a single object like a prop, character, vehicle, game map, etc does not require collision filtering. Most things don't need it.
  • As discussed here on MSFT physics, there are a lot of edge cases that must be defined.
  • Collision filtering could be an additional extension. Considering it's a big layer of complexity, and IMO is not needed in many cases, I think it should be, so that implementers are not required to implement it as part of rigid bodies.

Physics bodies without collision shapes on them will not have any function.

This not true.

You are correct, it can still be simulated and could have a function such as by connecting to a joint. We need to update the wording to say "Physics bodies without collision shapes on them will not collide with anything".

The default mass is 1.0, but the default inertia is [0]? Is this to be interpreted that, by default, "rigid" types can't rotate? This will cause no end of end-user confusion.

No, this is explicitly written in the spec:

If zero or not specified, the inertia should be automatically calculated by the physics engine.

I don't think the inertia tensor should be a required part of the physics body. It's not something that most users are expected to understand, and it's unreasonable to require this to be hand-calculated if writing glTF JSON by hand.

Some engines only support specifying the inertia around the principle axes as a Vector3. In those engines, when importing a glTF file and reading the inertia matrix, only the diagonal principle axis values should be used, and the non-diagonal coupling values should be discarded. Similarly, when exporting a glTF file while writing the inertia matrix, write the Vector3 values to the matrix diagonal principle axis values, and set the non-diagonal coupling values to zero.

This is absolutely the wrong thing to do. ... By saying "you can ignore the orientation of the inertia tensor and when writing a glTF file, set that value to the identity rotation" you're going to get the wrong result and objects will not behave realistically. ... I don't think anyone would agree that this sort behaviour should be permitted by a specification.

I completely disagree. It's not acceptable to say that an engine which does not support the full matrix is not allowed to have any kind of fallback. This seriously hinders portability. Keep in mind that most objects will have their parts symmetrical around the local axes, a complex inertia tensor (or even specifying one at all) is for niche use cases. I completely disagree that engines should be required to handle it, it significantly hinders implementations.

I'll also note that Godot does actually support inertia orientation -- it's just not exposed in the UI.

That's great - but until it's exposed, it's not suitable for the importer to touch this.

What does it mean to be a vehicle?

The specifics are implementation defined. The spec only requires that it behaves like a rigid body. In Godot and Unreal, there is a distinction. In Unity, there is no distinction. To me this only helps.

As for Godot changing the mass to 40 kg, this is arbitrary and obviously should not be a part of the spec - importers should explicitly set the mass to whatever the file specifies, or the default of 1 kg.

Unreal has a "Character" prefab - why is that not suitable for OMI_rigid_body.type=character objects, and instead, you mandate "Pawn, Simulate Physics = false"?

I'm not an expert on Unreal, this is what I was told by Unreal users is the best equivalent type in Unreal.

The character type is only required to be treated similarly to kinematic, if so then it is following the spec. Having these additional values in the enum helps implementations use a more specific type that is suitable to the intent. Again, it's implementation-specific. For example, maybe an implementation wants to look through a glTF scene for the characters and make them navigate, but it will need to not do the same with moving platforms. This is very implementation-specific, even more so than engine-specific (even in Unity you could store a flag for character when importing) but is one possible use case that I don't see a reason to forbid. Of course this is not the only way to accomplish this, there could be a separate spec or an extension, but it could be as simple as reading an additional string value.

Character bodies are kinematic, ... They can also move through static objects

Come on, I have no choice but to assume this was in bad faith. Of course they collide with static objects...

So, a "trigger" body is composed of multiple colliders which also have an "isTrigger" property, but the body should ensure that the collider's setting is used? This sure seems like the OMI_rigid_body.type=trigger actually has no effect?

No, because trigger shapes under a trigger body means that the trigger shapes are combined together into one trigger, and trigger shapes under a non-trigger body means that each trigger is separate. (see below: "My view...")

Quick note, the way that MSFT defines triggers as a body without a physics material is very bizarre to me. It makes no sense, it's very surprising and I would imagine that implementations may get this wrong. It's also very weird to require physics materials on all objects or else they end up being triggers, it requires all implementations to be aware of physics materials, while OMI's specs do not because whether something is a trigger is defined explicitly.

"Static" should not be a body type; all the motion properties which come with an OMI_physics_body will be ignored

The properties like mass and inertia tensor are also usually ignored with kinematic. You could argue that the mass could be used in a niche case with kinematic, but the same argument could be applied to static (example: an object that does not move but can be deformed when something impacts it could use the mass to determine density).

My view is different from yours: the primary function of a body is to combine together physics shapes. The motion of the body is secondary to that. This is what all physics types have in common, they combine shapes.

The need to combine shapes together is very important. To me combining together shapes is similar to how you feel about collision filtering, it's an essential vital feature. Without being able to combine together trigger shapes with a trigger body, it is impossible to build an L-shaped trigger volume. One option would be to allow OMI_collider to combine shapes, but this is a bad idea because OMI_physics_body already has that job.

Regardless, even if we only had "kinematic" and "rigid" in the enum, I still think this is more readable than MSFT's boolean. What does "not isKinematic" mean? Kinematics is the motion of bodies, so I guess this means the body should be frozen and not move. Of course I know what you mean by not having this enabled, I'm not going to make a bizzare assumption like you did with character bodies and static objects, I'm just illustrating a point.

A joint cannot be connected to a trigger body, and cannot be connected to a non-OMI_physics_body glTF node.

Why not? Again, both of these requirements are overly-restrictive.

Fundamentally, in the most simple case, what do joints connect? They connect bodies. For example, connecting 2 rigid bodies. I think it is not surprising at all and is very sensible to have connecting 2 bodies be the only case.

This is another argument in favor of static bodies. Having a joint only connect bodies is the simplest way to do it. Without static bodies, if you wanted to have a joint connect to a static object, you would have to connect it to a kinematic body, or we would have to change joints to allow connecting to things that aren't a body, but that would be odd because most of the time you are connecting to a body anyway (for example, a rope hanging from the ceiling depends on the position of the ceiling, so connect it to the static body for the building).

You replaced the two pivots with a single one.

What? Where does MSFT define 2 pivots? I can't find any mention of this in the README or the JSON schema.

Physically speaking, a joint does work with a single real world physical space, and makes no preference to either body. It's also more intuitive, and is how Godot, Unreal, Roblox, and Blender expose this to the user. The way it's implemented under the hood is simply not relevant, and it seems like Unity is the odd one out here that defines the joint as a component of one rigid body that references another body.

Godot's joint implementation really does represent the constraint space as a relative transform from each body:

This is not relevant, it's an implementation detail - the joint position relative to each body is always overlapping in global space when a stable simulation is saved. Therefore, we only need one position. It's neither useful nor generally portable to allow defining these separately. But again, I'm confused because MSFT doesn't do that either as far as I can see, I can't find any mention of 2 pivot points as you claim.

In MSFT_rigid_bodies, a "joint" object indexes into a single object in the document-level array, and that object contains the set of limits. Not sure what this adds, as those "separated" limit sets can't generally be shared

Joint constraints very much can be shared. For example, a default pin joint looks like this:

        "OMI_physics_joint": {
            "constraints": [
                {
                    "linearAxes": [0, 1, 2]
                }
            ]
        }

It's not unreasonable at all to have the joint parameters shared by many joint nodes.

I feel like there is a fundamental misunderstanding here - I have tried to read the MSFT joints spec and I still do not understand, so perhaps you need to significantly reword it to be more clear and understandable.

When physics body nodes are joined, they should not collide with each other.

This should be optional. Collision geometry can still impose limits which can't be described with joints.

We could change this. Godot has support for it already. I just don't know how useful or portable it is, but if we determine that it is both useful and portable then yes we should change this.

Also, unrelated to the spec discussion, but Aaron, all of your PRs have been force-pushed.

This is best practice according to the Godot documentation, to squash and force-push, it's the workflow I am used to. The history is relevant once it's merged, and you can also follow the changes over time by comparing the diffs between force-pushes, but force-pushing keeps the final history clean. But I will avoid this in the future.

@eoineoineoin
Copy link
Member

eoineoineoin commented Jun 6, 2023

While I'd be delighted to get MSFT_rigid_bodies and OMI_physics_body more aligned (and possibly, to prefix with "EXT_",) I think there's a couple of barriers. Naming and documentation can obviously be changed pretty easily, but I think there's a number of serious gaps and mistakes in OMI_physics_body (in particular, the restrictions on document structure that OMI_physics_body imposes) which we need to close and I suspect that solving those problems in OMI_physics_body will provide a lot of clarity on the more minor issues. I'm aware we're in very different timezones, but if you want, I'm happy to jump into a call where we can diagram and throw around some ideas without having to be as rigorous as we're trying to be this discussion.

The reason is simple: We want to support multiple collider shapes per body, which requires child nodes. We want to support transforming those collider shapes, which requires child nodes. Therefore we make it the only option to have child nodes, instead of having 2 different ways of adding a collider shape: on the node and on child nodes. Unity allows offsetting the center on the shape itself, but this seems to add unnecessary complexity and reinvents transforms when it's not necessary since you can just use child nodes.

Similarly, meshes may want to be transformed from the physics body (since the physics body origin is the center of mass), so it makes sense to have those on child nodes. However, I won't die on this hill, if we think it is very important to allow this then we can.

To be clear, I have already written the ability for the Godot implementation to import colliders placed on the same node as the body since it happened to be easy, but I don't know if that's worth standardizing when we can instead standardize having less possibilities for the implementer to worry about (just child nodes).

Trying to be as clear as possible; in my forklift example from the previous comment, that's really how people make content. Requiring people to restructure their content because it's "unnecessary complexity" is bad. I also fail to see how this is complex -- the collider is always added to the body with the transform of (worldFromRigidBodyNode^-1 * worldFromColliderNode).

MSFT_rigid_bodies can describe the following scenarios:

A node which is renderable, movable and collidable:
{
    "extensions":{
        "MSFT_rigid_bodies":{
            "rigidBody": { },
            "collider":0,
            "physicsMaterial":0
        }
    },
    "mesh":0,
    "name": "EverythingInOne"
}
A node which has direct-child colliders
{
    "extensions":{
        "MSFT_rigid_bodies":{
            "rigidBody": { },
        }
    },
    "mesh":0,
    "children": [1, 2],
    "name": "Root"
},
{
    "extensions":{
        "MSFT_rigid_bodies":{
            "collider":0,
            "physicsMaterial":0
        }
    },
    "name": "ColliderA"
},
{
    "extensions":{
        "MSFT_rigid_bodies":{
            "collider":1,
            "physicsMaterial":0
        }
    },
    "name": "ColliderB"
}
A node which has indirect-child colliders:
{
    "extensions":{
        "MSFT_rigid_bodies":{
            "rigidBody": { },
        }
    },
    "mesh":0,
    "children": [1],
    "name": "Root"
},
{
    "children": [2, 3],
    "name": "Intermediate"
},
{
    "extensions":{
        "MSFT_rigid_bodies":{
            "collider":0,
            "physicsMaterial":0
        }
    },
    "name": "ColliderA"
},
{
    "extensions":{
        "MSFT_rigid_bodies":{
            "collider":1,
            "physicsMaterial":0
        }
    },
    "name": "ColliderB"
}

These are all important use-cases which reflect how content creators actually want to create and structure their assets. Of these, OMI_physics_body only supports the second, and even then, requires an additional node to draw the mesh:

{
    "extensions": {
        "OMI_physics_body": {
            "type": "rigid"
        }
    },
    "children": [1, 2, 3],
    "name": "Root"
},
{
    "extensions": {
        "OMI_collider": {
            "collider": 0
        }
    },
    "name": "ColliderA"
},
{
    "extensions": {
        "OMI_collider": {
            "collider": 1
        }
    },
    "name": "ColliderB"
},
{
    "mesh": 0,
    "name": RenderGeom"
}

As an example of why this is particularly bad; consider that "ColliderA" and "ColliderB" describe the collision volume of "RenderGeom" - this document structure disconnects that logical relationship. If the transform of "RenderGeom" changes, you now also have to update the transform of "ColliderA" and "ColliderB" - something which wouldn't have had to be done if the colliders were children of the mesh.

However, the document layout proposed by OMI_physics_body also has an additional problem in that the use of OMI_collider is ambiguous. If another extension wants to make use of OMI_collider (which we do want to enable), with the layout proposed by OMI_physics_body, it's impossible to discern if "SomeCollider" should be used by "OMI_physics_body" or "MY_new_extension_which_uses_OMI_collider" (or both? or neither?):

{
    "extensions": {
        "OMI_physics_body": {
            "type": "rigid"
        },
        "MY_new_extension_which_uses_OMI_collider": {
            ...
        }
    },
    "children": [1],
    "name": "Root"
},
{
    "extensions": {
        "OMI_collider": {
            "collider": 0
        }
    },
    "name": "SomeCollider"
}

MSFT_rigid_bodies does not have this problem; the collider is referenced directly by the MSFT_rigid_bodies object, rather than being implicitly linked -- visible in the examples above.

On triggers:

However, the reason we decided on this course of action is that we wanted to support the use case of importers only supporting OMI_collider and not OMI_physics_body, for importing simple level geometry. The problem would arise when an importer accidentally turns a trigger shape solid, so what the content creator thought was a harmless trigger volume for detection is now blocking the ability of objects to move.

This is still ascribing physical properties to an extension which should not be describing physics. What does isTrigger mean for MY_new_extension_which_uses_OMI_collider? "Triggerness" does not belong in an extension that describes collision, and by trying to support this use-case in this way, it makes OMI_collider less useful, as you won't be able to untangle use-cases of OMI_collider's implicit physics behaviour from MY_new_extension_which_uses_OMI_collider.

Your use-case here seems to suggest that OMI_collider is still very much a "rigid body" extension not a more general-purpose set of collision primitives? In MSFT_rigid_bodies, we split out the collision geometry into a more abstract "collision" extension by request from people who wanted to use the same primitives for other, non-physics applications.

On custom shapes:

EXT_texture_webp defines a PNG/JPG source image as a fallback, and a WebP image to use instead for implementations that support EXT_texture_webp. Similarly, you could define a fallback OMI_collider shape and provide an extension MY_SDF_Shape_Extension that overrides it for implementations that support it.

With your example JSON, you are only providing an extension with no fallback, which means that the shape has nothing to load as a fallback. This breaks implementations that follow MSFT_CollisionPrimitives. Since you are already breaking compatibility with that, you could argue that it's no less bad to just add a new string to OMI_collider, but having an extension + fallback as EXT_texture_webp does is better.

Thanks for bringing up the EXT_texture_webp example. The README has a note and some discussion of the use-case:

Defining a fallback texture is optional.

Using Without a Fallback

To use WebP images without a fallback, define EXT_texture_webp in both extensionsUsed and extensionsRequired. The texture node will then have only an extensions property as shown below.

This use-case does not work in OMI_collider. To be clear, MSFT_collision_primitives does support the case where the "MY_SDF_Shape_Extension" is required (illustrated in my previous comment) and the case where it is optional; explicitly illustrated here, in case it wasn't obvious:

"extensions":{
    "MSFT_CollisionPrimitives":{
        "colliders":[
        {
            "sphere": { "radius": 1 },
            "extensions":{
                "MY_SDF_Shape_Extension": {
                    "sdf_data": ...
                }
            }
        }
        ]
    }
}

I agree that collision filtering is important to have somewhere, but:

I disagree that this is vital for non-trivial uses. Maybe it is vital in an implementation to change filtering at runtime, but we are talking about glTF here. Storing the physics information for a single object like a prop, character, vehicle, game map, etc does not require collision filtering. Most things don't need it.

I think you underestimate how important a collision filter is. I genuinely don't think any of our customers have ever shipped a product without using a filter. Here's a very simple asset I made recently:

2023-06-06_13-13

I've got a stand, constrained (represented by the black frames) to one blade of a scissor, which in turn, is constrained to the other blade of the scissor. However, because I just need the stand to collide with -say- a table, I don't need complex collision on it, so I'll use a convex hull but the convex hull of the stand looks like this:

2023-06-06_13-14

This causes forces to be applied to the second blade of the scissor and pushes that blade out unnaturally. As an artist, my options are either "decompose the stand into a more complex set of colliders" or "add a constraint with no limits, to get it's pseudo-filtering behaviour" or "use a collision filter" -- I opted for the latter as it was the most appropriate for that use-case.

Some engines only support specifying the inertia around the principle axes as a Vector3. In those engines, when importing a glTF file and reading the inertia matrix, only the diagonal principle axis values should be used, and the non-diagonal coupling values should be discarded. Similarly, when exporting a glTF file while writing the inertia matrix, write the Vector3 values to the matrix diagonal principle axis values, and set the non-diagonal coupling values to zero.

This is absolutely the wrong thing to do. ... By saying "you can ignore the orientation of the inertia tensor and when writing a glTF file, set that value to the identity rotation" you're going to get the wrong result and objects will not behave realistically. ... I don't think anyone would agree that this sort behaviour should be permitted by a specification.

I completely disagree. It's not acceptable to say that an engine which does not support the full matrix is not allowed to have any kind of fallback. This seriously hinders portability. Keep in mind that most objects will have their parts symmetrical around the local axes, a complex inertia tensor (or even specifying one at all) is for niche use cases. I completely disagree that engines should be required to handle it, it significantly hinders implementations.

Again, I think you're underestimating the importance of this and maybe only considering trivial assets. Here's another asset; the purple box represents the calculated inertia tensor; most important to note is the orientation of that box (which is the basis formed from the eigenvectors of the inertia tensor):

2023-06-06_13-16

This box is rotated ~30 degrees from the asset, which is quite a large difference from just using the identity. I don't think chairs are a particularly nice use-case and this has implications for both how the object behaves and how to physically drive an object to a particular transform. To be clear; this is just about the wording of the document. There is no physics simulation which does not support an inertia tensor, as it's absolutely required to get correct physical behaviour. The OMI_physics_body spec as it is right now, gives implementers a free pass to throw away vital simulation information seemingly arbitrarily?

While on this topic, I see you made a change to the center-of-mass definition - this seems real weird to me for a number of reasons. First, inertia and center-of-mass are both manifestations of the exact same physical phenomenon - they're intrinsically linked, so it's really weird to split them up; why is the center-of-mass provided by a node translation, while the inertia tensor is specified via a property on the OMI_physics_body? More importantly, though, this interferes with how assets are authored; while this can be hidden with tooling (to a limited degree) you won't be able to get around the fact that you can't opt out of this behaviour - either an artist or the exporter needs to calculate the COM when exporting and, when importing, you'll get an extra node with an arbitrary (from the artist's perspective) transform, which is not how artists make assets - by far the vast majority of assets are modelled with their origin at the base of the object. If you add a piece of geometry (which would change the COM,) that artist has to then move all the child nodes and then apply the inverse of that translation to the body node, in order for it to appear in the correct location in the scene.

With MSFT_rigid_bodies, the COM and IT are on the same footing (inside the extension object, rather than leaking into the document structure,) they're both optional and they also permit description of infinite mass/inertia (which OMI_physics_body cannot, but is important for some use-cases). If an artist isn't using tooling which can automatically calculate the COM, they don't have to provide a COM manually and the physics simulation will calculate an appropriate one.

Character bodies are kinematic, ... They can also move through static objects

Come on, I have no choice but to assume this was in bad faith. Of course they collide with static objects...

To be clear; "kinematic" is a term used in both physics and computer animation; note that these definitions are different from the one you've used:

https://en.wikipedia.org/wiki/Kinematics
Kinematics is a subfield of physics, developed in classical mechanics, that describes the motion of points, bodies (objects), and systems of bodies (groups of objects) without considering the forces that cause them to move.

https://en.wikipedia.org/wiki/Forward_kinematics#Computer_animation
Forward kinematic animation can be distinguished from inverse kinematic animation by this means of calculation - in inverse kinematics the orientation of articulated parts is calculated from the desired position of certain points on the model. It is also distinguished from other animation systems by the fact that the motion of the model is defined directly by the animator - no account is taken of any physical laws that might be in effect on the model, such as gravity or collision with other models.

So, the defining characteristic of a "kinematic" body describes one which will pass through static geometry. So, when you describe a character as "kinematic but of course collides with static objects," there's something wrong.

The more important point I'm trying to make here is that without a rigorous description of what it means to be a character, you'll get as many different interpretations as there are implementors. As I highlighted, Unreal, Unity3D and Godot all have native objects to represent "characters" and they're all different and you've chosen that Godot's character is the most appropriate and that Unity3D's and Unreal's character are not correct. Why?

A "character" type is not required for a rigid body specification and should not be an option here. Same with vehicles.

Quick note, the way that MSFT defines triggers as a body without a physics material is very bizarre to me. It makes no sense, it's very surprising and I would imagine that implementations may get this wrong. It's also very weird to require physics materials on all objects or else they end up being triggers, it requires all implementations to be aware of physics materials, while OMI's specs do not because whether something is a trigger is defined explicitly.

Here's the rationale. For a trigger, they can't make use of a material - it just does not make sense to add friction/restitution to them, as it will never have an effect, while, for non-triggers, the physics simulation needs to choose some value for friction/restitution. As it is, a trigger is defined explicitly in MSFT_rigid_bodies - a collider that has a physics material (i.e. it has attributes which describe a collision response) is not a trigger, while a collider which lacks those attributes is a trigger. I think this makes sense, however, how does something like this look? I think It's a little bit awkward, in that it introduces a new object, but maybe feels less weird to you?

You replaced the two pivots with a single one.

What? Where does MSFT define 2 pivots? I can't find any mention of this in the README or the JSON schema.

From the README:

The transform of the node (or the connectedNode) from the first parent rigidBody defines the constraint space - for example if a joint were to eliminate all degrees of freedom, the physics simulation should attempt to move the rigidBody nodes such that the transforms of the constrained child nodes become aligned with each other.

I can certainly clarify the language around here if you find it confusing.

Physically speaking, a joint does work with a single real world physical space, and makes no preference to either body. It's also more intuitive, and is how Godot, Unreal, Roblox, and Blender expose this to the user. The way it's implemented under the hood is simply not relevant, and it seems like Unity is the odd one out here that defines the joint as a component of one rigid body that references another body.

This is not relevant, it's an implementation detail - the joint position relative to each body is always overlapping in global space when a stable simulation is saved. Therefore, we only need one position. It's neither useful nor generally portable to allow defining these separately. But again, I'm confused because MSFT doesn't do that either as far as I can see, I can't find any mention of 2 pivot points as you claim.

Both of these claims are incorrect; you need two pivots with distinct transforms in order to represent a joint. A single joint transform does not overlap in a shared reference frame, even for whatever definition of "stable simulation" you're using. I can write a proof or pull some citations from the literature, but please, please, please ask any physics experts in the OMI group about this.

Also, unrelated to the spec discussion, but Aaron, all of your PRs have been force-pushed.

This is best practice according to the Godot documentation, to squash and force-push, it's the workflow I am used to. The history is relevant once it's merged, and you can also follow the changes over time by comparing the diffs between force-pushes, but force-pushing keeps the final history clean. But I will avoid this in the future.

Thanks. Changes can still be squashed on merge, but it's much handier to be able to see specific changes during a review, rather than having to dig through a large monolithic change.

@aaronfranke
Copy link
Contributor Author

aaronfranke commented Jun 7, 2023

(in particular, the restrictions on document structure that OMI_physics_body imposes)

I have opened a PR to start the discussion to change this: omigroup/gltf-extensions#169

I am still skeptical of the portability of having indirect children, but I think it would make sense to allow it and just recommend direct children for those desiring a simple document structure. You are 100% correct that there is no conceptual reason for why it can't be allowed, it's just a matter of implementation.

Your use-case here seems to suggest that OMI_collider is still very much a "rigid body" extension not a more general-purpose set of collision primitives?

Again, the consensus of the discussion was that people desire to have OMI_collider act by itself. So yes, it's not just the collision shapes (primitives). If it was purely up to me I would replace this with OMI_physics_shape and simply require that implementers also support OMI_physics_body or else it won't work right, but having that requirement goes directly against the OMI consensus for OMI_collider to be standalone. But, we will discuss this. EDIT: The consensus is no longer the same, we may change this. omigroup/gltf-extensions#171 EDIT 2: Changed.

Using Without a Fallback

This use-case does not work in OMI_collider.

...yes it does.

If your glTF document requires an extension, then it can only be imported by implementations that have that extension. Therefore, by requiring the extension, you are automatically requiring that there is no fallback.

"extensions": {
    "OMI_collider": {
        "colliders": [
            {
                "extensions": {
                    "MY_SDF_Shape_Extension": {
                        "sdf_data": ...
                    }
                }
            }
        ]
    }
}

If MY_SDF_Shape_Extension says "If an OMI_collider has MY_SDF_Shape_Extension in it, this overrides the type property", and you don't provide a type fallback, then... you have just used it without a fallback...

I think you underestimate how important a collision filter is. I genuinely don't think any of our customers have ever shipped a product without using a filter. Here's a very simple asset I made recently:

A product is much bigger than a single asset. Single assets might not require collision filtering. In fact, many games may not require collision filtering, such as countless indie or game jam games. I don't think it should be a required feature baked into the physics body spec, I think it should be separate.

I've got a stand, constrained (represented by the black frames) to one blade of a scissor

Your example has 2 objects in it. I am thinking simpler. A glTF file with only one physics object in it does not need collision filtering to work, unless it was expecting to be combined with other files, but I consider that to be niche. A similar thought goes to joints: they aren't needed for simple situations, so I think they should be a separate spec.

To be clear, putting it on a separate spec is not saying that it's unimportant. It may be absolutely vital for a ton of use cases. However, it's also not needed for other use cases. Putting it on a separate spec also gives users the freedom to use an alternative spec if an application has even more specific needs.

So, the defining characteristic of a "kinematic" body describes one which will pass through static geometry.

Sorry, I don't have anything nice to say in reply to this. Maybe someone else can help explain better.

is the most appropriate and that Unity3D's and Unreal's character are not correct. Why?

You can use any character type you want in Unity3D and Unreal. But it's not required. As long as it is some kind of physics body that is kinematic, then it's compliant. You can implement any kind of superset you want. For example, maybe an implementation wants to take characters and have them pathfind around a map - sure, why not? The point is that we can say it is a character, which benefits implementations that make the distinction. If a Unity3D implementation decides it wants to attach custom scripts to all characters, but not moving platforms - this would be helpful for that.

You replaced the two pivots with a single one.

What? Where does MSFT define 2 pivots? I can't find any mention of this in the README or the JSON schema.

From the README:

Sorry, I still don't understand what you mean by 2 pivots. However...

Both of these claims are incorrect; you need two pivots with distinct transforms in order to represent a joint.

...if you are talking about the pivots relative to the participating bodies, that is already the case with OMI_physics_joint. The joint node's position relative to each body is the pivot point for that body. There are two bodies, therefore there are two pivots. If you want one of the body's pivot points to be offset from that, you can modify the lowerLimit and/or upperLimit properties. Setting them both to 5 will force the pivots to be offset by 5.

However, I insist that the common case is for the pivots to overlap, and this is a niche feature that should not hinder the more common cases with a more complex representation. Being able to specify a type of joint, like a pin joint, only once, and share that definition, is very useful and very readable. All of the basic use cases I can think of involve overlapping pivots. For example, a rope has its segments connected. A rope with gaps would be very weird. From Truck Town:

Screenshot 2023-06-07 at 12 22 02 AM

Why should this simple use case be any more complicated than "here are a few joint nodes that constrain linearly"?

I try to follow the philosophy of "if it seems like it should be that simple, make it that simple".

and is how Godot, Unreal, Roblox, and Blender expose this to the user. ... it seems like Unity is the odd one out here

Both of these claims are incorrect

This is just factually not incorrect. This is how these game engines expose the behavior in the form of objects, which is also the layer with which importers and exporters will work, because we are creating objects, or creating from them.

@aaronfranke
Copy link
Contributor Author

aaronfranke commented Jun 14, 2023

So, the defining characteristic of a "kinematic" body describes one which will pass through static geometry.

@eoineoineoin I apologize for the confusion before, I have discussed this with many more people and now I understand the confusion. There are two competing, but not conflicting, ideas about what a kinematic body is:

  1. A kinematic body can move, but is not affected by forces. It can be moved by animation or script. The movement is handled by setting the position, which necessarily means we are bypassing the physics engine for performing movement. The only thing the physics engine does is allow rigid bodies to collide with it. This is very similar to the question what happens if an unstoppable force meets an immovable object.

  2. A kinematic body can move, but is not affected by forces. It can be moved by animation or script. The movement could be defined by setting the position to teleport the body, but the intended use case is to allow for precise control over movement of an object within a scene (ex: a character) that still collides with all of the scene's geometry. For example, Godot's CharacterBody3D.move_and_slide and KinematicBody.move_and_slide methods, Unity's CharacterController.Move and Rigidbody2D.Slide, and Unreal's UMovementComponent::SlideAlongSurface.

Our conflict came from me only thinking of idea 2 and you only thinking of idea 1. Ask any Godot Engine user, and they will very strongly answer with idea 2, and would be completely taken off guard by the statement "kinematic body [...] will pass through static geometry". Ask a Unity user or an animator, and they will likely answer with idea 1, since in Unity the idea 2 behavior is achieved by attaching an additional component.

Furthermore, idea 1 is farther from a rigid body than idea 2, since idea 2 is meant for objects that may still behave the laws of physics like not passing through geometry, but has finer control than a rigid body, and idea 1 is for things like moving platforms that are not expected to care about any other physical objects when they move.

Similarly, Unity has a "CharacterController" component; why is that not suitable?

So, yeah, my lack of Unity knowledge completely blew up in my face here. The CharacterController component would be correct type in Unity (see note).1

A "character" type is not required for a rigid body specification and should not be an option here. Same with vehicles.

I don't see it as a rigid body specification, I see it as a physics body specification. Kinematic bodies are not rigid bodies, trigger bodies are not rigid bodies.

Anyway, now that we know this, I think this actually significantly increases the importance of having a separate character type, because we have an important distinction:

  • "type": "kinematic": "this body is intended to pass through solid geometry (move through animation/splines/etc)"
  • "type": "character": "this body is intended to collide with solid geometry (move smoothly via method calls)"
  • Also rigid of course, and I still am in favor of explicit static and trigger, and I can make similar argument for vehicle, though the case for vehicle is considerably less strong than for character.
  • To be clear, I am in no way saying that these types are fundamental to the universe or anything. These are useful common things for game engines and physics engines. Everything except rigid is made up.

Note that the body extension does not need to define further details (like these properties), that should be left to another extension since it's out-of-scope for bodies. Even without an extension with further details, users can adjust the details later post-import, the most important thing is for the right type to be defined.

Having the type defined in the same extension is important because it allows importers to select the correct type of body. Otherwise, if there is no character type defined on the body extension, things get complicated. Let's assume a hypothetical character extension separated from the rigid body extension:

  • In Unity, you could have code for another extension attach the CharacterController component. From the documentation it looks like CharacterController component is intended to be used without a Rigidbody component, so an importer would need to prevent the Rigidbody component from being added (bad because an extension implementation is controlling another), OR remove the Rigidbody component afterwards (bad because a useless component is created then deleted), and copy over the values like linearVelocity and capsule size. This could be avoided if the spec had a character type as one of the possible body types.

  • In Godot, only one type of physics body node can be on a node, so we need to get it right on import. We could have another extension's code race to create a different node type before the main body extension, but that is a sub-optimal race condition that would change depending on the execution order of the extensions. Similarly to the Unity import issue, import would be a lot better if the body extension could be explicitly marked as a character.

The TL;DR is that I consider the type to be critically important to define on the body so that the correct type of object is created, and it's fine if further adjustments of data on those types would be left to other extensions, or users modifying the objects post-import. Critical on the level of specifying if a mesh shape is convex hull or concave trimesh.

  • But I won't die on this hill, if we decide to not have character and vehicle types, it doesn't make those impossible, it just means those cases will be a bit harder and must be handled by other extensions.

Footnotes

  1. Unless... the character does not use a capsule shape, because it seems like CharacterController only works with a capsule shape? So for non-capsules I guess it would have to generate a Rigidbody? Or it could reimplement CharacterController for arbitrary shapes, though that's a lot more work. But this is an implementation issue with Unity, similar to the situation with limiting the number of vertices in a convex hull being wrong, there is no reason for us to add a limitation of "you can only use one capsule shape with a character" to the spec, dealing with a box collider character is something for that implementation to deal with. In practice though characters are usually capsules, so I think this won't be a big issue, most users would probably be fine with the importer giving an error message of "character behavior is only supported for characters a single capsule shape, but this object has a different shape, importing a Rigidbody as fallback". Also I'm confused why Unity's 3D Rigidbody doesn't have a Slide method like its 2D counterpart does.

@aaronfranke
Copy link
Contributor Author

aaronfranke commented Jun 14, 2023

For triggers: I will break it down.

Here's the rationale. For a trigger, they can't make use of a material - it just does not make sense to add friction/restitution to them, as it will never have an effect

Yes, I agree with that.

while, for non-triggers, the physics simulation needs to choose some value for friction/restitution.

Not necessarily.

  • In some cases, like checking if a kinematic body enters a trigger volume, the friction/etc is never used.
    • By requiring a material to not be a trigger, you force files to have a maybe-unused physics material.
  • In some cases, the friction does not matter, like a basic character controller with cartoon physics navigating a level.
    • Such an object would instantly stop when the user input stops, and instantly start moving when input is given. It would never experience friction or any other forces like a rigid body would.
    • By requiring a material to not be a trigger, you force files to have a maybe-unused physics material.
  • In some cases, you may just want a glTF to hold geometry, and the material will be replaced at runtime.
    • By requiring a material to not be a trigger, you force files to have a placeholder physics material.
  • What if an application wants to define different physics materials for different shapes?
    • Physically speaking, it's perfectly logical to have an object with different friction on different sides.
      • I don't consider that to be portable, since most physics engines define a material per body, but I see it as a sensible possible extension that we shouldn't disallow.
    • By requiring a material to not be a trigger, you force bodies to have a body-wide physics material, which would likely be unused in a situation with per-collider physics materials.
  • What if an application has different needs for the physics material? What if it needs to specify different parameters? To me in this case it makes sense to have the physics material be a separate extension, so that it can be composed together with rigid bodies, but optionally not be.
    • But it can't be a separate extension as long as you are requiring it to define trigger or not trigger.
  • As an asset creator, I would not want to be forced to specify a physics material. What if I want to say "I don't care about the physics material, use whatever the default is"?
    • You could have the 3D modeling app like Blender do this, but what about when hand-writing JSON?
      • To be clear, I'm not saying hand-writing JSON is ideal, but my point is that I don't see a strong reason to make that harder when we could instead define triggers another way.
    • A counter-argument would be "we should force asset creators to explicitly define this". Which I disagree with. But if you want to have an asset's physics material behavior explicitly defined, my suggestion would be to just... add on the physics material extension to your asset.
      • Making it a separate extension is not saying that it's unimportant, just that it's abstracted.

As it is, a trigger is defined explicitly in MSFT_rigid_bodies - a collider that has a physics material (i.e. it has attributes which describe a collision response) is not a trigger, while a collider which lacks those attributes is a trigger.

I understand this, but I disagree that this is a good idea.

Let me ask you this: If a body does not have a physics material, and is therefore a trigger by the MSFT definition, what should isKinematic be set to? True? False? Neither seems appropriate, because it's neither kinematic or rigid. The trigger type is distinct from either, which indicates to me that this boolean should instead be an enum.

This last point I really want to emphasize:

If the node is part of a rigid body (i.e. itself or an ascendent has rigidBody properties) then the collider belongs to that rigid body and should move with it during simulation. Otherwise the collider exists as a static object in the physics simulation which can be collided with but can not be moved.

So, a physics material is per-MSFT_RigidBodies body node, but a MSFT_CollisionPrimitives shape node can be used by itself. These things are mutually incompatible with each other. If you have a collider by itself, what values for friction/restitution should it use? I see two answers:

  • Define the material on the collider. This would be greatly helped by having the physics material be a separate spec.
  • Define that it uses the "default" settings. My argument above is basically "why not allow this for the body too?".

Is there a third possibility thing I am missing? As far as I can tell, the friction for standalone MSFT_CollisionPrimitives is undefined in these specs, so I don't even know what the correct behavior is supposed to be.

@eoineoineoin
Copy link
Member

I realize we're having a very similar conversation on two threads (and I think your opinion has changed on some of these matters) but this is something important to consider:

As it is, a trigger is defined explicitly in MSFT_rigid_bodies - a collider that has a physics material (i.e. it has attributes which describe a collision response) is not a trigger, while a collider which lacks those attributes is a trigger.

I understand this, but I disagree that this is a good idea.

Let me ask you this: If a body does not have a physics material, and is therefore a trigger by the MSFT definition, what should isKinematic be set to? True? False? Neither seems appropriate, because it's neither kinematic or rigid. The trigger type is distinct from either, which indicates to me that this boolean should instead be an enum.

"triggerness" is a property of how an object responds to collision; "kinematicness" describes of the motion of the object. They are orthogonal properties. You can have a static or dynamic or kinematic trigger - they each have different use-cases and are all equally valid.

Earlier, I suggested a change which would make triggers more explicit and allow for implementations to have some sort of "default" physics material - eoineoineoin@34cbe95 - have you given any thought to that?

@aaronfranke
Copy link
Contributor Author

aaronfranke commented Jun 19, 2023

"triggerness" is a property of how an object responds to collision; "kinematicness" describes of the motion of the object. They are orthogonal properties. You can have a static or dynamic or kinematic trigger - they each have different use-cases and are all equally valid.

Technically yes, but there is no sense in having a dynamic rigid body trigger, because a trigger's defining feature is that its shapes are not solid and only are used for detection, so a trigger moving around dynamically with forces would just fall forever. A trigger does not have its motion directly determined by the physics engine, and it does not directly determine any other body's motion, it only exists in physics to detect things.

Earlier, I suggested a change which would make triggers more explicit and allow for implementations to have some sort of "default" physics material - eoineoineoin@34cbe95 - have you given any thought to that?

A node may have a trigger property set; this is similar to the collider in that it references a collision volume defined by the MSFT_CollisionPrimitives extension but lacks a physics material.

So it references only one shape for the trigger volume? This would prevent having triggers with multiple shapes, like an L-shaped trigger volume specified by 2 box shapes.

@eoineoineoin
Copy link
Member

"triggerness" is a property of how an object responds to collision; "kinematicness" describes of the motion of the object. They are orthogonal properties. You can have a static or dynamic or kinematic trigger - they each have different use-cases and are all equally valid.

Technically yes, but there is no sense in having a dynamic rigid body trigger, because a trigger's defining feature is that its shapes are not solid and only are used for detection, so a trigger moving around dynamically with forces would just fall forever. A trigger does not have its motion directly determined by the physics engine, and it does not directly determine any other body's motion, it only exists in physics to detect things.

This is wrong. Application or game code can apply forces to dynamic triggers. Joints can apply forces to dynamic triggers.
Non-trigger shapes attached to the same dynamic body can apply forces to triggers. A trigger does have it's motion determined by the physics engine.

Earlier, I suggested a change which would make triggers more explicit and allow for implementations to have some sort of "default" physics material - eoineoineoin@34cbe95 - have you given any thought to that?

A node may have a trigger property set; this is similar to the collider in that it references a collision volume defined by the MSFT_CollisionPrimitives extension but lacks a physics material.

So it references only one shape for the trigger volume? This would prevent having triggers with multiple shapes, like an L-shaped trigger volume specified by 2 box shapes.

It has the exact same semantics as the "collider" property, so of course you can build L-shaped triggers, just as you can build L-shaped collision volumes.

@aaronfranke
Copy link
Contributor Author

aaronfranke commented Jun 20, 2023

This is wrong. Application or game code can apply forces to dynamic triggers. Joints can apply forces to dynamic triggers. Non-trigger shapes attached to the same dynamic body can apply forces to triggers.

Well, technically yes, but I would consider that to be 2 bodies, not 1. A trigger would be added as a child of the body that is simulated. At least, this is how what you describe would have to be done in Godot to implement a dynamic trigger body.

A trigger does have it's motion determined by the physics engine.

At best, "can", not "does".

It has the exact same semantics as the "collider" property, so of course you can build L-shaped triggers, just as you can build L-shaped collision volumes.

collider | integer | The index of a top level Collider.

Sorry, I don't understand. It's "a" collider, how do you reference multiple?

@eoineoineoin
Copy link
Member

eoineoineoin commented Jun 20, 2023

This is wrong. Application or game code can apply forces to dynamic triggers. Joints can apply forces to dynamic triggers. Non-trigger shapes attached to the same dynamic body can apply forces to triggers.

Well, technically yes, but I would consider that to be 2 bodies, not 1. A trigger would be added as a child of the body that is simulated. At least, this is how what you describe would have to be done in Godot to implement a dynamic trigger body.

No. That's not 2 bodies. Adding an additional body adds six degrees of freedom, which the compound of a non-trigger and a trigger does not have. If you want to implement it that way, that's fine, but that's not what is actually being described, which is that the trigger and non-trigger colliders are both attached to the same frame.

It has the exact same semantics as the "collider" property, so of course you can build L-shaped triggers, just as you can build L-shaped collision volumes.

collider | integer | The index of a top level Collider.

Sorry, I don't understand. It's "a" collider, how do you reference multiple?

Exactly the same way you would make a non-trigger with an L-shape. You add a child node to position two boxes relative to one another:

```
{
		"name":"HorizontalPartOfTheL",
		"children":[
			1
		],
		"extensions":{
			"MSFT_rigid_bodies":{
				"rigidBody":{
					"inverseMass":1
				},
				"trigger": { "collider": 0 }
			}
		},
		"mesh":1,
		"name":"Cube"
	},
	{
		"name":"VerticalPartOfTheL",
		"extensions":{
			"MSFT_rigid_bodies":{
				"trigger": { "collider": 0 }
			}
		},
		"mesh":0,
		"rotation":[ 0, 0.7071067094802856, 0, 0.7071068286895752
		],
		"translation":[ -1.3, 0, -0.7
		]
	}

@aaronfranke
Copy link
Contributor Author

aaronfranke commented Jun 21, 2023

No. That's not 2 bodies. Adding an additional body adds six degrees of freedom

Those degrees of freedom can just not be set.

Exactly the same way you would make a non-trigger with an L-shape. You add a child node to position two boxes relative to one another:

So for a solid collider, you add shapes with MSFT_CollisionPrimitives, but for a trigger, you add shapes with MSFT_rigid_bodies? That seems odd to me (particularly because you have a "rigid_bodies" extension on a child node which isn't a body). But it's much better than what you had before with the material, since it no longer requires a body to have a material to be solid.

What you propose works but I still think it would be simpler to have a body simply be an object that takes its child shapes and combines them into a singular purpose (like static collision, kinematic body, dynamic rigid body, or trigger detection). For the edge case of wanting a trigger with dynamic physics simulated on it, to me it's perfectly fine to say this should be 2 body nodes, because you have 2 different sets of shapes, and that's what all bodies do, combine shapes. Comparing your proposal to this, you are just replacing the case of "a body can be a trigger" with "a body has a separate set of shapes for trigger", which I think is worse.

By the way, yes I am biased in favor of how Godot works, but Godot works really well. We like to break compatibility often, and nobody has proposed changing how triggers work, so Godot users like how it works. It's simple and makes intuitive sense for users to have a body that combines shapes for one purpose.

Also, please fix the naming inconsistencies in your spec. Is it MSFT_RigidBodies or MSFT_rigid_bodies?

@aaronfranke
Copy link
Contributor Author

@eoineoineoin Do you have any further objections to the OMI physics standards relating to anything that's impossible to do? To me this is the simplest approach that makes every desired situation possible without requiring any unnecessary information. I know you have complained about the document structure about where the shapes are allowed, we are changing that based on your feedback. I know you have complained about the extensibility of OMI_collider's shape types, but I explained how you can extend it. I know you have complained about the lack of a trigger simulated dynamically with forces, but I explained that you can use a 2nd body. I know you have complained about physics materials, joints, and filtering not being in the base spec, but that can be made possible by adding more extensions.

More about the trigger definition: I want to emphasize further how much I dislike the no-material-means-trigger thing. It feels like one of those "clever" tricks that seems neat until it inevitably causes problems later on. Clever tricks should not be a design goal, in fact we should strive for the polar opposite, we should strive for making the spec as clear as possible to prevent implementations from getting it wrong. With no-material-means-trigger, I would have to write a comment in the implementation describing this behavior, because it's unexpected and surprising. We should not be surprising people, we should design the spec to match an implementer's intuitive expectations. The spec's text is there to elaborate on the details, not to go contrary to the expectations if you only read the property names.

The precedent in glTF is that everything clear, simple, easy to parse, and intuitive, even if it means some redundancy (for example, camera types both having "type" and a sub-dict). Following this pattern, in a similar style to the rest of MSFT_RigidBodies with isKinematic, you could add an isTrigger boolean to the body extension. This would make the standard not surprising at all to read. If you read the code that parses isTrigger, no comment is necessary, its purpose is obvious from its name. Situations like specifying a material on a trigger body are irrelevant, in the same way that we don't care about a camera defined with { "type": "perspective", "orthographic": { ... } }, such invalid situations are not expected to be in valid glTF files. What I propose is fundamentally similar, just that my argument is to take this one step further, replace isKinematic + isTrigger with a "type" string enum.

I think it speaks volumes about your MSFT physics specs that I am continually misunderstanding it and being confused, and other people that I have discussed this with are also confused. Of course the OMI specs are not immune from confusion too - for example the character type was confusing - I'm not saying that what OMI has on the table is perfect, but I consider it miles better, it's clear, concise, and it's simple as can be. While I liked this clever trick about the semantics of defining shape behavior, even that is kinda pushing it and to me that is only permissible because it's a purely semantic difference compared to the shapes saying they are solid by default.

Sorry if this seems harsh - but I really need to make it clear that it's not just a minor issue, my belief is that "clever" tricks like no-material-means-trigger are fundamentally the completely wrong approach to take.

@eoineoineoin
Copy link
Member

So for a solid collider, you add shapes with MSFT_CollisionPrimitives, but for a trigger, you add shapes with MSFT_rigid_bodies? That seems odd to me (particularly because you have a "rigid_bodies" extension on a child node which isn't a body).

I don't know what you mean by this. As far as I can see, the colliders and triggers are defined in exactly the same way (modulo the material property). Can you clarify?

@aaronfranke
Copy link
Contributor Author

aaronfranke commented Jul 10, 2023

@eoineoineoin It's possible that I am misunderstanding something, your specs are unclear and in disagreement with the example files. The example files have data like this (inside "MSFT_physics"):

"name": "LanternPole_Body",
"extensions": {
	"MSFT_physics": {
		"collider": {
			"trimesh": {
				"mesh": 0
			}
		}
	}
}

The MSFT_CollisionPrimitives extension spec says "The MSFT_CollisionPrimitives extension can be added to any node", which to me indicates a structure like this:

"name": "ExampleNode",
"extensions": {
	"MSFT_CollisionPrimitives": {
		"collider": 0
	}
},

Your example above defines a trigger shape like this (inside "MSFT_rigid_bodies"):

"name":"VerticalPartOfTheL",
"extensions":{
	"MSFT_rigid_bodies":{
		"trigger": { "collider": 0 }
	}
},

That's 3 completely different data structures, with 3 different extension names. The top one is defining the shape locally, while the others point to a document-level array. The collider uses a MSFT_CollisionPrimitives extension added to a node, while the trigger is defined inside of the body spec. Even little things like the casing of the extension name and the formatting of the JSON are different. It's really difficult to parse through your confusing spec when the different parts are not consistent with each other.

@aaronfranke aaronfranke changed the title OMI_collider and OMI_physics_body OMI_physics_shape and OMI_physics_body Jul 15, 2023
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.

None yet

5 participants