eoineoineoin / glTF_Physics

Proposal for adding rigid body dynamics extension to glTF
41 stars 1 forks source link

Replace `isKinematic` with a string enum for the body type #4

Closed aaronfranke closed 8 months ago

aaronfranke commented 1 year ago

The draft spec README currently has this isKinematic boolean value:

Type Description
isKinematic boolean Treat the rigid body as having infinite mass. Its velocity will be constant during simulation.

However, this boolean is limiting and somewhat confusing.

I understand why the current draft spec was designed as it is, it was probably inspired by Unity which has an isKinematic boolean on their Rigidbody component (link). However, we can do better, we can provide a clear and flexible API that works across many engines. The string enum that I am proposing can still be easily imported into Unity.

I propose that we replace the isKinematic boolean with a type string enum with the following values: "static", "kinematic", "dynamic".

Old text:

I propose that we replace the `isKinematic` boolean with a `type` string enum with the following values: `"static"`, `"kinematic"`, `"character"`, `"rigid"`, `"vehicle"`, `"trigger"`. * `"static"` would be for non-moving bodies. In Unity this means to not have a Rigidbody component. * `"kinematic"` ~~and `"character"`~~ would both map to Unity's Rigidbody with `isKinematic`. * `"rigid"` ~~and `"vehicle"`~~ would both map to Unity's Rigidbody without `isKinematic`. * ~~The reason for separate `"character"` and `"vehicle"` types is two-fold, one is that some engines have special types for characters and vehicles (like Godot and Unreal), so this improves importing into those engines, and two is that even for engines without specialized types (like Unity), it can give games a machine-readable hint of what a body is intended to be used for.~~ * `"trigger"` means that all colliders that are a part of this body are intended to be used as the same trigger. * Note: This list is the same as in [`OMI_physics_body`](https://github.com/aaronfranke/gltf-extensions/tree/OMI_physics_body/extensions/2.0/OMI_physics_body#physics-types) which has been thoroughly debated in the OMI group. * Note: Even if we only wanted kinematic/rigid, the enum is more readable and more future-proof than a boolean. ~~On a related note, it would make sense to rename the `rigidBody` property to `physicsBody`. Even without this change, a body with `isKinematic` enabled doesn't use rigid body physics anymore, so it doesn't really make sense to call it rigid. With this change, the case is stronger, since only `"rigid"` and `"vehicle"` bodies are bodies using rigid body physics.~~
eoineoineoin commented 1 year ago

So, I think there's a couple of fundamental issues touched on here, and I'd like to justify the current state of the proposal:

It's limiting because it means there are only two body types, kinematic and non-kinematic.

There's three types; static bodies (colliders who do not have a parent with a rigidBody property), dynamic bodies (nodes with a rigidBody), and kinematic (nodes with a rigidBody whose isKinematic=true)

The rationale for this is that you start with objects which don't move; then, by adding a rigidBody property, that imbues that object with velocity, mass, inertia, etc.. Finally, because "kinematic" (sometimes called "animated" or "keyframed", etc.) are really a special case of a dynamic body, where the body has infinite effective mass/inertia, that specialization is indicated with this boolean.

I would resist putting a "static" parameter on the rigid body object, as it leads to ambiguity due to the existence of parameters which don't make sense for a static body - what does it mean if a body is specified as static, but also has a non-zero velocity?

By making the "static" body implicit by using a "bare" collider, it avoids the issue where static bodies have properties which don't make sense and allows for freedom in the implementation. Typically, a game will have (at least) an order of magnitude more static bodies than dynamic bodies, so performance of those static bodies is a serious concern. By avoiding specifying an explicit "body" type for those objects, an implementation is free to handle them as appropriate - e.g. it might create individual bodies or it might merge multiple "static" colliders together. I genuinely think this is important as scene organization here /has a direct impact on performance and without knowing (e.g.) the broadphase implementation in use, we shouldn't prescribe any particular behaviour here, lest we accidentally dictate behaviour which is accidentally a worst-case performance scenario for some engine.

While some physics simulations do have "vehicle" and "character" motion types, they seem to be stuck between being too specific and not specific enough; they don't have a solid mathematical foundation (compare with the static/dynamic/keyframed distinction) and without such a foundation, I don't think they're useful. They provide a setting which may not have an effect (or, an application-dependent effect) which is confusing for artists. Further, who is to say that the motion types for a forklift (a kind of "vehicle") is suitable for an F1 racecar (also a kind of "vehicle")? For those kinds of use-cases, you probably want to add an extension that explicitly enables the kind of functionality and parameters you want.

As for trigger - I think putting it on the body is the wrong thing to do. I really do believe it's a property of the collider. If you put that property on the body level, you can only have objects that are completely triggers or completely non-triggers - you can't have a combination; one example use-case for this is if you have a car, you might want a trigger for the headlight beams. You want those beams to be parented to the car, so you can detect obstacles entering the headlights, but you don't want them to cause a collision response.

On a related note, it would make sense to rename the rigidBody property to physicsBody.

There was some discussion about this already and the prevaling opinion was to stick with "rigid body physics" versus the more generic "physics" - rationale was that there are many different kinds of physics and we wouldn't want to give a reader the impression this specification supports deformable objects (for example)

Even without this change, a body with isKinematic enabled doesn't use rigid body physics anymore, so it doesn't really make sense to call it rigid.

A rigid body is just a body whose shape does not change, so a kinematic body still fits the definition of rigid? A kinematic body is just a special case of a general dynamic rigid body, with infinite effective mass/inertia.

aaronfranke commented 1 year ago

The rationale for this is that you start with objects which don't move; then, by adding a rigidBody property, that imbues that object with velocity, mass, inertia, etc.

Colliders with no parent body are mostly the same in OMI physics, except that the body property allows grouping together colliders for any usage defined by the body.

I genuinely think this is important as scene organization here /has a direct impact on performance and without knowing (e.g.) the broadphase implementation in use, we shouldn't prescribe any particular behaviour here, lest we accidentally dictate behaviour which is accidentally a worst-case performance scenario for some engine.

You make good points here, but I disagree. It's still useful to group together static colliders under a body so that they can be moved together (while static bodies do not have smooth motion, you can still teleport them, bypassing the physics system) and physics engines can do per-body checks instead of checking every collider shape. With explicit static bodies we provide the opportunity for files to specify things, but it would be the same behavior if an engine decided to treat all static colliders individually, so it does not complicate the situation for Unity to import them.

In some engines, like Godot, all colliders are required to be a member of a body to function. This model is much simpler than "some colliders move, some don't, depending on if they have a parent body", instead we have "colliders are always a part of this body and their movement is purely determined by the body"

When importing a body-less collider in Godot, we have to create a static body and add the collider as a child. In order to do this without altering the scene tree, we end up with more body nodes than necessary. If we tried to optimize this by putting all static colliders in a glTF file under the same static body parent, it would require Godot to potentially significantly alter the scene tree of the imported asset by moving all of the static collider nodes to the same place. Allowing a static body type in a glTF file allows us to optimize the case for Godot without increasing the difficulty of importing into Unity (since a static body could just be imported as an empty game object).

what does it mean if a body is specified as static, but also has a non-zero velocity?

Since a static body by definition does not move (except for teleporting), it would not move. This would be a scenario we can define as invalid in the spec and show a warning in the validator that the velocity will not be used.

As for trigger - I think putting it on the body is the wrong thing to do. I really do believe it's a property of the collider. If you put that property on the body level, you can only have objects that are completely triggers or completely non-triggers - you can't have a combination; one example use-case for this is if you have a car, you might want a trigger for the headlight beams. You want those beams to be parented to the car, so you can detect obstacles entering the headlights, but you don't want them to cause a collision response.

The purpose of the body is to combine colliders together and give them a purpose. If trigger is purely a property of colliders and not bodies, then this makes it impossible to use two collider shapes together as the same trigger.

What if you want to have an L-shaped trigger volume made of two boxes? In Godot and in OMI physics, this is easy, you can have one body that combines two colliders into one trigger volume. With the current draft MSFT physics, it seems that this is not possible, and that two trigger shapes can only ever be imported separately. What if you move from one part of the L-shape to another, would a trigger detecting objects entering and existing cause events to fire even though you have not left the whole shape? Not allowing multiple colliders per trigger is a limiting design flaw IMO.

For the car with headlights example, OMI handles this case perfectly fine. OMI actually provides the ability to specify a trigger on either the body or the collider, so there are actually two distinct and valid ways to implement this. One is to give the car body a child trigger collider, and the other is to give the car body a child trigger body with a trigger child. OMI supports trigger in either place to help with simple use cases and improve Unity compatibility, but if we had to pick one I would suggest that the trigger only exist on the body, not the collider.

A rigid body is just a body whose shape does not change, so a kinematic body still fits the definition of rigid? A kinematic body is just a special case of a general dynamic rigid body, with infinite effective mass/inertia.

You're mixing definitions here.

It's very confusing to talk about both rigid and kinematic at the same time with definitions from different contexts.

EDIT: But the solution to the last point is to use the term "dynamic" to describe bodies that move with forces.

aaronfranke commented 1 year ago

EDIT: This is off-topic, I'll collapse it.

Another consideration: > **Collision Filtering** > > If you want certain objects in your scene to ignore collisions with others, you can provide the following optional collider properties: > > | |Type|Description| > |-|-|-| > |**collisionLayers**|`integer`|A 32-bit bit field representing the node's layer membership.| > |**collisionMask**|`integer`|A 32-bit bit field representing which layers the node can collide with.| > > Implementions should interpret these as a bitwise comparison - collision should be occur between a pair of colliders `colliderA` and `colliderB` only if `colliderA.collisionMask & colliderB.collisionLayers != 0` or vice versa. Where are these applied? The text seems to indicate this is per-collider, and not per-body. What if you have ten colliders on a rigid body and you want all of them to have the same collision layer and masks, do you have to apply these properties to every collider? In Godot, there are collision layers and masks, but they are on the body, not on the collider shape. If collision layers and masks are per-collider, then it's not possible to import a MSFT_physics file into Godot if that file has different layers and masks between one body's colliders. This hinders the portability of MSFT_physics. If we wanted to instead put layers and masks on the body, then it would make sense to allow a static body type. In a nutshell, my opinion is that colliders should exclusively (or almost-exclusively) define the *shape* of a collider, and then the body defines how its child collider shapes interact with the world (whether that be kinematics, rigid movement, layers and masks, etc). Therefore, putting these properties on the body makes the most sense to me.
UE-FlavienP commented 1 year ago

Hello,

First some precision request to @eoineoineoin : _A collider attached to a node with MSFTRigidBodies should be interpreted as an object which cannot generate a collision response (i.e. no impulses are applied as a result of collision detection), but instead, should be used as a "trigger"

I am confused, I thought a node with rigid body and collider is either dynamic or kinematic. Do we have, for a node:

Regarding defining more types such as vehicle and character it looks that they are either too or not enough specific: physics handling of character, vehicle, trigger bodies are vastly different between engines. For instance importing a body that is a "character" or "vehicle" does not provide enough information to do anything, so it would get ignored.

Regarding explicitly defining static nodes, that does not prevent the importer and physic engine to optimize the way it sees fit. However, explicitly defining a body just for the static property does come with the cost of carrying all the other rigid bodies related properties that are unused in that context.

For the discussion on taking out the joints part in a separate extension, that could be a discussion at the Khronos group level. My thought is that we should split and avoid having applications doing half implementation of the extension.

Regarding triggers, feedback from the team was that triggers shouldn't be required to have a material but having a trigger body seems better than just trigger shapes. And filtering needs more discussion due to lack of consistency in the approaches.

aaronfranke commented 1 year ago

@UE-FlavienP I agree regarding triggers and filtering.

EDIT: This is outdated, I'll collapse it.

For the proposed character and vehicle types, note that these can just be treated as kinematic and rigid in implementations where there is no distinction. However, they do have a distinction in Godot (different node types) and Unreal (["Pawn" and "Vehicle" Physics Body Object Type as opposed to "WorldDynamic" and "PhysicsBody"](https://docs.unrealengine.com/5.2/en-US/collision-response-reference-in-unreal-engine/)).
eoineoineoin commented 1 year ago

Hey Flavien,

I can certainly improve the language in the readme around how these should be interpreted, but what I'm going for:

A collider without physic material => a trigger

Correct

A collider + physic material Not in a hierarchy of rigid body => static

Correct

A rigid body + X(collider + physic material) on the same node or in hierarchy => dynamic/kinematic

Nearly; a rigidbody on a node will cause that node to be either kinematic or dynamic, as appropriate. The shape associated with that rigid body will be the set of child colliders, some of which may have physics materials (so should cause a normal collision response) and some which may not (so should behave as triggers.)

Rigid body without colliders => Not simulated?

No, you'll still want to simulate these; a rigid body without a shape is useful for adding additional DoFs to constrained systems, and, from my experience, robotics applications (and similar domains) frequently don't even perform collision detection and exclusively rely on the non-contact constraints.

Regarding triggers, feedback from the team was that triggers shouldn't be required to have a material but having a trigger body seems better than just trigger shapes.

I'd be concerned about adding a "triggerness" property to the rigidbody node, as it seems like it could contradict the configuration of the colliders associated with that node. A body can be determined to be a trigger iff collider.isTrigger ∀ childColliders

aaronfranke commented 1 year ago

I have updated the OP in this issue to remove character/vehicle/trigger (since the section has been renamed to "motion", which is good, I think triggerness does not make sense here anymore) and renamed "rigid" to "dynamic".

aaronfranke commented 1 year ago

Regarding triggers, feedback from the team was that triggers shouldn't be required to have a material but having a trigger body seems better than just trigger shapes.

@UE-FlavienP See this issue I opened https://github.com/eoineoineoin/glTF_Physics/issues/19 I agree that trigger bodies are a vital feature, but as far as I can see the MSFT spec allows this by defining "extensions": { "MSFT_rigid_bodies": { "trigger": {} } } on a node, which works fine.

aaronfranke commented 1 year ago

Another point of comparison: https://defold.com/manuals/physics-objects/

Defold defines a Type that is one of Dynamic, Kinematic, Static, or Trigger. These are all of the essential types that I want in a physics standard (I edited the OP to remove character/vehicle). If we replaced isKinematic with a string enum that allows defining "dynamic", "kinematic", or "static", that would make sense to me. We still need a way to define trigger bodies, as I mentioned "trigger": {} would work fine for this.

eoineoineoin commented 1 year ago

There are several problems with this enum suggestion:

  1. It's a new property which doesn't add any actual new capabilities. Everything it describes is already exposed.
  2. It makes a new kind of error possible - you would be able to have a "static" body with a non-zero velocity
  3. You're erroneously assuming that those types (and future items which might want to use the "type") are mutually exclusive, which they're not - we already saw examples of why this is bad; the OMI spec couldn't distinguish between a dynamic/keyframed vehicle, character or trigger.
aaronfranke commented 1 year ago
  1. Doesn't add new capabilities a. False, it adds static bodies. This is a new capability. b. Static bodies cannot be smoothly moved, but they can be teleported. For example if you instance a glTF scene and move it to X=100, you want the static body to move with it. It makes sense to allow defining these explicitly to allow grouping static shapes instead of teleporting a scene having to account for every shape. c. It improves the clarity of the meaning of "non-kinematic" to be explicitly "dynamic" (or "static"). d. Imagine if glTF cameras had isOrthographic or isPerspective booleans.
  2. Static with velocity a. This problem already exists with kinematic. There is nothing about a keyframed kinematic body that requires considering many of the properties like inertia. b. I would not make the spec require an implementation to do anything special if a static body had those properties defined, however, it is meaningful to do so: Godot StaticBody3D.constant_*_velocity. c. Therefore static with velocity is not a problem and in fact has at least one use case.
  3. I have changed my mind about future items, I think this enum should not have any more than these 3 items (or 2 items if you don't want to add "static", so just "dynamic" and "kinematic").
eoineoineoin commented 1 year ago
  1. Doesn't add new capabilities a. False, it adds static bodies. This is a new capability. b. Static bodies cannot be smoothly moved, but they can be teleported. For example if you instance a glTF scene and move it to X=100, you want the static body to move with it. It makes sense to allow defining these explicitly to allow grouping static shapes instead of teleporting a scene having to account for every shape.

glTF describes a node hierarcy. In a node hierarchy, if you transform a node, all children of that node will also be transformed. The node hierarchy is already giving you the functionality you're describing. Adding an additional grouping of child nodes via an extension doesn't actually do anything.

2. Static with velocity
   a. This problem already exists with kinematic. There is nothing about a keyframed kinematic body that requires considering many of the properties like inertia.

This is incorrect. You need the inertia to calculate equivalent forces for external simulations as well as to specify the motion space that the body rotates in. You also need the mass properties for those kinematic bodies which can change to dynamic, which is an extremely common use-case.

   b. I would not make the spec require an implementation to do anything special if a static body had those properties defined, however, it is meaningful to do so: [Godot StaticBody3D.constant_*_velocity](https://docs.godotengine.org/en/stable/classes/class_staticbody3d.html#class-staticbody3d-property-constant-angular-velocity).
   c. Therefore static with velocity is not a problem and in fact has at least one use case.

3. I have changed my mind about future items, I think this enum should not have any more than these 3 items (or 2 items if you don't want to add "static", so just "dynamic" and "kinematic").

Right, so, if there's only two options, a boolean is much better suited to describe the possibilities.

aaronfranke commented 1 year ago

Another use case of static bodies is in optimization. By grouping related collider shapes under one static body, the physics engine can perform broadphase with the whole body. Without explicit static bodies, physics implementations would have to either perform broadphase for every static shape individually, or group them automatically.

The node hierarchy is already giving you the functionality you're describing.

Not in a way that is convenient for the physics engine. It would have to update every shape. But if there's a static body, it can just update the body, and the positions of each shape relative to that body stays the same.

You also need the mass properties for those kinematic bodies which can change to dynamic, which is an extremely common use-case.

What about changing a static body to kinematic or dynamic? That seems like a similarly common use-case to changing between kinematic and dynamic. In fact, I use this very thing often in projects - Godot has a "freeze" property on dynamic rigid bodies that I use to temporarily make the body static. It would be both trivial and sensible to allow the Godot importer to import "static" bodies as frozen rigid bodies so users can make them dynamic later. But this is only possible if the file contains static bodies in the first place.

Right, so, if there's only two options, a boolean is much better suited to describe the possibilities.

I disagree, because 1) it digs us into a hole if KHR_rigid_bodies is expanded with static in the future, and 2) "not kinematic" is not a robust way to describe dynamic bodies. Booleans are best suited when there are only two states and there are no conceivable other states. Godot has several enums with only 2 possible values. But anyway, this conversation has convinced me even moreso that we should have "type": "static" as a valid value, specifically because just as you mentioned a common use-case is converting kinematic <-> dynamic, we should also allow converting static <-> dynamic and static <-> kinematic by allowing the spec to define static bodies with mass/inertia/etc.

eoineoineoin commented 1 year ago

Another use case of static bodies is in optimization. By grouping related collider shapes under one static body, the physics engine can perform broadphase with the whole body. Without explicit static bodies, physics implementations would have to either perform broadphase for every static shape individually, or group them automatically.

Not all physics engines can group static shapes with bodies; in the engines which do, the broadphase performance can be impacted by a huge number of things - the particular broadphase algorithm(s) in use, the number and layout of objects inside the broadphase, and the application's needs for streaming/updates will all have impacts on performance. An artist clicking a button in an unrelated tool is not the best place to decide this grouping - the physics programmer implementing the extension for a particular physics engine/application has much more information on what the best thing to here is. In that respect, the current formulation of the spec is much better for broadphase performance than what you're suggesting.

What about changing a static body to kinematic or dynamic? That seems like a similarly common use-case to changing between kinematic and dynamic. In fact, I use this very thing often in projects - Godot has a "freeze" property on dynamic rigid bodies that I use to temporarily make the body static. It would be both trivial and sensible to allow the Godot importer to import "static" bodies as frozen rigid bodies so users can make them dynamic later. But this is only possible if the file contains static bodies in the first place.

You're describing a kinematic body with zero velocity. Adding a type: static doesn't allow you to describe anything new.

Right, so, if there's only two options, a boolean is much better suited to describe the possibilities.

I disagree, because 1) it digs us into a hole if KHR_rigid_bodies is expanded with static in the future, and 2) "not kinematic" is not a robust way to describe dynamic bodies. Booleans are best suited when there are only two states and there are no conceivable other states.

Two posts up, you said there should only be these options! Repeating myself, you're assuming those non-existent types are mutually exclusive - even if you did have a type: aaron, you'd still need to be able to distinguish between a kinematic and non-kinematic version.

... by allowing the spec to define static bodies with mass/inertia/etc.

Again, a static+mass doesn't add anything you don't already have with isKinematic: true bodies. In a number of broad-interest meetings discussing this spec, I brought up the topic of adding an object similar to USD's PhysicsMassPropertiesAPI (and all the complex combination rules that would entail) and there was zero interest. The preference was clearly that the current description was good enough and there was no need for additional complications.

aaronfranke commented 1 year ago

An artist clicking a button in an unrelated tool is not the best place to decide this grouping

I want to preserve static bodies when exporting from Godot, so that when importing a file in Godot or another tool that uses static bodies, it can preserve that information. I also want to preserve static bodies when exporting from other tools and importing into Godot, if both endpoints have static bodies. It's not an extra button, this is data already present, I want to preserve it, and I believe KHR_rigid_bodies is the right extension to do it in. It's not a downside for engines that don't have explicit static bodies - they could ignore this data, or import it as kinematic, or treat it as a frozen dynamic body - all of which is fine, and even if they keep only the colliders the behavior will be preserved, and it allows for use cases where an engine wants to import static but allow converting to something else later.

One option would to have Godot export a static body with isKinematic set to true, but why should the file have this object marked as kinematic when it does not use kinematics (motion)? If anything it would semantically make more sense to have isDynamic, but "type" is better than both options.

You're describing a kinematic body with zero velocity.

What if you want to describe a frozen body that will have a specified velocity after unfreezing?

What about the use case of a static body with velocity as Godot defines it? (yes this is an edge case and if this was the only concern I would dismiss this myself, but, it's a minor argument in favor of having explicit static bodies).

Two posts up, you said there should only be these options!

I think there should be 3 options. But even if we only had 2 options, there are more than 2 conceivable options, so a boolean is unsuitable. I am conceiving of static right now in this thread. Describing motion is not as trivial as flipping a light switch. Regardless of how many motion types KHR_rigid_bodies defines, there are clearly more than two possible states for a body's motion (since many engines have more than two), so a boolean is unsuitable.

Actually, I would argue that for all words, if the state can't be fully described by negating that word, it should be an enum. For example not isEnabled has the exact inverse meaning of isDisabled, so isEnabled can be kept as a boolean. For kinematic, not isKinematic is not the exact inverse meaning of isDynamic. Kinematic and dynamic describe two types of motion, which is not as trivial as "motion enabled or not", they are two kinds of motion.

eoineoineoin commented 1 year ago

What about the use case of a static body with velocity as Godot defines it? (yes this is an edge case and if this was the only concern I would dismiss this myself, but, it's a minor argument in favor of having explicit static bodies).

There seems to be some confusion here. The Godot documentation you linked to is describing conceptually different phenomenon than is described by the existing linearVelocity/angularVelocity fields. In Godot, that field doesn't actually move the body; it's describing a surface velocity, which is certainly a feature supported by other physics engines. However, the formulation used by Godot is not good enough for many real use-cases. In particular, dynamic bodies are just as capable of having a surface velocity and that velocity needs to be variable across the surface of the shape, so a single vector won't do. Using the existing fields to describe this functionality would not be the wrong thing to do. So it is an error to have a static body with a velocity.

Actually, I would argue that for all words, if the state can't be fully described by negating that word, it should be an enum. For example not isEnabled has the exact inverse meaning of isDisabled, so isEnabled can be kept as a boolean. For kinematic, not isKinematic is not the exact inverse meaning of isDynamic. Kinematic and dynamic describe two types of motion, which is not as trivial as "motion enabled or not", they are two kinds of motion.

There are only two options. Either a body is kinematic and is driven by a system outside of the rigid body simulation or it is not kinematic and the rigid body simulation is driving it.

I want to preserve static bodies when exporting from Godot, so that when importing a file in Godot or another tool that uses static bodies, it can preserve that information.

KHR_rigid_bodies is designed to be a portable spec. If you want to add Godot-specific data, that should be in a Godot-specific extension. Adding a "static" type has several downsides, which I've already highlighted:

  1. It doesn't actually do anything - the physics behaves identically
  2. It allows new kinds of errors which aren't possible in the existing formulation
  3. It's incompatible with some existing engines, and reduces flexibility in engines it is compatible with
  4. It's incompatible with existing file formats - USD, Collada and X3D do not have this concept
aaronfranke commented 1 year ago

There are only two options. Either a body is kinematic and is driven by a system outside of the rigid body simulation or it is not kinematic and the rigid body simulation is driving it.

I can see why you came to this conclusion, with "isKinematic" effectively being a flag for "is something externally driving this", which could make sense as a single on/off switch. However, it's not clear that one is more deserving to be the true state (why not "isDynamic"?), so in my view, it's not appropriate to use a boolean here.

Similarly, there is also the possibility that nothing is driving it. Blender has two separate booleans for "Dynamic" and "Animated". With them both disabled, it's static. A boolean describing "in one state A is driving, in one state B is driving" does not make sense to me, because each state has something enabled. Rapier has "Dynamic", "Fixed", and two types of "Kinematic", so likewise there is value in having one of the enum values being static / fixed.

  1. It doesn't actually do anything - the physics behaves identically

What about the case where you want to have a body start out static, and be converted to dynamic later?

Two posts up, you said "You also need the mass properties for those kinematic bodies which can change to dynamic, which is an extremely common use-case". So converting between these is a common use case we must support.

You could have a body with "isKinematic" and zero velocity, and use this to describe the mass etc. However, if "isKinematic" is effectively a flag for "is something externally driving this", then that is misleading. Because there is nothing driving it. In this situation, there are two states, not moving, and dynamic motion. It doesn't make sense to describe the "not moving" state as "isKinematic" because nothing external is driving it. Similarly, it doesn't make sense to describe the "not moving" state as "isKinematic" because Kinematics describes motion without forces, not the lack of motion. The best way to describe this state is to have "motion": { "type": "static" }.

  1. It allows new kinds of errors which aren't possible in the existing formulation

What you call errors, I call edge cases. They aren't possible and I want them to be possible.

  1. It's incompatible with some existing engines, and reduces flexibility in engines it is compatible with
  2. It's incompatible with existing file formats - USD, Collada and X3D do not have this concept

It's not incompatible. You can convert between these representations easily. The difference is that with converting to "isKinematic" there is data loss, while "type" can represent all cases.

The semantics of "type" is better because it makes it clear what "not kinematic" means. In physics, the term Kinematics describes the motion of bodies without considering forces. So, if you say "not kinematic", from the physics perspective it's not clear if that should mean "no motion" or "motion with considering forces". Since you have previously insisted on using proper physics terms (see "rigid"), I am confused as to why you have so much resistance to using a "type" enum to clearly define dynamic motion considering forces as "dynamic".

eoineoineoin commented 1 year ago

Just to clear up your last few misconceptions/questions:

There are only two options. Either a body is kinematic and is driven by a system outside of the rigid body simulation or it is not kinematic and the rigid body simulation is driving it.

I can see why you came to this conclusion, with "isKinematic" effectively being a flag for "is something externally driving this", which could make sense as a single on/off switch. However, it's not clear that one is more deserving to be the true state (why not "isDynamic"?), so in my view, it's not appropriate to use a boolean here.

Similarly, there is also the possibility that nothing is driving it. Blender has two separate booleans for "Dynamic" and "Animated". With them both disabled, it's static.

This is just the UI - the UI could very much be improved here, and you have to stop basing your understanding of physics on the presentation layer of different applications; this is why we saw so many mistakes in OMI_physics_body.

In Blender's case, two booleans would imply that there are four options. However, if you actually look at Blender's implementation, there are, in fact, only two. Blender always creates either a kinematic body, or a non-kinematic body. Despite support for them in the physics engine it uses, it never creates a "static" body:

https://projects.blender.org/blender/blender/src/branch/main/source/blender/blenkernel/intern/rigidbody.cc#L848

Rapier has "Dynamic", "Fixed", and two types of "Kinematic", so likewise there is value in having one of the enum values being static / fixed.

If you read through the document you linked to, the "two types of kinematic" are identical. The only difference is the API the programmer uses to drive the body at runtime, which is inappropriate information to put into a glTF file, as even two implementations using Rapier may prefer to use a different API.

  1. It doesn't actually do anything - the physics behaves identically

Two posts up, you said "You also need the mass properties for those kinematic bodies which can change to dynamic, which is an extremely common use-case". So converting between these is a common use case we must support.

You could have a body with "isKinematic" and zero velocity, and use this to describe the mass etc. However, if "isKinematic" is effectively a flag for "is something externally driving this", then that is misleading. Because there is nothing driving it. In this situation, there are two states, not moving, and dynamic motion. It doesn't make sense to describe the "not moving" state as "isKinematic" because nothing external is driving it.

There most certainly is something driving it. You just described that exact scenario:

What about the case where you want to have a body start out static, and be converted to dynamic later?

So, something has decided that it is not currently moving and, in the future, will change that state. That something is driving the body.

  1. It allows new kinds of errors which aren't possible in the existing formulation

What you call errors, I call edge cases. They aren't possible and I want them to be possible.

No. This is not an edge case. Velocity is a change in position with respect to time. A static body is a body whose position does not change. So, you're allowing a file to describe a "static body with non-zero velocity" which is a contradiction, and unambiguously an error.

The semantics of "type" is better because it makes it clear what "not kinematic" means.

Bool isKinematic is much better than String type - anyone with any experience in this domain would be able to tell you exactly what the former means.

So, if you say "not kinematic", from the physics perspective it's not clear if that should mean "no motion" or "motion with considering forces".

As well as being described in the documentation, this field is located in a structure called motion, and is adjacent to fields named linearVelocity and angularVelocity. Considering that this is also a well-understood term in the industry, I don't think there is any risk that anyone would interpret this incorrectly.

SlateyDev commented 1 year ago

I don't see why a decision would be made to reduce the information that can be stored (isKinematic does not give the amount of information that a body type would, such as allowing multiple collision meshes to exist under a single static body) and be helpful to more DCC and game engines if we can get extra information into the format for no real extra space. Rather than later deciding that we need it and having to redo the GLTF anyway to support it.

Regarding having velocity on static bodies, why wouldn't that just be defined as mutually exclusive in the specification so as to avoid any issues.

Mikey-Mikey commented 1 year ago

I'm pretty sure having an Enum that explicitly shows the type of physics a body has is better than Bool isKinematic that which only satisfies two of the three physics types that an engine could have. And even if the engine doesn't have that specific physics type, like if an engine doesn't have Static bodies for example, you could just make that specific engine ignore that type and fallback to a Kinematic body type that doesn't move. I honestly think that having an Enum instead of Bool isKinematic would be easier to comprehend because it would show the absolute value of the physics type and not only just two.

AndreaCatania commented 1 year ago

Hello, I have been integrating an FBX importer in the past and it was a difficult task as it's an unclear format and each software implements it slightly differently. It was impossible for me to support all the FBX features cross compatible with Maya and Blender. Keeping the format as easy as possible is really important.

I think that the enum with the three properties (Dynamic | Kinematic | Static) make it unequivocally simple to define a static body.

Being able to define a body as static is a crucial feature, as ALL the modern physics engines have their own way of optimizing the static bodies. There is no need to make "defining a static body" cumbersome: with the benefit that static body spawning, by importing a GLTF, will be correctly implemented and supported by ANY software.

aaronfranke commented 12 months ago

Another argument in favor of static bodies is with triggers. If you have 5 collider shapes grouped together under one static body, and a trigger begins to overlap one or some of them, you can detect that in the trigger as the body entering the trigger volume. Without a static body, this cannot properly be done.

This is not the same as detecting each shape individually, because that would result in multiple enter events. It's also not possible to just ignore events from some shapes if already overlapping others, because the glTF file would not be providing a way to determine which shapes are grouped together. You could use sibling nodes, but that's a hack.

Illauriel commented 10 months ago

Hi. I'd like to join the discussion and opine on this issue. I can see the arguments for isKinematic as a bool, but reading the whole lengthy discussion, I'm still not convinced it's the best approach.

eoineoineoin commented 10 months ago

To re-hash the arguments already made against this proposal:

Disagrees with existing formats

USD, Collada, X3D all have functionality to describe rigid body physics.

These specifications were written independently and, like KHR_physics_rigid_bodies, were written by domain experts.

These formats do not have an explicit "static" body type, so the current description of KHR_physics_rigid_bodies is aligned with these already-existing formats - the proposed "static body type" diverges from expert opinion on the best way to describe static geometry.

I am unaware of any engine-agnostic file-format for rigid bodies which has a "static body" type.

Unnecessary:

Consider the hierarchy below. Each node has a collider providing static collision geometry. This is all the paramaterization that's required for static bodies:

Node A (+collider shape)
    Child A (+collider shape)
        Child B (+collider shape)
    Child C (+collider shape)

Allowing a node to declare a "static body" would mean that an alternate way to describe this hierarchy would be:

Node A (+static body, +collider shape)
    Child A (+collider shape)
        Child B (+collider shape)
    Child C (+collider shape)

Or:

Node A (+static body, +collider shape)
    Child A (+static body, +collider shape)
        Child B (+collider shape)
    Child C (+collider shape)

Or:

Node A (+static body, +collider shape)
    Child A (+static body, +collider shape)
        Child B (+static body, +collider shape)
    Child C (+collider shape)

Or:

Node A (+static body, +collider shape)
    Child A (+static body, +collider shape)
        Child B (+static body, +collider shape)
    Child C (+static body, +collider shape)

etc. However, all of these behave identically! If there were a "static body" type, there's now 16 ways to represent this hierarchy of shapes, all of which are functionally the same. There is no reason for a user to choose one over another. There are no additional fields or properties that are added by the "static type". This "static body type" does not add anything except additional complexity, both for the artist generating the data as well as the implementer. Without a "static body" type, there's only one way to represent this hierarchy in the file, significantly simplifying the scene description.

Some physics engines do have static bodies; in those engines, choosing the best grouping of collision geometries with each static body is specific to the engine itself, as well as the particular use-case and the application. Different engines will have differing requirements for how those static bodies should be grouped, which can be affected by other properties outside of the node. e.g., in some engines, a node which is animated may need to be a unique "body" - this requirement can contradict what has been specified by the "type: static body" property on the node.

An example of where this might be bad is that it's possible to describe a grouping of static bodies which gives best-case performance in one engine, and worse-case performance in a different engine. The person implementing the extension for a particular engine has the most information here and is in the best position to choose such a grouping. By not having an explicit "static body type", the implementer has the flexibility to do the right thing for their engine.

Adds additional errors:

By adding an additional "static body type", a "static body" object receives additional fields which do not make sense for static bodies. e.g. "angular velocity" - a "static body" with a non-zero velocity is clearly a contradiction.

These additional fields would need to be checked by the glTF validator, adding complexity. However, not every asset will be validated, so having such checks in the validator makes no guarantees about assets.

In this discussion, we've already seen Aaron confused about the meaning of "velocity," where he suggested that, in Godot, the combination of "static body with non-zero velocity" would be handled in a particular way. This is non-portable, and does not make sense in other engines.

The current specification of KHR_physics_rigid_bodies makes it impossible to make such errors, resulting in more robust and portable assets.

Can add "static bodies" by other means

For use-cases where an artist is targeting a particular engine or extension, it's still possible to use an additional extension to describe static bodies. e.g. for Aaron's "static body with velocity" case, one might construct a node:

"nodes": [
    {
        name/mesh/etc...
        "extensions": {
            "KHR_physics_rigid_bodies": { "collider": 0 },
            "GODOT_static_body": { "velocity": [1,2,3] }
        }
    }

This structure keeps KHR_physics_rigid_bodies as portable and engine-agnostic as possible, while still allowing for non-portable engine-specific details to be handled when necessary.

Use of an enum "type" explodes complexity

An enum type could be extended by future extensions; by allowing the "enum type" to be "kinematic", we end up with an explosion of types. For example, earlier iterations of the OMI_physics_body spec specified several different "types":

The latter two were non-portable, Godot-specific types. However, with such a parameterization, it's impossible to describe a "kinematic vehicle," which would necessitate a new type. Similarly, you couldn't describe an object which was both a character+vehicle, nor could you describe a kinematic character+vehicle. In order to allow users to describe these, you're required to make $n^2$ different "types," significantly complicating import/export. The current design of using a boolean for kinematic maps directly into the physics engine representation, and still allows users to describe vehicle/character/etc. nodes with additional extensions, using the same method above.

aaronfranke commented 10 months ago

To re-hash the arguments already made in favor of this proposal

Compatible with existing formats

USD, Collada, X3D all have functionality to describe rigid body physics.

The proposed "type" string enum can easily be flattened into a boolean by discarding "static" motion.

Converting the other way is also simple. There is no incompatibility present.

Necessary

There are absolutely situations in which you'd want to group together colliders on a static object in a way where it does change the behavior. For example, let's say you have a moving trigger, and you want to detect static objects. Without a static motion type to define a static body, each collision shape would be detected by that trigger individually. Since this repo's proposed specs do not contain any way to have compound colliders, the only way to combine colliders is to use "motion", which is explicitly not available for static objects, therefore it's not possible to have compound static objects detected as one object.

Preserves information

Lots of game engines and physics engines, including Godot, Defold, Babylon.js, Bevy, Rapier, Jolt, and more, have explicit static motion types. Excluding static from the spec hurts the ability to use glTF as a transmission format to exchange information between game engines with a static body type, because it would be discarded.

In-scope

The ability to describe a motion type of static is absolutely within the scope of the base extension for rigid body physics. While it could be done with another extension, that is worse, because defining the motion type is inherently a part of this base spec. Any other spec adding static bodies would essentially be overriding the specs in this repo.

In contrast, I would still argue that joints are out-of-scope for the base physics extension specification and should be put in their own extension, certainly they are less in-scope than static bodies.

In-line with Khronos best practices

https://github.com/KhronosGroup/glTF/issues/2311

Iff such a 'type' is supposed to be defined, then one can still argue that a type:string is easier to evolve, easier to make consistent, and scales better when new types are added in the future. (You don't want to end up with a bunch of isA, isB, ... isZ boolean fields, where a type:string could suffice)

I opened an issue to discuss best practices for glTF. Type enums are the normal way to define types in glTF, Eoin's proposal to not use one is going against the grain. Cameras use "type": "orthogonal", not "isOrthogonal": true. In the above quote @javagl points out in the quote above several more reasons.

Also, in the upcoming Khronos audio emitter extension, emitter types are specified to be either global or positional using "type": "global" or "type": "positional". Even though the specification only has 2 states, we do not use "isPositional" or "isGlobal" because there are more than 2 conceivable states.

Enums are clearer

"isKinematic": false means that the motion is dynamic and uses forces. But this is not inherently clear from the description itself. "not kinematic" doesn't necessarily mean dynamic. However, "type": "dynamic" does inherently mean dynamic.

Even if the enum only had 2 possible values, kinematic and dynamic, an enum is cannot be misinterpreted as easily as a boolean, is easy to evolve later, and is in-line with best practices.

eoineoineoin commented 8 months ago

Closing as "not planned" for the reasons discussed above.