Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add OMI_vehicle_* implementation and test files #11

Merged
merged 1 commit into from
Jan 4, 2025
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Godot 4+ specific ignores
.godot/
*.uid

.DS_Store
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -6,11 +6,15 @@ This repository is used by the [Open Metaverse Interoperability Group](https://o

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_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) |
| 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_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) |
| **OMI_vehicle_hover_thruster** | Yes | Yes | 4.3+ | [OMI_vehicle_hover_thruster extension spec](https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_vehicle_hover_thruster) |
| **OMI_vehicle_thruster** | Yes | Yes | 4.3+ | [OMI_vehicle_thruster extension spec](https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_vehicle_thruster) |
| **OMI_vehicle_wheel** | Yes | Yes | 4.3+ | [OMI_vehicle_wheel extension spec](https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_vehicle_wheel) |

Extensions implemented upstream in Godot Engine:

2 changes: 2 additions & 0 deletions addons/omi_extensions/omi_extensions_plugin.gd
Original file line number Diff line number Diff line change
@@ -15,6 +15,8 @@ func _enter_tree() -> void:
GLTFDocument.register_gltf_document_extension(ext)
ext = GLTFDocumentExtensionOMIPhysicsJoint.new()
GLTFDocument.register_gltf_document_extension(ext)
ext = GLTFDocumentExtensionOMIVehicle.new()
GLTFDocument.register_gltf_document_extension(ext, true)
add_node_3d_gizmo_plugin(seat_gizmo_plugin)


13 changes: 13 additions & 0 deletions addons/omi_extensions/physics_gravity/global_gravity_setter.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class_name GlobalGravitySetter
extends Node


@export var gravity: float = 9.80665
@export var direction: Vector3 = Vector3.DOWN


