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

Update OMI_physics_joint to be more like KHR physics joints #12

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Extensions implemented in this repository:

| Extension name | Import | Export | Godot version | Link |
| ------------------------------ | ------ | ------ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| **OMI_physics_joint** | Yes | Yes | 4.1+ | [OMI_physics_joint extension spec](https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_joint) |
| **OMI_physics_joint** | Yes | Yes | 4.4+ | [OMI_physics_joint extension spec](https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_joint) |
| **OMI_seat** | Yes | Yes | 4.0+ | [OMI_seat extension spec](https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_seat) |
| **OMI_spawn_point** | Yes | No | 4.0+ | [OMI_spawn_point extension spec](https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_spawn_point) |
| **OMI_vehicle_body** | Yes | Yes | 4.3+ | [OMI_vehicle_body extension spec](https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_vehicle_body) |
Expand Down
77 changes: 77 additions & 0 deletions addons/omi_extensions/physics_joint/gltf_physics_joint_drive.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
@tool
class_name GLTFPhysicsJointDrive
extends Resource


## Determines the degree of freedom which this drive controls.
@export_enum("linear", "angular") var type: String = "linear"
## Specifies the force calculation mode.
@export_enum("force", "acceleration") var mode: String = "force"
## The index of the axis which this drive applies forces on.
@export_enum("X:0", "Y:1", "Z:2") var axis: int = 0
## The maximum force (or torque, for angular drives) the drive can apply. If not provided, this drive is not force-limited.
@export var max_force: float = INF
## The target translation/angle along the axis that this drive attempts to achieve. If NaN, the drive should not target a position.
@export var position_target: float = NAN
## The target velocity along/about the axis that this drive attempts to achieve. If NaN, the drive should not target a velocity.
@export var velocity_target: float = NAN
## The stiffness of the drive, scaling the force based on the position target.
@export var stiffness: float = 0.0
## The damping of the drive, scaling the force based on the velocity target.
@export var damping: float = 0.0


func to_dictionary() -> Dictionary:
var ret: Dictionary = {}
ret["type"] = type
ret["mode"] = mode
ret["axis"] = axis
if max_force != 0.0:
ret["maxForce"] = max_force
if not is_nan(position_target):
ret["positionTarget"] = position_target
if not is_nan(velocity_target):
ret["velocityTarget"] = velocity_target
if stiffness != 0.0:
ret["stiffness"] = stiffness
if damping != 0.0:
ret["damping"] = damping
return ret


static func from_dictionary(joint_drive_dict: Dictionary) -> GLTFPhysicsJointDrive:
var ret = GLTFPhysicsJointDrive.new()
if joint_drive_dict.has("type"):
ret.type = joint_drive_dict["type"]
if joint_drive_dict.has("mode"):
ret.mode = joint_drive_dict["mode"]
if joint_drive_dict.has("axis"):
ret.axis = joint_drive_dict["axis"]
if joint_drive_dict.has("maxForce"):
ret.max_force = joint_drive_dict["maxForce"]
if ret.max_force < 0.0:
ret.max_force = INF
if joint_drive_dict.has("positionTarget"):
ret.position_target = joint_drive_dict["positionTarget"]
if joint_drive_dict.has("velocityTarget"):
ret.velocity_target = joint_drive_dict["velocityTarget"]
if joint_drive_dict.has("stiffness"):
ret.stiffness = joint_drive_dict["stiffness"]
if ret.stiffness < 0.0:
ret.stiffness = INF
if joint_drive_dict.has("damping"):
ret.damping = joint_drive_dict["damping"]
if ret.mode != "force":
push_warning("glTF Physics Joint: Godot's joint motors only support force mode. The mode '" + ret.mode + "' will be ignored.")
return ret


