diff --git a/addons/omi_extensions/vehicle/gltf_vehicle_hover_thruster.gd b/addons/omi_extensions/vehicle/gltf_vehicle_hover_thruster.gd index 94c0174..ae268dc 100644 --- a/addons/omi_extensions/vehicle/gltf_vehicle_hover_thruster.gd +++ b/addons/omi_extensions/vehicle/gltf_vehicle_hover_thruster.gd @@ -3,57 +3,97 @@ class_name GLTFVehicleHoverThruster extends Resource -## The ratio of the maximum hover energy the hover thruster is currently using for propulsion. +# Gimbal +## The maximum angle the hover thruster can gimbal or rotate in radians. +@export_custom(PROPERTY_HINT_NONE, "suffix:rad") +var max_gimbal_radians: float = 0.0 +## Optionally, you may also want to allow the gimbal to be adjusted based on linear input. +## For example, if the user wants to go forward, and the thruster points downward, +## we can gimbal the thruster slightly backward to help thrust forward. @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) +var linear_gimbal_adjust_ratio: float = 0.5 +## The speed at which the gimbal angle changes, in radians per second. If negative, the angle changes instantly. +@export_custom(PROPERTY_HINT_NONE, "suffix:rad/s") +var gimbal_radians_per_second: float = 1.0 +## The ratio of the maximum gimbal angles the hover thruster is targeting to be rotated to. The vector length may not be longer than 1.0. +@export var target_gimbal_ratio := Vector2(0.0, 0.0) + +# Hover Thrust ## 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 +## The speed at which the hover energy changes, in Newtons-meters per second. If negative, the force changes instantly. +@export_custom(PROPERTY_HINT_NONE, "suffix:N\u22C5m/s (kg\u22C5m\u00B2/s\u00B3)") +var hover_energy_change_per_second: float = -1.0 +## The ratio of the maximum hover energy the hover thruster is targeting for propulsion. +@export_range(0.0, 1.0, 0.01) +var target_hover_ratio: 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 + # Gimbal + ret.max_gimbal_radians = thruster_node.max_gimbal_radians + ret.linear_gimbal_adjust_ratio = thruster_node.linear_gimbal_adjust_ratio + ret.gimbal_radians_per_second = thruster_node.gimbal_radians_per_second + ret.target_gimbal_ratio = thruster_node.target_gimbal_ratio + # Hover Thrust ret.max_hover_energy = thruster_node.max_hover_energy - ret.max_gimbal = thruster_node.max_gimbal + ret.hover_energy_change_per_second = thruster_node.hover_energy_change_per_second + ret.target_hover_ratio = thruster_node.target_hover_ratio 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 + # Gimbal + thruster_node.max_gimbal_radians = max_gimbal_radians + thruster_node.linear_gimbal_adjust_ratio = linear_gimbal_adjust_ratio + thruster_node.gimbal_radians_per_second = gimbal_radians_per_second + thruster_node.target_gimbal_ratio = target_gimbal_ratio + # Hover Thrust thruster_node.max_hover_energy = max_hover_energy - thruster_node.max_gimbal = max_gimbal + thruster_node.hover_energy_change_per_second = hover_energy_change_per_second + thruster_node.target_hover_ratio = target_hover_ratio 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"] + # Gimbal + if dict.has("maxGimbal"): + ret.max_gimbal_radians = dict["maxGimbal"] + if dict.has("linearGimbalAdjustRatio"): + ret.linear_gimbal_adjust_ratio = dict["linearGimbalAdjustRatio"] + if dict.has("gimbalChangeRate"): + ret.gimbal_radians_per_second = dict["gimbalChangeRate"] + if dict.has("targetGimbalRatio"): + ret.target_gimbal_ratio = dict["targetGimbalRatio"] + # Hover Thrust if dict.has("maxHoverEnergy"): ret.max_hover_energy = dict["maxHoverEnergy"] - if dict.has("maxGimbal"): - ret.max_gimbal = dict["maxGimbal"] + if dict.has("hoverEnergyChangeRate"): + ret.hover_energy_change_per_second = dict["hoverEnergyChangeRate"] + if dict.has("targetHoverRatio"): + ret.target_hover_ratio = dict["targetHoverRatio"] return ret func to_dictionary() -> Dictionary: var ret: Dictionary = {} + # Alphabetical order when converting to Dictionary. + if gimbal_radians_per_second != 1.0: + ret["gimbalChangeRate"] = gimbal_radians_per_second + if hover_energy_change_per_second != -1.0: + ret["hoverEnergyChangeRate"] = hover_energy_change_per_second + if linear_gimbal_adjust_ratio != 0.5: + ret["linearGimbalAdjustRatio"] = linear_gimbal_adjust_ratio + if max_gimbal_radians != 0.0: + ret["maxGimbal"] = max_gimbal_radians + # Always include maxHoverEnergy since it is a required property for hover thrusters. 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 + if target_gimbal_ratio != Vector2.ZERO: + ret["targetGimbalRatio"] = target_gimbal_ratio + if target_hover_ratio != 0.0: + ret["targetHoverRatio"] = target_hover_ratio return ret diff --git a/addons/omi_extensions/vehicle/gltf_vehicle_thruster.gd b/addons/omi_extensions/vehicle/gltf_vehicle_thruster.gd index 2fbaad9..8c31f74 100644 --- a/addons/omi_extensions/vehicle/gltf_vehicle_thruster.gd +++ b/addons/omi_extensions/vehicle/gltf_vehicle_thruster.gd @@ -3,57 +3,97 @@ class_name GLTFVehicleThruster extends Resource -## The ratio of the maximum thrust force the thruster is currently using for propulsion. +# Gimbal +## The maximum angle the thruster can gimbal or rotate in radians. +@export_custom(PROPERTY_HINT_NONE, "suffix:rad") +var max_gimbal_radians: float = 0.0 +## Optionally, you may also want to allow the gimbal to be adjusted based on linear input. +## For example, if the user wants to go forward, and the thruster points downward, +## we can gimbal the thruster slightly backward to help thrust forward. @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) +var linear_gimbal_adjust_ratio: float = 0.0 +## The speed at which the gimbal angle changes, in radians per second. If negative, the angle changes instantly. +@export_custom(PROPERTY_HINT_NONE, "suffix:rad/s") +var gimbal_radians_per_second: float = 1.0 +## The ratio of the maximum gimbal angles the thruster is targeting to be rotated to. The vector length may not be longer than 1.0. +@export var target_gimbal_ratio := Vector2(0.0, 0.0) + +# Thrust Force ## 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 +## The speed at which the thruster force changes, in Newtons per second. If negative, the force changes instantly. +@export_custom(PROPERTY_HINT_NONE, "suffix:N/s (kg\u22C5m/s\u00B3)") +var force_change_per_second: float = -1.0 +## The ratio of the maximum thrust force the thruster is targeting for propulsion. +@export_range(0.0, 1.0, 0.01) +var target_force_ratio: 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 + # Gimbal + ret.max_gimbal_radians = thruster_node.max_gimbal_radians + ret.linear_gimbal_adjust_ratio = thruster_node.linear_gimbal_adjust_ratio + ret.gimbal_radians_per_second = thruster_node.gimbal_radians_per_second + ret.target_gimbal_ratio = thruster_node.target_gimbal_ratio + # Thrust Force ret.max_force = thruster_node.max_force - ret.max_gimbal = thruster_node.max_gimbal + ret.force_change_per_second = thruster_node.force_change_per_second + ret.target_force_ratio = thruster_node.target_force_ratio 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 + # Gimbal + thruster_node.max_gimbal_radians = max_gimbal_radians + thruster_node.linear_gimbal_adjust_ratio = linear_gimbal_adjust_ratio + thruster_node.gimbal_radians_per_second = gimbal_radians_per_second + thruster_node.target_gimbal_ratio = target_gimbal_ratio + # Thrust Force thruster_node.max_force = max_force - thruster_node.max_gimbal = max_gimbal + thruster_node.force_change_per_second = force_change_per_second + thruster_node.target_force_ratio = target_force_ratio 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"] + # Gimbal + if dict.has("maxGimbal"): + ret.max_gimbal_radians = dict["maxGimbal"] + if dict.has("linearGimbalAdjustRatio"): + ret.linear_gimbal_adjust_ratio = dict["linearGimbalAdjustRatio"] + if dict.has("gimbalChangeRate"): + ret.gimbal_radians_per_second = dict["gimbalChangeRate"] + if dict.has("targetGimbalRatio"): + ret.target_gimbal_ratio = dict["targetGimbalRatio"] + # Thrust Force if dict.has("maxForce"): ret.max_force = dict["maxForce"] - if dict.has("maxGimbal"): - ret.max_gimbal = dict["maxGimbal"] + if dict.has("forceChangeRate"): + ret.force_change_per_second = dict["forceChangeRate"] + if dict.has("targetForceRatio"): + ret.target_force_ratio = dict["targetForceRatio"] return ret func to_dictionary() -> Dictionary: var ret: Dictionary = {} + # Alphabetical order when converting to Dictionary. + if force_change_per_second != -1.0: + ret["forceChangeRate"] = force_change_per_second + if gimbal_radians_per_second != 1.0: + ret["gimbalChangeRate"] = gimbal_radians_per_second + if linear_gimbal_adjust_ratio != 0.0: + ret["linearGimbalAdjustRatio"] = linear_gimbal_adjust_ratio + # Always include maxForce since it is a required property for thrusters. 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 + if max_gimbal_radians != 0.0: + ret["maxGimbal"] = max_gimbal_radians + if target_force_ratio != 0.0: + ret["targetForceRatio"] = target_force_ratio + if target_gimbal_ratio != Vector2.ZERO: + ret["targetGimbalRatio"] = target_gimbal_ratio return ret diff --git a/addons/omi_extensions/vehicle/gltf_vehicle_wheel.gd b/addons/omi_extensions/vehicle/gltf_vehicle_wheel.gd index 2f40ae6..45e3517 100644 --- a/addons/omi_extensions/vehicle/gltf_vehicle_wheel.gd +++ b/addons/omi_extensions/vehicle/gltf_vehicle_wheel.gd @@ -2,19 +2,31 @@ class_name GLTFVehicleWheel extends Resource - -## The ratio of the maximum force the wheel is using for propulsion. -@export_range(-1.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(-1.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 +# Steering ## 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 speed at which the wheel steering angle changes, in radians per second. +@export var steering_radians_per_second: float = 1.0 +## The ratio of the maximum steering angle the wheel is targeting to be rotated to. +@export_range(-1.0, 1.0, 0.01) +var target_steering_ratio: float = 0.0 + +# Force +## The maximum force in Newtons (kg⋅m/s²) that the wheel can provide. +@export_custom(PROPERTY_HINT_NONE, "suffix:N (kg\u22C5m/s\u00B2)") +var max_propulsion_force: float = 0.0 +## The maximum braking force in Newtons (kg⋅m/s²) that the wheel can provide. If negative or not specified, use propulsion force for braking. +@export_custom(PROPERTY_HINT_NONE, "suffix:N (kg\u22C5m/s\u00B2)") +var braking_force: float = -1.0 +## The speed at which the wheel propulsion force changes, in Newtons per second. If negative, the force changes instantly. +@export_custom(PROPERTY_HINT_NONE, "suffix:N/s (kg\u22C5m/s\u00B3)") +var propulsion_force_change_per_second: float = -1.0 +## The ratio of the maximum force the wheel is targeting for propulsion. +@export_range(-1.0, 1.0, 0.01) +var target_propulsion_force_ratio: float = 0.0 + +# Physics Material ## 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 @@ -23,35 +35,48 @@ var max_steering_angle: float = 0.0 @export var physics_material_friction: float = 1.0 ## TODO: This is currently unimplemented, pending glTF physics material support. @export var physics_material: PhysicsMaterial = null + +# Size ## 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 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 + +# Suspension ## 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)") +@export_custom(PROPERTY_HINT_NONE, "suffix:N\u22C5s/m (kg/s)") 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)") +@export_custom(PROPERTY_HINT_NONE, "suffix:N\u22C5s/m (kg/s)") 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)") +@export_custom(PROPERTY_HINT_NONE, "suffix:N/m (kg/s\u00B2)") 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 + if wheel_node is PilotedVehicleWheel3D: + # Steering + ret.max_steering_angle = wheel_node.max_steering_angle + ret.steering_radians_per_second = wheel_node.steering_radians_per_second + ret.target_steering_ratio = wheel_node.target_steering_ratio + # Force + ret.max_propulsion_force = wheel_node.max_propulsion_force + ret.braking_force = wheel_node.braking_force + ret.propulsion_force_change_per_second = wheel_node.propulsion_force_change_per_second + ret.target_propulsion_force_ratio = wheel_node.target_propulsion_force_ratio + # Physics Material ret.physics_material_friction = wheel_node.wheel_friction_slip + # Size ret.radius = wheel_node.wheel_radius + # Suspension # 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 @@ -61,14 +86,22 @@ static func from_node(wheel_node: VehicleWheel3D) -> GLTFVehicleWheel: return ret -func to_node() -> VehicleWheel3D: +func to_node() -> PilotedVehicleWheel3D: 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 + # Steering wheel_node.max_steering_angle = max_steering_angle + wheel_node.steering_radians_per_second = steering_radians_per_second + wheel_node.target_steering_ratio = target_steering_ratio + # Force + wheel_node.max_propulsion_force = max_propulsion_force + wheel_node.braking_force = braking_force + wheel_node.propulsion_force_change_per_second = propulsion_force_change_per_second + wheel_node.target_propulsion_force_ratio = target_propulsion_force_ratio + # Physics Material wheel_node.wheel_friction_slip = physics_material_friction + # Size wheel_node.wheel_radius = radius + # Suspension # 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 @@ -81,18 +114,20 @@ func to_node() -> VehicleWheel3D: 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("brakingForce"): + ret.braking_force = dict["brakingForce"] + if dict.has("maxPropulsionForce"): + ret.max_propulsion_force = dict["maxPropulsionForce"] if dict.has("maxSteeringAngle"): ret.max_steering_angle = dict["maxSteeringAngle"] if dict.has("physicsMaterial"): ret.physics_material_index = dict["physicsMaterial"] + if dict.has("propulsionForceChangeRate"): + ret.propulsion_force_change_per_second = dict["propulsionForceChangeRate"] if dict.has("radius"): ret.radius = dict["radius"] + if dict.has("steeringChangeRate"): + ret.steering_radians_per_second = dict["steeringChangeRate"] if dict.has("suspensionDampingCompression"): ret.suspension_damping_compression = dict["suspensionDampingCompression"] if dict.has("suspensionDampingRebound"): @@ -101,6 +136,10 @@ static func from_dictionary(dict: Dictionary) -> GLTFVehicleWheel: ret.suspension_stiffness = dict["suspensionStiffness"] if dict.has("suspensionTravel"): ret.suspension_travel = dict["suspensionTravel"] + if dict.has("targetPropulsionForceRatio"): + ret.target_propulsion_force_ratio = dict["targetPropulsionForceRatio"] + if dict.has("targetSteeringRatio"): + ret.target_steering_ratio = dict["targetSteeringRatio"] if dict.has("width"): ret.width = dict["width"] return ret @@ -108,18 +147,21 @@ static func from_dictionary(dict: Dictionary) -> GLTFVehicleWheel: 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 + # Alphabetical order when converting to Dictionary. + if braking_force != -1.0: + ret["brakingForce"] = braking_force + if max_propulsion_force != 0.0: + ret["maxPropulsionForce"] = max_propulsion_force if max_steering_angle != 0.0: ret["maxSteeringAngle"] = max_steering_angle if physics_material_index != -1: ret["physicsMaterial"] = physics_material_index + if propulsion_force_change_per_second != -1.0: + ret["propulsionForceChangeRate"] = propulsion_force_change_per_second if radius != 0.25: ret["radius"] = radius + if steering_radians_per_second != 1.0: + ret["steeringChangeRate"] = steering_radians_per_second if suspension_damping_compression != 500.0: ret["suspensionDampingCompression"] = suspension_damping_compression if suspension_damping_rebound != 500.0: @@ -128,6 +170,10 @@ func to_dictionary() -> Dictionary: ret["suspensionStiffness"] = suspension_stiffness if suspension_travel != 0.25: ret["suspensionTravel"] = suspension_travel + if target_propulsion_force_ratio != 0.0: + ret["targetPropulsionForceRatio"] = target_propulsion_force_ratio + if target_steering_ratio != 0.0: + ret["targetSteeringRatio"] = target_steering_ratio if width != 0.125: ret["width"] = width return ret diff --git a/addons/omi_extensions/vehicle/nodes/pilot_seat_3d.gd b/addons/omi_extensions/vehicle/nodes/pilot_seat_3d.gd index 4be743a..da3d1d9 100644 --- a/addons/omi_extensions/vehicle/nodes/pilot_seat_3d.gd +++ b/addons/omi_extensions/vehicle/nodes/pilot_seat_3d.gd @@ -12,12 +12,12 @@ enum ControlScheme { CAR, ## Uses WASDRF for linear movement, QE roll, mouse pitch/yaw, or IJKLUO rotation. SIX_DOF, + ## Like SIX_DOF but flattens the horizontal WASD input, good for hovercrafts. + SIX_DOF_HORIZONTAL, ## 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 @@ -60,6 +60,8 @@ func _physics_process(delta: float) -> void: 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_angular_dampeners"): + piloted_vehicle_node.angular_dampeners = not piloted_vehicle_node.angular_dampeners 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 @@ -94,6 +96,11 @@ func exit_pilot_seat() -> void: piloted_vehicle_node.linear_activation = Vector3.ZERO +func does_pilot_seat_want_to_keep_upright() -> bool: + var actual_control_scheme: ControlScheme = _get_actual_control_scheme() + return actual_control_scheme == ControlScheme.CAR or actual_control_scheme == ControlScheme.SIX_DOF_HORIZONTAL + + func _get_actual_control_scheme() -> ControlScheme: if control_scheme != ControlScheme.AUTO: return control_scheme @@ -102,7 +109,7 @@ func _get_actual_control_scheme() -> ControlScheme: 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_HORIZONTAL return ControlScheme.SIX_DOF @@ -157,7 +164,7 @@ func _get_linear_input(actual_control_scheme: ControlScheme) -> Vector3: Input.get_axis(&"rotate_pitch_down", &"rotate_pitch_up"), Input.get_axis(&"throttle_decrease", &"throttle_increase") ) - ControlScheme.HORIZONTAL_SIX_DOF: + ControlScheme.SIX_DOF_HORIZONTAL: 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( diff --git a/addons/omi_extensions/vehicle/nodes/piloted_vehicle_body_3d.gd b/addons/omi_extensions/vehicle/nodes/piloted_vehicle_body_3d.gd index f28477e..f86f4ad 100644 --- a/addons/omi_extensions/vehicle/nodes/piloted_vehicle_body_3d.gd +++ b/addons/omi_extensions/vehicle/nodes/piloted_vehicle_body_3d.gd @@ -16,6 +16,7 @@ const INERTIA_DAMPENER_RATE_LINEAR: float = 1.0 @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. +## If throttle is used, activation is a ratio of this speed if positive, or a ratio of thrust power if negative. @export var max_speed: float = -1.0 ## The node to use as the pilot seat / driver seat. @export var pilot_seat_node: PilotSeat3D = null: @@ -27,13 +28,13 @@ var gyroscope_torque := Vector3.ZERO @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 = true -## 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. +## If true, the vehicle should use a throttle for linear movement. Pilot seat input "sticks around" when let go. +## 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: @@ -46,17 +47,16 @@ func _physics_process(delta: float) -> void: return var actual_angular: Vector3 = angular_activation var actual_linear: Vector3 = linear_activation - var global_to_local_rot: Quaternion = quaternion.inverse() + var global_to_local_rot: Quaternion = global_transform.basis.get_rotation_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() + var local_up_direction: Vector3 = -_get_local_gravity_direction() # 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 + actual_linear = (target_velocity - local_linear_vel) / max_speed elif linear_dampeners: if is_zero_approx(actual_linear.x): actual_linear.x = local_linear_vel.x * -INERTIA_DAMPENER_RATE_LINEAR @@ -65,13 +65,14 @@ func _physics_process(delta: float) -> void: 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 + if linear_activation == Vector3.ZERO: + actual_linear += local_up_direction * 0.75 + else: + actual_linear += local_up_direction # 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) + wheel.set_from_vehicle_input(actual_angular, actual_linear) # Determine the actual angular values to use based on activation and dampeners. if angular_dampeners: if is_zero_approx(angular_activation.x): @@ -81,13 +82,15 @@ func _physics_process(delta: float) -> void: 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() + if pilot_seat_node != null and pilot_seat_node.does_pilot_seat_want_to_keep_upright(): + var to_up: Quaternion = _get_rotation_to_upright(local_up_direction) 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 + # Clamp the actual inputs to the range of -1.0 to 1.0 per each axis (can be longer than 1.0 overall). + # The individual parts (thrusters etc) may clamp these further as needed (such as to a length of 1.0). 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 @@ -96,8 +99,7 @@ func _physics_process(delta: float) -> void: 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) + thruster.set_from_vehicle_input(actual_angular, actual_linear) func has_hover_thrusters() -> bool: @@ -111,18 +113,16 @@ func has_wheels() -> bool: 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() +func _get_rotation_to_upright(up_direction: Vector3) -> Quaternion: + var y = up_direction if y == Vector3.ZERO: return Quaternion.IDENTITY var x = y.cross(Vector3.BACK) @@ -133,4 +133,5 @@ func _get_rotation_to_upright() -> Quaternion: func _get_local_gravity_direction() -> Vector3: - return (quaternion.inverse() * get_gravity()).normalized() + var global_to_local_rot: Quaternion = global_transform.basis.get_rotation_quaternion().inverse() + return global_to_local_rot * get_gravity().normalized() diff --git a/addons/omi_extensions/vehicle/nodes/piloted_vehicle_wheel_3d.gd b/addons/omi_extensions/vehicle/nodes/piloted_vehicle_wheel_3d.gd index 994e974..f4e84f3 100644 --- a/addons/omi_extensions/vehicle/nodes/piloted_vehicle_wheel_3d.gd +++ b/addons/omi_extensions/vehicle/nodes/piloted_vehicle_wheel_3d.gd @@ -3,18 +3,34 @@ 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 +## If false, the wheel will not steer or apply forces. +@export var active: bool = true + +# Steering ## The maximum angle in radians that the wheel can steer. @export_range(0.0, 90.0, 0.1, "radians") var max_steering_angle: float = 0.0 +## The speed at which the wheel steering angle changes, in radians per second. +@export var steering_radians_per_second: float = 1.0 +## The ratio of the maximum steering angle the wheel is targeting to be rotated to. +@export_range(0.0, 1.0, 0.01) +var target_steering_ratio: float = 0.0 + +# Force +## The maximum force in Newtons (kg⋅m/s²) that the wheel can provide. +@export_custom(PROPERTY_HINT_NONE, "suffix:N (kg\u22C5m/s\u00B2)") +var max_propulsion_force: float = 0.0 +## The braking force in Newtons (kg⋅m/s²) that the wheel applies when the vehicle is trying to stop. +## If negative, the wheel uses propulsion force as braking instead. +@export_custom(PROPERTY_HINT_NONE, "suffix:N (kg\u22C5m/s\u00B2)") +var braking_force: float = 0.0 +## The speed at which the wheel propulsion force changes, in Newtons per second. +## If negative, the force changes instantly. +@export_custom(PROPERTY_HINT_NONE, "suffix:N/s (kg\u22C5m/s\u00B3)") +var propulsion_force_change_per_second: float = -1.0 +## The ratio of the maximum force the wheel is targeting for propulsion. +@export_range(0.0, 1.0, 0.01) +var target_propulsion_force_ratio: float = 0.0 var _negate_steering: bool = false var _parent_body: RigidBody3D = null @@ -27,25 +43,50 @@ func _enter_tree() -> void: func _physics_process(delta: float) -> void: - steering = move_toward(steering, current_steering_ratio * max_steering_angle, delta) + if not active: + brake = 0.0 + engine_force = 0.0 + return + # Move the wheel's steering angle towards the target. + var steer_target: float = target_steering_ratio * max_steering_angle + if steering_radians_per_second < 0.0: + steering = steer_target + else: + steering = move_toward(steering, steer_target, steering_radians_per_second * delta) + # Figure out the target force the wheel is moving towards. + var force_target: float = 0.0 + var should_wheels_brake: bool = false + if _parent_body != null: + force_target = target_propulsion_force_ratio * max_propulsion_force + should_wheels_brake = (_parent_body.angular_dampeners + and _parent_body.linear_dampeners + and _parent_body.angular_activation == Vector3.ZERO + and _parent_body.linear_activation == Vector3.ZERO) + if should_wheels_brake: + brake = absf(force_target if braking_force < 0.0 else braking_force) + force_target = 0.0 # Ramp down the engine force to zero while braking. + else: + brake = 0.0 + # Move the wheel's engine force towards the target. + if propulsion_force_change_per_second < 0.0: + engine_force = force_target + else: + engine_force = move_toward(engine_force, force_target, propulsion_force_change_per_second * delta) -func set_steering_from_vehicle_angular_input(angular_input: Vector3) -> void: +## Sets the wheel's steering and thrust based on vehicle input. +func set_from_vehicle_input(angular_input: Vector3, 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. - var steer_ratio: float = angular_input.y * angular_input.y - if (angular_input.y < 0) != _negate_steering: + # Set steering, prioritizing linear input when it's stronger, otherwise using angular input. + var source: float = linear_input.x if (abs(linear_input.x) * 2.0 > abs(angular_input.y)) else angular_input.y + var steer_ratio: float = source * source + if (source < 0.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 + target_steering_ratio = clampf(steer_ratio, -1.0, 1.0) + # Set thrust from vehicle linear input. + target_propulsion_force_ratio = clampf(linear_input.z * cos(steering), -1.0, 1.0) func _get_parent_body() -> RigidBody3D: diff --git a/addons/omi_extensions/vehicle/nodes/vehicle_hover_thruster_3d.gd b/addons/omi_extensions/vehicle/nodes/vehicle_hover_thruster_3d.gd index d3e60ea..dcdb8fc 100644 --- a/addons/omi_extensions/vehicle/nodes/vehicle_hover_thruster_3d.gd +++ b/addons/omi_extensions/vehicle/nodes/vehicle_hover_thruster_3d.gd @@ -9,17 +9,38 @@ extends RayCast3D # 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. +# Gimbal +## The maximum angle the hover thruster can gimbal or rotate in radians. +## Note: The initial gimbal must be set before adding the node to the tree. +@export_custom(PROPERTY_HINT_NONE, "suffix:rad") +var max_gimbal_radians: float = 0.0 +## Optionally, you may also want to allow the gimbal to be adjusted based on linear input. +## For example, if the user wants to go forward, and the thruster points downward, +## we can gimbal the thruster slightly backward to help thrust forward. +## The default is 0.0 for thrusters and 0.5 for hover thrusters. @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) +var linear_gimbal_adjust_ratio: float = 0.5 +## The speed at which the gimbal angle changes, in radians per second. If negative, the angle changes instantly. +@export_custom(PROPERTY_HINT_NONE, "suffix:rad/s") +var gimbal_radians_per_second: float = 1.0 +## The ratio of the maximum gimbal angles the hover thruster is targeting to be rotated to. The vector length may not be longer than 1.0. +## Note: The initial gimbal must be set before adding the node to the tree. +@export var target_gimbal_ratio := Vector2(0.0, 0.0) +## The current gimbal angles in radians, tending towards target_gimbal_ratio * max_gimbal_radians. +## If gimbal_radians_per_second is negative, this will equal the target value. +var _current_gimbal_radians := Vector2(0.0, 0.0) + +# Hover Thrust ## 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)") +@export_custom(PROPERTY_HINT_NONE, "suffix:N\u22C5m (kg\u22C5m\u00B2/s\u00B2)") 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 +## The speed at which the hover energy changes, in Newtons-meters per second. If negative, the force changes instantly. +@export_custom(PROPERTY_HINT_NONE, "suffix:N\u22C5m/s") +var hover_energy_change_per_second: float = -1.0 +## The ratio of the maximum hover energy the hover thruster is targeting for propulsion. +@export_range(0.0, 1.0, 0.01) +var target_hover_ratio: float = 0.0 +var _current_hover_energy: float = 0.0 var _parent_body: RigidBody3D = null var _particles_node: GPUParticles3D = null @@ -28,7 +49,7 @@ 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 _body_to_rest_quaternion := Quaternion.IDENTITY var _negate_gimbal: bool = true @@ -42,6 +63,7 @@ func _enter_tree() -> void: func _ready() -> void: + _make_debug_mesh() for child in get_children(): if child is GPUParticles3D: _particles_node = child @@ -52,15 +74,23 @@ 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 + # Move the current gimbal radians towards the target value. + var target_gimbal_radians: Vector2 = target_gimbal_ratio.limit_length() * max_gimbal_radians + if gimbal_radians_per_second < 0.0: + _current_gimbal_radians = target_gimbal_radians + else: + var gimbal_change: float = gimbal_radians_per_second * delta + _current_gimbal_radians = _current_gimbal_radians.move_toward(target_gimbal_radians, gimbal_change) + quaternion = _rest_quaternion * _get_gimbal_rotation_quaternion() + # Set force and particles to zero if inactive. 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() + var actual_hover: float = clampf(target_hover_ratio, 0.0, 1.0) if _particles_node: - _particles_node.amount_ratio = current_hover_ratio + _particles_node.amount_ratio = target_hover_ratio if _parent_body == null: return var hit_distance: float = maxf(get_collision_point().distance_to(global_position), 0.1) @@ -73,51 +103,55 @@ func _physics_process(delta: float) -> void: func recalculate_transforms() -> void: if _parent_body == null: - printerr("Error: VehicleThruster3D must be a descendant of a RigidBody3D node (preferably PilotedVehicleBody3D).") + printerr("Error: VehicleHoverThruster3D must be a descendant of a RigidBody3D node (preferably PilotedVehicleBody3D).") return + # Get the transform from the parent to the body. _parent_transform_to_body = Transform3D.IDENTITY var parent: Node = get_parent() - while parent != _parent_body: + while parent != _parent_body and parent is Node3D: _parent_transform_to_body = parent.transform * _parent_transform_to_body parent = parent.get_parent() + # Get the rotation of the rest orientation of the part's gimbal. _rest_quaternion = quaternion * _get_gimbal_rotation_quaternion().inverse() + # Use both of those to determine the rest quaternion to body and its inverse. _parent_quaternion_to_body = _parent_transform_to_body.basis.get_rotation_quaternion() _rest_quaternion_to_body = _parent_quaternion_to_body * _rest_quaternion + _body_to_rest_quaternion = _rest_quaternion_to_body.inverse() + # Where is this part relative to the center of mass? We may need to negate the gimbal. 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() + if max_gimbal_radians == 0.0: + target_gimbal_ratio = Vector2.ZERO + return + # Set the gimbal based on the local angular input. + var local_angular_input: Vector3 = _body_to_rest_quaternion * angular_input + target_gimbal_ratio = Vector2(-local_angular_input.x, -local_angular_input.y).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) + if linear_input == Vector3.ZERO or linear_gimbal_adjust_ratio == 0.0: + return + var current_rot: Quaternion = _rest_quaternion_to_body * _get_gimbal_rotation_quaternion() + var local_linear_input: Vector3 = current_rot.inverse() * linear_input + var linear_gimbal_adjust: Vector2 = Vector2(-local_linear_input.y, local_linear_input.x).limit_length() * linear_gimbal_adjust_ratio + target_gimbal_ratio = (target_gimbal_ratio + linear_gimbal_adjust).limit_length() + # Set the hover ratio based on angular torque and linear input. + var thrust_direction: Vector3 = current_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: Vector3 = (_parent_transform_to_body * 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) + target_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): + if _current_gimbal_radians.is_zero_approx(): 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) + var angle_mag: float = _current_gimbal_radians.length() + var sin_norm: float = sin(angle_mag / 2.0) / angle_mag + var cos_half: float = cos(angle_mag / 2.0) + return Quaternion(_current_gimbal_radians.x * sin_norm, _current_gimbal_radians.y * sin_norm, 0.0, cos_half) func _get_parent_body() -> RigidBody3D: @@ -133,7 +167,9 @@ func _get_parent_body() -> RigidBody3D: 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 + var capsule := CapsuleMesh.new() + capsule.radius = 0.05 + mi.basis = Basis(Vector3.RIGHT, Vector3.FORWARD, Vector3.UP) + mi.position = Vector3(0.0, 0.0, -1.0) + mi.mesh = capsule add_child(mi) diff --git a/addons/omi_extensions/vehicle/nodes/vehicle_thruster_3d.gd b/addons/omi_extensions/vehicle/nodes/vehicle_thruster_3d.gd index 1c695d0..ee23337 100644 --- a/addons/omi_extensions/vehicle/nodes/vehicle_thruster_3d.gd +++ b/addons/omi_extensions/vehicle/nodes/vehicle_thruster_3d.gd @@ -6,18 +6,40 @@ 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 var active: bool = true + +# Gimbal +## The maximum angle the thruster can gimbal or rotate in radians. +## Note: The initial gimbal must be set before adding the node to the tree. +@export_custom(PROPERTY_HINT_NONE, "suffix:rad") +var max_gimbal_radians: float = 0.0 +## Optionally, you may also want to allow the gimbal to be adjusted based on linear input. +## For example, if the user wants to go forward, and the thruster points downward, +## we can gimbal the thruster slightly backward to help thrust forward. +## The default is 0.0 for thrusters and 0.5 for hover thrusters. @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) +var linear_gimbal_adjust_ratio: float = 0.0 +## The speed at which the gimbal angle changes, in radians per second. If negative, the angle changes instantly. +@export_custom(PROPERTY_HINT_NONE, "suffix:rad/s") +var gimbal_radians_per_second: float = 1.0 +## The ratio of the maximum gimbal angles the thruster is targeting to be rotated to. The vector length may not be longer than 1.0. +## Note: The initial gimbal must be set before adding the node to the tree. +@export var target_gimbal_ratio := Vector2(0.0, 0.0) +## The current gimbal angles in radians, tending towards target_gimbal_ratio * max_gimbal_radians. +## If gimbal_radians_per_second is negative, this will equal the target value. +var _current_gimbal_radians := Vector2(0.0, 0.0) + +# Thrust Force ## 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)") +@export_custom(PROPERTY_HINT_NONE, "suffix:N (kg\u22C5m/s\u00B2)") 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 +## The speed at which the thruster force changes, in Newtons per second. If negative, the force changes instantly. +@export_custom(PROPERTY_HINT_NONE, "suffix:N/s (kg\u22C5m/s\u00B3)") +var force_change_per_second: float = -1.0 +## The ratio of the maximum thrust force the thruster is targeting for propulsion. +@export_range(0.0, 1.0, 0.01) +var target_force_ratio: float = 0.0 +var _current_force: float = 0.0 var _parent_body: RigidBody3D = null var _particles_node: GPUParticles3D = null @@ -25,6 +47,7 @@ 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 _body_to_rest_quaternion := Quaternion.IDENTITY var _negate_gimbal: bool = true @@ -42,57 +65,84 @@ func _ready() -> void: func _physics_process(delta: float) -> void: - if _parent_body == null or not enabled: - if _particles_node: + # Move the current gimbal radians towards the target value. + var target_gimbal_radians: Vector2 = target_gimbal_ratio.limit_length() * max_gimbal_radians + if gimbal_radians_per_second < 0.0: + _current_gimbal_radians = target_gimbal_radians + else: + var gimbal_change: float = gimbal_radians_per_second * delta + _current_gimbal_radians = _current_gimbal_radians.move_toward(target_gimbal_radians, gimbal_change) + quaternion = _rest_quaternion * _get_gimbal_rotation_quaternion() + # Set force and particles to zero if inactive. + if _parent_body == null or max_force == 0.0 or not active: + _current_force = 0.0 + if _particles_node != null: _particles_node.amount_ratio = 0.0 return - quaternion = _rest_quaternion * _get_gimbal_rotation_quaternion() - if _particles_node: - _particles_node.amount_ratio = current_force_ratio + # Move the current thrust force towards the target value. + var target_force: float = target_force_ratio * max_force + if force_change_per_second < 0.0: + _current_force = target_force + else: + var force_change: float = force_change_per_second * delta + _current_force = move_toward(_current_force, target_force, force_change) + if _particles_node != null: + _particles_node.amount_ratio = absf(_current_force / max_force) 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) + _parent_body.apply_force(force_dir * _current_force, force_pos) func recalculate_transforms() -> void: if _parent_body == null: printerr("Error: VehicleThruster3D must be a descendant of a RigidBody3D node (preferably PilotedVehicleBody3D).") return + # Get the transform from the parent to the body. _parent_transform_to_body = Transform3D.IDENTITY var parent: Node = get_parent() - while parent != _parent_body: + while parent != _parent_body and parent is Node3D: _parent_transform_to_body = parent.transform * _parent_transform_to_body parent = parent.get_parent() + # Get the rotation of the rest orientation of the part's gimbal. _rest_quaternion = quaternion * _get_gimbal_rotation_quaternion().inverse() + # Use both of those to determine the rest quaternion to body and its inverse. _parent_quaternion_to_body = _parent_transform_to_body.basis.get_rotation_quaternion() - _body_to_rest_quaternion = (_parent_quaternion_to_body * _rest_quaternion).inverse() + _rest_quaternion_to_body = _parent_quaternion_to_body * _rest_quaternion + _body_to_rest_quaternion = _rest_quaternion_to_body.inverse() + # Where is this part relative to the center of mass? We may need to negate the gimbal. 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 set_from_vehicle_input(angular_input: Vector3, linear_input: Vector3) -> void: + if max_gimbal_radians == 0.0: + target_gimbal_ratio = Vector2.ZERO + return + # Set the gimbal based on the local angular input. + var local_angular_input: Vector3 = _body_to_rest_quaternion * angular_input + target_gimbal_ratio = Vector2(-local_angular_input.x, -local_angular_input.y).limit_length() + # Adjust the gimbal based on linear input (optional but significantly improves handling). + if linear_input == Vector3.ZERO or linear_gimbal_adjust_ratio == 0.0: + return + var current_rot: Quaternion = _rest_quaternion_to_body * _get_gimbal_rotation_quaternion() + var local_linear_input: Vector3 = current_rot.inverse() * linear_input + var linear_gimbal_adjust: Vector2 = Vector2(-local_linear_input.y, local_linear_input.x).limit_length() * linear_gimbal_adjust_ratio + target_gimbal_ratio = (target_gimbal_ratio + linear_gimbal_adjust).limit_length() + # Set thrust from vehicle linear input. + var thrust_direction: Vector3 = current_rot * Vector3(0.0, 0.0, 1.0) + target_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): + if _current_gimbal_radians.is_zero_approx(): 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) + var angle_mag: float = _current_gimbal_radians.length() + var sin_norm: float = sin(angle_mag / 2.0) / angle_mag + var cos_half: float = cos(angle_mag / 2.0) + return Quaternion(_current_gimbal_radians.x * sin_norm, _current_gimbal_radians.y * sin_norm, 0.0, cos_half) func _get_parent_body() -> RigidBody3D: diff --git a/examples/omi_vehicle/gltf/hovercraft/hovercraft.gltf b/examples/omi_vehicle/gltf/hovercraft/hovercraft.gltf index 2d6232d..9c9ad2d 100644 --- a/examples/omi_vehicle/gltf/hovercraft/hovercraft.gltf +++ b/examples/omi_vehicle/gltf/hovercraft/hovercraft.gltf @@ -176,7 +176,7 @@ "extensions": { "OMI_vehicle_hover_thruster": { "hoverThruster": 0 } }, - "name": "HoverThrusterBL", + "name": "HoverThrusterRearLeft", "rotation": [-0.42660024762154, -0.33944433927536, -0.17670360207558, 0.81949108839035], "translation": [1.2, -0.25, -2.1] }, @@ -184,7 +184,7 @@ "extensions": { "OMI_vehicle_hover_thruster": { "hoverThruster": 0 } }, - "name": "HoverThrusterBR", + "name": "HoverThrusterRearRight", "rotation": [-0.42660024762154, 0.33944433927536, 0.176703602075577, 0.81949108839035], "translation": [-1.2, -0.25, -2.1] }, @@ -192,7 +192,7 @@ "extensions": { "OMI_vehicle_hover_thruster": { "hoverThruster": 0 } }, - "name": "HoverThrusterFL", + "name": "HoverThrusterFrontLeft", "rotation": [0.176703602075577, 0.81949108839035, 0.426600247621536, -0.33944433927536], "translation": [1.2, -0.25, 2.1] }, @@ -200,7 +200,7 @@ "extensions": { "OMI_vehicle_hover_thruster": { "hoverThruster": 0 } }, - "name": "HoverThrusterFR", + "name": "HoverThrusterFrontRight", "rotation": [-0.17670360207558, 0.81949108839035, 0.426600247621536, 0.33944433927536], "translation": [-1.2, -0.25, 2.1] }, diff --git a/examples/omi_vehicle/gltf/simple_car.gltf b/examples/omi_vehicle/gltf/simple_car.gltf index 19696c8..515c66f 100644 --- a/examples/omi_vehicle/gltf/simple_car.gltf +++ b/examples/omi_vehicle/gltf/simple_car.gltf @@ -37,7 +37,7 @@ }, { "radius": 0.35, - "maxForce": 2000 + "maxPropulsionForce": 2000 } ] } diff --git a/examples/omi_vehicle/source/hovercraft.tscn b/examples/omi_vehicle/source/hovercraft.tscn index b472824..6721bcf 100644 --- a/examples/omi_vehicle/source/hovercraft.tscn +++ b/examples/omi_vehicle/source/hovercraft.tscn @@ -1,9 +1,9 @@ [gd_scene load_steps=10 format=4 uid="uid://nkwkk4uwowrr"] -[ext_resource type="Script" uid="uid://vr67n3dj53we" path="res://addons/omi_extensions/vehicle/nodes/piloted_vehicle_body_3d.gd" id="1_umllk"] -[ext_resource type="Script" uid="uid://bnru3duyqum34" path="res://addons/omi_extensions/vehicle/nodes/vehicle_hover_thruster_3d.gd" id="2_ky2er"] -[ext_resource type="Script" uid="uid://bheenmqo6ebnf" path="res://addons/omi_extensions/vehicle/nodes/pilot_seat_3d.gd" id="3_g7xpw"] -[ext_resource type="Script" uid="uid://28prhymdgbeh" path="res://addons/omi_extensions/seat/seat_3d.gd" id="4_bgiin"] +[ext_resource type="Script" uid="uid://digm3y42mikqe" path="res://addons/omi_extensions/vehicle/nodes/piloted_vehicle_body_3d.gd" id="1_umllk"] +[ext_resource type="Script" uid="uid://bjl5ks1qrha5b" path="res://addons/omi_extensions/vehicle/nodes/vehicle_hover_thruster_3d.gd" id="2_ky2er"] +[ext_resource type="Script" uid="uid://ci1jce7fwkldv" path="res://addons/omi_extensions/vehicle/nodes/pilot_seat_3d.gd" id="3_g7xpw"] +[ext_resource type="Script" uid="uid://de2fci4ti5sbv" path="res://addons/omi_extensions/seat/seat_3d.gd" id="4_bgiin"] [sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_qvg40"] points = PackedVector3Array(1.98311, -0.213089, -2.2914, 1.8911, -0.516431, -1.8172, 1.47297, -0.819774, -1.57556, 0.694942, -0.516431, -2.66917, 1.11307, -0.213089, -2.91081, 1.6434, -0.0992711, -2.73554, 1.06493, 1.58753, -2.17455, 1.87091, 0.114041, -2.11272, 1.86547, 0.114041, 2.12448, 1.96835, -0.213089, 2.30826, 1.90041, -0.516431, 1.83008, 1.49471, -0.819774, 1.56802, 0.942636, -0.933335, -1.75083, 0.60293, -0.819774, -2.19497, -0.69533, -0.516431, -2.66917, -1.11346, -0.213089, -2.91081, 0.548577, 1.58753, -2.33848, 0.931765, 1.58753, -2.29594, 0.868483, 1.63766, -2.26134, 0.931765, 1.64866, -2.14392, 0.819177, 1.51182, -0.134229, 1.58788, 0.400247, 1.90269, 1.49315, 0.238345, 2.5567, 1.60691, -0.0992711, 2.73481, 1.06842, -0.213089, 2.88399, 0.595165, -0.819774, 2.14319, 0.956224, -0.933335, 1.71664, -0.943024, -0.933335, -1.75083, -0.603318, -0.819774, -2.19497, -1.64379, -0.0992711, -2.73554, -1.47335, -0.819774, -1.57556, -0.932154, 1.58753, -2.29594, -0.548965, 1.58753, -2.33848, 0.542365, 1.65147, -2.26134, 0.548577, 1.67551, -2.05543, 0.661553, 1.54277, -0.134229, 0.678248, 1.05271, 1.72515, 0.820342, 0.994911, 1.76939, 0.944577, 0.114041, 2.71269, -1.06881, -0.213089, 2.88399, 0.663106, -0.516431, 2.62137, -0.595553, -0.819774, 2.14319, -0.956612, -0.933335, 1.71664, -1.4951, -0.819774, 1.56802, -1.98311, -0.213089, -2.2914, -1.06532, 1.58753, -2.17455, -1.89148, -0.516431, -1.8172, -0.868871, 1.63766, -2.26134, -0.542753, 1.65147, -2.26134, -0.548965, 1.67551, -2.05543, 0, 1.67935, -1.92951, -0.661942, 1.54277, -0.134229, -0.678636, 1.05271, 1.72515, -0.944965, 0.114041, 2.71269, -0.663495, -0.516431, 2.62137, -1.49354, 0.238345, 2.5567, -1.6073, -0.0992711, 2.73481, -1.96874, -0.213089, 2.30826, -1.9008, -0.516431, 1.83008, -1.86586, 0.114041, 2.12448, -1.8713, 0.114041, -2.11272, -0.819565, 1.51182, -0.134229, -0.932154, 1.64866, -2.14392, -0.82073, 0.994911, 1.76939, -1.58827, 0.400247, 1.90269) @@ -55,7 +55,6 @@ mass = 2000.0 center_of_mass_mode = 1 script = ExtResource("1_umllk") pilot_seat_node = NodePath("PilotSeat") -linear_dampeners = true [node name="HovercraftCollider" type="CollisionShape3D" parent="."] shape = SubResource("ConvexPolygonShape3D_qvg40") @@ -68,29 +67,29 @@ skeleton = NodePath("") transform = Transform3D(0.707107, 0.579228, -0.40558, 0, 0.573576, 0.819152, 0.707107, -0.579228, 0.40558, 1.2, -0.25, -2.1) target_position = Vector3(0, 0, -1000) script = ExtResource("2_ky2er") +max_gimbal_radians = 0.25 max_hover_energy = 30000.0 -max_gimbal = 0.25 [node name="HoverThrusterBR" type="RayCast3D" parent="."] transform = Transform3D(0.707107, -0.579228, 0.40558, 0, 0.573576, 0.819152, -0.707107, -0.579228, 0.40558, -1.2, -0.25, -2.1) target_position = Vector3(0, 0, -1000) script = ExtResource("2_ky2er") +max_gimbal_radians = 0.25 max_hover_energy = 30000.0 -max_gimbal = 0.25 [node name="HoverThrusterFL" type="RayCast3D" parent="."] transform = Transform3D(-0.707107, 0.579228, -0.40558, 0, 0.573576, 0.819152, 0.707107, 0.579228, -0.40558, 1.2, -0.25, 2.1) target_position = Vector3(0, 0, -1000) script = ExtResource("2_ky2er") +max_gimbal_radians = 0.25 max_hover_energy = 30000.0 -max_gimbal = 0.25 [node name="HoverThrusterFR" type="RayCast3D" parent="."] transform = Transform3D(-0.707107, -0.579228, 0.40558, 0, 0.573576, 0.819152, -0.707107, 0.579228, -0.40558, -1.2, -0.25, 2.1) target_position = Vector3(0, 0, -1000) script = ExtResource("2_ky2er") +max_gimbal_radians = 0.25 max_hover_energy = 30000.0 -max_gimbal = 0.25 [node name="PassengerSeatFront" type="Area3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 0.5, 0) diff --git a/examples/omi_vehicle/source/rocket_ship.tscn b/examples/omi_vehicle/source/rocket_ship.tscn index a5037fc..71594b4 100644 --- a/examples/omi_vehicle/source/rocket_ship.tscn +++ b/examples/omi_vehicle/source/rocket_ship.tscn @@ -1,9 +1,9 @@ [gd_scene load_steps=12 format=4 uid="uid://c60hy356x1vfn"] -[ext_resource type="Script" uid="uid://vr67n3dj53we" path="res://addons/omi_extensions/vehicle/nodes/piloted_vehicle_body_3d.gd" id="1_yd6sw"] +[ext_resource type="Script" uid="uid://digm3y42mikqe" path="res://addons/omi_extensions/vehicle/nodes/piloted_vehicle_body_3d.gd" id="1_yd6sw"] [ext_resource type="Texture2D" uid="uid://msnktntaysgn" path="res://examples/omi_vehicle/source/rocket_ship_RocketShip_BaseColor.png" id="2_ep1pm"] -[ext_resource type="Script" uid="uid://c1vsgtq6nx77k" path="res://addons/omi_extensions/vehicle/nodes/vehicle_thruster_3d.gd" id="3_ijsqw"] -[ext_resource type="Script" uid="uid://bheenmqo6ebnf" path="res://addons/omi_extensions/vehicle/nodes/pilot_seat_3d.gd" id="4_h40gw"] +[ext_resource type="Script" uid="uid://8gmos7qh4x83" path="res://addons/omi_extensions/vehicle/nodes/vehicle_thruster_3d.gd" id="3_ijsqw"] +[ext_resource type="Script" uid="uid://ci1jce7fwkldv" path="res://addons/omi_extensions/vehicle/nodes/pilot_seat_3d.gd" id="4_h40gw"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cqufy"] resource_name = "RocketShipMaterial" @@ -89,7 +89,7 @@ shape = SubResource("ConvexPolygonShape3D_6llia") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -2.8) script = ExtResource("3_ijsqw") max_force = 50000.0 -max_gimbal = 0.125 +max_gimbal_radians = 0.125 [node name="RocketShipPilotSeat3D" type="Area3D" parent="." node_paths=PackedStringArray("piloted_vehicle_node")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2) diff --git a/examples/omi_vehicle/test/car_in_truck_town/walking_player/walking_player.tscn b/examples/omi_vehicle/test/car_in_truck_town/walking_player/walking_player.tscn index cb4acad..bfaf8a7 100644 --- a/examples/omi_vehicle/test/car_in_truck_town/walking_player/walking_player.tscn +++ b/examples/omi_vehicle/test/car_in_truck_town/walking_player/walking_player.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=5 format=3 uid="uid://cbbund73l3qmj"] -[ext_resource type="Script" uid="uid://bn5opvkiheo0s" path="res://examples/omi_vehicle/test/car_in_truck_town/walking_player/walking_player.gd" id="1_32edx"] -[ext_resource type="Script" uid="uid://df1uwcybhk00r" path="res://examples/omi_vehicle/test/space_station/astronaut_player/seat_interact_ray.gd" id="2_7jn46"] -[ext_resource type="Script" uid="uid://br06st1iho5ku" path="res://examples/omi_vehicle/test/space_station/astronaut_player/camera.gd" id="3_v8ljx"] +[ext_resource type="Script" uid="uid://tutxb5waci6w" path="res://examples/omi_vehicle/test/car_in_truck_town/walking_player/walking_player.gd" id="1_32edx"] +[ext_resource type="Script" uid="uid://bbhcafvomy844" path="res://examples/omi_vehicle/test/space_station/astronaut_player/seat_interact_ray.gd" id="2_7jn46"] +[ext_resource type="Script" uid="uid://nk4xuki5wn7" path="res://examples/omi_vehicle/test/space_station/astronaut_player/camera.gd" id="3_v8ljx"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_wjd78"] radius = 0.375 @@ -27,6 +27,7 @@ script = ExtResource("2_7jn46") player_body = NodePath("../..") [node name="CameraHolder" type="Node3D" parent="CollisionShape3D/SeatInteractRay"] +process_mode = 3 script = ExtResource("3_v8ljx") [node name="Camera3D" type="Camera3D" parent="CollisionShape3D/SeatInteractRay/CameraHolder"] diff --git a/examples/omi_vehicle/test/space_station/astronaut_player/camera.gd b/examples/omi_vehicle/test/space_station/astronaut_player/camera.gd index 5bbdd51..a2e4117 100644 --- a/examples/omi_vehicle/test/space_station/astronaut_player/camera.gd +++ b/examples/omi_vehicle/test/space_station/astronaut_player/camera.gd @@ -1,6 +1,11 @@ extends Node3D +func _input(some_input_event: InputEvent) -> void: + if some_input_event.is_action_pressed(&"ui_cancel"): + get_tree().quit() + + func set_first_person() -> void: position = Vector3.ZERO rotation = Vector3.ZERO diff --git a/project.godot b/project.godot index 01c65b3..1acf7c0 100644 --- a/project.godot +++ b/project.godot @@ -11,6 +11,7 @@ config_version=5 [application] config/name="OMI extensions for Godot" +run/main_scene="uid://mvug8a7m8825" config/features=PackedStringArray("4.4") config/icon="res://icon.svg" @@ -32,6 +33,7 @@ enabled=PackedStringArray("res://addons/gltf_khr_xmp_copyright/plugin.cfg", "res interact={ "deadzone": 0.5, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":84,"key_label":0,"unicode":116,"location":0,"echo":false,"script":null) +, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null) ] } jump={ @@ -125,6 +127,11 @@ throttle_zero={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":88,"key_label":0,"unicode":120,"location":0,"echo":false,"script":null) ] } +toggle_angular_dampeners={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":59,"key_label":0,"unicode":59,"location":0,"echo":false,"script":null) +] +} toggle_linear_dampeners={ "deadzone": 0.5, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":90,"key_label":0,"unicode":122,"location":0,"echo":false,"script":null)