func _ready() -> void:
var world_space_rid: RID = get_viewport().find_world_3d().space
PhysicsServer3D.area_set_param(world_space_rid, PhysicsServer3D.AREA_PARAM_GRAVITY, gravity)
PhysicsServer3D.area_set_param(world_space_rid, PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR, direction)
queue_free()
9 changes: 7 additions & 2 deletions addons/omi_extensions/seat/omi_seat_doc_ext.gd
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ func _generate_scene_node(state: GLTFState, gltf_node: GLTFNode, scene_parent: N
return null
# If this node is both a seat and a glTF trigger, generate the Area3D-derived Seat3D node.
# Else, if this is not any kind of trigger, don't generate a Seat3D/Area3D, just set node metadata later.
var trigger = gltf_node.get_additional_data(&"GLTFPhysicsTrigger")
var trigger = gltf_node.get_additional_data(&"GLTFPhysicsTriggerShape")
if trigger == null:
trigger = gltf_node.get_additional_data(&"GLTFPhysicsCompoundTriggerNodes")
if trigger == null:
@@ -42,7 +42,12 @@ func _generate_scene_node(state: GLTFState, gltf_node: GLTFNode, scene_parent: N
return null
if trigger.body_type != "trigger":
return null
return Seat3D.from_points(seat_dict["back"], seat_dict["foot"], seat_dict["knee"], seat_dict.get("angle", TAU * 0.25))
var seat = Seat3D.from_points(seat_dict["back"], seat_dict["foot"], seat_dict["knee"], seat_dict.get("angle", TAU * 0.25))
if trigger is GLTFPhysicsShape:
var shape: CollisionShape3D = trigger.to_node(true)
shape.name = gltf_node.resource_name + "Shape"
seat.add_child(shape)
return seat


func _import_node(_state: GLTFState, gltf_node: GLTFNode, json: Dictionary, node: Node) -> Error:
127 changes: 127 additions & 0 deletions addons/omi_extensions/vehicle/gltf_vehicle_body.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
@tool
class_name GLTFVehicleBody
extends Resource


## The input value controlling the ratio of the vehicle's angular forces.
@export var angular_activation := Vector3.ZERO
## The input value controlling the ratio of the vehicle's linear forces.
@export var linear_activation := Vector3.ZERO
## The gyroscope torque intrinsic to the vehicle, excluding torque from parts, measured in Newton-meters per radian (kg⋅m²/s²/rad).
@export_custom(PROPERTY_HINT_NONE, "suffix:kg\u22C5m\u00B2/s\u00B2/rad (N\u22C5m/rad)")
var gyroscope_torque := Vector3.ZERO
## If non-negative, the speed in meters per second at which the vehicle should stop driving acceleration further.
@export var max_speed: float = -1.0
## The index of the `OMI_seat` glTF node to use as the pilot / driver seat.
@export var pilot_seat_index: int = -1
## The Godot node to use as the pilot seat / driver seat.
var pilot_seat_node: Node3D = null
## If true, the vehicle should slow its rotation down when not given angular activation input for a specific rotation.
@export var angular_dampeners: bool = true
## If true, the vehicle should slow itself down when not given linear activation input for a specific direction.
@export var linear_dampeners: bool = false
## If true, the vehicle should use a throttle for linear movement. If max_speed is non-negative, the throttle should be a ratio of that speed, otherwise it should be a ratio of thrust power.
@export var use_throttle: bool = false


static func from_node(vehicle_node: VehicleBody3D) -> GLTFVehicleBody:
var ret := GLTFVehicleBody.new()
if vehicle_node is PilotedVehicleBody3D:
ret.angular_activation = vehicle_node.angular_activation
ret.linear_activation = vehicle_node.linear_activation
ret.gyroscope_torque = vehicle_node.gyroscope_torque
ret.max_speed = vehicle_node.max_speed
ret.pilot_seat_node = vehicle_node.pilot_seat_node
ret.angular_dampeners = vehicle_node.angular_dampeners
ret.linear_dampeners = vehicle_node.linear_dampeners
ret.use_throttle = vehicle_node.use_throttle
return ret


func to_node(gltf_state: GLTFState, gltf_node: GLTFNode) -> PilotedVehicleBody3D:
# Set up the body node.
var vehicle_node := PilotedVehicleBody3D.new()
var gltf_physics_body: GLTFPhysicsBody = gltf_node.get_additional_data(&"GLTFPhysicsBody")
if gltf_physics_body == null:
printerr("GLTF vehicle body: Expected the vehicle body to also be a physics body. Continuing anyway.")
else:
vehicle_node.mass = gltf_physics_body.mass
vehicle_node.linear_velocity = gltf_physics_body.linear_velocity
vehicle_node.angular_velocity = gltf_physics_body.angular_velocity
vehicle_node.inertia = gltf_physics_body.inertia_diagonal
vehicle_node.center_of_mass = gltf_physics_body.center_of_mass
vehicle_node.center_of_mass_mode = RigidBody3D.CENTER_OF_MASS_MODE_CUSTOM
# If there is a collider shape, set it up.
var gltf_collider_shape: GLTFPhysicsShape = gltf_node.get_additional_data(&"GLTFPhysicsColliderShape")
if gltf_collider_shape != null:
_setup_shape_mesh_resource_from_index_if_needed(gltf_state, gltf_collider_shape)
var col_shape: CollisionShape3D = gltf_collider_shape.to_node(true)
col_shape.name = gltf_node.resource_name + "Collider"
vehicle_node.add_child(col_shape)
# Set up the vehicle properties.
vehicle_node.angular_activation = angular_activation
vehicle_node.linear_activation = linear_activation
vehicle_node.gyroscope_torque = gyroscope_torque
vehicle_node.max_speed = max_speed
vehicle_node.angular_dampeners = angular_dampeners
vehicle_node.linear_dampeners = linear_dampeners
vehicle_node.use_throttle = use_throttle
return vehicle_node


static func from_dictionary(dict: Dictionary) -> GLTFVehicleBody:
var ret := GLTFVehicleBody.new()
if dict.has("angularActivation"):
var ang_arr: Array = dict["angularActivation"]
ret.angular_activation = Vector3(ang_arr[0], ang_arr[1], ang_arr[2])
if dict.has("linearActivation"):
var lin_arr: Array = dict["linearActivation"]
ret.linear_activation = Vector3(lin_arr[0], lin_arr[1], lin_arr[2])
if dict.has("gyroTorque"):
var gyro_arr: Array = dict["gyroTorque"]
ret.gyroscope_torque = Vector3(gyro_arr[0], gyro_arr[1], gyro_arr[2])
if dict.has("maxSpeed"):
ret.max_speed = dict["maxSpeed"]
if dict.has("pilotSeat"):
ret.pilot_seat_index = dict["pilotSeat"]
if dict.has("angularDampeners"):
ret.angular_dampeners = dict["angularDampeners"]
if dict.has("linearDampeners"):
ret.linear_dampeners = dict["linearDampeners"]
if dict.has("useThrottle"):
ret.use_throttle = dict["useThrottle"]
return ret


func to_dictionary() -> Dictionary:
var ret: Dictionary = {}
if angular_activation != Vector3.ZERO:
ret["angularActivation"] = [angular_activation.x, angular_activation.y, angular_activation.z]
if linear_activation != Vector3.ZERO:
ret["linearActivation"] = [linear_activation.x, linear_activation.y, linear_activation.z]
if gyroscope_torque != Vector3.ZERO:
ret["gyroTorque"] = [gyroscope_torque.x, gyroscope_torque.y, gyroscope_torque.z]
if max_speed != -1.0:
ret["maxSpeed"] = max_speed
if pilot_seat_index != -1:
ret["pilotSeat"] = pilot_seat_index
if not angular_dampeners: # Default is true.
ret["angularDampeners"] = angular_dampeners
if linear_dampeners:
ret["linearDampeners"] = linear_dampeners
if use_throttle:
ret["useThrottle"] = use_throttle
return ret


func _setup_shape_mesh_resource_from_index_if_needed(gltf_state: GLTFState, gltf_shape: GLTFPhysicsShape) -> void:
var shape_mesh_index: int = gltf_shape.mesh_index
if shape_mesh_index == -1:
return # No mesh for this shape.
var importer_mesh: ImporterMesh = gltf_shape.importer_mesh
if importer_mesh != null:
return # The mesh resource is already set up.
var state_meshes: Array[GLTFMesh] = gltf_state.meshes
var gltf_mesh: GLTFMesh = state_meshes[shape_mesh_index]
importer_mesh = gltf_mesh.mesh
gltf_shape.importer_mesh = importer_mesh
59 changes: 59 additions & 0 deletions addons/omi_extensions/vehicle/gltf_vehicle_hover_thruster.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
@tool
class_name GLTFVehicleHoverThruster
extends Resource


## The ratio of the maximum hover energy the hover thruster is currently using for propulsion.
@export_range(0.0, 1.0, 0.01)
var current_hover_ratio: float = 0.0
## The ratio of the maximum gimbal angles the hover thruster is rotated to. The vector length may not be longer than 1.0.
@export var current_gimbal_ratio := Vector2(0.0, 0.0)
## The maximum hover energy in Newton-meters (N⋅m or kg⋅m²/s²) that the hover thruster can provide.
@export_custom(PROPERTY_HINT_NONE, "suffix:kg\u22C5m\u00B2/s\u00B2 (N\u22C5m)")
var max_hover_energy: float = 0.0
## The maximum angle the hover thruster can gimbal or rotate in radians.
@export_custom(PROPERTY_HINT_NONE, "suffix:rad")
var max_gimbal: float = 0.0


static func from_node(thruster_node: VehicleHoverThruster3D) -> GLTFVehicleHoverThruster:
var ret := GLTFVehicleHoverThruster.new()
ret.current_hover_ratio = thruster_node.current_hover_ratio
ret.current_gimbal_ratio = thruster_node.current_gimbal_ratio
ret.max_hover_energy = thruster_node.max_hover_energy
ret.max_gimbal = thruster_node.max_gimbal
return ret


func to_node() -> VehicleHoverThruster3D:
var thruster_node := VehicleHoverThruster3D.new()
thruster_node.current_hover_ratio = current_hover_ratio
thruster_node.current_gimbal_ratio = current_gimbal_ratio
thruster_node.max_hover_energy = max_hover_energy
thruster_node.max_gimbal = max_gimbal
return thruster_node


static func from_dictionary(dict: Dictionary) -> GLTFVehicleHoverThruster:
var ret := GLTFVehicleHoverThruster.new()
if dict.has("currentHoverRatio"):
ret.current_force_ratio = dict["currentHoverRatio"]
if dict.has("currentGimbalRatio"):
ret.current_gimbal_ratio = dict["currentGimbalRatio"]
if dict.has("maxHoverEnergy"):
ret.max_hover_energy = dict["maxHoverEnergy"]
if dict.has("maxGimbal"):
ret.max_gimbal = dict["maxGimbal"]
return ret


func to_dictionary() -> Dictionary:
var ret: Dictionary = {}
ret["maxHoverEnergy"] = max_hover_energy
if current_hover_ratio != 0.0:
ret["currentHoverRatio"] = current_hover_ratio
if current_gimbal_ratio != Vector2.ZERO:
ret["currentGimbalRatio"] = current_gimbal_ratio
if max_gimbal != 0.0:
ret["maxGimbal"] = max_gimbal
return ret
59 changes: 59 additions & 0 deletions addons/omi_extensions/vehicle/gltf_vehicle_thruster.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
@tool
class_name GLTFVehicleThruster
extends Resource


## The ratio of the maximum thrust force the thruster is currently using for propulsion.
@export_range(0.0, 1.0, 0.01)
var current_force_ratio: float = 0.0
## The ratio of the maximum gimbal angles the thruster is rotated to. The vector length may not be longer than 1.0.
@export var current_gimbal_ratio := Vector2(0.0, 0.0)
## The maximum thrust force in Newtons (kg⋅m/s²) that the thruster can provide.
@export_custom(PROPERTY_HINT_NONE, "suffix:kg\u22C5m/s\u00B2 (N)")
var max_force: float = 0.0
## The maximum angle the thruster can gimbal or rotate in radians.
@export_custom(PROPERTY_HINT_NONE, "suffix:rad")
var max_gimbal: float = 0.0


static func from_node(thruster_node: VehicleThruster3D) -> GLTFVehicleThruster:
var ret := GLTFVehicleThruster.new()
ret.current_force_ratio = thruster_node.current_force_ratio
ret.current_gimbal_ratio = thruster_node.current_gimbal_ratio
ret.max_force = thruster_node.max_force
ret.max_gimbal = thruster_node.max_gimbal
return ret


func to_node() -> VehicleThruster3D:
var thruster_node := VehicleThruster3D.new()
thruster_node.current_force_ratio = current_force_ratio
thruster_node.current_gimbal_ratio = current_gimbal_ratio
thruster_node.max_force = max_force
thruster_node.max_gimbal = max_gimbal
return thruster_node


static func from_dictionary(dict: Dictionary) -> GLTFVehicleThruster:
var ret := GLTFVehicleThruster.new()
if dict.has("currentForceRatio"):
ret.current_force_ratio = dict["currentForceRatio"]
if dict.has("currentGimbalRatio"):
ret.current_gimbal_ratio = dict["currentGimbalRatio"]
if dict.has("maxForce"):
ret.max_force = dict["maxForce"]
if dict.has("maxGimbal"):
ret.max_gimbal = dict["maxGimbal"]
return ret


func to_dictionary() -> Dictionary:
var ret: Dictionary = {}
ret["maxForce"] = max_force
if current_force_ratio != 0.0:
ret["currentForceRatio"] = current_force_ratio
if current_gimbal_ratio != Vector2.ZERO:
ret["currentGimbalRatio"] = current_gimbal_ratio
if max_gimbal != 0.0:
ret["maxGimbal"] = max_gimbal
return ret
133 changes: 133 additions & 0 deletions addons/omi_extensions/vehicle/gltf_vehicle_wheel.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
@tool
class_name GLTFVehicleWheel
extends Resource


## The ratio of the maximum force the wheel is using for propulsion.
@export_range(0.0, 1.0, 0.01)
var current_force_ratio: float = 0.0
## The ratio of the maximum steering angle the wheel is rotated to.
@export_range(0.0, 1.0, 0.01)
var current_steering_ratio: float = 0.0
## The maximum force in Newtons (kg⋅m/s²) that the wheel can provide.
@export_custom(PROPERTY_HINT_NONE, "suffix:kg\u22C5m/s\u00B2 (N)")
var max_force: float = 0.0
## The maximum angle in radians that the wheel can steer.
@export_range(0, 90, 0.1, "radians")
var max_steering_angle: float = 0.0
## The index of the physics material in the top level physicsMaterials array.
## TODO: This is currently unimplemented, pending glTF physics material support.
@export var physics_material_index: int = -1
## Godot only uses friction for wheel physics materials, not restitution.
## Godot allows wheel friction to go above 1.0, but this is unrealistic.
@export var physics_material_friction: float = 1.0
## TODO: This is currently unimplemented, pending glTF physics material support.
@export var physics_material: PhysicsMaterial = null
## The radius of the wheel in meters. This is the radius of a circle in the local YZ plane.
@export_custom(PROPERTY_HINT_NONE, "suffix:m")
var radius: float = 0.25
## The damping of the suspension during compression, the resistance to the velocity of the suspension. It is measured in Newton-seconds per meter (N⋅s/m), or kilograms per second (kg/s) in SI base units.
@export_custom(PROPERTY_HINT_NONE, "suffix:kg/s (N\u22C5s/m)")
var suspension_damping_compression: float = 2000.0
## The damping of the suspension during rebound/relaxation, the resistance to the velocity of the suspension. It is measured in Newton-seconds per meter (N⋅s/m), or kilograms per second (kg/s) in SI base units.
@export_custom(PROPERTY_HINT_NONE, "suffix:kg/s (N\u22C5s/m)")
var suspension_damping_rebound: float = 2000.0
## The stiffness of the suspension, the resistance to traveling away from the start point. It is measured in Newtons per meter, or kg/s² in SI base units.
@export_custom(PROPERTY_HINT_NONE, "suffix:kg/s\u00B2 (N/m)")
var suspension_stiffness: float = 20000.0
## The maximum distance the suspension can move up or down in meters.
@export_custom(PROPERTY_HINT_NONE, "suffix:m")
var suspension_travel: float = 0.25
## The width of the wheel in meters. This is the width of the wheel in the local X axis.
## Note: Width is not used by Godot VehicleWheel3D but we will still import/export it to/from glTF.
@export_custom(PROPERTY_HINT_NONE, "suffix:m")
var width: float = 0.125


static func from_node(wheel_node: VehicleWheel3D) -> GLTFVehicleWheel:
var ret := GLTFVehicleWheel.new()
ret.current_force_ratio = wheel_node.current_force_ratio
ret.current_steering_ratio = wheel_node.current_steering_ratio
ret.max_force = wheel_node.max_force
ret.max_steering_angle = wheel_node.max_steering_angle
ret.physics_material_friction = wheel_node.wheel_friction_slip
ret.radius = wheel_node.wheel_radius
# Note: Godot uses damping values in Mg/s while glTF uses kg/s.
ret.suspension_damping_compression = wheel_node.damping_compression * 1000.0
ret.suspension_damping_rebound = wheel_node.damping_relaxation * 1000.0
# Note: Godot uses stiffness values in Mg/s² (N/mm) while glTF uses kg/s² (N/m).
ret.suspension_stiffness = wheel_node.suspension_stiffness * 1000.0
ret.suspension_travel = wheel_node.suspension_travel
return ret


func to_node() -> VehicleWheel3D:
var wheel_node := PilotedVehicleWheel3D.new()
wheel_node.current_force_ratio = current_force_ratio
wheel_node.current_steering_ratio = current_steering_ratio
wheel_node.max_force = max_force
wheel_node.max_steering_angle = max_steering_angle
wheel_node.wheel_friction_slip = physics_material_friction
wheel_node.wheel_radius = radius
# Note: Godot uses damping values in Mg/s while glTF uses kg/s.
wheel_node.damping_compression = suspension_damping_compression / 1000.0
wheel_node.damping_relaxation = suspension_damping_rebound / 1000.0
# Note: Godot uses stiffness values in Mg/s² (N/mm) while glTF uses kg/s² (N/m).
wheel_node.suspension_stiffness = suspension_stiffness / 1000.0
wheel_node.suspension_travel = suspension_travel
wheel_node.wheel_rest_length = suspension_travel
return wheel_node


static func from_dictionary(dict: Dictionary) -> GLTFVehicleWheel:
var ret := GLTFVehicleWheel.new()
if dict.has("currentForceRatio"):
ret.current_force_ratio = dict["currentForceRatio"]
if dict.has("currentSteeringRatio"):
ret.current_steering_ratio = dict["currentSteeringRatio"]
if dict.has("maxForce"):
ret.max_force = dict["maxForce"]
if dict.has("maxSteeringAngle"):
ret.max_steering_angle = dict["maxSteeringAngle"]
if dict.has("physicsMaterial"):
ret.physics_material_index = dict["physicsMaterial"]
if dict.has("radius"):
ret.radius = dict["radius"]
if dict.has("suspensionDampingCompression"):
ret.suspension_damping_compression = dict["suspensionDampingCompression"]
if dict.has("suspensionDampingRebound"):
ret.suspension_damping_rebound = dict["suspensionDampingRebound"]
if dict.has("suspensionStiffness"):
ret.suspension_stiffness = dict["suspensionStiffness"]
if dict.has("suspensionTravel"):
ret.suspension_travel = dict["suspensionTravel"]
if dict.has("width"):
ret.width = dict["width"]
return ret


func to_dictionary() -> Dictionary:
var ret: Dictionary = {}
if current_force_ratio != 0.0:
ret["currentForceRatio"] = current_force_ratio
if current_steering_ratio != 0.0:
ret["currentSteeringRatio"] = current_steering_ratio
if max_force != 0.0:
ret["maxForce"] = max_force
if max_steering_angle != 0.0:
ret["maxSteeringAngle"] = max_steering_angle
if physics_material_index != -1:
ret["physicsMaterial"] = physics_material_index
if radius != 0.25:
ret["radius"] = radius
if suspension_damping_compression != 500.0:
ret["suspensionDampingCompression"] = suspension_damping_compression
if suspension_damping_rebound != 500.0:
ret["suspensionDampingRebound"] = suspension_damping_rebound
if suspension_stiffness != 20000.0:
ret["suspensionStiffness"] = suspension_stiffness
if suspension_travel != 0.25:
ret["suspensionTravel"] = suspension_travel
if width != 0.125:
ret["width"] = width
return ret
1 change: 1 addition & 0 deletions addons/omi_extensions/vehicle/nodes/icons/PilotSeat3D.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions addons/omi_extensions/vehicle/nodes/icons/PilotSeat3D.svg.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://dr11x5imv6k72"
path="res://.godot/imported/PilotSeat3D.svg-41ee0744bff179e41b8139d83cedc12f.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://addons/omi_extensions/vehicle/nodes/icons/PilotSeat3D.svg"
dest_files=["res://.godot/imported/PilotSeat3D.svg-41ee0744bff179e41b8139d83cedc12f.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=4.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://duyicn5d75dej"
path="res://.godot/imported/PilotedVehicleBody3D.svg-4dd97fb3a03a3a5cc0e0f562b8673ceb.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://addons/omi_extensions/vehicle/nodes/icons/PilotedVehicleBody3D.svg"
dest_files=["res://.godot/imported/PilotedVehicleBody3D.svg-4dd97fb3a03a3a5cc0e0f562b8673ceb.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=4.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://bfco3nxeqjpxw"
path="res://.godot/imported/PilotedVehicleWheel3D.svg-26c55183cf30b95687eaf62b3c4f8b76.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://addons/omi_extensions/vehicle/nodes/icons/PilotedVehicleWheel3D.svg"
dest_files=["res://.godot/imported/PilotedVehicleWheel3D.svg-26c55183cf30b95687eaf62b3c4f8b76.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=4.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://dp2ay6syfi5jj"
path="res://.godot/imported/VehicleHoverThruster3D.svg-a141cdc2cf3bcab4cd13e698ff39b60c.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://addons/omi_extensions/vehicle/nodes/icons/VehicleHoverThruster3D.svg"
dest_files=["res://.godot/imported/VehicleHoverThruster3D.svg-a141cdc2cf3bcab4cd13e698ff39b60c.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=4.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://ctqm641v56pgp"
path="res://.godot/imported/VehicleThruster3D.svg-6ce976670e46f52b419323e88a5f961f.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://addons/omi_extensions/vehicle/nodes/icons/VehicleThruster3D.svg"
dest_files=["res://.godot/imported/VehicleThruster3D.svg-6ce976670e46f52b419323e88a5f961f.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=4.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
178 changes: 178 additions & 0 deletions addons/omi_extensions/vehicle/nodes/pilot_seat_3d.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
## A Seat3D designed for its occupant to pilot a PilotedVehicleBody3D node.
@tool
@icon("icons/PilotSeat3D.svg")
class_name PilotSeat3D
extends Seat3D


enum ControlScheme {
## Automatically determine the control scheme based on the vehicle's components.
AUTO,
## Uses WS for forward/back movement and AD for steering, like most driving games.
CAR,
## Uses WASDRF for linear movement, QE roll, mouse pitch/yaw, or IJKLUO rotation.
SIX_DOF,
## Uses WASDQE for rotation, with W as up and S as down, Shift throttle up, Ctrl throttle down.
NAVBALL,
## Uses WASDQE for rotation, with W as down and S as up, like Kerbal Space Program.
NAVBALL_INVERTED,
## Like SIX_DOF but flattens the horizontal WASD input, good for hovercrafts.
HORIZONTAL_SIX_DOF,
}

const MOUSE_SENSITIVITY: float = 0.1
const THROTTLE_RATE: float = 0.5

## The control scheme to use. More can be added by editing pilot_seat_3d.gd.
@export var control_scheme := ControlScheme.AUTO

## Will be automatically when player_node is assigned to.
## Can also be overridden for custom use cases.
@export var use_local_controls: bool = false

@export var piloted_vehicle_node: PilotedVehicleBody3D = null:
set(value):
piloted_vehicle_node = value
if piloted_vehicle_node.pilot_seat_node != self:
piloted_vehicle_node.pilot_seat_node = self

## Should be set at runtime when a player enters the pilot seat.
@export var _player_node: Node3D = null

var _mouse_input := Vector2.ZERO


func _ready() -> void:
if piloted_vehicle_node == null:
var parent: Node = get_parent()
if parent is PilotedVehicleBody3D:
piloted_vehicle_node = parent
if piloted_vehicle_node != null:
piloted_vehicle_node.pilot_seat_node = self
if Engine.is_editor_hint():
return


func _physics_process(delta: float) -> void:
if Engine.is_editor_hint() or not use_local_controls or piloted_vehicle_node == null:
return
var actual_control_scheme: ControlScheme = _get_actual_control_scheme()
var angular_input: Vector3 = _get_angular_input(actual_control_scheme)
var linear_input: Vector3 = _get_linear_input(actual_control_scheme)
_mouse_input = Vector2.ZERO
if Input.is_action_just_pressed(&"toggle_linear_dampeners"):
piloted_vehicle_node.linear_dampeners = not piloted_vehicle_node.linear_dampeners
piloted_vehicle_node.angular_activation = angular_input
if Input.is_action_pressed(&"throttle_zero"):
piloted_vehicle_node.linear_activation = Vector3.ZERO
elif piloted_vehicle_node.use_throttle:
var change: Vector3 = (delta * THROTTLE_RATE) * linear_input
piloted_vehicle_node.linear_activation = (piloted_vehicle_node.linear_activation + change).clampf(-1.0, 1.0)
else:
piloted_vehicle_node.linear_activation = linear_input


func _unhandled_input(input_event: InputEvent) -> void:
if Engine.is_editor_hint():
return
if input_event is InputEventMouseMotion:
if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
_mouse_input += input_event.relative


func enter_pilot_seat(player: Node3D) -> void:
assert(player != null)
_player_node = player
use_local_controls = player.is_multiplayer_authority()


func exit_pilot_seat() -> void:
_player_node = null
use_local_controls = false
if piloted_vehicle_node:
piloted_vehicle_node.angular_activation = Vector3.ZERO
piloted_vehicle_node.linear_activation = Vector3.ZERO


func _get_actual_control_scheme() -> ControlScheme:
if control_scheme != ControlScheme.AUTO:
return control_scheme
if piloted_vehicle_node.use_throttle:
return ControlScheme.NAVBALL
if piloted_vehicle_node.has_wheels():
return ControlScheme.CAR
if piloted_vehicle_node.has_hover_thrusters():
return ControlScheme.HORIZONTAL_SIX_DOF
return ControlScheme.SIX_DOF


func _get_angular_input(actual_control_scheme: ControlScheme) -> Vector3:
match actual_control_scheme:
ControlScheme.CAR:
return Vector3(
Input.get_axis(&"rotate_pitch_up", &"rotate_pitch_down"),
Input.get_axis(&"move_right", &"move_left"),
Input.get_axis(&"rotate_yaw_left", &"rotate_yaw_right")
)
ControlScheme.NAVBALL:
return Vector3(
Input.get_axis(&"move_forward", &"move_back"),
Input.get_axis(&"move_right", &"move_left"),
Input.get_axis(&"rotate_roll_ccw", &"rotate_roll_clockwise"),
)
ControlScheme.NAVBALL_INVERTED:
return Vector3(
Input.get_axis(&"move_back", &"move_forward"),
Input.get_axis(&"move_right", &"move_left"),
Input.get_axis(&"rotate_roll_ccw", &"rotate_roll_clockwise"),
)
var angular_input := Vector3(
Input.get_axis(&"rotate_pitch_up", &"rotate_pitch_down"),
Input.get_axis(&"rotate_yaw_right", &"rotate_yaw_left"),
Input.get_axis(&"rotate_roll_ccw", &"rotate_roll_clockwise")
)
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
angular_input.x += _mouse_input.y * MOUSE_SENSITIVITY
angular_input.y -= _mouse_input.x * MOUSE_SENSITIVITY
return angular_input


func _get_linear_input(actual_control_scheme: ControlScheme) -> Vector3:
match actual_control_scheme:
ControlScheme.CAR:
return Vector3(
Input.get_axis(&"rotate_roll_clockwise", &"rotate_roll_ccw"),
Input.get_axis(&"move_down", &"move_up"),
Input.get_axis(&"move_back", &"move_forward")
)
ControlScheme.NAVBALL:
return Vector3(
Input.get_axis(&"rotate_yaw_right", &"rotate_yaw_left"),
Input.get_axis(&"rotate_pitch_down", &"rotate_pitch_up"),
Input.get_axis(&"throttle_decrease", &"throttle_increase")
)
ControlScheme.NAVBALL_INVERTED:
return Vector3(
Input.get_axis(&"rotate_yaw_right", &"rotate_yaw_left"),
Input.get_axis(&"rotate_pitch_down", &"rotate_pitch_up"),
Input.get_axis(&"throttle_decrease", &"throttle_increase")
)
ControlScheme.HORIZONTAL_SIX_DOF:
var vehicle_euler: Vector3 = piloted_vehicle_node.basis.get_euler()
var flatten := Basis.from_euler(Vector3(-vehicle_euler.x, 0.0, -vehicle_euler.z))
return flatten * Vector3(
Input.get_axis(&"move_right", &"move_left"),
Input.get_axis(&"move_down", &"move_up"),
Input.get_axis(&"move_back", &"move_forward")
)
return Vector3(
Input.get_axis(&"move_right", &"move_left"),
Input.get_axis(&"move_down", &"move_up"),
Input.get_axis(&"move_back", &"move_forward")
)


static func from_points(p_back: Vector3, p_foot: Vector3, p_knee: Vector3, p_angle: float = TAU * 0.25) -> PilotSeat3D:
var seat = PilotSeat3D.new()
seat.set_points(p_back, p_foot, p_knee, p_angle)
return seat
136 changes: 136 additions & 0 deletions addons/omi_extensions/vehicle/nodes/piloted_vehicle_body_3d.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
## General-purpose piloted VehicleBody3D. Can be used for cars, spacecraft, airplanes, boats, and more.
@icon("icons/PilotedVehicleBody3D.svg")
class_name PilotedVehicleBody3D
extends VehicleBody3D


const INERTIA_DAMPENER_RATE_ANGULAR: float = 4.0
const INERTIA_DAMPENER_RATE_LINEAR: float = 1.0


## The input value controlling the ratio of the vehicle's angular forces.
@export var angular_activation := Vector3.ZERO
## The input value controlling the ratio of the vehicle's linear forces.
@export var linear_activation := Vector3.ZERO
## The gyroscope torque intrinsic to the vehicle, excluding torque from parts, measured in Newton-meters per radian (kg⋅m²/s²/rad).
@export_custom(PROPERTY_HINT_NONE, "suffix:kg\u22C5m\u00B2/s\u00B2/rad (N\u22C5m/rad)")
var gyroscope_torque := Vector3.ZERO
## If non-negative, the speed in meters per second at which the vehicle should stop driving acceleration further.
@export var max_speed: float = -1.0
## The node to use as the pilot seat / driver seat.
@export var pilot_seat_node: PilotSeat3D = null:
set(value):
pilot_seat_node = value
if pilot_seat_node.piloted_vehicle_node != self:
pilot_seat_node.piloted_vehicle_node = self
## If true, the vehicle should slow its rotation down when not given angular activation input for a specific rotation.
@export var angular_dampeners: bool = true
## If true, the vehicle should slow itself down when not given linear activation input for a specific direction.
@export var linear_dampeners: bool = false
## If true, the vehicle should use a throttle for linear movement. If max_speed is non-negative, the throttle should be a ratio of that speed, otherwise it should be a ratio of thrust power.
@export var use_throttle: bool = false

var _hover_thrusters: Array[VehicleHoverThruster3D] = []
var _thrusters: Array[VehicleThruster3D] = []
var _wheels: Array[PilotedVehicleWheel3D] = []
var _keep_upright: bool = false


func _ready() -> void:
if pilot_seat_node:
pilot_seat_node.piloted_vehicle_node = self


func _physics_process(delta: float) -> void:
if Engine.is_editor_hint():
return
var actual_angular: Vector3 = angular_activation
var actual_linear: Vector3 = linear_activation
var global_to_local_rot: Quaternion = quaternion.inverse()
var local_angular_vel: Vector3 = global_to_local_rot * get_angular_velocity()
var local_linear_vel: Vector3 = global_to_local_rot * get_linear_velocity()
# Determine the actual linear values to use based on activation, throttle, and dampeners.
if use_throttle and max_speed >= 0.0:
# In this case, the throttle should be a ratio of the maximum speed,
# with the thrust adjusting so that the vehicle meets the target speed.
var target_velocity: Vector3 = max_speed * linear_activation
actual_linear.x = (target_velocity.x - local_linear_vel.x) / max_speed
actual_linear.y = (target_velocity.y - local_linear_vel.y) / max_speed
actual_linear.z = (target_velocity.z - local_linear_vel.z) / max_speed
elif linear_dampeners:
if is_zero_approx(actual_linear.x):
actual_linear.x = local_linear_vel.x * -INERTIA_DAMPENER_RATE_LINEAR
if is_zero_approx(actual_linear.y):
actual_linear.y = local_linear_vel.y * -INERTIA_DAMPENER_RATE_LINEAR
if is_zero_approx(actual_linear.z):
actual_linear.z = local_linear_vel.z * -INERTIA_DAMPENER_RATE_LINEAR
if not _hover_thrusters.is_empty():
var up: Vector3 = -_get_local_gravity_direction()
actual_linear += up if linear_activation != Vector3.ZERO else up * 0.75
# Vehicle wheels should never rotate due to dampeners, because for wheels,
# pointing straight is a vehicle's best attempt to stop rotating.
for wheel in _wheels:
wheel.set_steering_from_vehicle_angular_input(actual_angular)
wheel.set_thrust_from_vehicle_linear_input(actual_linear)
# Determine the actual angular values to use based on activation and dampeners.
if angular_dampeners:
if is_zero_approx(angular_activation.x):
actual_angular.x = local_angular_vel.x * -INERTIA_DAMPENER_RATE_ANGULAR
if is_zero_approx(angular_activation.y):
actual_angular.y = local_angular_vel.y * -INERTIA_DAMPENER_RATE_ANGULAR
if is_zero_approx(angular_activation.z):
actual_angular.z = local_angular_vel.z * -INERTIA_DAMPENER_RATE_ANGULAR
# Hovercraft, cars, etc should attempt to keep themselves upright.
if _keep_upright:
var to_up: Quaternion = _get_rotation_to_upright()
var v = Vector3(to_up.x, 0.0, to_up.z).limit_length()
if is_zero_approx(angular_activation.x):
actual_angular.x += v.x
if is_zero_approx(angular_activation.z):
actual_angular.z += v.z
actual_angular = actual_angular.clampf(-1.0, 1.0)
actual_linear = actual_linear.clampf(-1.0, 1.0)
# Now that we've calculated the actual angular/linear inputs including
# throttle and dampeners, apply them to everything (except wheels).
apply_torque(basis * (gyroscope_torque * actual_angular))
for hover_thruster in _hover_thrusters:
hover_thruster.set_from_vehicle_input(actual_angular, actual_linear)
for thruster in _thrusters:
thruster.set_gimbal_from_vehicle_angular_input(actual_angular)
thruster.set_thrust_from_vehicle_linear_input(actual_linear)


func has_hover_thrusters() -> bool:
return not _hover_thrusters.is_empty()


func has_wheels() -> bool:
return not _wheels.is_empty()


func register_part(part: Node3D) -> void:
if part is VehicleHoverThruster3D:
_hover_thrusters.append(part)
_keep_upright = true
elif part is VehicleThruster3D:
_thrusters.append(part)
elif part is PilotedVehicleWheel3D:
_wheels.append(part)
_keep_upright = true
else:
printerr("PilotedVehicleBody3D: Unknown part type: ", part)


func _get_rotation_to_upright() -> Quaternion:
var y = -_get_local_gravity_direction()
if y == Vector3.ZERO:
return Quaternion.IDENTITY
var x = y.cross(Vector3.BACK)
var z = x.cross(y).normalized()
x = y.cross(z)
var b = Basis(x, y, z)
return b.get_rotation_quaternion()


func _get_local_gravity_direction() -> Vector3:
return (quaternion.inverse() * get_gravity()).normalized()
59 changes: 59 additions & 0 deletions addons/omi_extensions/vehicle/nodes/piloted_vehicle_wheel_3d.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
@icon("icons/PilotedVehicleWheel3D.svg")
class_name PilotedVehicleWheel3D
extends VehicleWheel3D


## The ratio of the maximum force the wheel is using for propulsion.
@export_range(0.0, 1.0, 0.01)
var current_force_ratio: float = 0.0
## The ratio of the maximum steering angle the wheel is rotated to.
@export_range(0.0, 1.0, 0.01)
var current_steering_ratio: float = 0.0
## The maximum force in Newtons (kg⋅m/s²) that the wheel can provide.
@export_custom(PROPERTY_HINT_NONE, "suffix:kg\u22C5m/s\u00B2 (N)")
var max_force: float = 0.0
## The maximum angle in radians that the wheel can steer.
@export_range(0, 90, 0.1, "radians")
var max_steering_angle: float = 0.0

var _negate_steering: bool = false
var _parent_body: RigidBody3D = null


func _enter_tree() -> void:
_parent_body = _get_parent_body()
if position.z < 0.0:
_negate_steering = true


func _physics_process(delta: float) -> void:
steering = move_toward(steering, current_steering_ratio * max_steering_angle, delta)


func set_steering_from_vehicle_angular_input(angular_input: Vector3) -> void:
# Note: This code only supports wheels where 0 steering means forward.
# Ideally we should allow for wheels in other rotations but that would be more complicated.
# Other implementations of OMI_vehicle_wheel can use more complex algorithms if they wish.
var steer_ratio: float = angular_input.y * angular_input.y
if (angular_input.y < 0) != _negate_steering:
steer_ratio = -steer_ratio
current_steering_ratio = clampf(steer_ratio, -1.0, 1.0)


func set_thrust_from_vehicle_linear_input(linear_input: Vector3) -> void:
# Note: This code only supports wheels where 0 steering means forward.
# Ideally we should allow for wheels in other rotations but that would be more complicated.
# Other implementations of OMI_vehicle_wheel can use more complex algorithms if they wish.
current_force_ratio = linear_input.z * cos(steering)
engine_force = current_force_ratio * max_force


func _get_parent_body() -> RigidBody3D:
var parent = get_parent()
while parent != null:
if parent is RigidBody3D:
if parent is PilotedVehicleBody3D:
parent.register_part(self)
return parent
parent = parent.get_parent()
return null
139 changes: 139 additions & 0 deletions addons/omi_extensions/vehicle/nodes/vehicle_hover_thruster_3d.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
## Hover thruster for vehicles, used for hovercraft thrust. This is not realistic, it is sci-fi.
## For the "enabled" property supplied by RayCast3D: If false, the hover thruster will not gimbal or apply forces.
@icon("icons/VehicleHoverThruster3D.svg")
class_name VehicleHoverThruster3D
extends RayCast3D


# Controls how much the vehicle's angular input should affect the hover ratio.
# 0 = none, 1 or more = too much stabilization (overcorrection/bounciness).
const TORQUE_STABILIZATION = 0.5

## The ratio of the maximum hover energy the hover thruster is using for propulsion.
@export_range(0.0, 1.0, 0.01)
var current_hover_ratio: float = 0.0
## The ratio of the maximum gimbal angles the hover thruster is rotated to. The vector length may not be longer than 1.0. Note: Gimbal must be set before adding the node to the tree.
@export var current_gimbal_ratio := Vector2(0.0, 0.0)
## The maximum gimbal energy in Newton-meters (N⋅m or kg⋅m²/s²) that the hover thruster can provide.
@export_custom(PROPERTY_HINT_NONE, "suffix:kg\u22C5m\u00B2/s\u00B2 (N\u22C5m)")
var max_hover_energy: float = 0.0
## The maximum angle the hover thruster can gimbal or rotate in radians. Note: Gimbal must be set before adding the node to the tree.
@export_custom(PROPERTY_HINT_NONE, "suffix:rad")
var max_gimbal: float = 0.0

var _parent_body: RigidBody3D = null
var _particles_node: GPUParticles3D = null

var _parent_transform_to_body := Transform3D.IDENTITY
var _parent_quaternion_to_body := Quaternion.IDENTITY
var _rest_quaternion := Quaternion.IDENTITY
var _rest_quaternion_to_body := Quaternion.IDENTITY
var _maximum_linear_gimbal_adjust: float = 0.0
var _negate_gimbal: bool = true


func _init() -> void:
target_position = Vector3(0.0, 0.0, -1000.0)


func _enter_tree() -> void:
_parent_body = _get_parent_body()
recalculate_transforms()


func _ready() -> void:
for child in get_children():
if child is GPUParticles3D:
_particles_node = child
break


func _physics_process(delta: float) -> void:
if Engine.is_editor_hint():
# This isn't a tool script so I have no clue why it's running in the editor but it does?
return
if _parent_body == null or not enabled:
if _particles_node:
_particles_node.amount_ratio = 0.0
return
# First, find the actual hover ratio we need. Hover thrusters should naturally provide
var actual_hover: float = clampf(current_hover_ratio, 0.0, 1.0)
quaternion = _rest_quaternion * _get_gimbal_rotation_quaternion()
if _particles_node:
_particles_node.amount_ratio = current_hover_ratio
if _parent_body == null:
return
var hit_distance: float = maxf(get_collision_point().distance_to(global_position), 0.1)
var max_hover_force: float = max_hover_energy / hit_distance # Nm / m == N
var force_amount: float = actual_hover * max_hover_force
var force_dir: Vector3 = _parent_body.basis * _parent_transform_to_body.basis * basis.z
var force_pos: Vector3 = Transform3D(_parent_body.basis) * _parent_transform_to_body * position
_parent_body.apply_force(force_dir * force_amount, force_pos)


func recalculate_transforms() -> void:
if _parent_body == null:
printerr("Error: VehicleThruster3D must be a descendant of a RigidBody3D node (preferably PilotedVehicleBody3D).")
return
_parent_transform_to_body = Transform3D.IDENTITY
var parent: Node = get_parent()
while parent != _parent_body:
_parent_transform_to_body = parent.transform * _parent_transform_to_body
parent = parent.get_parent()
_rest_quaternion = quaternion * _get_gimbal_rotation_quaternion().inverse()
_parent_quaternion_to_body = _parent_transform_to_body.basis.get_rotation_quaternion()
_rest_quaternion_to_body = _parent_quaternion_to_body * _rest_quaternion
var rest_transform_to_body: Transform3D = _parent_transform_to_body * Transform3D(Basis(_rest_quaternion), position)
var offset: Vector3 = rest_transform_to_body.origin - _parent_body.center_of_mass
_maximum_linear_gimbal_adjust = maxf(asin(rest_transform_to_body.basis.z.y) - TAU / 12.0, 0.0)
_negate_gimbal = offset.dot(rest_transform_to_body.basis.z) < 0.0


func set_from_vehicle_input(angular_input: Vector3, linear_input: Vector3) -> void:
# Set the gimbal based on angular input.
if max_gimbal != 0.0:
var rotated: Vector3 = _rest_quaternion_to_body.inverse() * angular_input
var gimbal_amount: float = -max_gimbal if _negate_gimbal else max_gimbal
current_gimbal_ratio = (Vector2(rotated.x, rotated.y) / gimbal_amount).limit_length()
# Adjust the gimbal based on linear input (optional but significantly improves handling).
var rot: Quaternion = _rest_quaternion_to_body * _get_gimbal_rotation_quaternion()
var local_input: Vector3 = rot.inverse() * linear_input
var max_linear_gimbal_adjust: float = _maximum_linear_gimbal_adjust / max_gimbal
var linear_gimbal_adjust: Vector2 = Vector2(-local_input.y, local_input.x).limit_length() * max_linear_gimbal_adjust
current_gimbal_ratio = (current_gimbal_ratio + linear_gimbal_adjust).limit_length()
# Set the hover ratio based on linear input and angular torque.
rot = _rest_quaternion_to_body * _get_gimbal_rotation_quaternion()
var thrust_direction: Vector3 = rot * Vector3(0, 0, 1)
var thrust_hover: float = maxf(linear_input.dot(thrust_direction), 0.0)
var torque: Vector3 = position.cross(thrust_direction)
var torque_hover: float = maxf(angular_input.dot(torque) * TORQUE_STABILIZATION, 0.0)
current_hover_ratio = clampf(thrust_hover + torque_hover, 0.0, 1.0)


func _get_gimbal_rotation_quaternion() -> Quaternion:
if current_gimbal_ratio.is_zero_approx() or is_zero_approx(max_gimbal):
return Quaternion.IDENTITY
var rot_angles: Vector2 = current_gimbal_ratio.limit_length() * max_gimbal
var angle_mag: float = rot_angles.length()
var sin_norm_angle: float = sin(angle_mag / 2.0) / angle_mag
var cos_half_angle: float = cos(angle_mag / 2.0)
return Quaternion(rot_angles.x * sin_norm_angle, rot_angles.y * sin_norm_angle, 0.0, cos_half_angle)


func _get_parent_body() -> RigidBody3D:
var parent = get_parent()
while parent != null:
if parent is RigidBody3D:
if parent is PilotedVehicleBody3D:
parent.register_part(self)
return parent
parent = parent.get_parent()
return null


func _make_debug_mesh() -> void:
var mi := MeshInstance3D.new()
var box := BoxMesh.new()
box.size = Vector3(0.1, 0.1, 4.0)
mi.mesh = box
add_child(mi)
106 changes: 106 additions & 0 deletions addons/omi_extensions/vehicle/nodes/vehicle_thruster_3d.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
## General-purpose thruster for vehicles. Has a gimbal and emits a force.
## VehicleThruster3D can be used to create rocket engines, jet engines, control thrusters, or any other kind of thruster.
@icon("icons/VehicleThruster3D.svg")
class_name VehicleThruster3D
extends Node3D


## If false, the thruster will not gimbal or apply forces.
@export var enabled: bool = true
## The ratio of the maximum thrust force the thruster is currently using for propulsion.
@export_range(0.0, 1.0, 0.01)
var current_force_ratio: float = 0.0
## The ratio of the maximum gimbal angles the thruster is rotated to. The vector length may not be longer than 1.0. Note: Gimbal must be set before adding the node to the tree.
@export var current_gimbal_ratio := Vector2(0.0, 0.0)
## The maximum thrust force in Newtons (kg⋅m/s²) that the thruster can provide.
@export_custom(PROPERTY_HINT_NONE, "suffix:kg\u22C5m/s\u00B2 (N)")
var max_force: float = 0.0
## The maximum angle the thruster can gimbal or rotate in radians. Note: Gimbal must be set before adding the node to the tree.
@export_custom(PROPERTY_HINT_NONE, "suffix:rad")
var max_gimbal: float = 0.0

var _parent_body: RigidBody3D = null
var _particles_node: GPUParticles3D = null

var _parent_transform_to_body := Transform3D.IDENTITY
var _parent_quaternion_to_body := Quaternion.IDENTITY
var _rest_quaternion := Quaternion.IDENTITY
var _body_to_rest_quaternion := Quaternion.IDENTITY
var _negate_gimbal: bool = true


func _enter_tree() -> void:
_parent_body = _get_parent_body()
recalculate_transforms()


func _ready() -> void:
for child in get_children():
if child is GPUParticles3D:
_particles_node = child
break


func _physics_process(delta: float) -> void:
if _parent_body == null or not enabled:
if _particles_node:
_particles_node.amount_ratio = 0.0
return
quaternion = _rest_quaternion * _get_gimbal_rotation_quaternion()
if _particles_node:
_particles_node.amount_ratio = current_force_ratio
if _parent_body == null:
return
var force_amount: float = current_force_ratio * max_force
var force_dir: Vector3 = _parent_body.basis * _parent_transform_to_body.basis * basis.z
var force_pos: Vector3 = Transform3D(_parent_body.basis) * _parent_transform_to_body * position
_parent_body.apply_force(force_dir * force_amount, force_pos)


func recalculate_transforms() -> void:
if _parent_body == null:
printerr("Error: VehicleThruster3D must be a descendant of a RigidBody3D node (preferably PilotedVehicleBody3D).")
return
_parent_transform_to_body = Transform3D.IDENTITY
var parent: Node = get_parent()
while parent != _parent_body:
_parent_transform_to_body = parent.transform * _parent_transform_to_body
parent = parent.get_parent()
_rest_quaternion = quaternion * _get_gimbal_rotation_quaternion().inverse()
_parent_quaternion_to_body = _parent_transform_to_body.basis.get_rotation_quaternion()
_body_to_rest_quaternion = (_parent_quaternion_to_body * _rest_quaternion).inverse()
var rest_transform_to_body: Transform3D = _parent_transform_to_body * Transform3D(Basis(_rest_quaternion), position)
var offset: Vector3 = rest_transform_to_body.origin - _parent_body.center_of_mass
_negate_gimbal = offset.dot(rest_transform_to_body.basis.z) < 0.0


func set_gimbal_from_vehicle_angular_input(angular_input: Vector3) -> void:
var rotated: Vector3 = _body_to_rest_quaternion * angular_input
var gimbal_amount: float = -max_gimbal if _negate_gimbal else max_gimbal
current_gimbal_ratio = Vector2(rotated.x, rotated.y) / gimbal_amount


func set_thrust_from_vehicle_linear_input(linear_input: Vector3) -> void:
var thrust_direction: Vector3 = (_parent_quaternion_to_body * quaternion) * Vector3(0.0, 0.0, 1.0)
current_force_ratio = clampf(linear_input.dot(thrust_direction), 0.0, 1.0)


func _get_gimbal_rotation_quaternion() -> Quaternion:
if current_gimbal_ratio.is_zero_approx() or is_zero_approx(max_gimbal):
return Quaternion.IDENTITY
var rot_angles: Vector2 = current_gimbal_ratio.limit_length() * max_gimbal
var angle_mag: float = rot_angles.length()
var sin_norm_angle: float = sin(angle_mag * 0.5) / angle_mag
var cos_half_angle: float = cos(angle_mag * 0.5)
return Quaternion(rot_angles.x * sin_norm_angle, rot_angles.y * sin_norm_angle, 0.0, cos_half_angle)


func _get_parent_body() -> RigidBody3D:
var parent = get_parent()
while parent != null:
if parent is RigidBody3D:
if parent is PilotedVehicleBody3D:
parent.register_part(self)
return parent
parent = parent.get_parent()
return null
215 changes: 215 additions & 0 deletions addons/omi_extensions/vehicle/omi_vehicle_doc_ext.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
@tool
class_name GLTFDocumentExtensionOMIVehicle
extends GLTFDocumentExtension


# Import process.
func _import_preflight(state: GLTFState, extensions: PackedStringArray) -> Error:
if not extensions.has("OMI_vehicle_body") and not extensions.has("OMI_vehicle_hover_thruster") and not extensions.has("OMI_vehicle_thruster") and not extensions.has("OMI_vehicle_wheel"):
return ERR_SKIP
var state_json: Dictionary = state.get_json()
if state_json.has("extensions"):
var state_extensions: Dictionary = state_json["extensions"]
if state_extensions.has("OMI_vehicle_hover_thruster"):
var omi_vehicle_hover_thruster_ext: Dictionary = state_extensions["OMI_vehicle_hover_thruster"]
if omi_vehicle_hover_thruster_ext.has("hoverThrusters"):
var state_hover_thruster_dicts: Array = omi_vehicle_hover_thruster_ext["hoverThrusters"]
if state_hover_thruster_dicts.size() > 0:
var state_hover_thrusters: Array[GLTFVehicleHoverThruster] = []
for i in range(state_hover_thruster_dicts.size()):
state_hover_thrusters.append(GLTFVehicleHoverThruster.from_dictionary(state_hover_thruster_dicts[i]))
state.set_additional_data(&"GLTFVehicleHoverThrusters", state_hover_thrusters)
if state_extensions.has("OMI_vehicle_thruster"):
var omi_vehicle_thruster_ext: Dictionary = state_extensions["OMI_vehicle_thruster"]
if omi_vehicle_thruster_ext.has("thrusters"):
var state_thruster_dicts: Array = omi_vehicle_thruster_ext["thrusters"]
if state_thruster_dicts.size() > 0:
var state_thrusters: Array[GLTFVehicleThruster] = []
for i in range(state_thruster_dicts.size()):
state_thrusters.append(GLTFVehicleThruster.from_dictionary(state_thruster_dicts[i]))
state.set_additional_data(&"GLTFVehicleThrusters", state_thrusters)
if state_extensions.has("OMI_vehicle_wheel"):
var omi_vehicle_wheel_ext: Dictionary = state_extensions["OMI_vehicle_wheel"]
if omi_vehicle_wheel_ext.has("wheels"):
var state_wheel_dicts: Array = omi_vehicle_wheel_ext["wheels"]
if state_wheel_dicts.size() > 0:
var state_wheels: Array[GLTFVehicleWheel] = []
for i in range(state_wheel_dicts.size()):
state_wheels.append(GLTFVehicleWheel.from_dictionary(state_wheel_dicts[i]))
state.set_additional_data(&"GLTFVehicleWheels", state_wheels)
return OK


func _get_supported_extensions() -> PackedStringArray:
return PackedStringArray(["OMI_vehicle_body", "OMI_vehicle_hover_thruster", "OMI_vehicle_thruster", "OMI_vehicle_wheel"])


func _parse_node_extensions(state: GLTFState, gltf_node: GLTFNode, extensions: Dictionary) -> Error:
if extensions.has("OMI_vehicle_body"):
gltf_node.set_additional_data(&"GLTFVehicleBody", GLTFVehicleBody.from_dictionary(extensions["OMI_vehicle_body"]))
if extensions.has("OMI_vehicle_hover_thruster"):
var node_hover_thruster_ext: Dictionary = extensions["OMI_vehicle_hover_thruster"]
if node_hover_thruster_ext.has("hoverThruster"):
# "hoverThruster" is the index of the hover thruster parameters in the state hover thrusters array.
var node_hover_thruster_index: int = node_hover_thruster_ext["hoverThruster"]
var state_hover_thrusters: Array[GLTFVehicleHoverThruster] = state.get_additional_data(&"GLTFVehicleHoverThrusters")
if node_hover_thruster_index < 0 or node_hover_thruster_index >= state_hover_thrusters.size():
printerr("GLTF Physics: On node " + gltf_node.get_name() + ", the hover thruster index " + str(node_hover_thruster_index) + " is not in the state hover thrusters (size: " + str(state_hover_thrusters.size()) + ").")
return ERR_FILE_CORRUPT
gltf_node.set_additional_data(&"GLTFVehicleHoverThruster", state_hover_thrusters[node_hover_thruster_index])
else:
gltf_node.set_additional_data(&"GLTFVehicleHoverThruster", GLTFVehicleHoverThruster.from_dictionary(extensions["OMI_vehicle_hover_thruster"]))
if extensions.has("OMI_vehicle_thruster"):
var node_thruster_ext: Dictionary = extensions["OMI_vehicle_thruster"]
if node_thruster_ext.has("thruster"):
# "thruster" is the index of the thruster parameters in the state thrusters array.
var node_thruster_index: int = node_thruster_ext["thruster"]
var state_thrusters: Array[GLTFVehicleThruster] = state.get_additional_data(&"GLTFVehicleThrusters")
if node_thruster_index < 0 or node_thruster_index >= state_thrusters.size():
printerr("GLTF Physics: On node " + gltf_node.get_name() + ", the thruster index " + str(node_thruster_index) + " is not in the state thrusters (size: " + str(state_thrusters.size()) + ").")
return ERR_FILE_CORRUPT
gltf_node.set_additional_data(&"GLTFVehicleThruster", state_thrusters[node_thruster_index])
else:
gltf_node.set_additional_data(&"GLTFVehicleThruster", GLTFVehicleThruster.from_dictionary(extensions["OMI_vehicle_thruster"]))
if extensions.has("OMI_vehicle_wheel"):
var node_wheel_ext: Dictionary = extensions["OMI_vehicle_wheel"]
if node_wheel_ext.has("wheel"):
# "wheel" is the index of the wheel parameters in the state wheels array.
var node_wheel_index: int = node_wheel_ext["wheel"]
var state_wheels: Array[GLTFVehicleWheel] = state.get_additional_data(&"GLTFVehicleWheels")
if node_wheel_index < 0 or node_wheel_index >= state_wheels.size():
printerr("GLTF Physics: On node " + gltf_node.get_name() + ", the wheel index " + str(node_wheel_index) + " is not in the state wheels (size: " + str(state_wheels.size()) + ").")
return ERR_FILE_CORRUPT
gltf_node.set_additional_data(&"GLTFVehicleWheel", state_wheels[node_wheel_index])
else:
gltf_node.set_additional_data(&"GLTFVehicleWheel", GLTFVehicleWheel.from_dictionary(extensions["OMI_vehicle_wheel"]))
return OK


func _import_post_parse(state: GLTFState) -> Error:
# If a vehicle is using a seat as a pilot seat, inform that seat that it's a pilot seat.
# This must be done after parsing node extensions and before generating nodes.
var gltf_nodes: Array[GLTFNode] = state.get_nodes()
for gltf_node in gltf_nodes:
var gltf_vehicle_body = gltf_node.get_additional_data(&"GLTFVehicleBody")
if gltf_vehicle_body is GLTFVehicleBody:
if gltf_vehicle_body.pilot_seat_index != -1:
var seat_node: GLTFNode = gltf_nodes[gltf_vehicle_body.pilot_seat_index]
if seat_node is GLTFNode:
seat_node.set_additional_data(&"GLTFPilotedVehicleBody", gltf_nodes.find(gltf_node))
return OK


func _generate_scene_node(gltf_state: GLTFState, gltf_node: GLTFNode, scene_parent: Node) -> Node3D:
var gltf_vehicle_body = gltf_node.get_additional_data(&"GLTFVehicleBody")
if gltf_vehicle_body is GLTFVehicleBody:
return gltf_vehicle_body.to_node(gltf_state, gltf_node)
var gltf_vehicle_hover_thruster = gltf_node.get_additional_data(&"GLTFVehicleHoverThruster")
if gltf_vehicle_hover_thruster is GLTFVehicleHoverThruster:
return gltf_vehicle_hover_thruster.to_node()
var gltf_vehicle_thruster = gltf_node.get_additional_data(&"GLTFVehicleThruster")
if gltf_vehicle_thruster is GLTFVehicleThruster:
return gltf_vehicle_thruster.to_node()
var gltf_vehicle_wheel = gltf_node.get_additional_data(&"GLTFVehicleWheel")
if gltf_vehicle_wheel is GLTFVehicleWheel:
return gltf_vehicle_wheel.to_node()
var gltf_seat_dict = gltf_node.get_additional_data(&"OMI_seat")
if gltf_seat_dict != null:
var gltf_piloted_body = gltf_node.get_additional_data(&"GLTFPilotedVehicleBody")
if gltf_piloted_body is int and gltf_piloted_body != -1:
var seat = PilotSeat3D.from_points(gltf_seat_dict["back"], gltf_seat_dict["foot"], gltf_seat_dict["knee"], gltf_seat_dict.get("angle", TAU * 0.25))
# If this pilot seat node has a glTF trigger shape, generate that shape too.
var trigger = gltf_node.get_additional_data(&"GLTFPhysicsTriggerShape")
if trigger is GLTFPhysicsShape:
var shape: CollisionShape3D = trigger.to_node(true)
shape.name = gltf_node.resource_name + "Shape"
seat.add_child(shape)
return seat
return null


func _import_node(gltf_state: GLTFState, gltf_node: GLTFNode, json: Dictionary, node: Node) -> Error:
if node is PilotedVehicleBody3D:
var vehicle_node: PilotedVehicleBody3D = node
var gltf_vehicle_body: GLTFVehicleBody = gltf_node.get_additional_data(&"GLTFVehicleBody")
if gltf_vehicle_body.pilot_seat_index != -1:
vehicle_node.pilot_seat_node = gltf_state.get_scene_node(gltf_vehicle_body.pilot_seat_index)
if vehicle_node.pilot_seat_node != null:
vehicle_node.pilot_seat_node.piloted_vehicle_node = vehicle_node
elif node is PilotSeat3D:
var gltf_piloted_body = gltf_node.get_additional_data(&"GLTFPilotedVehicleBody")
if gltf_piloted_body is int and gltf_piloted_body != -1:
node.piloted_vehicle_node = gltf_state.get_scene_node(gltf_piloted_body)
return OK


# Export process.
func _convert_scene_node(state: GLTFState, gltf_node: GLTFNode, scene_node: Node) -> void:
if scene_node is VehicleBody3D:
var gltf_vehicle_body := GLTFVehicleBody.from_node(scene_node)
gltf_node.set_additional_data(&"GLTFVehicleBody", gltf_vehicle_body)
elif scene_node is VehicleHoverThruster3D:
var gltf_vehicle_hover_thruster := GLTFVehicleHoverThruster.from_node(scene_node)
gltf_node.set_additional_data(&"GLTFVehicleHoverThruster", gltf_vehicle_hover_thruster)
elif scene_node is VehicleThruster3D:
var gltf_vehicle_thruster := GLTFVehicleThruster.from_node(scene_node)
gltf_node.set_additional_data(&"GLTFVehicleThruster", gltf_vehicle_thruster)
elif scene_node is VehicleWheel3D:
var gltf_vehicle_wheel := GLTFVehicleWheel.from_node(scene_node)
gltf_node.set_additional_data(&"GLTFVehicleWheel", gltf_vehicle_wheel)


func _get_or_create_array_in_state(gltf_state: GLTFState, ext_name: String, ext_key: String) -> Array:
var state_json: Dictionary = gltf_state.get_json()
var state_extensions: Dictionary = state_json.get_or_add("extensions", {})
var ext: Dictionary = state_extensions.get_or_add(ext_name, {})
gltf_state.add_used_extension(ext_name, false)
var state_ext: Array = ext.get_or_add(ext_key, [])
return state_ext


func _node_index_from_scene_node(state: GLTFState, scene_node: Node) -> int:
var index: int = 0
var node: Node = state.get_scene_node(index)
while node != null:
if node == scene_node:
return index
index = index + 1
node = state.get_scene_node(index)
return -1


func _export_node_item(gltf_state: GLTFState, data_name: StringName, ext_name: String, ext_item_key: String, gltf_node: GLTFNode, node_json: Dictionary) -> void:
var data_resource: Resource = gltf_node.get_additional_data(data_name)
if data_resource == null:
return
var node_extensions: Dictionary = node_json.get_or_add("extensions", {})
var state_array: Array = _get_or_create_array_in_state(gltf_state, ext_name, ext_item_key + "s")
var size: int = state_array.size()
var node_ext: Dictionary = {}
node_extensions[ext_name] = node_ext
var serialized_dict: Dictionary = data_resource.to_dictionary()
for i in range(size):
var other: Dictionary = state_array[i]
if other == serialized_dict:
# De-duplication: If we already have an identical item,
# set the item index to the existing one and return.
node_ext[ext_item_key] = i
return
# If we don't have an identical item, add it to the array.
state_array.append(serialized_dict)
node_ext[ext_item_key] = size


func _export_node(state: GLTFState, gltf_node: GLTFNode, json: Dictionary, _node: Node) -> Error:
var gltf_vehicle_body: GLTFVehicleBody = gltf_node.get_additional_data(&"GLTFVehicleBody")
if gltf_vehicle_body != null:
gltf_vehicle_body.pilot_seat_index = _node_index_from_scene_node(state, gltf_vehicle_body.pilot_seat_node)
var node_extensions = json.get_or_add("extensions", {})
state.add_used_extension("OMI_vehicle_body", false)
node_extensions["OMI_vehicle_body"] = gltf_vehicle_body.to_dictionary()
# Use GDScript's dynamic typing to avoid repeating the same code for each type.
_export_node_item(state, &"GLTFVehicleHoverThruster", "OMI_vehicle_hover_thruster", "hoverThruster", gltf_node, json)
_export_node_item(state, &"GLTFVehicleThruster", "OMI_vehicle_thruster", "thruster", gltf_node, json)
_export_node_item(state, &"GLTFVehicleWheel", "OMI_vehicle_wheel", "wheel", gltf_node, json)
return OK
3 changes: 0 additions & 3 deletions examples/omi_physics_joint/gltf/hanging_rope.gltf.import
Original file line number Diff line number Diff line change
@@ -32,8 +32,5 @@ animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
gltf/naming_version=1
gltf/embedded_image_handling=1
3 changes: 0 additions & 3 deletions examples/omi_physics_joint/gltf/pendulum_balls.gltf.import
Original file line number Diff line number Diff line change
@@ -32,8 +32,5 @@ animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
gltf/naming_version=1
gltf/embedded_image_handling=1
3 changes: 0 additions & 3 deletions examples/omi_physics_joint/gltf/rope_railing.gltf.import
Original file line number Diff line number Diff line change
@@ -32,8 +32,5 @@ animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
gltf/naming_version=1
gltf/embedded_image_handling=1
3 changes: 0 additions & 3 deletions examples/omi_physics_joint/gltf/simple_joint.gltf.import
Original file line number Diff line number Diff line change
@@ -32,8 +32,5 @@ animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
gltf/naming_version=1
gltf/embedded_image_handling=1
3 changes: 0 additions & 3 deletions examples/omi_physics_joint/gltf/slider_ball.gltf.import
Original file line number Diff line number Diff line change
@@ -32,8 +32,5 @@ animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
gltf/naming_version=1
gltf/embedded_image_handling=1
3 changes: 0 additions & 3 deletions examples/omi_physics_joint/gltf/swing_and_slide.gltf.import
Original file line number Diff line number Diff line change
@@ -32,8 +32,5 @@ animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
gltf/naming_version=1
gltf/embedded_image_handling=1
3 changes: 0 additions & 3 deletions examples/omi_physics_joint/gltf/weld_joint.gltf.import
Original file line number Diff line number Diff line change
@@ -32,8 +32,5 @@ animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1
gltf/naming_version=1
gltf/embedded_image_handling=1
Binary file not shown.
267 changes: 267 additions & 0 deletions examples/omi_vehicle/gltf/hovercraft/hovercraft.gltf
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
{
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5126,
"count": 378,
"max": [1.98310995101929, 1.67935001850128, 2.88399004936218],
"min": [-1.98310995101929, -0.93333500623703, -2.9108099937439],
"normalized": false,
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5123,
"count": 378,
"max": [377],
"min": [0],
"normalized": false,
"type": "SCALAR"
},
{
"bufferView": 2,
"byteOffset": 0,
"componentType": 5126,
"count": 3793,
"max": [1.98310720920563, 1.6796053647995, 2.88399434089661],
"min": [-1.98310720920563, -0.93333530426025, -2.91081380844116],
"normalized": false,
"type": "VEC3"
},
{
"bufferView": 3,
"byteOffset": 0,
"componentType": 5126,
"count": 3793,
"max": [1, 0.999988317489624, 0.996253252029419, 1],
"min": [-1, -0.99999952316284, -0.9926170706749, -1],
"normalized": false,
"type": "VEC4"
},
{
"bufferView": 4,
"byteOffset": 0,
"componentType": 5126,
"count": 3793,
"max": [1, 0.999989926815033, 0.999467015266418],
"min": [-1, -1, -0.99987590312958],
"normalized": false,
"type": "VEC3"
},
{
"bufferView": 5,
"byteOffset": 0,
"componentType": 5126,
"count": 3793,
"max": [0.87500011920929, 1],
"min": [0.125, -0.00000011920929],
"normalized": false,
"type": "VEC2"
},
{
"bufferView": 6,
"byteOffset": 0,
"componentType": 5123,
"count": 6792,
"max": [1594],
"min": [0],
"normalized": false,
"type": "SCALAR"
}
],
"asset": {
"extensions": { "KHR_xmp_json_ld": { "packet": 0 } },
"generator": "Godot Engine v4.3.stable.official@77dcf97d82cbfe4e4615475fa52ca03da645dbd8",
"version": "2.0"
},
"bufferViews": [
{ "buffer": 0, "byteLength": 4536, "byteOffset": 0, "byteStride": 12, "target": 34962 },
{ "buffer": 0, "byteLength": 756, "byteOffset": 4536, "target": 34963 },
{ "buffer": 0, "byteLength": 45516, "byteOffset": 5292, "byteStride": 12, "target": 34962 },
{ "buffer": 0, "byteLength": 60688, "byteOffset": 50808, "byteStride": 16, "target": 34962 },
{ "buffer": 0, "byteLength": 45516, "byteOffset": 111496, "byteStride": 12, "target": 34962 },
{ "buffer": 0, "byteLength": 30344, "byteOffset": 157012, "byteStride": 8, "target": 34962 },
{ "buffer": 0, "byteLength": 13584, "byteOffset": 187356, "target": 34963 }
],
"buffers": [{ "byteLength": 200940, "uri": "hovercraft.bin" }],
"extensions": {
"KHR_xmp_json_ld": {
"packets": [
{
"@context": { "dc": "http://purl.org/dc/elements/1.1/" },
"@id": "",
"dc:creator": { "@list": ["Tameno", "aaronfranke"] },
"dc:description": "Hovercraft test file for the OMI_vehicle_body and OMI_vehicle_hover_thruster extensions.",
"dc:format": "model/gltf+json",
"dc:rights": "Public domain",
"dc:subject": { "@set": ["Hovercraft"] },
"dc:title": "Hovercraft",
"dc:type": { "@set": ["Vehicle", "Demo", "Test"] }
}
]
},
"OMI_physics_shape": {
"shapes": [
{ "convex": { "mesh": 0 }, "type": "convex" },
{ "box": { "size": [2, 2, 2] }, "type": "box" }
]
},
"OMI_vehicle_hover_thruster": {
"hoverThrusters": [
{
"maxGimbal": 0.25,
"maxHoverEnergy": 30000
}
]
}
},
"extensionsUsed": [
"GODOT_single_root",
"KHR_xmp_json_ld",
"OMI_physics_body",
"OMI_physics_shape",
"OMI_seat",
"OMI_vehicle_body",
"OMI_vehicle_hover_thruster"
],
"materials": [
{
"extensions": {},
"name": "HovercraftMaterial",
"pbrMetallicRoughness": {
"baseColorFactor": [1, 1, 1, 1],
"metallicFactor": 1,
"roughnessFactor": 0.2
}
}
],
"meshes": [
{
"extras": { "targetNames": [] },
"primitives": [{ "attributes": { "POSITION": 0 }, "indices": 1, "mode": 4 }]
},
{
"extras": { "targetNames": [] },
"primitives": [
{
"attributes": { "NORMAL": 4, "POSITION": 2, "TANGENT": 3, "TEXCOORD_0": 5 },
"indices": 6,
"material": 0,
"mode": 4
}
]
}
],
"nodes": [
{
"children": [1, 2, 3, 4, 5, 6, 7, 9, 11, 13],
"extensions": {
"OMI_physics_body": { "motion": { "mass": 2000, "type": "dynamic" } },
"OMI_vehicle_body": { "linearDampeners": true, "pilotSeat": 13 }
},
"name": "HovercraftBody"
},
{
"extensions": {
"OMI_physics_body": { "collider": { "shape": 0 } }
},
"name": "HovercraftCollider"
},
{
"mesh": 1,
"name": "HovercraftMesh"
},
{
"extensions": {
"OMI_vehicle_hover_thruster": { "hoverThruster": 0 }
},
"name": "HoverThrusterBL",
"rotation": [-0.42660024762154, -0.33944433927536, -0.17670360207558, 0.81949108839035],
"translation": [1.2, -0.25, -2.1]
},
{
"extensions": {
"OMI_vehicle_hover_thruster": { "hoverThruster": 0 }
},
"name": "HoverThrusterBR",
"rotation": [-0.42660024762154, 0.33944433927536, 0.176703602075577, 0.81949108839035],
"translation": [-1.2, -0.25, -2.1]
},
{
"extensions": {
"OMI_vehicle_hover_thruster": { "hoverThruster": 0 }
},
"name": "HoverThrusterFL",
"rotation": [0.176703602075577, 0.81949108839035, 0.426600247621536, -0.33944433927536],
"translation": [1.2, -0.25, 2.1]
},
{
"extensions": {
"OMI_vehicle_hover_thruster": { "hoverThruster": 0 }
},
"name": "HoverThrusterFR",
"rotation": [-0.17670360207558, 0.81949108839035, 0.426600247621536, 0.33944433927536],
"translation": [-1.2, -0.25, 2.1]
},
{
"children": [8],
"extensions": {
"OMI_physics_body": { "trigger": { "nodes": [8] } },
"OMI_seat": { "back": [0, 0, -0.25], "foot": [0, -0.5, 0.25], "knee": [0, 0, 0.25] }
},
"name": "PassengerSeatFront",
"translation": [-0.5, 0.5, 0]
},
{
"extensions": { "OMI_physics_body": { "trigger": { "shape": 1 } } },
"name": "PassengerSeatFrontShape",
"translation": [-0.75, 0, 0.75]
},
{
"children": [10],
"extensions": {
"OMI_physics_body": { "trigger": { "nodes": [10] } },
"OMI_seat": { "back": [0, 0, -0.25], "foot": [0, -0.5, 0.25], "knee": [0, 0, 0.25] }
},
"name": "PassengerSeatRearLeft",
"translation": [0.5, 0.5, -1.125]
},
{
"extensions": { "OMI_physics_body": { "trigger": { "shape": 1 } } },
"name": "PassengerSeatRearLeftShape",
"translation": [0.75, 0, -0.75]
},
{
"children": [12],
"extensions": {
"OMI_physics_body": { "trigger": { "nodes": [12] } },
"OMI_seat": { "back": [0, 0, -0.25], "foot": [0, -0.5, 0.25], "knee": [0, 0, 0.25] }
},
"name": "PassengerSeatRearRight",
"translation": [-0.5, 0.5, -1.125]
},
{
"extensions": { "OMI_physics_body": { "trigger": { "shape": 1 } } },
"name": "PassengerSeatRearRightShape",
"translation": [-0.75, 0, -0.75]
},
{
"children": [14],
"extensions": {
"OMI_physics_body": { "trigger": { "nodes": [14] } },
"OMI_seat": { "back": [0, 0, -0.25], "foot": [0, -0.5, 0.25], "knee": [0, 0, 0.25] }
},
"name": "PilotSeat",
"translation": [0.5, 0.5, 0]
},
{
"extensions": { "OMI_physics_body": { "trigger": { "shape": 1 } } },
"name": "PilotSeatShape",
"translation": [0.75, 0, 0.75]
}
],
"scene": 0,
"scenes": [{ "nodes": [0] }]
}
36 changes: 36 additions & 0 deletions examples/omi_vehicle/gltf/hovercraft/hovercraft.gltf.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[remap]

importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cghtq66o4561u"
path="res://.godot/imported/hovercraft.gltf-1b610d389f3f60a42b4292e3da7099cc.scn"

[deps]

source_file="res://examples/omi_vehicle/gltf/hovercraft/hovercraft.gltf"
dest_files=["res://.godot/imported/hovercraft.gltf-1b610d389f3f60a42b4292e3da7099cc.scn"]

[params]

nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=1
gltf/embedded_image_handling=1
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
{
"asset": {
"generator": "Khronos glTF Blender I/O v4.1.62",
"version": "2.0"
},
"extensionsUsed": [
"GODOT_single_root",
"KHR_xmp_json_ld",
"OMI_physics_body",
"OMI_physics_shape"
],
"extensions": {
"KHR_xmp_json_ld": {
"packets": [
{
"@context": { "dc": "http://purl.org/dc/elements/1.1/" },
"@id": "",
"dc:creator": { "@list": ["Šimon Ustal", "aaronfranke"] },
"dc:description": "Low-Poly Space Station - 3December by Šimon Ustal with OMI_physics added by aaronfranke.",
"dc:format": "model/gltf+json",
"dc:rights": "CC-BY 4.0 Attribution",
"dc:source": "https://sketchfab.com/3d-models/low-poly-space-station-3december-df0259b2555d41fabed95c2fdcf23daa",
"dc:subject": { "@set": ["Space", "Space Station"] },
"dc:title": "Low-Poly Space Station - 3December",
"dc:type": { "@set": ["Spacecraft"] }
}
]
},
"OMI_physics_shape": {
"shapes": [
{
"type": "trimesh",
"trimesh": { "mesh": 0 }
}
]
}
},
"scene": 0,
"scenes": [
{
"name": "Scene",
"nodes": [0]
}
],
"nodes": [
{
"children": [1, 2],
"name": "SpaceStationBody",
"extensions": {
"OMI_physics_body": {
"motion": {
"type": "static"
}
}
}
},
{
"mesh": 0,
"name": "SpaceStationMesh"
},
{
"name": "SpaceStationTrimeshShape",
"extensions": {
"OMI_physics_body": {
"collider": {
"shape": 0
}
}
}
}
],
"materials": [
{
"doubleSided": true,
"name": "SpaceStationMaterial",
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0
},
"metallicFactor": 0,
"roughnessFactor": 0.5
}
}
],
"meshes": [
{
"name": "SpaceStationMeshData",
"primitives": [
{
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"indices": 3,
"material": 0
}
]
}
],
"textures": [
{
"sampler": 0,
"source": 0
}
],
"images": [
{
"mimeType": "image/png",
"name": "low_poly_space_station_tex",
"uri": "low_poly_space_station_tex.png"
}
],
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 14589,
"max": [25.112323760986328, 8.236722946166992, 9.480366706848145],
"min": [-25.112323760986328, -8.420032501220703, -9.480371475219727],
"type": "VEC3"
},
{
"bufferView": 1,
"componentType": 5126,
"count": 14589,
"type": "VEC3"
},
{
"bufferView": 2,
"componentType": 5126,
"count": 14589,
"type": "VEC2"
},
{
"bufferView": 3,
"componentType": 5123,
"count": 27450,
"type": "SCALAR"
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 175068,
"byteOffset": 0,
"target": 34962
},
{
"buffer": 0,
"byteLength": 175068,
"byteOffset": 175068,
"target": 34962
},
{
"buffer": 0,
"byteLength": 116712,
"byteOffset": 350136,
"target": 34962
},
{
"buffer": 0,
"byteLength": 54900,
"byteOffset": 466848,
"target": 34963
}
],
"samplers": [
{
"magFilter": 9729,
"minFilter": 9987
}
],
"buffers": [
{
"byteLength": 521748,
"uri": "low_poly_space_station.bin"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[remap]

importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cwnqk815f83vx"
path="res://.godot/imported/low_poly_space_station.gltf-5644cc612bf601b095c75926e879fe7a.scn"

[deps]

source_file="res://examples/omi_vehicle/gltf/low_poly_space_station/low_poly_space_station.gltf"
dest_files=["res://.godot/imported/low_poly_space_station.gltf-5644cc612bf601b095c75926e879fe7a.scn"]

[params]

nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=1
gltf/embedded_image_handling=1
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://cu5my2xwd0lr8"
path.s3tc="res://.godot/imported/low_poly_space_station_tex.png-478d9b28be0771ed32cfae361b80ccf5.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}

[deps]

source_file="res://examples/omi_vehicle/gltf/low_poly_space_station/low_poly_space_station_tex.png"
dest_files=["res://.godot/imported/low_poly_space_station_tex.png-478d9b28be0771ed32cfae361b80ccf5.s3tc.ctex"]

[params]

compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
66 changes: 66 additions & 0 deletions examples/omi_vehicle/gltf/minimal_vehicle_body.gltf
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"asset": {
"version": "2.0"
},
"extensionsUsed": ["GODOT_single_root", "OMI_physics_body", "OMI_physics_shape", "OMI_seat", "OMI_vehicle_body"],
"extensions": {
"OMI_physics_shape": {
"shapes": [
{
"type": "box",
"box": { "size": [0.65, 0.65, 0.65] }
},
{
"type": "box",
"box": { "size": [1.4, 1.5, 2.2] }
}
]
}
},
"nodes": [
{
"children": [1, 2],
"name": "MinimalVehicle",
"extensions": {
"OMI_physics_body": {
"motion": {
"type": "dynamic",
"mass": 1000
}
},
"OMI_vehicle_body": {
"pilotSeat": 1
}
}
},
{
"name": "SeatPilot",
"translation": [0.4, 0.4, 0.6],
"extensions": {
"OMI_physics_body": {
"trigger": {
"shape": 0
}
},
"OMI_seat": {
"back": [0, 0, -0.3],
"foot": [0, -0.5, 0.3],
"knee": [0, 0, 0.3]
}
}
},
{
"name": "MinimalCollider",
"translation": [0, 0.6, 0],
"extensions": {
"OMI_physics_body": {
"collider": {
"shape": 1
}
}
}
}
],
"scene": 0,
"scenes": [{ "nodes": [0] }]
}
36 changes: 36 additions & 0 deletions examples/omi_vehicle/gltf/minimal_vehicle_body.gltf.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[remap]

importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cmcp0bu4h76y"
path="res://.godot/imported/minimal_vehicle_body.gltf-a830fa40755298993a2f60915b818fe7.scn"

[deps]

source_file="res://examples/omi_vehicle/gltf/minimal_vehicle_body.gltf"
dest_files=["res://.godot/imported/minimal_vehicle_body.gltf-a830fa40755298993a2f60915b818fe7.scn"]

[params]

nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=1
gltf/embedded_image_handling=1
Binary file not shown.
261 changes: 261 additions & 0 deletions examples/omi_vehicle/gltf/rocket_ship/rocket_ship.gltf
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
{
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5126,
"count": 980,
"max": [1.97742998600006, 2.2833399772644, 5.4761004447937],
"min": [-1.97742998600006, -1.49512994289398, -3.59999990463257],
"normalized": false,
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 980,
"max": [0.997367382049561, 0.998750805854797, 0.981513559818268, 1],
"min": [-0.99995929002762, -0.99766159057617, -0.99944770336151, -1],
"normalized": false,
"type": "VEC4"
},
{
"bufferView": 2,
"byteOffset": 0,
"componentType": 5126,
"count": 980,
"max": [1, 0.999686658382416, 0.998062670230865],
"min": [-1, -0.99944996833801, -0.99985074996948],
"normalized": false,
"type": "VEC3"
},
{
"bufferView": 3,
"byteOffset": 0,
"componentType": 5126,
"count": 980,
"max": [0.988067448139191, 0.991119265556335],
"min": [0.00279240100644529, 0.0032501716632396],
"normalized": false,
"type": "VEC2"
},
{
"bufferView": 4,
"byteOffset": 0,
"componentType": 5123,
"count": 1311,
"max": [755],
"min": [0],
"normalized": false,
"type": "SCALAR"
},
{
"bufferView": 5,
"byteOffset": 0,
"componentType": 5126,
"count": 66,
"max": [0.223891004920006, 2.28324007987976, -0.56534498929977],
"min": [-0.22389100492001, 1.22107005119324, -3.59999990463257],
"normalized": false,
"type": "VEC3"
},
{
"bufferView": 6,
"byteOffset": 0,
"componentType": 5123,
"count": 66,
"max": [65],
"min": [0],
"normalized": false,
"type": "SCALAR"
},
{
"bufferView": 7,
"byteOffset": 0,
"componentType": 5126,
"count": 504,
"max": [1.49483001232147, 1.68090999126434, 4.35096979141235],
"min": [-1.49512994289398, -1.49512994289398, -3.08471989631653],
"normalized": false,
"type": "VEC3"
},
{
"bufferView": 8,
"byteOffset": 0,
"componentType": 5123,
"count": 504,
"max": [503],
"min": [0],
"normalized": false,
"type": "SCALAR"
}
],
"asset": {
"extensions": { "KHR_xmp_json_ld": { "packet": 0 } },
"generator": "Godot Engine v4.4.dev.custom_build@04f6321ecc2d24d08cb3368d87f483c6cc308105",
"version": "2.0"
},
"bufferViews": [
{ "buffer": 0, "byteLength": 11760, "byteOffset": 0, "byteStride": 12, "target": 34962 },
{ "buffer": 0, "byteLength": 15680, "byteOffset": 11760, "byteStride": 16, "target": 34962 },
{ "buffer": 0, "byteLength": 11760, "byteOffset": 27440, "byteStride": 12, "target": 34962 },
{ "buffer": 0, "byteLength": 7840, "byteOffset": 39200, "byteStride": 8, "target": 34962 },
{ "buffer": 0, "byteLength": 2622, "byteOffset": 47040, "target": 34963 },
{ "buffer": 0, "byteLength": 792, "byteOffset": 49664, "byteStride": 12, "target": 34962 },
{ "buffer": 0, "byteLength": 132, "byteOffset": 50456, "target": 34963 },
{ "buffer": 0, "byteLength": 6048, "byteOffset": 50588, "byteStride": 12, "target": 34962 },
{ "buffer": 0, "byteLength": 1008, "byteOffset": 56636, "target": 34963 }
],
"buffers": [{ "byteLength": 57644, "uri": "rocket_ship.bin" }],
"extensions": {
"KHR_xmp_json_ld": {
"packets": [
{
"@context": { "dc": "http://purl.org/dc/elements/1.1/" },
"@id": "",
"dc:creator": { "@list": ["Poly by Google", "aaronfranke"] },
"dc:description": "Low poly rpcket ship as a test file for the OMI_vehicle_body and OMI_vehicle_thruster extensions.",
"dc:format": "model/gltf+json",
"dc:rights": "CC-BY 3.0 Attribution",
"dc:source": "https://poly.pizza/m/4mPkOKdzAk-",
"dc:subject": { "@set": ["Space", "Spacecraft"] },
"dc:title": "Low poly rocket ship vehicle",
"dc:type": { "@set": ["Rocket", "Vehicle", "Demo", "Test"] }
}
]
},
"OMI_physics_shape": {
"shapes": [
{ "capsule": { "height": 1.5, "radius": 0.14 }, "type": "capsule" },
{ "convex": { "mesh": 1 }, "type": "convex" },
{ "convex": { "mesh": 2 }, "type": "convex" },
{ "box": { "size": [1, 2, 1] }, "type": "box" }
]
},
"OMI_vehicle_thruster": { "thrusters": [{ "maxForce": 50000, "maxGimbal": 0.125 }] }
},
"extensionsUsed": [
"GODOT_single_root",
"KHR_xmp_json_ld",
"OMI_physics_body",
"OMI_physics_shape",
"OMI_seat",
"OMI_vehicle_body",
"OMI_vehicle_thruster"
],
"images": [{ "uri": "rocket_ship_base_color.png" }],
"materials": [
{
"extensions": {},
"name": "RocketShipMaterial",
"pbrMetallicRoughness": {
"baseColorFactor": [1, 1, 1, 1],
"baseColorTexture": { "index": 0 },
"metallicFactor": 0,
"roughnessFactor": 1
}
}
],
"meshes": [
{
"extras": { "targetNames": [] },
"primitives": [
{
"attributes": { "NORMAL": 2, "POSITION": 0, "TANGENT": 1, "TEXCOORD_0": 3 },
"indices": 4,
"material": 0,
"mode": 4
}
]
},
{
"extras": { "targetNames": [] },
"primitives": [{ "attributes": { "POSITION": 5 }, "indices": 6, "mode": 4 }]
},
{
"extras": { "targetNames": [] },
"primitives": [{ "attributes": { "POSITION": 7 }, "indices": 8, "mode": 4 }]
}
],
"nodes": [
{
"children": [1, 2, 3, 4, 5, 6, 7, 8],
"extensions": {
"OMI_physics_body": {
"motion": {
"mass": 20000,
"type": "dynamic"
}
},
"OMI_vehicle_body": {
"gyroTorque": [10000, 10000, 10000],
"pilotSeat": 8,
"useThrottle": true
}
},
"name": "RocketShipBody"
},
{
"mesh": 0,
"name": "RocketShipMesh"
},
{
"extensions": { "OMI_physics_body": { "collider": { "shape": 0 } } },
"name": "RocketShipShapeAntenna",
"rotation": [0.70710676908493, 0, 0, 0.70710676908493],
"translation": [0, 0, 4.71999979019165]
},
{
"extensions": { "OMI_physics_body": { "collider": { "shape": 1 } } },
"name": "RocketShipShapeFinLeft",
"rotation": [0, 0, 0.866025447845459, -0.5]
},
{
"extensions": { "OMI_physics_body": { "collider": { "shape": 1 } } },
"name": "RocketShipShapeFinRight",
"rotation": [0, 0, 0.866025447845459, 0.5]
},
{
"extensions": { "OMI_physics_body": { "collider": { "shape": 1 } } },
"name": "RocketShipShapeFinTop"
},
{
"extensions": { "OMI_physics_body": { "collider": { "shape": 2 } } },
"name": "RocketShipShapeMainHull"
},
{
"extensions": {
"OMI_vehicle_thruster": {
"thruster": 0
}
},
"name": "RocketShipThruster",
"translation": [0, 0, -2.8]
},
{
"children": [9],
"extensions": {
"OMI_physics_body": {
"trigger": { "nodes": [9] }
},
"OMI_seat": {
"back": [0, 0, -0.25],
"foot": [0, -0.5, 0.25],
"knee": [0, 0, 0.25]
}
},
"name": "RocketShipPilotSeat3D",
"translation": [0, 0, 2]
},
{
"extensions": { "OMI_physics_body": { "trigger": { "shape": 3 } } },
"name": "RocketShipPilotSeatShape",
"translation": [0, 1, 0]
}
],
"samplers": [{ "magFilter": 9729, "minFilter": 9987, "wrapS": 10497, "wrapT": 10497 }],
"scene": 0,
"scenes": [{ "nodes": [0] }],
"textures": [{ "sampler": 0, "source": 0 }]
}
36 changes: 36 additions & 0 deletions examples/omi_vehicle/gltf/rocket_ship/rocket_ship.gltf.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[remap]

importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bm08gpxby4g2w"
path="res://.godot/imported/rocket_ship.gltf-452bf888b81e4fa7692a48c70c6f7341.scn"

[deps]

source_file="res://examples/omi_vehicle/gltf/rocket_ship/rocket_ship.gltf"
dest_files=["res://.godot/imported/rocket_ship.gltf-452bf888b81e4fa7692a48c70c6f7341.scn"]

[params]

nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=1
gltf/embedded_image_handling=1
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://ce85e76coy7sb"
path.bptc="res://.godot/imported/rocket_ship_base_color.png-b936b93c129e22b66a0ef75266f3c02c.bptc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}

[deps]

source_file="res://examples/omi_vehicle/gltf/rocket_ship/rocket_ship_base_color.png"
dest_files=["res://.godot/imported/rocket_ship_base_color.png-b936b93c129e22b66a0ef75266f3c02c.bptc.ctex"]

[params]

compress/mode=2
compress/high_quality=true
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
633 changes: 633 additions & 0 deletions examples/omi_vehicle/gltf/simple_car.gltf

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions examples/omi_vehicle/gltf/simple_car.gltf.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[remap]

importer="scene"
importer_version=1
type="PackedScene"
uid="uid://db0a284ld0msa"
path="res://.godot/imported/simple_car.gltf-da60391d183f2cd5780bf0dae45af6ef.scn"

[deps]

source_file="res://examples/omi_vehicle/gltf/simple_car.gltf"
dest_files=["res://.godot/imported/simple_car.gltf-da60391d183f2cd5780bf0dae45af6ef.scn"]

[params]

nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=1
gltf/embedded_image_handling=1
Binary file not shown.
304 changes: 304 additions & 0 deletions examples/omi_vehicle/gltf/simple_spaceship/simple_spaceship.gltf
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
{
"asset": {
"extensions": { "KHR_xmp_json_ld": { "packet": 0 } },
"generator": "Khronos glTF Blender I/O v4.1.62",
"version": "2.0"
},
"extensionsUsed": [
"GODOT_single_root",
"KHR_xmp_json_ld",
"OMI_physics_body",
"OMI_physics_shape",
"OMI_seat",
"OMI_vehicle_body",
"OMI_vehicle_thruster"
],
"extensions": {
"KHR_xmp_json_ld": {
"packets": [
{
"@context": { "dc": "http://purl.org/dc/elements/1.1/" },
"@id": "",
"dc:creator": { "@list": ["chrisonciuconcepts", "aaronfranke"] },
"dc:description": "Low poly space ship as a test file for the OMI_vehicle_body and OMI_vehicle_thruster extensions.",
"dc:format": "model/gltf+json",
"dc:rights": "CC-BY 4.0 Attribution",
"dc:source": "https://sketchfab.com/3d-models/low-poly-space-ship-587941c9c11742c6b82dfb99e7b210b9",
"dc:subject": { "@set": ["Space", "Spacecraft"] },
"dc:title": "Low poly space ship vehicle",
"dc:type": { "@set": ["Vehicle", "Demo", "Test"] }
}
]
},
"OMI_physics_shape": {
"shapes": [
{
"type": "convex",
"convex": { "mesh": 0 }
},
{
"type": "box",
"box": { "size": [0.75, 0.75, 1] }
}
]
},
"OMI_vehicle_thruster": {
"thrusters": [
{
"maxForce": 1000,
"maxGimbal": 0.25
},
{
"maxForce": 2000,
"maxGimbal": 0.2
},
{
"maxForce": 5000,
"maxGimbal": 0.25
}
]
}
},
"scene": 0,
"scenes": [
{
"name": "SimpleSpaceshipScene",
"nodes": [0]
}
],
"nodes": [
{
"children": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
"name": "SimpleSpaceshipBody",
"extensions": {
"OMI_physics_body": {
"motion": {
"type": "dynamic",
"mass": 2000
}
},
"OMI_vehicle_body": {
"gyroTorque": [3000, 3000, 3000],
"linearDampeners": true,
"pilotSeat": 6
}
}
},
{
"name": "SimpleSpaceshipBottomThruster",
"rotation": [-0.7071068286895752, 0, 0, 0.7071068286895752],
"translation": [0, -0.3, 0],
"extensions": {
"OMI_vehicle_thruster": {
"thruster": 0
}
}
},
{
"name": "SimpleSpaceshipConvexHull",
"extensions": {
"OMI_physics_body": {
"collider": {
"shape": 0
}
}
}
},
{
"name": "SimpleSpaceshipMainThrusterLeft",
"translation": [0.5333333, 0, -1.25],
"extensions": {
"OMI_vehicle_thruster": {
"thruster": 2
}
}
},
{
"name": "SimpleSpaceshipMainThrusterRight",
"translation": [-0.5333333, 0, -1.25],
"extensions": {
"OMI_vehicle_thruster": {
"thruster": 2
}
}
},
{
"mesh": 0,
"name": "SimpleSpaceshipMesh"
},
{
"name": "SimpleSpaceshipPilotSeat",
"translation": [0, 0.2, 1.25],
"extensions": {
"OMI_physics_body": {
"trigger": {
"shape": 1
}
},
"OMI_seat": {
"back": [0, -0.2, -0.4],
"foot": [0, -0.4, 0.65],
"knee": [0, -0.2, 0.15]
}
}
},
{
"name": "SimpleSpaceshipReverseThrusterLeft",
"rotation": [0, -1, 0, 0],
"translation": [0.5333333, 0, 0.4166666865348816],
"extensions": {
"OMI_vehicle_thruster": {
"thruster": 1
}
}
},
{
"name": "SimpleSpaceshipReverseThrusterRight",
"rotation": [0, -1, 0, 0],
"translation": [-0.5333333, 0, 0.4166666865348816],
"extensions": {
"OMI_vehicle_thruster": {
"thruster": 1
}
}
},
{
"name": "SimpleSpaceshipSideThrusterLeft",
"rotation": [0, -0.7071068286895752, 0, 0.7071068286895752],
"translation": [0.8, 0, 0],
"extensions": {
"OMI_vehicle_thruster": {
"thruster": 0
}
}
},
{
"name": "SimpleSpaceshipSideThrusterRight",
"rotation": [0, 0.7071068286895752, 0, 0.7071068286895752],
"translation": [-0.8, 0, 0],
"extensions": {
"OMI_vehicle_thruster": {
"thruster": 0
}
}
},
{
"name": "SimpleSpaceshipTopThruster",
"rotation": [0.7071068286895752, 0, 0, 0.7071068286895752],
"translation": [0, 0.5, 0],
"extensions": {
"OMI_vehicle_thruster": {
"thruster": 0
}
}
}
],
"materials": [
{
"doubleSided": true,
"name": "SimpleSpaceshipMaterial",
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0
},
"metallicFactor": 0.75,
"roughnessFactor": 0.25
}
}
],
"meshes": [
{
"name": "SimpleSpaceshipMeshData",
"primitives": [
{
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"indices": 3,
"material": 0
}
]
}
],
"textures": [
{
"sampler": 0,
"source": 0
}
],
"images": [
{
"mimeType": "image/png",
"name": "simple_spaceship_color",
"uri": "simple_spaceship_color.png"
}
],
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 832,
"max": [2.4141058921813965, 0.8135462999343872, 2.1287012100219727],
"min": [-2.4141058921813965, -0.5360468626022339, -1.7438040971755981],
"type": "VEC3"
},
{
"bufferView": 1,
"componentType": 5126,
"count": 832,
"type": "VEC3"
},
{
"bufferView": 2,
"componentType": 5126,
"count": 832,
"type": "VEC2"
},
{
"bufferView": 3,
"componentType": 5123,
"count": 1380,
"type": "SCALAR"
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 9984,
"byteOffset": 0,
"target": 34962
},
{
"buffer": 0,
"byteLength": 9984,
"byteOffset": 9984,
"target": 34962
},
{
"buffer": 0,
"byteLength": 6656,
"byteOffset": 19968,
"target": 34962
},
{
"buffer": 0,
"byteLength": 2760,
"byteOffset": 26624,
"target": 34963
}
],
"samplers": [
{
"magFilter": 9729,
"minFilter": 9987
}
],
"buffers": [
{
"byteLength": 29384,
"uri": "simple_spaceship.bin"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[remap]

importer="scene"
importer_version=1
type="PackedScene"
uid="uid://csy3u0tn8hups"
path="res://.godot/imported/simple_spaceship.gltf-0603610c418ea88c2f1710f46d823f04.scn"

[deps]

source_file="res://examples/omi_vehicle/gltf/simple_spaceship/simple_spaceship.gltf"
dest_files=["res://.godot/imported/simple_spaceship.gltf-0603610c418ea88c2f1710f46d823f04.scn"]

[params]

nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=1
gltf/embedded_image_handling=1
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://by3117qq3uiti"
path.bptc="res://.godot/imported/simple_spaceship_color.png-b645e70b3a21f92a4c1e1ed25f6502ce.bptc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}

[deps]

source_file="res://examples/omi_vehicle/gltf/simple_spaceship/simple_spaceship_color.png"
dest_files=["res://.godot/imported/simple_spaceship_color.png-b645e70b3a21f92a4c1e1ed25f6502ce.bptc.ctex"]

[params]

compress/mode=2
compress/high_quality=true
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://wld0kk8xdfaw"
path.bptc="res://.godot/imported/cement_lossy.webp-efb141956745e54f6be00a43db87067f.bptc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}

[deps]

source_file="res://examples/omi_vehicle/gltf/truck_town/textures/cement_lossy.webp"
dest_files=["res://.godot/imported/cement_lossy.webp-efb141956745e54f6be00a43db87067f.bptc.ctex"]

[params]

compress/mode=2
compress/high_quality=true
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://33ijh7puyvie"
path.bptc="res://.godot/imported/grass_lossy.webp-87a86b2669068ef207c34cebbc794e42.bptc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}

[deps]

source_file="res://examples/omi_vehicle/gltf/truck_town/textures/grass_lossy.webp"
dest_files=["res://.godot/imported/grass_lossy.webp-87a86b2669068ef207c34cebbc794e42.bptc.ctex"]

[params]

compress/mode=2
compress/high_quality=true
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Loading