func is_equal_to(other: GLTFPhysicsJointDrive) -> bool:
return type == other.type \
and mode == other.mode \
and axis == other.axis \
and is_equal_approx(max_force, other.max_force) \
and ((is_nan(position_target) and is_nan(other.position_target)) or is_equal_approx(position_target, other.position_target)) \
and ((is_nan(velocity_target) and is_nan(other.velocity_target)) or is_equal_approx(velocity_target, other.velocity_target)) \
and is_equal_approx(stiffness, other.stiffness) \
and is_equal_approx(damping, other.damping)
73 changes: 73 additions & 0 deletions addons/omi_extensions/physics_joint/gltf_physics_joint_limit.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
@tool
class_name GLTFPhysicsJointLimit
extends Resource


## The indices of the linear axes which are limited, constraining the linear motion in 1, 2 or 3 dimensions. 1D keeps an object some distance from an infinite plane. 2D keeps an object some distance from an infinite line. 3D keeps an object some distance from a point. Can only contain 0 (X), 1 (Y), or 2 (Z), so [0, 1, 2] constrains all three axes.
@export_enum("X:0", "Y:1", "Z:2") var linear_axes: PackedInt32Array = []
## The indices of the angular axes which are limited, constraining the angular motion in 1, 2 or 3 dimensions. 1D limits rotation about one axis (e.g. a universal joint). 2D limits rotation about two axes (e.g. a cone). 3D limits rotation about all three axes. Can only contain 0 (X), 1 (Y), or 2 (Z), so [0, 1, 2] constrains all three axes.
@export_enum("X:0", "Y:1", "Z:2") var angular_axes: PackedInt32Array = []
## The minimum of the allowed range of relative distance in meters, or angle in radians.
@export var min: float = -INF
## The maximum of the allowed range of relative distance in meters, or angle in radians.
@export var max: float = INF
## The stiffness strength used to calculate a restorative force when the joint is extended beyond the limit.
@export var stiffness: float = INF
## Damping applied to the velocity when the joint is extended beyond the limit.
@export var damping: float = 0.0


func to_dictionary() -> Dictionary:
var ret: Dictionary = {}
if not linear_axes.is_empty():
ret["linearAxes"] = linear_axes
if not angular_axes.is_empty():
ret["angularAxes"] = angular_axes
if min != -INF:
ret["min"] = min
if max != INF:
ret["max"] = max
if stiffness != INF:
ret["stiffness"] = stiffness
if damping != 0.0:
ret["damping"] = damping
return ret


static func from_dictionary(joint_limit_dict: Dictionary) -> GLTFPhysicsJointLimit:
var ret = GLTFPhysicsJointLimit.new()
if joint_limit_dict.has("linearAxes"):
var dict_axes: Array = joint_limit_dict["linearAxes"]
for dict_axis in dict_axes:
ret.linear_axes.append(int(dict_axis))
if joint_limit_dict.has("angularAxes"):
var dict_axes: Array = joint_limit_dict["angularAxes"]
for dict_axis in dict_axes:
ret.angular_axes.append(int(dict_axis))
ret.min = joint_limit_dict.get("min", -INF)
ret.max = joint_limit_dict.get("max", INF)
if joint_limit_dict.has("stiffness"):
ret.stiffness = joint_limit_dict["stiffness"]
if ret.stiffness < 0.0:
ret.stiffness = INF
if joint_limit_dict.has("damping"):
ret.damping = joint_limit_dict["damping"]
return ret


func is_fixed_at_zero() -> bool:
return is_zero_approx(min) and is_zero_approx(max)


func limits_equal_to(other: GLTFPhysicsJointLimit) -> bool:
return ((is_nan(min) and is_nan(other.min)) or is_equal_approx(min, other.min)) \
and ((is_nan(max) and is_nan(other.max)) or is_equal_approx(max, other.max)) \
and is_equal_approx(stiffness, other.stiffness) \
and is_equal_approx(damping, other.damping)


func is_equal_to(other: GLTFPhysicsJointLimit) -> bool:
# Godot PackedInt32Array compares by value for the == operator.
return limits_equal_to(other) \
and linear_axes == other.linear_axes \
and angular_axes == other.angular_axes
49 changes: 49 additions & 0 deletions addons/omi_extensions/physics_joint/gltf_physics_joint_node.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
@tool
class_name GLTFPhysicsJointNode
extends Resource


## The index of the node to which this is connected.
@export var connected_node_index: int = -1
## The index of the joint settings in the top level physicsJoints array.
@export var joint_settings_index: int = -1
## The joint settings data, loaded from the top level physicsJoints array on import, and saved there on export.
@export var joint_settings_data: GLTFPhysicsJointSettings = null
## If true, allow the connected objects to collide. Connected objects do not collide by default.
@export var enable_collision: bool = false


func to_dictionary() -> Dictionary:
var ret: Dictionary = {}
if connected_node_index != -1:
ret["connectedNode"] = connected_node_index
if joint_settings_index != -1:
ret["joint"] = joint_settings_index
if enable_collision:
ret["enableCollision"] = enable_collision
return ret


func to_node() -> Joint3D:
var godot_joint_node: Joint3D = joint_settings_data.to_node()
godot_joint_node.exclude_nodes_from_collision = not enable_collision
godot_joint_node.node_a = ^".."
return godot_joint_node


static func from_dictionary(node_dict: Dictionary) -> GLTFPhysicsJointNode:
var ret = GLTFPhysicsJointNode.new()
if node_dict.has("connectedNode"):
ret.connected_node_index = node_dict["connectedNode"]
if node_dict.has("joint"):
ret.joint_settings_index = node_dict["joint"]
if node_dict.has("enableCollision"):
ret.enable_collision = node_dict["enableCollision"]
return ret


static func from_node(godot_joint_node: Joint3D) -> GLTFPhysicsJointNode:
var ret = GLTFPhysicsJointNode.new()
ret.joint_settings_data = GLTFPhysicsJointSettings.from_node(godot_joint_node)
ret.enable_collision = not godot_joint_node.exclude_nodes_from_collision
return ret
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@tool
class_name GLTFPhysicsJoint
class_name GLTFPhysicsJointOld
extends Resource


Expand All @@ -14,8 +14,8 @@ var angular_y: GLTFPhysicsJointConstraint = null
var angular_z: GLTFPhysicsJointConstraint = null


static func from_node(joint_node: Joint3D) -> GLTFPhysicsJoint:
var ret := GLTFPhysicsJoint.new()
static func from_node(joint_node: Joint3D) -> GLTFPhysicsJointOld:
var ret := GLTFPhysicsJointOld.new()
if not joint_node.node_a.is_empty():
ret.node_a = joint_node.get_node(joint_node.node_a)
if not joint_node.node_b.is_empty():
Expand Down Expand Up @@ -88,7 +88,7 @@ static func from_node(joint_node: Joint3D) -> GLTFPhysicsJoint:
ret.angular_z = fixed_angular_constraint
elif joint_node is ConeTwistJoint3D:
# It doesn't seem possible to fully represent ConeTwistJoint3D, so use an approximation.
push_warning("GLTF Physics Joint: Converting a ConeTwistJoint3D which cannot be properly represented as a GLTF joint, so it will only be approximated.")
push_warning("glTF Physics Joint: Converting a ConeTwistJoint3D which cannot be properly represented as a glTF joint, so it will only be approximated.")
var linear_constraint := GLTFPhysicsJointConstraint.new()
linear_constraint.linear_axes = [0, 1, 2]
ret.linear_x = linear_constraint
Expand Down Expand Up @@ -285,7 +285,7 @@ func _create_generic_joint_with_constraints() -> Generic6DOFJoint3D:
return ret


static func _convert_generic_joint_constraints(joint_node: Generic6DOFJoint3D, gltf_joint: GLTFPhysicsJoint) -> void:
static func _convert_generic_joint_constraints(joint_node: Generic6DOFJoint3D, gltf_joint: GLTFPhysicsJointOld) -> void:
if joint_node.get_flag_x(Generic6DOFJoint3D.FLAG_ENABLE_LINEAR_LIMIT):
var constraint := GLTFPhysicsJointConstraint.new()
constraint.lower_limit = joint_node.get_param_x(Generic6DOFJoint3D.PARAM_LINEAR_LOWER_LIMIT)
Expand Down
Loading