├── .gitignore ├── addons └── godot_aerodynamic_physics │ ├── plugin.gd.uid │ ├── utils │ ├── pid.gd.uid │ ├── math_utils.gd.uid │ ├── node_utils.gd.uid │ ├── plugin_utils.gd.uid │ ├── point_3d │ │ ├── point_3d.gd.uid │ │ ├── point_3d.gdshader.uid │ │ ├── point_3d.gdshader │ │ └── point_3d.gd │ ├── transform_utils.gd.uid │ ├── vector_3d │ │ ├── vector_3d.gd.uid │ │ ├── vector_3d.gdshader.uid │ │ ├── vector_3d.gdshader │ │ └── vector_3d.gd │ ├── transform_utils.gd │ ├── plugin_utils.gd │ ├── pid.gd │ ├── node_utils.gd │ └── math_utils.gd │ ├── demo │ ├── thruster.gd.uid │ ├── thruster.gd │ ├── demo_scene.tscn │ └── demo_plane.tscn │ ├── core │ ├── aero_body_3d.gd.uid │ ├── flight_assist.gd.uid │ ├── singletons │ │ ├── aero_units.gd.uid │ │ └── aero_units.gd │ ├── spatial_gizmo │ │ ├── aero_surface_gizmo.gd.uid │ │ └── aero_surface_gizmo.gd │ ├── aero_influencer_3d │ │ ├── aero_influencer_3d.gd.uid │ │ ├── aero_mover_3d │ │ │ ├── aero_mover_3d.gd.uid │ │ │ ├── aero_rotor_hinge_3d │ │ │ │ ├── aero_rotor_hinge_3d.gd.uid │ │ │ │ └── aero_rotor_hinge_3d.gd │ │ │ └── aero_mover_3d.gd │ │ ├── aero_rotator_3d │ │ │ ├── aero_propeller_3d.gd.uid │ │ │ ├── aero_cyclic_propeller_3d.gd.uid │ │ │ ├── aero_variable_propeller_3d.gd.uid │ │ │ ├── aero_variable_propeller_3d.gd │ │ │ ├── aero_cyclic_propeller_3d.gd │ │ │ └── aero_propeller_3d.gd │ │ ├── aero_surface_3d │ │ │ ├── aero_surface_3d.gd.uid │ │ │ ├── aero_surface_config.gd.uid │ │ │ ├── manual_aero_surface_3d │ │ │ │ ├── manual_aero_surface_3d.gd.uid │ │ │ │ ├── manual_aero_surface_config.gd.uid │ │ │ │ ├── manual_aero_surface_3d.gd │ │ │ │ └── manual_aero_surface_config.gd │ │ │ ├── aero_surface_config.gd │ │ │ └── aero_surface_3d.gd │ │ └── aero_influencer_3d.gd │ ├── control_configs │ │ ├── aero_control_config.gd.uid │ │ ├── aero_influencer_control_config.gd.uid │ │ ├── aero_influencer_control_axis_config.gd.uid │ │ ├── aero_influencer_control_axis_config.gd │ │ ├── aero_influencer_control_config.gd │ │ └── aero_control_config.gd │ ├── resources │ │ ├── default_buffet_aoa_curve.tres │ │ ├── default_sweep_drag_multiplier.tres │ │ ├── default_drag_at_mach_curve.tres │ │ ├── default_drag_aoa_curve.tres │ │ ├── pids │ │ │ ├── pitch.tres │ │ │ ├── roll_pid.tres │ │ │ ├── speed_pid.tres │ │ │ ├── yaw_pid.tres │ │ │ ├── altitude_pid.tres │ │ │ ├── heading_pid.tres │ │ │ ├── bank_angle_pid.tres │ │ │ ├── direction_roll_pid.tres │ │ │ ├── direction_yaw_pid.tres │ │ │ └── direction_pitch_pid.tres │ │ ├── default_lift_aoa_curve.tres │ │ └── flight_assist │ │ │ └── flight_assist.tres │ ├── flight_assist.gd │ └── aero_body_3d.gd │ ├── components │ ├── AeroControlComponent.gd.uid │ ├── aero_thruster_component │ │ ├── AeroJetThrusterComponent.gd.uid │ │ ├── AeroThrusterComponent.gd.uid │ │ ├── AeroThrusterComponent.gd │ │ └── AeroJetThrusterComponent.gd │ └── AeroControlComponent.gd │ ├── icon.png │ ├── plugin.cfg │ ├── icons │ ├── AeroBody3D.svg │ ├── node.svg │ ├── node2d.svg │ ├── node3d.svg │ ├── AeroSurface3D.svg │ ├── object.svg │ ├── AeroInfluencer3D.svg │ ├── AeroPropeller3D.svg │ ├── JetThrusterComponent.svg │ ├── node.svg.import │ ├── node2d.svg.import │ ├── node3d.svg.import │ ├── object.svg.import │ ├── AeroBody3D.svg.import │ ├── AeroSurface3D.svg.import │ ├── AeroPropeller3D.svg.import │ ├── AeroInfluencer3D.svg.import │ └── JetThrusterComponent.svg.import │ ├── icon.png.import │ ├── README.md │ ├── LICENSE │ └── plugin.gd ├── .gitattributes ├── pillars.tscn ├── icon.svg ├── icon.svg.import ├── project.godot ├── main.tscn └── aero_body_3d.tscn /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/plugin.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dw0u7odhnudld 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/pid.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b4xqlunpiyivk 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/demo/thruster.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cvivybph2vvwa 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_body_3d.gd.uid: -------------------------------------------------------------------------------- 1 | uid://3iji867c81k5 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/flight_assist.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cdp8dc63ytxx1 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/math_utils.gd.uid: -------------------------------------------------------------------------------- 1 | uid://co2dgoeubndu5 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/node_utils.gd.uid: -------------------------------------------------------------------------------- 1 | uid://uaimtsgfasqb 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/plugin_utils.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c45u32a0sbu5m 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/point_3d/point_3d.gd.uid: -------------------------------------------------------------------------------- 1 | uid://drxrjv32ykcvm 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/transform_utils.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dv3thvb1gf37j 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/singletons/aero_units.gd.uid: -------------------------------------------------------------------------------- 1 | uid://5vah4ayrdxci 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/point_3d/point_3d.gdshader.uid: -------------------------------------------------------------------------------- 1 | uid://eb0ftlq4m4ol 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/vector_3d/vector_3d.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dmckuukxkhgwa 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/components/AeroControlComponent.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cc5dwx5pqkaur 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/vector_3d/vector_3d.gdshader.uid: -------------------------------------------------------------------------------- 1 | uid://chdh08i2dbrj2 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/spatial_gizmo/aero_surface_gizmo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bfws77r6jec64 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_influencer_3d.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c3n4g48jfeemj 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/control_configs/aero_control_config.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cbgqb7g7mmwtu 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_mover_3d/aero_mover_3d.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c7iqa0yhmko7r 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/control_configs/aero_influencer_control_config.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cblxodmmwvpt6 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/components/aero_thruster_component/AeroJetThrusterComponent.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bjesvxrrkoocs 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/components/aero_thruster_component/AeroThrusterComponent.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ciooe5oesshcj 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_rotator_3d/aero_propeller_3d.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bsu5rdrtiqph7 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/aero_surface_3d.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b7h8dpvri0dwp 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/aero_surface_config.gd.uid: -------------------------------------------------------------------------------- 1 | uid://crkbhmxpjff4o 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/control_configs/aero_influencer_control_axis_config.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c46td0go5kvp6 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_rotator_3d/aero_cyclic_propeller_3d.gd.uid: -------------------------------------------------------------------------------- 1 | uid://xcgguqyfcrni 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_rotator_3d/aero_variable_propeller_3d.gd.uid: -------------------------------------------------------------------------------- 1 | uid://d1qklj3wtuuoc 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_mover_3d/aero_rotor_hinge_3d/aero_rotor_hinge_3d.gd.uid: -------------------------------------------------------------------------------- 1 | uid://clifrqvjsrq8v 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_3d.gd.uid: -------------------------------------------------------------------------------- 1 | uid://d1725od371u4a 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_config.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ctbwb7xlhft6t 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addmix/Godot-Aerodynamic-Tutorial/HEAD/addons/godot_aerodynamic_physics/icon.png -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Godot Aerodynamic Physics" 4 | description="A plugin for rigid body aerodynamic simulation." 5 | author="Addmix" 6 | version="0.7.1" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/AeroBody3D.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/node.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/node2d.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/node3d.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/default_buffet_aoa_curve.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Curve" format=3 uid="uid://unnjsmd6kaa4"] 2 | 3 | [resource] 4 | min_value = 1.0 5 | max_value = 1.01 6 | bake_resolution = 2 7 | _data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 1.01), 0.0, 0.0, 0, 0] 8 | point_count = 2 9 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/demo/thruster.gd: -------------------------------------------------------------------------------- 1 | extends Marker3D 2 | 3 | @export var enabled : bool = true 4 | @export var force : float = 100.0 5 | 6 | func _physics_process(delta : float) -> void: 7 | if enabled: 8 | get_parent().apply_force(-global_transform.basis.z * force, global_transform.basis * position) 9 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/default_sweep_drag_multiplier.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Curve" format=3 uid="uid://db2tsvmeabwc8"] 2 | 3 | [resource] 4 | bake_resolution = 4 5 | _data = [Vector2(0, 1), 0.0, -2.09824, 0, 0, Vector2(0.406542, 0.490909), -0.666575, -0.666575, 0, 0, Vector2(1, 0.290909), 0.0, 0.0, 0, 0] 6 | point_count = 3 7 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/AeroSurface3D.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/default_drag_at_mach_curve.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Curve" format=3 uid="uid://dwn08xp3ynaja"] 2 | 3 | [resource] 4 | _limits = [0.99, 1.69, 0.0, 1.0] 5 | bake_resolution = 23 6 | _data = [Vector2(0.053, 1), 0.0, 0.0, 0, 0, Vector2(0.088, 1.324), 4.057, 4.057, 0, 0, Vector2(0.256, 1), 0.181, 0.0, 0, 0] 7 | point_count = 3 8 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/default_drag_aoa_curve.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Curve" format=3 uid="uid://bf3v5qwdrjkmt"] 2 | 3 | [resource] 4 | bake_resolution = 21 5 | _data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.25, 1), 0.0, 0.0, 0, 0, Vector2(0.5, 0), 0.0, 0.0, 0, 0, Vector2(0.75, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] 6 | point_count = 5 7 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/pids/pitch.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="aero_PID" load_steps=2 format=3 uid="uid://dsteqcbn3er33"] 2 | 3 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="1_ffqiq"] 4 | 5 | [resource] 6 | script = ExtResource("1_ffqiq") 7 | p = 0.5 8 | i = 3.0 9 | d = 0.0 10 | clamp_integral = true 11 | min_integral = -0.1 12 | max_integral = 0.1 13 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/pids/roll_pid.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="aero_PID" load_steps=2 format=3 uid="uid://dkjwrbs3c0wvr"] 2 | 3 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="1_d3lg1"] 4 | 5 | [resource] 6 | script = ExtResource("1_d3lg1") 7 | p = 0.15 8 | i = 0.0 9 | d = 0.0 10 | clamp_integral = false 11 | min_integral = -1.0 12 | max_integral = 1.0 13 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/pids/speed_pid.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="aero_PID" load_steps=2 format=3 uid="uid://ck80cone11d5w"] 2 | 3 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="1_jfsco"] 4 | 5 | [resource] 6 | script = ExtResource("1_jfsco") 7 | p = 0.0 8 | i = 0.4 9 | d = 0.0 10 | clamp_integral = false 11 | min_integral = -1.0 12 | max_integral = 1.0 13 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/pids/yaw_pid.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="aero_PID" load_steps=2 format=3 uid="uid://c266ngwax13x5"] 2 | 3 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="1_kxvld"] 4 | 5 | [resource] 6 | script = ExtResource("1_kxvld") 7 | p = 1.0 8 | i = 0.0 9 | d = 0.0 10 | clamp_integral = false 11 | min_integral = -1.0 12 | max_integral = 1.0 13 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/pids/altitude_pid.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="aero_PID" load_steps=2 format=3 uid="uid://d8tp3u2ibcyh"] 2 | 3 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="1_mpxsa"] 4 | 5 | [resource] 6 | script = ExtResource("1_mpxsa") 7 | p = 0.001 8 | i = 0.0 9 | d = 0.01 10 | clamp_integral = false 11 | min_integral = -1.0 12 | max_integral = 1.0 13 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/pids/heading_pid.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="aero_PID" load_steps=2 format=3 uid="uid://dhn1xf3nrt43k"] 2 | 3 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="1_27bkk"] 4 | 5 | [resource] 6 | script = ExtResource("1_27bkk") 7 | p = 10.0 8 | i = 0.0 9 | d = 0.0 10 | clamp_integral = false 11 | min_integral = -1.0 12 | max_integral = 1.0 13 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/pids/bank_angle_pid.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="aero_PID" load_steps=2 format=3 uid="uid://dwdilr3th15ut"] 2 | 3 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="1_bqfmd"] 4 | 5 | [resource] 6 | script = ExtResource("1_bqfmd") 7 | p = 1.0 8 | i = 0.05 9 | d = 0.1 10 | clamp_integral = false 11 | min_integral = -1.0 12 | max_integral = 1.0 13 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/pids/direction_roll_pid.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="aero_PID" load_steps=2 format=3 uid="uid://cjcwr3fy8sjjr"] 2 | 3 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="1_44wvf"] 4 | 5 | [resource] 6 | script = ExtResource("1_44wvf") 7 | p = 0.8 8 | i = 0.0 9 | d = 0.0 10 | clamp_integral = false 11 | min_integral = -1.0 12 | max_integral = 1.0 13 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/pids/direction_yaw_pid.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="aero_PID" load_steps=2 format=3 uid="uid://cimkndlj6fknf"] 2 | 3 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="1_atsaq"] 4 | 5 | [resource] 6 | script = ExtResource("1_atsaq") 7 | p = 25.0 8 | i = 0.0 9 | d = 0.0 10 | clamp_integral = false 11 | min_integral = -1.0 12 | max_integral = 1.0 13 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/pids/direction_pitch_pid.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="aero_PID" load_steps=2 format=3 uid="uid://cqgo7v8jao5fi"] 2 | 3 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="1_a2mef"] 4 | 5 | [resource] 6 | script = ExtResource("1_a2mef") 7 | p = 10.0 8 | i = 0.3 9 | d = 4.0 10 | clamp_integral = false 11 | min_integral = -1.0 12 | max_integral = 1.0 13 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/object.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pillars.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=3 uid="uid://smf6u3tuyv3x"] 2 | 3 | [node name="Pillars" type="Node3D"] 4 | 5 | [node name="CSGCylinder3D" type="CSGCylinder3D" parent="."] 6 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 30, 25, 0) 7 | use_collision = true 8 | radius = 6.0 9 | height = 50.0 10 | cone = true 11 | 12 | [node name="CSGCylinder3D2" type="CSGCylinder3D" parent="."] 13 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -30, 25, 0) 14 | use_collision = true 15 | radius = 6.0 16 | height = 50.0 17 | cone = true 18 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/point_3d/point_3d.gdshader: -------------------------------------------------------------------------------- 1 | shader_type spatial; 2 | render_mode unshaded, depth_test_disabled; 3 | 4 | uniform float _width = 1.0; 5 | uniform vec3 _color : source_color = vec3(0, 0, 0); 6 | uniform bool checker_pattern = false; 7 | 8 | void vertex() { 9 | VERTEX = VERTEX * _width; 10 | COLOR = vec4(VERTEX, 0); 11 | } 12 | 13 | void fragment() { 14 | vec3 output_color = _color; 15 | if (checker_pattern) { 16 | ALBEDO = _color * sign(COLOR.r) * sign(COLOR.g) * sign(COLOR.b); 17 | } else { 18 | ALBEDO = _color; 19 | } 20 | } -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/default_lift_aoa_curve.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Curve" format=3 uid="uid://cx3q3y0bx1rv7"] 2 | 3 | [resource] 4 | min_value = -1.0 5 | bake_resolution = 36 6 | _data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.125, 1), 0.0, 0.0, 0, 0, Vector2(0.25, 1.45519e-11), 0.0, 0.0, 0, 0, Vector2(0.375, -0.4), 0.0, 0.0, 0, 0, Vector2(0.415, -1), 0.0, 0.0, 0, 0, Vector2(0.585, 1), 0.0, 0.0, 0, 0, Vector2(0.625, 0.4), 0.0, 0.0, 0, 0, Vector2(0.75, 1.45519e-11), 0.0, 0.0, 0, 0, Vector2(0.875, -1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] 7 | point_count = 10 8 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/AeroInfluencer3D.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/AeroPropeller3D.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/control_configs/aero_influencer_control_axis_config.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name AeroInfluencerControlAxisConfig 3 | 4 | const AeroMathUtils = preload("../../utils/math_utils.gd") 5 | ## Amount of rotation that throttle commands contribute to this node's rotation. 6 | @export var contribution := Vector3.ZERO 7 | @export_exp_easing("inout") var easing : float = 1.0 8 | 9 | func _init(_contribution : Vector3 = Vector3.ZERO) -> void: 10 | contribution = _contribution 11 | 12 | func get_value(command : float) -> Vector3: 13 | return contribution * AeroMathUtils.improved_ease(command, easing) 14 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/vector_3d/vector_3d.gdshader: -------------------------------------------------------------------------------- 1 | shader_type spatial; 2 | render_mode unshaded, depth_test_disabled; 3 | 4 | uniform float _length = 0.0; 5 | uniform float _width = 1.0; 6 | uniform vec3 _color = vec3(0, 0, 0); 7 | uniform bool checker_pattern = false; 8 | 9 | void vertex() { 10 | VERTEX = VERTEX * _width; 11 | COLOR = vec4(VERTEX, 0); 12 | VERTEX -= float(VERTEX.z < 0.0) * vec3(0, 0, _length); 13 | } 14 | 15 | void fragment() { 16 | vec3 output_color = _color; 17 | if (checker_pattern) { 18 | ALBEDO = _color * sign(COLOR.r) * sign(COLOR.g) * sign(COLOR.b); 19 | } else { 20 | ALBEDO = _color; 21 | } 22 | } -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/JetThrusterComponent.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/transform_utils.gd: -------------------------------------------------------------------------------- 1 | # From https://github.com/addmix/godot_utils 2 | 3 | static func quat_to_axis_angle(quat : Quaternion) -> Quaternion: 4 | var axis_angle := Quaternion(0, 0, 0, 0) 5 | 6 | if quat.w > 1: #if w>1 acos and sqrt will produce errors, this cant happen if quaternion is normalised 7 | quat = quat.normalized() 8 | 9 | var angle = 2.0 * acos(quat.w) 10 | axis_angle.w = sqrt(1 - quat.w * quat.w) #assuming quaternion normalised then w is less than 1, so term always positive. 11 | 12 | if axis_angle.w < 0.00001: #test to avoid divide by zero, s is always positive due to sqrt 13 | axis_angle.x = quat.x 14 | axis_angle.y = quat.y 15 | axis_angle.z = quat.z 16 | else: 17 | axis_angle.x = quat.x / axis_angle.w 18 | axis_angle.y = quat.y / axis_angle.w 19 | axis_angle.z = quat.z / axis_angle.w 20 | 21 | return axis_angle 22 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://du78avxh7pp62" 6 | path="res://.godot/imported/icon.png-360f52e0f3c2b9a9ced4187983f9d833.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/godot_aerodynamic_physics/icon.png" 14 | dest_files=["res://.godot/imported/icon.png-360f52e0f3c2b9a9ced4187983f9d833.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://t7dgr4btp0t4" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/README.md: -------------------------------------------------------------------------------- 1 | # Godot Aerodynamic Physics 2 | drawing 3 | 4 | # For Godot Asset Library downloads. 5 | 1. Ensure that addon files are installed inside `res://addons/godot_aerodynamic_physics` 6 | 7 | # Manual Installation 8 | 1. Download and un-zip files. 9 | 2. Place the `godot_aerodynamic_physics` folder inside your project's `addons` folder. 10 | 3. Enable plugin in project settings `Project > Project Settings > Plugins` 11 | 12 | # Usage 13 | Tutorial available [here](https://youtu.be/hpR1vvaQJaM) 14 | 1. Add an ![AeroBody3D icon](icons/AeroBody3D.svg) AeroBody to your scene, and add one or more ![AeroInfluencer3D icon](icons/AeroInfluencer3D.svg) AeroInfluencer3D derived classes as children, adjust settings to change the characteristics. 15 | 16 | # Development Direction 17 | 1. Usability, stability, and performance. 18 | 2. More AeroInfluencer3D derived nodes (Dynamic rotor systems, primitive shapes). 19 | 3. Preset profiles for AeroSurface nodes. 20 | 4. Energy–maneuverability performance profiling. 21 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/node.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dk7q2iv53aiy6" 6 | path="res://.godot/imported/node.svg-6aebf447c513cda1ac1c1da4eb2313e1.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/godot_aerodynamic_physics/icons/node.svg" 14 | dest_files=["res://.godot/imported/node.svg-6aebf447c513cda1ac1c1da4eb2313e1.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/node2d.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://covqt0o6abf80" 6 | path="res://.godot/imported/node2d.svg-1c4af45fbb253b3216298a31f51aca51.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/godot_aerodynamic_physics/icons/node2d.svg" 14 | dest_files=["res://.godot/imported/node2d.svg-1c4af45fbb253b3216298a31f51aca51.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/node3d.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dy6cp1j6t0ubg" 6 | path="res://.godot/imported/node3d.svg-6973f68609ef45bb203c34a1805eed0e.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/godot_aerodynamic_physics/icons/node3d.svg" 14 | dest_files=["res://.godot/imported/node3d.svg-6973f68609ef45bb203c34a1805eed0e.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/object.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://c1bag5utqyahp" 6 | path="res://.godot/imported/object.svg-b3f39239cbb4971124b96d1caf276b3f.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/godot_aerodynamic_physics/icons/object.svg" 14 | dest_files=["res://.godot/imported/object.svg-b3f39239cbb4971124b96d1caf276b3f.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/AeroBody3D.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://d2r2fh2twhxrq" 6 | path="res://.godot/imported/AeroBody3D.svg-c8fbf4a6b3a6da6bd3847c46a2ed7b71.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/godot_aerodynamic_physics/icons/AeroBody3D.svg" 14 | dest_files=["res://.godot/imported/AeroBody3D.svg-c8fbf4a6b3a6da6bd3847c46a2ed7b71.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/AeroSurface3D.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cu5my0u2kmadq" 6 | path="res://.godot/imported/AeroSurface3D.svg-02a1e4747a7cef0314016e8ababf4e07.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/godot_aerodynamic_physics/icons/AeroSurface3D.svg" 14 | dest_files=["res://.godot/imported/AeroSurface3D.svg-02a1e4747a7cef0314016e8ababf4e07.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/AeroPropeller3D.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bdltk6tk26vh8" 6 | path="res://.godot/imported/AeroPropeller3D.svg-975bf9b1b19013332a01e635cf92986e.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/godot_aerodynamic_physics/icons/AeroPropeller3D.svg" 14 | dest_files=["res://.godot/imported/AeroPropeller3D.svg-975bf9b1b19013332a01e635cf92986e.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/AeroInfluencer3D.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://ci6rx1mad5oc8" 6 | path="res://.godot/imported/AeroInfluencer3D.svg-eb204b96a1b8ff91f317f42215eb67dc.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/godot_aerodynamic_physics/icons/AeroInfluencer3D.svg" 14 | dest_files=["res://.godot/imported/AeroInfluencer3D.svg-eb204b96a1b8ff91f317f42215eb67dc.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/icons/JetThrusterComponent.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://brtgoqhoql28l" 6 | path="res://.godot/imported/JetThrusterComponent.svg-0094d7444fa45272e32ecd6bcad847db.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/godot_aerodynamic_physics/icons/JetThrusterComponent.svg" 14 | dest_files=["res://.godot/imported/JetThrusterComponent.svg-0094d7444fa45272e32ecd6bcad847db.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Addmix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/plugin_utils.gd: -------------------------------------------------------------------------------- 1 | # From https://github.com/addmix/godot_utils 2 | 3 | static func get_plugin_list() -> Array: 4 | var plugins := [] 5 | for directory in DirAccess.get_directories_at("res://addons"): 6 | var path := "res://addons/%s/plugin.cfg" % directory 7 | if FileAccess.file_exists(path): 8 | var config := ConfigFile.new() 9 | var err := config.load(path) 10 | 11 | plugins.append({ 12 | "name": config.get_value("plugin", "name", ""), 13 | "description": config.get_value("plugin", "description", ""), 14 | "author": config.get_value("plugin", "author", ""), 15 | "version": config.get_value("plugin", "version", ""), 16 | "script": config.get_value("plugin", "script", ""), 17 | "path": path.get_base_dir() 18 | }) 19 | 20 | return plugins 21 | 22 | static func get_plugin_dictionary() -> Dictionary: 23 | var plugin_dictionary := {} 24 | 25 | var list := get_plugin_list() 26 | for plugin in list: 27 | plugin_dictionary[plugin["name"]] = plugin 28 | 29 | return plugin_dictionary 30 | 31 | #very slow, use sparingly. 32 | static func get_plugin_path(plugin_name : String) -> String: 33 | var dictionary := get_plugin_dictionary() 34 | if not dictionary.has(plugin_name): 35 | push_error("Plugin name not found in plugin dictionary.") 36 | return dictionary[plugin_name ]["path"] 37 | 38 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/components/aero_thruster_component/AeroThrusterComponent.gd: -------------------------------------------------------------------------------- 1 | @icon("../../icons/JetThrusterComponent.svg") 2 | extends Marker3D 3 | class_name AeroThrusterComponent 4 | 5 | const AeroNodeUtils = preload("../../utils/node_utils.gd") 6 | 7 | @onready var rigid_body : RigidBody3D = AeroNodeUtils.get_first_parent_of_type(self, RigidBody3D) 8 | ## Enables simulation of the JetThrusterComponent. 9 | @export var enabled : bool = true 10 | @export_group("Control") 11 | ## If enabled, throttle is automatically read from the ancestor AeroBody3D. 12 | @export var get_throttle_from_aero_body : bool = true 13 | ## Throttle value used to simulate the JetThrusterComponent 14 | @export var throttle : float = 1.0 15 | 16 | @export_group("Simulation Parameters") 17 | @export var max_thrust_force : float = 1000.0 18 | 19 | func _physics_process(delta : float) -> void: 20 | if not enabled: 21 | return 22 | 23 | if rigid_body is AeroBody3D and get_throttle_from_aero_body: 24 | throttle = rigid_body.throttle_command 25 | 26 | var force_magnitude : float = get_thrust_magnitude() * delta 27 | 28 | var force := -global_transform.basis.z * force_magnitude 29 | var relative_position := global_position - rigid_body.global_position 30 | if rigid_body: 31 | #rigid_body.apply_force(-global_transform.basis.z * force_magnitude, rigid_body.global_transform.basis * position) 32 | rigid_body.apply_central_force(-global_transform.basis.z * force_magnitude) 33 | rigid_body.apply_torque(relative_position.cross(force)) 34 | 35 | func get_thrust_magnitude() -> float: 36 | return max_thrust_force * throttle 37 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/aero_surface_config.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Resource 3 | class_name AeroSurfaceConfig 4 | 5 | @export_group("Wing profile") 6 | ## Represents the length of the AeroSurface3D on the Z axis. 7 | @export_range(0.0, 100.0, 0.001, "or_greater", "exp", "suffix:m") var chord : float = 1.0: 8 | set(value): 9 | chord = abs(value) 10 | if auto_aspect_ratio: 11 | aspect_ratio = span / chord 12 | emit_changed() 13 | ## Represents the width of the AeroSurface3D on the X axis. 14 | @export_range(0.0, 100.0, 0.001, "or_greater", "exp", "suffix:m") var span : float = 2.0: 15 | set(value): 16 | span = abs(value) 17 | if auto_aspect_ratio: 18 | aspect_ratio = span / chord 19 | emit_changed() 20 | ## If disabled, span or chord will be modified to maintain the given aspect ratio. 21 | @export var auto_aspect_ratio : bool = true: 22 | set(value): 23 | auto_aspect_ratio = value 24 | if auto_aspect_ratio: 25 | aspect_ratio = span / chord 26 | emit_changed() 27 | ## Aspect ratio of the AeroSurface. This is the ratio of span and chord. 28 | @export_range(0.0, 100.0, 0.001, "or_greater", "suffix:%") var aspect_ratio : float = 2.0: 29 | set(value): 30 | aspect_ratio = value 31 | if !auto_aspect_ratio: 32 | #keep area 33 | var current_area : float = span * chord 34 | emit_changed() 35 | @export_group("") 36 | 37 | func _init(_chord : float = 1.0, _span : float = 2.0, _auto_aspect_ratio : bool = true, _aspect_ratio : float = 2.0) -> void: 38 | chord = _chord 39 | span = _span 40 | auto_aspect_ratio = _auto_aspect_ratio 41 | aspect_ratio = _aspect_ratio 42 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/pid.gd: -------------------------------------------------------------------------------- 1 | # From https://github.com/addmix/godot_utils 2 | extends Resource 3 | class_name aero_PID 4 | 5 | const aeroMathUtils = preload("../utils/math_utils.gd") 6 | 7 | @export var p : float = 0.2 8 | @export var i : float = 0.05 9 | @export var d : float = 1.0 10 | 11 | @export_category("Clamp Integral") 12 | @export var clamp_integral : bool = false 13 | @export var min_integral : float = -1 14 | @export var max_integral : float = 1 15 | @export_category("") 16 | 17 | var output : float = 0 18 | 19 | var _last_error : float 20 | var _integral_error : float 21 | 22 | var proportional_output : float = 0.0 23 | var integral_output : float = 0.0 24 | var derivative_output : float = 0.0 25 | 26 | func _init(_p : float = p, _i : float = i, _d : float = d, _clamp_integral : bool = clamp_integral, _min_integral : float = min_integral, _max_integral : float = max_integral) -> void: 27 | self.p = _p 28 | self.i = _i 29 | self.d = _d 30 | self.clamp_integral = _clamp_integral 31 | self.min_integral = _min_integral 32 | self.max_integral = _max_integral 33 | 34 | func update(delta : float, error : float) -> float: 35 | var derivative : float = (error - _last_error) / delta 36 | _integral_error += error * delta 37 | _integral_error = aeroMathUtils.float_toggle(clamp_integral, clamp(_integral_error, min_integral, max_integral), _integral_error) 38 | _last_error = error 39 | 40 | proportional_output = p * error 41 | integral_output = i * _integral_error 42 | derivative_output = d * derivative 43 | 44 | output = proportional_output + integral_output + derivative_output 45 | return output 46 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/demo/demo_scene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=3 uid="uid://cpbp7en0cerjl"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://dx0v1i14fyw8c" path="res://addons/godot_aerodynamic_physics/demo/demo_plane.tscn" id="1_bbdyo"] 4 | 5 | [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_lajrc"] 6 | sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) 7 | ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) 8 | 9 | [sub_resource type="Sky" id="Sky_dsdc3"] 10 | sky_material = SubResource("ProceduralSkyMaterial_lajrc") 11 | 12 | [sub_resource type="Environment" id="Environment_bkiet"] 13 | background_mode = 2 14 | sky = SubResource("Sky_dsdc3") 15 | tonemap_mode = 2 16 | glow_enabled = true 17 | 18 | [sub_resource type="FastNoiseLite" id="FastNoiseLite_ty4ai"] 19 | frequency = 0.2125 20 | 21 | [sub_resource type="NoiseTexture2D" id="NoiseTexture2D_rutke"] 22 | noise = SubResource("FastNoiseLite_ty4ai") 23 | 24 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1sawy"] 25 | albedo_color = Color(0, 0.490196, 0.0823529, 1) 26 | albedo_texture = SubResource("NoiseTexture2D_rutke") 27 | 28 | [node name="Main" type="Node3D"] 29 | 30 | [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] 31 | transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0) 32 | shadow_enabled = true 33 | 34 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."] 35 | environment = SubResource("Environment_bkiet") 36 | 37 | [node name="CSGBox3D" type="CSGBox3D" parent="."] 38 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -4.12099, -190.455) 39 | use_collision = true 40 | size = Vector3(10000, 1, 10000) 41 | material = SubResource("StandardMaterial3D_1sawy") 42 | 43 | [node name="AeroBody3D" parent="." instance=ExtResource("1_bbdyo")] 44 | show_debug = true 45 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_mover_3d/aero_rotor_hinge_3d/aero_rotor_hinge_3d.gd: -------------------------------------------------------------------------------- 1 | 2 | ### This is currently broken. 3 | 4 | @tool 5 | extends AeroMover3D 6 | class_name AeroRotorHinge3D 7 | 8 | ## Length of the simulated blade, from the hinge root, to the tip. 9 | @export var blade_length : float = 10.0 10 | ## Mass of the simulated blade. 11 | @export var blade_mass : float = 100.0 12 | 13 | var flap_velocity : float = 0.0 14 | var leadlag_velocity : float = 0.0 15 | 16 | var flap : float = 0.0 17 | var leadlag : float = 0.0 18 | 19 | @onready var rotation_z : float = rotation.z 20 | @onready var rotation_y : float = rotation.y 21 | 22 | func _update_transform_substep(substep_delta : float) -> void: 23 | super._update_transform_substep(substep_delta) 24 | 25 | #should we use the torque instead of the force? 26 | var force : Vector3 = _current_force * global_basis 27 | var linear_acceleration : Vector3 = get_linear_acceleration() * global_basis 28 | 29 | leadlag_velocity += force.z / blade_mass * substep_delta #might need to be negative 30 | leadlag_velocity += linear_acceleration.z * substep_delta 31 | leadlag += leadlag_velocity * substep_delta 32 | 33 | #if not leadlag == clamp(leadlag, -0.5, 0.5): 34 | #leadlag = clamp(leadlag, -0.5, 0.5) - 0.05 * sign(leadlag) 35 | #leadlag_velocity = 0.0 36 | 37 | 38 | # lead-lag and flap velocity isn't integrated properly by AeroMover3D 39 | 40 | #need conservation of angular momentum 41 | 42 | #need option for "delta 3" feathering control 43 | 44 | flap_velocity = force.y / blade_mass * substep_delta 45 | flap_velocity += -linear_acceleration.y * substep_delta 46 | flap += flap_velocity * substep_delta 47 | 48 | #if not flap == clamp(flap, -0.5, 0.5): 49 | #flap = clamp(flap, -0.5, 0.5) - 0.05 * sign(flap) 50 | #flap_velocity = 0.0 51 | 52 | rotation.z = rotation_z + flap 53 | rotation.y = rotation_y + leadlag 54 | 55 | func get_centrifugal_offset() -> Vector3: 56 | return position + Vector3(blade_length * 0.5, 0, 0) 57 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_rotator_3d/aero_variable_propeller_3d.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends AeroPropeller3D 3 | class_name AeroVariablePropeller3D 4 | 5 | ## Pitch angle for propeller blades. 6 | @export var propeller_pitch : float = 0.0 7 | #variable isn't used. Alias for propeller_pitch 8 | ## Pitch angle for propeller blades. Alias for `propeller_pitch` 9 | @export var collective : float = 0.0: 10 | set(x): 11 | collective = x 12 | propeller_pitch = collective 13 | get: 14 | return propeller_pitch 15 | 16 | @export_group("Collective Control") 17 | @export var propeller_collective_control_config := create_collective_control_config() 18 | func create_collective_control_config() -> AeroInfluencerControlConfig: 19 | var config := AeroInfluencerControlConfig.new() 20 | config.max_value.x = 0.5 21 | config.collective_config = AeroInfluencerControlAxisConfig.new(Vector3(1.0, 0.0, 0.0)) 22 | return config 23 | 24 | func _ready() -> void: 25 | super._ready() 26 | 27 | if not Engine.is_editor_hint(): 28 | if propeller_collective_control_config: 29 | propeller_collective_control_config = propeller_collective_control_config.duplicate(true) 30 | 31 | func _update_transform_substep(substep_delta : float) -> void: 32 | super._update_transform_substep(substep_delta) 33 | 34 | for influencer : AeroInfluencer3D in propeller_instances: 35 | influencer.default_transform.basis = Basis.from_euler(Vector3(propeller_pitch, influencer.default_transform.basis.get_euler().y, influencer.default_transform.basis.get_euler().z)) 36 | influencer.transform = influencer.default_transform 37 | 38 | func _update_control_transform(substep_delta : float) -> void: 39 | super._update_control_transform(substep_delta) 40 | 41 | var propeller_velocity_value := Vector3.ZERO 42 | if propeller_collective_control_config: 43 | propeller_velocity_value = apply_control_commands_to_config(substep_delta, propeller_collective_control_config) 44 | 45 | collective = propeller_collective_control_config.update(substep_delta).x 46 | 47 | 48 | func is_overriding_body_sleep() -> bool: 49 | return super.is_overriding_body_sleep() and not is_equal_approx(propeller_pitch, 0.0) 50 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_rotator_3d/aero_cyclic_propeller_3d.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends AeroVariablePropeller3D 3 | class_name AeroCyclicPropeller3D 4 | 5 | ## Cyclic control value. Control takes place cyclicly, and changes based on a blade's position through the cycle. 6 | @export var cyclic := Vector2.ZERO 7 | ## Maximum rotation angle (degrees) of cyclic control. 8 | @export var cyclic_pitch : float = 15.0 9 | 10 | @export var cyclic_control_config := create_cyclic_control_config() 11 | func create_cyclic_control_config() -> AeroInfluencerControlConfig: 12 | var config := AeroInfluencerControlConfig.new() 13 | config.max_value = Vector3(1.0, 1.0, 0.0) 14 | config.roll_config = AeroInfluencerControlAxisConfig.new(Vector3(1.0, 0.0, 0.0)) 15 | config.pitch_config = AeroInfluencerControlAxisConfig.new(Vector3(0.0, 1.0, 0.0)) 16 | return config 17 | 18 | func _ready() -> void: 19 | super._ready() 20 | if not Engine.is_editor_hint(): 21 | cyclic_control_config = cyclic_control_config.duplicate(true) 22 | 23 | func _update_transform_substep(substep_delta : float) -> void: 24 | super._update_transform_substep(substep_delta) 25 | 26 | var rotor_offset : float = basis.get_euler(EULER_ORDER_XZY).y 27 | for influencer : AeroInfluencer3D in propeller_instances: 28 | var angular_position : float = rotor_offset + influencer.rotation.y 29 | 30 | var cyclic_effect : float = cos(angular_position) * cyclic.x + sin(angular_position) * cyclic.y 31 | cyclic_effect *= deg_to_rad(cyclic_pitch) 32 | 33 | influencer.default_transform.basis = Basis.from_euler(Vector3(deg_to_rad(propeller_pitch) + cyclic_effect, influencer.default_transform.basis.get_euler().y, influencer.default_transform.basis.get_euler().z)) 34 | 35 | func _update_control_transform(substep_delta : float) -> void: 36 | super._update_control_transform(substep_delta) 37 | 38 | var cyclic_value := Vector3.ZERO 39 | if cyclic_control_config: 40 | cyclic_value = apply_control_commands_to_config(substep_delta, cyclic_control_config) 41 | 42 | cyclic_control_config.update(substep_delta) 43 | cyclic = Vector2(cyclic_control_config.current_value.x, cyclic_control_config.current_value.y) 44 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/spatial_gizmo/aero_surface_gizmo.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorNode3DGizmoPlugin 3 | 4 | var wing_opacity : float = 0.2 5 | var wing_material := StandardMaterial3D.new() 6 | var wing_color := Color(1, 1, 1, wing_opacity) 7 | var flap_color := Color(1, 1, 0, wing_opacity) 8 | 9 | func _init(): 10 | wing_material.flags_unshaded = true 11 | wing_material.flags_transparent = true 12 | wing_material.cull_mode = StandardMaterial3D.CULL_DISABLED 13 | wing_material.vertex_color_use_as_albedo = true 14 | wing_material.flags_no_depth_test = true 15 | 16 | func _get_gizmo_name() -> String: 17 | return "AeroSurfaceGizmo" 18 | 19 | func _has_gizmo(for_node_3d : Node3D) -> bool: 20 | return for_node_3d is AeroSurface3D 21 | 22 | func _redraw(gizmo : EditorNode3DGizmo) -> void: 23 | gizmo.clear() 24 | var spatial = gizmo.get_node_3d() 25 | 26 | var st := SurfaceTool.new() 27 | 28 | #origin 29 | var half_chord : float = spatial.wing_config.chord / 2.0 30 | var quater_chord : float = spatial.wing_config.chord / 4.0 31 | var half_span : float = spatial.wing_config.span / 2.0 32 | 33 | st.begin(Mesh.PRIMITIVE_TRIANGLES) 34 | #flap section 35 | var tl := Vector3(-half_span, 0, half_chord)#.rotated(Vector3(-1, 0, 0), flap_angle) 36 | var tr := Vector3(half_span, 0, half_chord)#.rotated(Vector3(-1, 0, 0), flap_angle) 37 | tl.z += quater_chord 38 | tr.z += quater_chord 39 | var bl := Vector3(-half_span, 0, quater_chord) 40 | var br := Vector3(half_span, 0, quater_chord) 41 | 42 | #first triangle 43 | st.set_color(flap_color) 44 | st.add_vertex(tl) 45 | st.add_vertex(tr) 46 | st.add_vertex(bl) 47 | #second triangle 48 | st.add_vertex(bl) 49 | st.add_vertex(tr) 50 | st.add_vertex(br) 51 | 52 | #wing section 53 | tl = Vector3(-half_span, 0, quater_chord) 54 | tr = Vector3(half_span, 0, quater_chord) 55 | bl = Vector3(-half_span, 0, -half_chord + quater_chord) 56 | br = Vector3(half_span, 0, -half_chord + quater_chord) 57 | 58 | #first triangle 59 | st.set_color(wing_color) 60 | st.add_vertex(tl) 61 | st.add_vertex(tr) 62 | st.add_vertex(bl) 63 | #second triangle 64 | st.add_vertex(bl) 65 | st.add_vertex(tr) 66 | st.add_vertex(br) 67 | 68 | var mesh : ArrayMesh = st.commit() 69 | gizmo.add_mesh(mesh, wing_material) 70 | gizmo.add_collision_triangles(mesh.generate_triangle_mesh()) 71 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_3d.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends AeroSurface3D 3 | class_name ManualAeroSurface3D 4 | 5 | ## Config resource used to define the aerodynamic profile of the ManualAeroSurface3D. This includes lift-aoa and drag-aoa evaluation curves. 6 | @export var manual_config := ManualAeroSurfaceConfig.new() 7 | 8 | func _calculate_forces(substep_delta : float = 0.0) -> PackedVector3Array: 9 | var force_and_torque : PackedVector3Array = super._calculate_forces(substep_delta) 10 | 11 | var force := Vector3.ZERO 12 | var torque := Vector3.ZERO 13 | 14 | var aero_reference := dynamic_pressure * area 15 | 16 | lift_force = aero_reference * manual_config.get_lift_coefficient(angle_of_attack) 17 | var drag_coefficient : float = manual_config.get_drag_coefficient(angle_of_attack) * manual_config.get_drag_at_sweep_angle(sweep_angle) * manual_config.get_drag_multiplier_at_mach(mach) 18 | var form_drag : float = aero_reference * drag_coefficient 19 | var induced_drag : float = (lift_force * lift_force) / (dynamic_pressure * PI * wing_config.span * wing_config.span) 20 | #dynamic pressure causes divide by zero when airspeed is 0, which results in NAN. 21 | if is_equal_approx(air_speed, 0.0): 22 | induced_drag = 0.0 23 | 24 | drag_force = form_drag + induced_drag 25 | # print("Percentages of total drag:\nSkin friction: %s\nForm drag: %s\nInduced drag: %s" % [skin_friction_drag / total_drag, form_drag / total_drag, induced_drag / total_drag]) 26 | 27 | # https://aviation.stackexchange.com/questions/84210/difference-in-lift-generation-for-a-swept-wing-and-straight-wing-in-subsonic-con 28 | # Since both the lift curve slope and the effective angle of attack are reduced 29 | # by the cosine of the sweep angle at quarter chord, the lift coefficient of a 30 | # swept wing at the same geometric angle of attack is reduced by the square of 31 | # the cosine of the sweep angle. 32 | 33 | var lift_vector : Vector3 = lift_direction * lift_force 34 | var drag_vector : Vector3 = drag_direction * drag_force 35 | 36 | #sum of all linear forces 37 | force = lift_vector + drag_vector 38 | #resultant torque from linear forces 39 | torque += relative_position.cross(force) 40 | 41 | #current values for debug 42 | _current_lift = lift_vector 43 | _current_drag = drag_vector 44 | _current_force = force 45 | _current_torque = torque 46 | 47 | return PackedVector3Array([force_and_torque[0] + force, force_and_torque[1] + torque]) 48 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/control_configs/aero_influencer_control_config.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name AeroInfluencerControlConfig 3 | 4 | const AeroMathUtils = preload("../../utils/math_utils.gd") 5 | 6 | var pitch_command : float = 0.0 7 | var yaw_command : float = 0.0 8 | var roll_command : float = 0.0 9 | var brake_command : float = 0.0 10 | var throttle_command : float = 0.0 11 | var collective_command : float = 0.0 12 | 13 | ## If enabled, this AeroInfluencer3D node will automatically rotate to accommodate control inputs. 14 | @export var enable_control : bool = true 15 | #X = pitch, Y = yaw, Z = roll 16 | 17 | @export_subgroup("Limits") 18 | var current_value := Vector3.ZERO 19 | 20 | #@export var min_value := Vector3.ZERO 21 | ## Maximum rotation (in radians) this AeroInfluencer can rotate for controls. 22 | @export var max_value := Vector3.ZERO 23 | @export var limit_movement_speed : bool = false 24 | @export var movement_speed : float = 1.0 25 | 26 | @export_subgroup("Axis Configs") 27 | @export var pitch_config : AeroInfluencerControlAxisConfig 28 | @export var yaw_config : AeroInfluencerControlAxisConfig 29 | @export var roll_config : AeroInfluencerControlAxisConfig 30 | @export var brake_config : AeroInfluencerControlAxisConfig 31 | @export var throttle_config : AeroInfluencerControlAxisConfig 32 | @export var collective_config : AeroInfluencerControlAxisConfig 33 | 34 | func update(delta : float) -> Vector3: 35 | if not enable_control: 36 | return current_value 37 | 38 | var pitch : Vector3 = get_value_safe(pitch_config, pitch_command) 39 | var yaw : Vector3 = get_value_safe(yaw_config, yaw_command) 40 | var roll : Vector3 = get_value_safe(roll_config, roll_command) 41 | var brake : Vector3 = get_value_safe(brake_config, brake_command) 42 | var throttle : Vector3 = get_value_safe(throttle_config, throttle_command) 43 | var collective : Vector3 = get_value_safe(collective_config, collective_command) 44 | 45 | var total_control : Vector3 = pitch + yaw + roll + brake + throttle + collective 46 | total_control = total_control.clamp(-Vector3.ONE, Vector3.ONE) 47 | var desired_control : Vector3 = total_control * max_value 48 | 49 | if limit_movement_speed: 50 | current_value = current_value.move_toward(desired_control, movement_speed * delta) 51 | else: 52 | current_value = desired_control 53 | 54 | return current_value 55 | 56 | static func get_value_safe(axis_config : AeroInfluencerControlAxisConfig, command : float = 0.0) -> Vector3: 57 | if axis_config: 58 | return axis_config.get_value(command) 59 | return Vector3.ZERO 60 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_config.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Resource 3 | class_name ManualAeroSurfaceConfig 4 | 5 | ## Lift coefficient used where the lift_aoa_curve has a value of -1. 6 | @export_range(-100.0, 0.0, 0.001, "or_greater", "exp", "suffix:cL") var min_lift_coefficient : float = -2.0 7 | ## Lift coefficient used where the lift_aoa_curve has a value of 1. 8 | @export_range(0.0, 100.0, 0.001, "or_greater", "exp", "suffix:cL") var max_lift_coefficient : float = 2.0 9 | ## Curve that determines the ManualAeroSurface3D's lift coefficient, depending on the ManualAeroSurface3D's angle of attack. 10 | @export var lift_aoa_curve : Curve = preload("../../../resources/default_lift_aoa_curve.tres").duplicate() 11 | ## Drag coefficient used where the drag_aoa_curve has a value of 0. 12 | @export_range(0.0, 100.0, 0.001, "or_greater", "exp", "suffix:cD") var min_drag_coefficient : float = 0.02 13 | ## Drag coefficient used where the drag_aoa_curve has a value of 1. 14 | @export_range(0.0, 100.0, 0.001, "or_greater", "exp", "suffix:cD") var max_drag_coefficient : float = 1.0 15 | @export var drag_aoa_curve : Curve = preload("../../../resources/default_drag_aoa_curve.tres").duplicate() 16 | ## Multiplier curve that modifies drag depending on the sweep angle of the AeroSurface3D. 17 | @export var sweep_drag_multiplier_curve : Curve = preload("../../../resources/default_sweep_drag_multiplier.tres").duplicate() 18 | func get_drag_at_sweep_angle(sweep_angle : float) -> float: 19 | return sweep_drag_multiplier_curve.sample_baked(abs(sweep_angle / PI)) 20 | ## Multiplier curve that modifies drag depending on the AeroBody3D's Mach number. 21 | @export var drag_at_mach_multiplier_curve : Curve = preload("../../../resources/default_drag_at_mach_curve.tres").duplicate() 22 | func get_drag_multiplier_at_mach(mach : float) -> float: 23 | return drag_at_mach_multiplier_curve.sample_baked(mach / 10.0) 24 | ## Evaluation curve for turbulent movements based on AeroSurface3D's angle of attack. (Unused) 25 | @export var buffet_aoa_curve : Curve #unused 26 | 27 | const MathUtils = preload("../../../../utils/math_utils.gd") 28 | 29 | func get_lift_coefficient(aoa : float) -> float: 30 | var sample : float = lift_aoa_curve.sample_baked(aoa / PI / 2.0 + 0.5) 31 | return sample * MathUtils.float_toggle(sign(sample) == 1.0, max_lift_coefficient, abs(min_lift_coefficient)) 32 | 33 | func get_drag_coefficient(aoa : float) -> float: 34 | return remap(drag_aoa_curve.sample_baked(aoa / PI / 2.0 + 0.5), 0, 1, min_drag_coefficient, max_drag_coefficient) 35 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/components/aero_thruster_component/AeroJetThrusterComponent.gd: -------------------------------------------------------------------------------- 1 | @icon("../../icons/JetThrusterComponent.svg") 2 | extends AeroThrusterComponent 3 | class_name AeroJetThrusterComponent 4 | 5 | @export_group("Simulation Parameters") 6 | ## Area (in meters squared) of the JetThrusterComponent's intake. 7 | @export var intake_area : float = 0.5 8 | ## Area (in meters squared) of the JetThrusterComponent's exhaust. (Unused) 9 | #@export var exit_area : float = 1.0 10 | ## Maxmimum air velocity the intake fan can create in static thrust, at max throttle. 11 | @export var intake_fan_max_velocity : float = 300.0 12 | ## Velocity of exhaust gasses at max throttle. 13 | @export var exhaust_velocity : float = 1000 14 | ## Maximum amount of fuel (in kilograms) the engine can burn per second. 15 | @export var max_fuel_flow : float = 0.1 #flow rate in kilograms per second 16 | ## Ratio of fuel volume before, and after combustion. (Unused) 17 | #@export var fuel_expansion_ratio : float = 10.0 18 | 19 | func get_thrust_magnitude() -> float: 20 | return calculate_mass_flow_acceleration_force() 21 | 22 | func calculate_mass_flow_acceleration_force() -> float: 23 | var altitude : float = 0.0 24 | var air_velocity : Vector3 = rigid_body.linear_velocity 25 | if rigid_body is AeroBody3D: 26 | altitude = rigid_body.altitude 27 | air_velocity = -rigid_body.air_velocity 28 | 29 | var intake_air_velocity : float = air_velocity.dot(-global_basis.z) 30 | var intake_air_density : float = 1.225 31 | var intake_air_pressure : float = 101325.0 32 | var intake_mass_flow_rate : float = intake_air_density * intake_air_velocity * intake_area 33 | 34 | var aero_units : Node = get_node_or_null("/root/AeroUnits") 35 | if aero_units: 36 | altitude = aero_units.get_altitude(self) 37 | intake_air_density = aero_units.get_density_at_altitude(altitude) 38 | intake_air_pressure = aero_units.get_pressure_at_altitude(altitude) 39 | 40 | var fuel_burn_rate : float = max_fuel_flow * throttle 41 | var exhaust_velocity : float = calculate_exhaust_velocity() 42 | #https://www.omnicalculator.com/physics/ideal-gas-law 43 | #var exhaust_pressure : float = 0.0 #will need to use fuel combustion expansion ratio 44 | var exhaust_mass_flow_rate : float = intake_mass_flow_rate + (intake_air_density * intake_fan_max_velocity * throttle * intake_area) + fuel_burn_rate 45 | 46 | #mass_flow_rate = mass/time 47 | #mass_flow_rate == density * velocity * area 48 | return (exhaust_mass_flow_rate * exhaust_velocity) - (intake_mass_flow_rate * intake_air_velocity)# + (exhaust_pressure - intake_air_pressure) * exit_area 49 | 50 | func calculate_exhaust_velocity() -> float: 51 | return exhaust_velocity 52 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="aero tutorial" 14 | run/main_scene="res://main.tscn" 15 | config/features=PackedStringArray("4.4", "Forward Plus") 16 | config/icon="res://icon.svg" 17 | 18 | [autoload] 19 | 20 | AeroUnits="*res://addons/godot_aerodynamic_physics/core/singletons/aero_units.gd" 21 | 22 | [editor_plugins] 23 | 24 | enabled=PackedStringArray("res://addons/godot_aerodynamic_physics/plugin.cfg") 25 | 26 | [input] 27 | 28 | yaw_left={ 29 | "deadzone": 0.5, 30 | "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":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null) 31 | ] 32 | } 33 | yaw_right={ 34 | "deadzone": 0.5, 35 | "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":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null) 36 | ] 37 | } 38 | throttle_up={ 39 | "deadzone": 0.5, 40 | "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":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) 41 | ] 42 | } 43 | throttle_down={ 44 | "deadzone": 0.5, 45 | "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":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) 46 | ] 47 | } 48 | brakes_up={ 49 | "deadzone": 0.5, 50 | "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":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null) 51 | ] 52 | } 53 | brakes_down={ 54 | "deadzone": 0.5, 55 | "events": [] 56 | } 57 | 58 | [physics] 59 | 60 | 3d/physics_engine="JoltPhysics3D" 61 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/point_3d/point_3d.gd: -------------------------------------------------------------------------------- 1 | # From https://github.com/addmix/godot_utils 2 | 3 | @tool 4 | extends MeshInstance3D 5 | class_name AeroDebugPoint3D 6 | 7 | @export var color := Color(1, 1, 1): 8 | set(x): 9 | color = x 10 | material.set_shader_parameter("_color", color) 11 | @export var width := 0.1: 12 | set(x): 13 | width = x 14 | material.set_shader_parameter("_width", width) 15 | @export var checker := true: 16 | set(x): 17 | checker = x 18 | material.set_shader_parameter("checker_pattern", checker) 19 | 20 | var material := ShaderMaterial.new() 21 | 22 | var _mesh : Array[Vector3] = [Vector3(0, -1, 0), Vector3(-1, 0, 0), Vector3(0, 1, 0), Vector3(1, 0, 0)] 23 | var _verticies : Array[Vector3] = [Vector3(0, -1, 0), #index 0 bottom point 24 | #go clockwise from -z position 25 | Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, 0, 1), Vector3(-1, 0, 0), #index 1-4 side poitns 26 | #top 27 | Vector3(0, 1, 0)] #index 5 top point 28 | 29 | # 5 30 | # 31 | # 1 32 | # 4 2 33 | # 3 34 | # 35 | # 0 36 | 37 | #bottom triangle order 38 | #021 39 | #032 40 | #043 41 | #014 42 | 43 | #top triangle order 44 | #512 45 | #523 46 | #534 47 | #541 48 | 49 | 50 | 51 | func _init(_color : Color = Color(), _width : float = 0.1, _checker : bool = true) -> void: 52 | material.shader = preload("./point_3d.gdshader") 53 | material.render_priority = 100 54 | 55 | color = _color 56 | width = _width 57 | checker = _checker 58 | 59 | var st := SurfaceTool.new() 60 | 61 | st.begin(Mesh.PRIMITIVE_TRIANGLES) 62 | st.set_material(material) 63 | #bottom 4 triangles 64 | #northeast 65 | st.add_vertex(_verticies[0]) 66 | st.add_vertex(_verticies[2]) 67 | st.add_vertex(_verticies[1]) 68 | #southeast 69 | st.add_vertex(_verticies[0]) 70 | st.add_vertex(_verticies[3]) 71 | st.add_vertex(_verticies[2]) 72 | #southwest 73 | st.add_vertex(_verticies[0]) 74 | st.add_vertex(_verticies[4]) 75 | st.add_vertex(_verticies[3]) 76 | 77 | st.add_vertex(_verticies[0]) 78 | st.add_vertex(_verticies[1]) 79 | st.add_vertex(_verticies[4]) 80 | 81 | #top 4 triangles 82 | st.add_vertex(_verticies[5]) 83 | st.add_vertex(_verticies[1]) 84 | st.add_vertex(_verticies[2]) 85 | 86 | st.add_vertex(_verticies[5]) 87 | st.add_vertex(_verticies[2]) 88 | st.add_vertex(_verticies[3]) 89 | 90 | st.add_vertex(_verticies[5]) 91 | st.add_vertex(_verticies[3]) 92 | st.add_vertex(_verticies[4]) 93 | 94 | st.add_vertex(_verticies[5]) 95 | st.add_vertex(_verticies[4]) 96 | st.add_vertex(_verticies[1]) 97 | 98 | st.generate_normals() 99 | mesh = st.commit() 100 | 101 | custom_aabb = AABB(Vector3.ZERO, Vector3(100, 100, 100)) 102 | -------------------------------------------------------------------------------- /main.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=10 format=3 uid="uid://x02vjyefplol"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://c3hcp4hfgwyr4" path="res://aero_body_3d.tscn" id="1_tjius"] 4 | [ext_resource type="PackedScene" uid="uid://smf6u3tuyv3x" path="res://pillars.tscn" id="2_bex3p"] 5 | 6 | [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_3pr32"] 7 | sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) 8 | ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) 9 | 10 | [sub_resource type="Sky" id="Sky_6ykpw"] 11 | sky_material = SubResource("ProceduralSkyMaterial_3pr32") 12 | 13 | [sub_resource type="Environment" id="Environment_cg6fh"] 14 | background_mode = 2 15 | sky = SubResource("Sky_6ykpw") 16 | tonemap_mode = 2 17 | glow_enabled = true 18 | 19 | [sub_resource type="BoxShape3D" id="BoxShape3D_mouss"] 20 | size = Vector3(1000, 1, 1000) 21 | 22 | [sub_resource type="FastNoiseLite" id="FastNoiseLite_8llo8"] 23 | frequency = 0.11 24 | 25 | [sub_resource type="NoiseTexture2D" id="NoiseTexture2D_ccx6r"] 26 | noise = SubResource("FastNoiseLite_8llo8") 27 | 28 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_uni3l"] 29 | albedo_color = Color(0.246682, 0.450129, 0.235876, 1) 30 | albedo_texture = SubResource("NoiseTexture2D_ccx6r") 31 | 32 | [node name="Main" type="Node3D"] 33 | 34 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."] 35 | environment = SubResource("Environment_cg6fh") 36 | 37 | [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] 38 | transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0) 39 | shadow_enabled = true 40 | 41 | [node name="StaticBody3D" type="StaticBody3D" parent="."] 42 | 43 | [node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"] 44 | shape = SubResource("BoxShape3D_mouss") 45 | 46 | [node name="CSGBox3D" type="CSGBox3D" parent="StaticBody3D"] 47 | size = Vector3(1000, 1, 1000) 48 | material = SubResource("StandardMaterial3D_uni3l") 49 | 50 | [node name="AeroBody3D" parent="." instance=ExtResource("1_tjius")] 51 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0126266, 784.214, 598.371) 52 | 53 | [node name="Pillars" parent="." instance=ExtResource("2_bex3p")] 54 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -28.3813, 1, 190.992) 55 | 56 | [node name="Pillars2" parent="." instance=ExtResource("2_bex3p")] 57 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 51.6474, 1, -109.429) 58 | 59 | [node name="Pillars3" parent="." instance=ExtResource("2_bex3p")] 60 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -30.4194, 1, -325.543) 61 | 62 | [node name="Pillars4" parent="." instance=ExtResource("2_bex3p")] 63 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 37.5745, 1, 432.927) 64 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_mover_3d/aero_mover_3d.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends AeroInfluencer3D 3 | class_name AeroMover3D 4 | 5 | const AeroTransformUtils = preload("../../../utils/transform_utils.gd") 6 | 7 | ## Rate (in meters per second) the AeroMover3D will move. 8 | @export var linear_motor : Vector3 = Vector3.ZERO 9 | ## Rate (in radians per second) the AeroMover3D will rotate. 10 | @export var angular_motor : Vector3 = Vector3.ZERO 11 | 12 | var _linear_velocity : Vector3 = Vector3.ZERO 13 | var _angular_velocity : Vector3 = Vector3.ZERO 14 | 15 | @onready var last_position : Vector3 = position 16 | @onready var last_rotation : Basis = basis 17 | 18 | func _get_configuration_warnings() -> PackedStringArray: 19 | var warnings := PackedStringArray([]) 20 | 21 | var influencers_have_automatic_control_enabled : bool = false 22 | for influencer : AeroInfluencer3D in aero_influencers: 23 | influencers_have_automatic_control_enabled = influencers_have_automatic_control_enabled or is_instance_valid(influencer.actuation_config) 24 | 25 | if influencers_have_automatic_control_enabled: 26 | warnings.append("1 or more child AeroInfluencer3D nodes have `enable_automatic_control` enabled. AeroMover3D may not be able to move them as needed.") 27 | 28 | return warnings 29 | 30 | func _update_transform_substep(substep_delta : float) -> void: 31 | _linear_velocity = Vector3.ZERO 32 | _angular_velocity = Vector3.ZERO 33 | #calculate velocity caused by moving the node 34 | _linear_velocity = (position - last_position) / substep_delta 35 | 36 | var axis_angle : Quaternion = AeroTransformUtils.quat_to_axis_angle(basis * last_rotation.inverse()) 37 | _angular_velocity = -Vector3(axis_angle.x, axis_angle.y, axis_angle.z) * axis_angle.w / substep_delta * basis 38 | #calculate angular velocity caused by rotating the node 39 | var rotation_quat : Quaternion = Quaternion(basis * last_rotation.inverse()) 40 | #_angular_velocity = rotation_quat.get_axis() * rotation_quat.get_angle() / substep_delta * basis 41 | 42 | #motors 43 | default_transform.origin += linear_motor * basis * substep_delta 44 | #rotate by angular velocity 45 | if not is_equal_approx(angular_motor.length_squared(), 0.0): 46 | default_transform.basis = default_transform.basis * Basis((angular_motor).normalized(), angular_motor.length() * substep_delta) 47 | 48 | _linear_velocity += linear_motor 49 | _angular_velocity += angular_motor 50 | 51 | last_position = position 52 | last_rotation = basis 53 | 54 | super._update_transform_substep(substep_delta) 55 | 56 | 57 | 58 | func get_linear_velocity() -> Vector3: 59 | return super.get_linear_velocity() + (_linear_velocity * global_basis.inverse()) 60 | 61 | func get_angular_velocity() -> Vector3: 62 | return super.get_angular_velocity() + (_angular_velocity * global_basis.inverse()) 63 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/vector_3d/vector_3d.gd: -------------------------------------------------------------------------------- 1 | # From https://github.com/addmix/godot_utils 2 | 3 | @tool 4 | extends MeshInstance3D 5 | class_name AeroDebugVector3D 6 | 7 | @export var value := Vector3.ZERO: 8 | set(x): 9 | value = x 10 | material.set_shader_parameter("_length", value.length()) 11 | 12 | #prevent error when value is 0 13 | var length_squared := value.length_squared() 14 | if is_equal_approx(length_squared, 0.0): 15 | #reset rotation 16 | basis = Basis() 17 | return 18 | 19 | var up = Vector3(0, 1, 0) 20 | var dot := value.dot(up) 21 | var dot_squared := dot * dot 22 | 23 | if is_equal_approx(dot_squared, length_squared): 24 | up = Vector3(1, 0, 0) 25 | 26 | transform.basis = transform.basis.looking_at(value, up) 27 | @export var color := Color(1, 1, 1): 28 | set(x): 29 | color = x 30 | material.set_shader_parameter("_color", color) 31 | @export var width := 0.1: 32 | set(x): 33 | width = x 34 | material.set_shader_parameter("_width", width) 35 | @export var checker := false: 36 | set(x): 37 | checker = x 38 | material.set_shader_parameter("checker_pattern", checker) 39 | 40 | var material := ShaderMaterial.new() 41 | 42 | var _mesh : Array[Vector3] = [Vector3(0, -1, 0), Vector3(-1, 0, 0), Vector3(0, 1, 0), Vector3(1, 0, 0)] 43 | var _verticies : Array[Vector3] = [Vector3(0, -1, 0), #index 0 bottom point 44 | #go clockwise from -z position 45 | Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, 0, 1), Vector3(-1, 0, 0), #index 1-4 side poitns 46 | #top 47 | Vector3(0, 1, 0)] #index 5 top point 48 | 49 | # 5 50 | # 51 | # 1 52 | # 4 2 53 | # 3 54 | # 55 | # 0 56 | 57 | #bottom triangle order 58 | #021 59 | #032 60 | #043 61 | #014 62 | 63 | #top triangle order 64 | #512 65 | #523 66 | #534 67 | #541 68 | 69 | func _init(_color : Color = Color(), _width : float = 0.1, _checker : bool = false) -> void: 70 | material.shader = preload("./vector_3d.gdshader") 71 | material.render_priority = 100 72 | 73 | color = _color 74 | width = _width 75 | checker = _checker 76 | 77 | var st := SurfaceTool.new() 78 | 79 | st.begin(Mesh.PRIMITIVE_TRIANGLES) 80 | st.set_material(material) 81 | #bottom 4 triangles 82 | st.add_vertex(_verticies[0]) 83 | st.add_vertex(_verticies[2]) 84 | st.add_vertex(_verticies[1]) 85 | 86 | st.add_vertex(_verticies[0]) 87 | st.add_vertex(_verticies[3]) 88 | st.add_vertex(_verticies[2]) 89 | 90 | st.add_vertex(_verticies[0]) 91 | st.add_vertex(_verticies[4]) 92 | st.add_vertex(_verticies[3]) 93 | 94 | st.add_vertex(_verticies[0]) 95 | st.add_vertex(_verticies[1]) 96 | st.add_vertex(_verticies[4]) 97 | 98 | #top 4 triangles 99 | st.add_vertex(_verticies[5]) 100 | st.add_vertex(_verticies[1]) 101 | st.add_vertex(_verticies[2]) 102 | 103 | st.add_vertex(_verticies[5]) 104 | st.add_vertex(_verticies[2]) 105 | st.add_vertex(_verticies[3]) 106 | 107 | st.add_vertex(_verticies[5]) 108 | st.add_vertex(_verticies[3]) 109 | st.add_vertex(_verticies[4]) 110 | 111 | st.add_vertex(_verticies[5]) 112 | st.add_vertex(_verticies[4]) 113 | st.add_vertex(_verticies[1]) 114 | 115 | st.generate_normals() 116 | mesh = st.commit() 117 | 118 | custom_aabb = AABB(Vector3.ZERO, Vector3(100, 100, 100)) 119 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/resources/flight_assist/flight_assist.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="FlightAssist" load_steps=14 format=3 uid="uid://bgiequn1eb1mo"] 2 | 3 | [ext_resource type="Resource" uid="uid://d8tp3u2ibcyh" path="res://addons/godot_aerodynamic_physics/core/resources/pids/altitude_pid.tres" id="1_r8yan"] 4 | [ext_resource type="Resource" uid="uid://dwdilr3th15ut" path="res://addons/godot_aerodynamic_physics/core/resources/pids/bank_angle_pid.tres" id="2_twr5t"] 5 | [ext_resource type="Resource" uid="uid://cqgo7v8jao5fi" path="res://addons/godot_aerodynamic_physics/core/resources/pids/direction_pitch_pid.tres" id="3_nvxre"] 6 | [ext_resource type="Resource" uid="uid://cjcwr3fy8sjjr" path="res://addons/godot_aerodynamic_physics/core/resources/pids/direction_roll_pid.tres" id="4_qjn0o"] 7 | [ext_resource type="Resource" uid="uid://cimkndlj6fknf" path="res://addons/godot_aerodynamic_physics/core/resources/pids/direction_yaw_pid.tres" id="5_m2x50"] 8 | [ext_resource type="Resource" uid="uid://dhn1xf3nrt43k" path="res://addons/godot_aerodynamic_physics/core/resources/pids/heading_pid.tres" id="6_e5trs"] 9 | [ext_resource type="Resource" uid="uid://dsteqcbn3er33" path="res://assets/vehicles/f16/resources/pids/pitch.tres" id="7_pjewb"] 10 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="7_tohlm"] 11 | [ext_resource type="Resource" uid="uid://dkjwrbs3c0wvr" path="res://addons/godot_aerodynamic_physics/core/resources/pids/roll_pid.tres" id="8_raf21"] 12 | [ext_resource type="Script" uid="uid://cdp8dc63ytxx1" path="res://addons/godot_aerodynamic_physics/core/flight_assist.gd" id="9_fw51e"] 13 | [ext_resource type="Resource" uid="uid://ck80cone11d5w" path="res://addons/godot_aerodynamic_physics/core/resources/pids/speed_pid.tres" id="10_f8rdn"] 14 | [ext_resource type="Resource" uid="uid://c266ngwax13x5" path="res://addons/godot_aerodynamic_physics/core/resources/pids/yaw_pid.tres" id="11_gb5dk"] 15 | 16 | [sub_resource type="Resource" id="Resource_qcbki"] 17 | script = ExtResource("7_tohlm") 18 | p = 10.0 19 | i = 0.0 20 | d = 0.0 21 | clamp_integral = false 22 | min_integral = -1.0 23 | max_integral = 1.0 24 | 25 | [resource] 26 | resource_local_to_scene = true 27 | script = ExtResource("9_fw51e") 28 | max_angular_rates = Vector3(2, 1, 6) 29 | enable_flight_assist_x = true 30 | pitch_assist_pid = ExtResource("7_pjewb") 31 | enable_flight_assist_y = true 32 | yaw_assist_pid = ExtResource("11_gb5dk") 33 | enable_flight_assist_z = true 34 | roll_assist_pid = ExtResource("8_raf21") 35 | enable_g_limiter = true 36 | g_limit = 9.0 37 | negative_g_limit = 3.0 38 | enable_aoa_limiter = true 39 | aoa_limit_start = 22.0 40 | aoa_limit_end = 25.0 41 | enable_control_adjustment = false 42 | tuned_airspeed = 100.0 43 | min_accounted_airspeed = 75.0 44 | tuned_density = 1.222 45 | min_accounted_air_density = 0.1 46 | enable_bank_angle_assist = false 47 | bank_angle_target = 0.0 48 | bank_angle_pid = ExtResource("2_twr5t") 49 | enable_heading_hold = false 50 | heading_target = 0.0 51 | heading_pid = ExtResource("6_e5trs") 52 | enable_inclination_hold = false 53 | inclination_target = 0.0 54 | inclination_pid = SubResource("Resource_qcbki") 55 | enable_speed_hold = false 56 | speed_target = 0.0 57 | speed_pid = ExtResource("10_f8rdn") 58 | enable_altitude_hold = false 59 | altitude_target = 0.0 60 | altitude_pid = ExtResource("1_r8yan") 61 | enable_target_direction = false 62 | direction_target = Vector3(0, 0, 0) 63 | direction_pitch_pid = ExtResource("3_nvxre") 64 | direction_yaw_pid = ExtResource("5_m2x50") 65 | direction_roll_pid = ExtResource("4_qjn0o") 66 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/control_configs/aero_control_config.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Resource 3 | class_name AeroControlConfig 4 | 5 | const AeroMathUtils = preload("../../utils/math_utils.gd") 6 | 7 | 8 | ## If enabled, this AeroControlComponent will read player inputs 9 | @export var use_bindings : bool = true 10 | ## Control input value read from player controls. 11 | @export var input : float = 0.0 12 | var command : float = 0.0 13 | var cumulative_input : float = 0.0 14 | var cumulative_command : float = 0.0 15 | 16 | @export_category("Limits") 17 | ## Minimum control limit 18 | @export var min_limit : float = -1.0 19 | ## Maximum control limit 20 | @export var max_limit : float = 1.0 21 | 22 | ## InputMap action for positive control. 23 | @export var positive_event : StringName = "" 24 | ## InputMap action for negative control. 25 | @export var negative_event : StringName = "" 26 | ## If enabled, input will change smoothly at `smoothing_rate` per second. 27 | @export var enable_smoothing : bool = false 28 | @export var smoothing_rate : float = 1.0 29 | ## Cumulative inputs don't reset to 0 automatically. This would be similar to trim, or throttle that stays in place when not interacted with. 30 | @export var cumulative_positive_event : StringName = "" 31 | ## Cumulative inputs don't reset to 0 automatically. This would be similar to trim, or throttle that stays in place when not interacted with. 32 | @export var cumulative_negative_event : StringName = "" 33 | ## Rate at which cumulative inputs change control value. 34 | @export var cumulative_rate : float = 1.0 35 | @export_exp_easing("inout") var easing := 1.0 36 | 37 | @export_subgroup("") 38 | @export_group("") 39 | 40 | func _init(_min_limit : float = -1.0, _max_limit : float = 1.0, default_increase_event : StringName = "", default_decrease_event : StringName = "") -> void: 41 | min_limit = _min_limit 42 | max_limit = _max_limit 43 | positive_event = default_increase_event 44 | negative_event = default_decrease_event 45 | pass 46 | 47 | func update(delta : float) -> float: 48 | var total_input : float = input + get_axis(negative_event, positive_event) 49 | var total_cumulative_input : float = cumulative_input + get_axis(cumulative_negative_event, cumulative_positive_event) 50 | 51 | cumulative_command = update_cumulative_control(delta, total_cumulative_input, cumulative_command, cumulative_rate, min_limit, max_limit) 52 | 53 | var total_desired_command : float = clamp(total_input + cumulative_command, min_limit, max_limit) 54 | 55 | #undo easing 56 | command = AeroMathUtils.improved_ease(command, 1.0 / easing) 57 | #input smoothing 58 | command = calculate_smoothing(command, total_desired_command, enable_smoothing, smoothing_rate, delta) 59 | #add easing 60 | command = AeroMathUtils.improved_ease(command, easing) 61 | 62 | return command 63 | 64 | static func update_cumulative_control(delta : float, input : float, current_value : float, cumulative_rate : float = 1.0, min_value : float = -1.0, max_value : float = 1.0) -> float: 65 | current_value += input * cumulative_rate * delta 66 | return clamp(current_value, min_value, max_value) 67 | 68 | static func calculate_smoothing(current_value : float, target : float, smoothing_enabled : bool = false, smoothing_rate : float = 1.0, delta : float = 1.0/60.0) -> float: 69 | if smoothing_enabled: 70 | return move_toward(current_value, target, smoothing_rate * delta) 71 | else: 72 | return target 73 | 74 | func get_axis(negative_event : StringName, positive_event : StringName) -> float: 75 | if not use_bindings: 76 | return 0.0 77 | 78 | var input : float = 0.0 79 | 80 | if not negative_event == "": 81 | input -= Input.get_action_strength(negative_event) 82 | if not positive_event == "": 83 | input += Input.get_action_strength(positive_event) 84 | 85 | return input 86 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/aero_surface_3d.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends AeroInfluencer3D 3 | class_name AeroSurface3D 4 | 5 | ## Config resource used to define the size of the AeroSurface3D node. 6 | @export var wing_config : AeroSurfaceConfig = AeroSurfaceConfig.new(): 7 | set(value): 8 | wing_config = value 9 | if wing_config == null: 10 | return 11 | if not wing_config.is_connected("changed", update_gizmos): 12 | wing_config.changed.connect(update_gizmos) 13 | update_gizmos() 14 | 15 | @export_group("Debug") 16 | ## Controls visibility of the AeroSurface3D's lift debug vector 17 | @export var show_lift : bool = true 18 | ## Controls visibility of the AeroSurface3D's drag debug vector 19 | @export var show_drag : bool = true 20 | 21 | var angle_of_attack : float = 0.0 22 | var sweep_angle := 0.0 23 | 24 | var area : float = wing_config.chord * wing_config.span 25 | var projected_wing_area : float = 0.0 26 | 27 | var lift_direction := Vector3.ZERO 28 | var lift_force : float = 0.0 29 | var drag_direction := Vector3.ZERO 30 | var drag_force : float = 0.0 31 | 32 | var _current_lift : Vector3 33 | var _current_drag : Vector3 34 | 35 | var lift_debug_vector : AeroDebugVector3D 36 | var drag_debug_vector : AeroDebugVector3D 37 | 38 | func _init(): 39 | super._init() 40 | #initialize debug vectors 41 | lift_debug_vector = AeroDebugVector3D.new(Color(0, 0, 1)) 42 | lift_debug_vector.visible = false 43 | lift_debug_vector.sorting_offset = 0.02 44 | add_child(lift_debug_vector, INTERNAL_MODE_FRONT) 45 | 46 | drag_debug_vector = AeroDebugVector3D.new(Color(1, 0, 0)) 47 | drag_debug_vector.visible = false 48 | drag_debug_vector.sorting_offset = 0.01 49 | add_child(drag_debug_vector, INTERNAL_MODE_FRONT) 50 | 51 | func _enter_tree() -> void: 52 | super._enter_tree() 53 | #initialize signal connections from resources 54 | if wing_config: 55 | if not wing_config.is_connected("changed", update_gizmos): 56 | wing_config.changed.connect(update_gizmos) 57 | update_gizmos() 58 | 59 | func _calculate_forces(substep_delta : float = 0.0) -> PackedVector3Array: 60 | var force_and_torque : PackedVector3Array = super._calculate_forces(substep_delta) 61 | #calculate some common values, some necessary for debugging 62 | #air velocity in local space 63 | angle_of_attack = global_basis.y.angle_to(-world_air_velocity) - (PI / 2.0) 64 | sweep_angle = global_basis.x.angle_to(-world_air_velocity) - (PI / 2.0) 65 | 66 | area = wing_config.chord * wing_config.span 67 | projected_wing_area = abs(wing_config.span * wing_config.chord * sin(angle_of_attack)) 68 | 69 | drag_direction = world_air_velocity.normalized() 70 | var right_facing_air_vector : Vector3 = world_air_velocity.cross(-global_transform.basis.y).normalized() 71 | lift_direction = drag_direction.cross(right_facing_air_vector).normalized() 72 | 73 | return force_and_torque 74 | 75 | func update_debug_visibility(_show_debug : bool = false) -> void: 76 | super.update_debug_visibility(_show_debug) 77 | #check that debug vectors exist 78 | if lift_debug_vector and drag_debug_vector: 79 | lift_debug_vector.visible = show_debug and show_lift 80 | drag_debug_vector.visible = show_debug and show_drag 81 | 82 | func update_debug_scale(_scale : float, _width : float) -> void: 83 | super.update_debug_scale(_scale, _width) 84 | 85 | if lift_debug_vector: 86 | lift_debug_vector.width = debug_width 87 | if drag_debug_vector: 88 | drag_debug_vector.width = debug_width 89 | 90 | func update_debug_vectors() -> void: 91 | super.update_debug_vectors() 92 | 93 | #check that debug vectors exist 94 | if !lift_debug_vector or !drag_debug_vector: 95 | return 96 | 97 | #don't update invisible vectors 98 | if lift_debug_vector.visible: 99 | lift_debug_vector.value = global_transform.basis.inverse() * AeroMathUtils.v3log_with_base(_current_lift, 2.0) * debug_scale 100 | if drag_debug_vector.visible: 101 | drag_debug_vector.value = global_transform.basis.inverse() * AeroMathUtils.v3log_with_base(_current_drag, 2.0) * debug_scale 102 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | const PluginUtils = preload("./utils/plugin_utils.gd") 5 | 6 | var path := PluginUtils.get_plugin_path("Godot Aerodynamic Physics") 7 | 8 | #icons 9 | const object_icon = preload("./icons/object.svg") 10 | const node_icon = preload("./icons/node.svg") 11 | const node2d_icon = preload("./icons/node2d.svg") 12 | const node3d_icon = preload("./icons/node3d.svg") 13 | const aerobody3d_icon = preload("./icons/AeroBody3D.svg") 14 | const aeroinfluencer3d_icon = preload("./icons/AeroInfluencer3D.svg") 15 | const aerosurface3d_icon = preload("./icons/AeroSurface3D.svg") 16 | const aeropropeller3d_icon = preload("./icons/AeroPropeller3D.svg") 17 | 18 | #plugin gizmos 19 | const gizmo_plugin = preload("./core/spatial_gizmo/aero_surface_gizmo.gd") 20 | var gizmo_plugin_instance = gizmo_plugin.new() 21 | 22 | #nodes 23 | const aero_body_3d = preload("./core/aero_body_3d.gd") 24 | const aero_influencer_3d = preload("./core/aero_influencer_3d/aero_influencer_3d.gd") 25 | const aero_mover_3d = preload("./core/aero_influencer_3d/aero_mover_3d/aero_mover_3d.gd") 26 | const aero_propeller_3d = preload("./core/aero_influencer_3d/aero_rotator_3d/aero_propeller_3d.gd") 27 | const aero_variable_propeller_3d = preload("./core/aero_influencer_3d/aero_rotator_3d/aero_variable_propeller_3d.gd") 28 | const aero_cyclic_propeller_3d = preload("./core/aero_influencer_3d/aero_rotator_3d/aero_cyclic_propeller_3d.gd") 29 | const aero_surface_3d = preload("./core/aero_influencer_3d/aero_surface_3d/aero_surface_3d.gd") 30 | const aero_surface_config = preload("./core/aero_influencer_3d/aero_surface_3d/aero_surface_config.gd") 31 | const manual_aero_surface_3d = preload("./core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_3d.gd") 32 | const manual_aero_surface_config = preload("./core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_config.gd") 33 | #const procedural_aero_surface_3d = preload("./core/aero_influencer_3d/aero_surface_3d/procedural_aero_surface_3d/procedural_aero_surface_3d.gd") 34 | #const procedural_aero_surface_config = preload("./core/aero_influencer_3d/aero_surface_3d/procedural_aero_surface_3d/procedural_aero_surface_config.gd") 35 | const flight_assist = preload("./core/flight_assist.gd") 36 | 37 | func _enter_tree(): 38 | ifndef("physics/3d/aerodynamics/substeps", 1) 39 | add_autoload_singleton("AeroUnits", path + "/core/singletons/aero_units.gd") 40 | add_node_3d_gizmo_plugin(gizmo_plugin_instance) 41 | 42 | add_custom_type("AeroBody3D", "VehicleBody3D", aero_body_3d, aerobody3d_icon) 43 | add_custom_type("AeroInfluencer3D", "Node3D", aero_influencer_3d, aeroinfluencer3d_icon) 44 | add_custom_type("AeroMover3D", "Node3D", aero_mover_3d, aeroinfluencer3d_icon) 45 | add_custom_type("AeroPropeller3D", "Node3D", aero_propeller_3d, aeropropeller3d_icon) 46 | add_custom_type("AeroVariablePropeller3D", "Node3D", aero_variable_propeller_3d, aeropropeller3d_icon) 47 | add_custom_type("AeroCyclicPropeller3D", "Node3D", aero_cyclic_propeller_3d, aeropropeller3d_icon) 48 | add_custom_type("AeroSurface3D", "Node3D", aero_surface_3d, aerosurface3d_icon) 49 | add_custom_type("AeroSurfaceConfig", "Resource", aero_surface_config, object_icon) 50 | add_custom_type("ManualAeroSurface3D", "Node3D", manual_aero_surface_3d, aerosurface3d_icon) 51 | add_custom_type("ManualAeroSurfaceConfig", "Resource", manual_aero_surface_config, object_icon) 52 | add_custom_type("FlightAssist", "Resource", flight_assist, object_icon) 53 | 54 | func _exit_tree(): 55 | remove_custom_type("AeroBody3D") 56 | remove_custom_type("AeroInfluencer3D") 57 | remove_custom_type("AeroMover3D") 58 | remove_custom_type("AeroPropeller3D") 59 | remove_custom_type("AeroVariablePropeller3D") 60 | remove_custom_type("AeroCyclicPropeller3D") 61 | remove_custom_type("AeroSurface3D") 62 | remove_custom_type("AeroSurfaceConfig") 63 | remove_custom_type("ManualAeroSurface3D") 64 | remove_custom_type("ManualAeroSurfaceConfig") 65 | remove_custom_type("FlightAssist") 66 | 67 | remove_autoload_singleton("AeroUnits") 68 | remove_node_3d_gizmo_plugin(gizmo_plugin_instance) 69 | 70 | static func ifndef(setting : String, default_value : Variant) -> Variant: 71 | if not ProjectSettings.has_setting(setting): 72 | ProjectSettings.set_setting(setting, default_value) 73 | if not default_value == null: 74 | ProjectSettings.set_initial_value(setting, default_value) 75 | 76 | return ProjectSettings.get_setting(setting) 77 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/node_utils.gd: -------------------------------------------------------------------------------- 1 | # From https://github.com/addmix/godot_utils 2 | 3 | #returns signal connection error, if any. Mainly for plugin nodes 4 | static func connect_signal_safe(node : Node, _signal : StringName, callable : Callable, flags : int = 0, silence_warning : bool = false) -> int: 5 | if not node.has_signal(_signal): 6 | if not silence_warning: 7 | push_warning("Signal does not exist on object") 8 | return ERR_DOES_NOT_EXIST 9 | elif node.is_connected(_signal, callable): 10 | if not silence_warning: 11 | push_warning("Signal already connected") 12 | return ERR_INVALID_PARAMETER 13 | 14 | return node.connect(_signal, callable, flags) 15 | 16 | 17 | static func get_descendants(node : Node, include_internal : bool = false) -> Array[Node]: 18 | var children : Array[Node] = node.get_children(include_internal) 19 | 20 | #array for storing all descendants 21 | var arr := [] 22 | 23 | for i in range(children.size()): 24 | arr.append(children[i]) 25 | #add child's array contents to self's array 26 | arr += get_descendants(children[i]) 27 | 28 | return arr 29 | 30 | static func get_first_child_of_type(node : Node, type) -> Node: 31 | for child in node.get_children(): 32 | if is_instance_of(child, type): 33 | return child 34 | return null 35 | 36 | static func get_first_descandant_of_type(node : Node, type, include_internal : bool = false) -> Node: 37 | if is_instance_of(node, type): 38 | return node 39 | 40 | for child in node.get_children(include_internal): 41 | #returns node or null 42 | var descendant : Node = get_first_descandant_of_type(child, type, include_internal) 43 | #if not null, we have found a match 44 | if descendant: 45 | return descendant 46 | 47 | return null 48 | 49 | static func get_descendants_of_type(node : Node, type, include_internal : bool = false) -> Array[Node]: 50 | var descandants_of_type : Array[Node] = [] 51 | for child in node.get_children(include_internal): 52 | if is_instance_of(child, type): 53 | descandants_of_type.append(child) 54 | descandants_of_type = descandants_of_type + get_descendants_of_type(child, type) 55 | return descandants_of_type 56 | 57 | static func has_node_of_type(node : Node, type) -> bool: 58 | for child in node.get_children(): 59 | if is_instance_of(child, type): 60 | return true 61 | return false 62 | 63 | 64 | 65 | static func get_first_parent_of_type(node : Node, type) -> Node: 66 | var parent := node.get_parent() 67 | if parent == null: 68 | return null 69 | elif is_instance_of(parent, type): 70 | return parent 71 | else: 72 | return get_first_parent_of_type(parent, type) 73 | 74 | #extremely slow 75 | static func get_first_parent_of_type_with_string(node : Node, type : String) -> Node: 76 | var parent := node.get_parent() 77 | if parent == null: 78 | return null 79 | elif is_instance_of_string(parent, type): 80 | return parent 81 | else: 82 | return get_first_parent_of_type_with_string(parent, type) 83 | 84 | 85 | static func get_first_parent_with_name(node : Node, _name : String) -> Node: 86 | var parent := node.get_parent() 87 | if parent == null: 88 | return null 89 | elif parent.name == _name: 90 | return parent 91 | else: 92 | return get_first_parent_with_name(parent, _name) 93 | 94 | #extremely slow 95 | static func is_instance_of_string(obj : Object, given_class_name : String) -> bool: 96 | if ClassDB.class_exists(given_class_name): 97 | # We have a build in class 98 | return obj.is_class(given_class_name) 99 | else: 100 | # We don't have a build in class 101 | # It must be a script class 102 | var class_script : Script 103 | # Assume it is a script path and try to load it 104 | if ResourceLoader.exists(given_class_name): 105 | class_script = load(given_class_name) as Script 106 | 107 | if class_script == null: 108 | # Assume it is a class name and try to find it 109 | for x in ProjectSettings.get_global_class_list(): 110 | 111 | if str(x["class"]) == given_class_name: 112 | class_script = load(str(x["path"])) 113 | break 114 | 115 | if class_script == null: 116 | # Unknown class 117 | return false 118 | 119 | # Get the script of the object and try to match it 120 | var check_script : Script = obj.get_script() 121 | while check_script != null: 122 | if check_script == class_script: 123 | return true 124 | 125 | check_script = check_script.get_base_script() 126 | 127 | # Match not found 128 | return false 129 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_rotator_3d/aero_propeller_3d.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends AeroMover3D 3 | class_name AeroPropeller3D 4 | 5 | ## If enabled, the AeroPropeller3D will automatically duplicate and arrange `propeller` to form `propeller_amount` of radially symmetric blades. 6 | @export var do_propeller_setup : bool = true 7 | 8 | ## Amount of propeller blades to generate. See `do_propeller_setup` 9 | @export_range(1, 128) var propeller_amount : int = 2: 10 | set(x): 11 | update_configuration_warnings() 12 | propeller_amount = x 13 | update_propeller_amount() 14 | ## Propeller blade that will be duplicated and arranged. See `do_propeller_setup` 15 | @export var propeller_blade : AeroInfluencer3D: 16 | set(x): 17 | update_configuration_warnings() 18 | if propeller_blade == x: 19 | return 20 | 21 | propeller_blade = x 22 | 23 | if Engine.is_editor_hint(): 24 | return 25 | 26 | if is_node_ready(): 27 | #remove all propellers 28 | while propeller_instances.size() > 0: 29 | var instance : AeroInfluencer3D = propeller_instances.pop_back() 30 | if instance: 31 | instance.queue_free() 32 | 33 | #reinitialize instances array 34 | propeller_instances = [propeller_blade] 35 | update_propeller_amount() 36 | var propeller_instances : Array[AeroInfluencer3D] = [] 37 | 38 | @export_group("Speed Control") 39 | @export var propeller_speed_control_config := create_speed_control_config() 40 | func create_speed_control_config() -> AeroInfluencerControlConfig: 41 | var config := AeroInfluencerControlConfig.new() 42 | config.max_value.y = 100.0 43 | config.throttle_config = AeroInfluencerControlAxisConfig.new(Vector3.ONE) 44 | return config 45 | 46 | # Called when the node enters the scene tree for the first time. 47 | func _ready(): 48 | super._ready() 49 | update_configuration_warnings() 50 | if not Engine.is_editor_hint(): 51 | update_propeller_amount() 52 | 53 | if propeller_speed_control_config: 54 | propeller_speed_control_config = propeller_speed_control_config.duplicate(true) 55 | 56 | func _get_configuration_warnings() -> PackedStringArray: 57 | var arr : PackedStringArray = super._get_configuration_warnings() 58 | if not propeller_blade: 59 | arr.append("Propeller has not been assigned, or is not a valid instance. Make sure you have assigned an AeroInfluencer3D as a propeller.") 60 | 61 | return arr 62 | 63 | func update_propeller_amount() -> void: 64 | if not do_propeller_setup: 65 | for wing in get_children(): 66 | if wing is AeroInfluencer3D: 67 | propeller_instances.append(wing) 68 | return 69 | if Engine.is_editor_hint() or not is_node_ready(): 70 | return 71 | if not propeller_blade: 72 | push_warning("No propeller defined. Aborting...") 73 | 74 | var change_in_amount : int = propeller_amount - propeller_instances.size() 75 | 76 | if not is_instance_valid(propeller_blade): 77 | push_warning("Propeller blade not assigned.") 78 | return 79 | 80 | #return early 81 | if change_in_amount == 0: 82 | return 83 | 84 | #instance new propellers 85 | elif change_in_amount > 0: 86 | for i : int in change_in_amount: 87 | var new_prop : AeroInfluencer3D = propeller_blade.duplicate() 88 | add_child(new_prop) 89 | propeller_instances.append(new_prop) 90 | 91 | #remove propellers 92 | elif change_in_amount < 0: 93 | for i : int in abs(change_in_amount): 94 | propeller_instances.pop_back().queue_free() 95 | 96 | update_propeller_transforms() 97 | 98 | func update_propeller_transforms() -> void: 99 | var base_transform : Transform3D = propeller_blade.default_transform 100 | 101 | for i in propeller_amount - 1: 102 | var prop_index = i + 1 #we already have 1 surface, so we add 1 103 | var new_blade : AeroInfluencer3D = propeller_instances[prop_index] 104 | 105 | new_blade.default_transform = base_transform.rotated(Vector3(0, 1, 0), deg_to_rad(360.0 / propeller_amount) * prop_index) 106 | new_blade.transform = new_blade.default_transform 107 | new_blade.default_transform.origin = base_transform.origin.rotated(Vector3(0, 1, 0), deg_to_rad(360.0 / propeller_amount) * prop_index) 108 | new_blade.position = new_blade.default_transform.origin 109 | 110 | func _update_control_transform(substep_delta : float) -> void: 111 | super._update_control_transform(substep_delta) 112 | 113 | var propeller_velocity_value := Vector3.ZERO 114 | if propeller_speed_control_config: 115 | propeller_velocity_value = apply_control_commands_to_config(substep_delta, propeller_speed_control_config) 116 | 117 | angular_motor = propeller_speed_control_config.update(substep_delta) 118 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/utils/math_utils.gd: -------------------------------------------------------------------------------- 1 | # From https://github.com/addmix/godot_utils 2 | 3 | const E := 2.718281828459045 4 | 5 | 6 | 7 | #type-less functions 8 | 9 | 10 | 11 | static func toggle(condition : bool, _true : Variant, _false : Variant) -> Variant: 12 | return float(condition) * _true + float(!condition) * _false 13 | 14 | 15 | 16 | #float math functions 17 | 18 | 19 | 20 | static func bias(x : float, bias : float) -> float: 21 | var f : float = 1 - bias 22 | var k : float = f * f * f 23 | return (x * k) / (x * k - x + 1) 24 | 25 | static func improved_ease(value : float, curve : float = 1.0) -> float: 26 | return ease(abs(value), curve) * sign(value) 27 | 28 | #branchlessly toggles a float between two values given a condition 29 | static func float_toggle(condition : bool, _true : float, _false : float) -> float: 30 | return float(condition) * _true + float(!condition) * _false 31 | 32 | static func log_with_base(value : float, base : float) -> float: 33 | return log(value) / log(base) 34 | 35 | static func mix(a : float, b : float, amount : float) -> float: 36 | return (a - amount) * a + amount * b 37 | 38 | #deprecated, use GDScript global move_toward() function 39 | static func move_to(position : float, target : float, speed : float = 1.0) -> float: 40 | var direction : float = sign(target - position) 41 | var new_position = position + direction * speed 42 | var new_direction : float = sign(target - new_position) 43 | 44 | return float_toggle(direction == new_direction, new_position, target) 45 | 46 | #smooth minimum 47 | static func polynomial_smin(a : float, b : float, k : float =0.1) -> float: 48 | var h = clamp(0.5 + 0.5 * (a - b) / k, 0.0, 1.0) 49 | return mix(a, b, h) - k * h * (1.0 - h) 50 | 51 | static func sigmoid(x : float, e : float = E) -> float: 52 | return pow(e, x) / pow(e, x) + 1.0 53 | 54 | 55 | 56 | # 57 | 58 | 59 | 60 | static func v3log_with_base(vector : Vector3, base : float) -> Vector3: 61 | return vector.normalized() * log_with_base(vector.length() + 1, base) 62 | 63 | 64 | 65 | #matrix math stuff, very inefficient stuff 66 | 67 | 68 | 69 | #unused 70 | static func check_squareness(a : Array) -> void: 71 | if a.size() != a[0].size(): 72 | push_error("Matrix not square") 73 | 74 | static func identity_matrix(n : int) -> Array: 75 | var matrix := [] 76 | 77 | for y in range(n): 78 | var row := [] 79 | for x in range(n): 80 | row.append(int(y == x)) 81 | matrix.append(row) 82 | 83 | return matrix 84 | 85 | #https://integratedmlai.com/matrixinverse/ 86 | static func inverse(a : Array) -> Array: 87 | var n := a.size() 88 | var am := a.duplicate(true) 89 | var I = identity_matrix(n) 90 | var im = I.duplicate(true) 91 | 92 | for fd in range(n): 93 | var fdScaler : float = 1.0 / am[fd][fd] 94 | 95 | for j in range(n): 96 | am[fd][j] *= fdScaler 97 | im[fd][j] *= fdScaler 98 | 99 | for i in range(n): 100 | if i == fd: 101 | continue 102 | 103 | var crScaler : float = am[i][fd] 104 | for j in range(n): 105 | am[i][j] = am[i][j] - crScaler * am[fd][j] 106 | im[i][j] = im[i][j] - crScaler * im[fd][j] 107 | 108 | return im 109 | 110 | static func multiply(a : Array, b : Array) -> Array: 111 | var matrix := zero_matrix(a.size(), b[0].size()) 112 | 113 | for i in range(a.size()): 114 | for j in range(b[0].size()): 115 | for k in range(a[0].size()): 116 | matrix[i][j] = matrix[i][j] + a[i][k] * b[k][j] 117 | return matrix 118 | 119 | #plane intersection function 120 | #http://tbirdal.blogspot.com/2016/10/a-better-approach-to-plane-intersection.html 121 | static func ray_plane_intersection(p1 : Vector3, n1 : Vector3, p2 : Vector3, n2 : Vector3, p0 : Vector3) -> Array[Vector3]: 122 | 123 | var M := [ 124 | [2, 0, 0, n1.x, n2.x], 125 | [0, 2, 0, n1.y, n2.y], 126 | [0, 0, 2, n1.z, n2.z], 127 | [n1.x, n1.y, n1.z, 0, 0], 128 | [n2.x, n2.y, n2.z, 0, 0]] 129 | 130 | var bx := p1 * n1 131 | var by := p2 * n2 132 | 133 | var b4 := bx.x + bx.y + bx.z 134 | var b5 := by.x + by.y + by.z 135 | 136 | var b = [ 137 | [2*p0.x], 138 | [2*p0.y], 139 | [2*p0.z], 140 | [b4], 141 | [b5]] 142 | 143 | # warning-ignore:unused_variable 144 | var x := multiply(inverse(M), b) 145 | var p = Vector3(x[0][0], x[0][1], x[0][2]) 146 | var n = n1.cross(n2) 147 | return [p, n] 148 | 149 | #matrix multiplication funcs 150 | #https://godotengine.org/qa/41768/matrix-matrix-vector-multiplication 151 | static func zero_matrix(nX : int, nY : int) -> Array: 152 | var matrix := [] 153 | for x in range(nX): 154 | matrix.append([]) 155 | # warning-ignore:unused_variable 156 | for y in range(nY): 157 | matrix[x].append(0) 158 | return matrix 159 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/singletons/aero_units.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Node 3 | 4 | func _init() -> void: 5 | recalculate_curves() 6 | 7 | #air is 1.402 8 | @export var ratio_of_specific_heat : float = 1.402 9 | 10 | #http://www.braeunig.us/space/atmos.htm 11 | 12 | var valid_keys : Array = ["temperature", "pressure", "density", "mach"] 13 | 14 | @export var altitude_values : Dictionary[float, Dictionary] = { 15 | 0.0: {"temperature": 288.0, "pressure": 101325.0, "density": 1.225}, 16 | 2000.0: {"temperature": 275.0, "pressure": 79495.2, "density": 1.0065}, 17 | 5000.0: {"temperature": 255.0, "pressure": 54019.9, "density": 0.7361}, 18 | 10000.0: {"temperature": 223.0, "pressure": 26436.3, "density": 0.4127}, 19 | 15000.0: {"temperature": 216.0, "pressure": 12044.6, "density": 0.1937}, 20 | 20000.0: {"temperature": 216.0, "pressure": 5474.8, "density": 0.08803}, 21 | 30000.0: {"temperature": 226.0, "pressure": 1171.8, "density": 0.01803}, 22 | 40000.0: {"temperature": 251.0, "pressure": 277.5, "density": 0.003851}, 23 | 50000.0: {"temperature": 270.0, "pressure": 75.9, "density": .0009775}, 24 | 60000.0: {"temperature": 245.0, "pressure": 20.3, "density": .0002883}, 25 | 70000.0: {"temperature": 217.0, "pressure": 4.6, "density": .00007424}, 26 | 80000.0: {"temperature": 196.0, "pressure": 0.89, "density": .0000157}, 27 | }: 28 | set(x): 29 | altitude_values = x 30 | recalculate_curves() 31 | 32 | 33 | #get the min/max value for the given key 34 | func get_bounds(key : String) -> Array: 35 | var min : float 36 | var max : float 37 | 38 | if key == "altitude": 39 | min = altitude_values.keys()[0] 40 | max = altitude_values.keys()[0] 41 | for altitude : float in altitude_values.keys(): 42 | if altitude < min: 43 | min = altitude 44 | if altitude > max: 45 | max = altitude 46 | 47 | return [min, max] 48 | 49 | min = altitude_values[altitude_values.keys()[0]][key] 50 | max = altitude_values[altitude_values.keys()[0]][key] 51 | for altitude : float in altitude_values.keys(): 52 | var value : float = altitude_values[altitude][key] 53 | 54 | if value < min: 55 | min = value 56 | if value > max: 57 | max = value 58 | 59 | return [min, max] 60 | 61 | #points are added dynamically during _init() 62 | var temperature_at_altitude_curve := Curve.new() 63 | func get_temp_at_altitude(altitude : float) -> float: 64 | return temperature_at_altitude_curve.sample_baked(altitude) 65 | 66 | #points are added dynamically during _init() 67 | var pressure_at_altitude_curve := Curve.new() 68 | func get_pressure_at_altitude(altitude : float) -> float: 69 | return pressure_at_altitude_curve.sample_baked(altitude) 70 | 71 | #points are added dynamically during _init() 72 | var density_at_altitude_curve := Curve.new() 73 | func get_density_at_altitude(altitude : float) -> float: 74 | return density_at_altitude_curve.sample_baked(altitude) 75 | 76 | #points are added dynamically during _init() 77 | var mach_at_altitude_curve := Curve.new() 78 | func get_mach_at_altitude(altitude : float) -> float: 79 | return mach_at_altitude_curve.sample_baked(altitude) 80 | 81 | 82 | 83 | func recalculate_curves() -> void: 84 | var altitude_bounds : Array = get_bounds("altitude") 85 | 86 | #mach is dynamically calculated based on pressure and density and added to the dataset 87 | for altitude : float in altitude_values.keys(): 88 | altitude_values[altitude]["mach"] = get_speed_of_sound_at_pressure_and_density(altitude_values[altitude]["pressure"], altitude_values[altitude]["density"]) 89 | 90 | #dynamically configure the min/max domain for curves 91 | for key : String in valid_keys: 92 | var bounds : Array = get_bounds(key) 93 | var curve_name : String = key + "_at_altitude_curve" 94 | var curve : Curve = get(curve_name) 95 | if not curve: 96 | continue 97 | 98 | curve.min_domain = altitude_bounds[0] 99 | curve.max_domain = altitude_bounds[1] 100 | curve.min_value = bounds[0] 101 | curve.max_value = bounds[1] 102 | 103 | for altitude : float in altitude_values.keys(): 104 | 105 | curve.add_point(Vector2(altitude, altitude_values[altitude][key]), 0.0, 0.0, Curve.TANGENT_LINEAR, Curve.TANGENT_LINEAR) 106 | #save curve to disk for debugging 107 | #ResourceSaver.save(curve, "res://" + curve_name + ".tres") 108 | 109 | #bake curve for performance 110 | curve.bake() 111 | 112 | 113 | 114 | #http://www.sengpielaudio.com/calculator-speedsound.htm 115 | #kelvin 116 | func get_speed_of_sound_at_pressure_and_density(pressure : float, density : float) -> float: 117 | return sqrt(ratio_of_specific_heat * (pressure / density)) 118 | 119 | func speed_to_mach_at_altitude(speed : float, altitude : float) -> float: 120 | return speed / get_mach_at_altitude(altitude) 121 | 122 | func get_altitude(node : Node3D) -> float: 123 | if has_node("/root/FloatingOriginHelper"): 124 | return $"/root/FloatingOriginHelper".get_altitude(node) 125 | else: 126 | return node.global_position.y 127 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/components/AeroControlComponent.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Node 3 | class_name AeroControlComponent 4 | 5 | const AeroMathUtils = preload("../utils/math_utils.gd") 6 | 7 | @onready var aero_body : AeroBody3D = get_parent() 8 | @export var flight_assist : FlightAssist 9 | 10 | @export var pitch_control_config := AeroControlConfig.new(-1.0, 1.0, "ui_down", "ui_up") 11 | @export var yaw_control_config := AeroControlConfig.new() 12 | @export var roll_control_config := AeroControlConfig.new(-1.0, 1.0, "ui_left", "ui_right") 13 | @export var throttle_control_config := AeroControlConfig.new(0.0, 1.0) 14 | @export var brake_control_config := AeroControlConfig.new(0.0, 1.0) 15 | @export var collective_control_config := AeroControlConfig.new() 16 | 17 | func _ready() -> void: 18 | #resource_local_to_scene impacts editability in some cases, so we manually duplicate the flight_assist resource. 19 | if not Engine.is_editor_hint(): 20 | #if proportional_navigation: proportional_navigation = proportional_navigation.duplicate(true) 21 | if flight_assist: 22 | flight_assist = flight_assist.duplicate(true) 23 | 24 | pitch_control_config = pitch_control_config.duplicate(true) 25 | yaw_control_config = yaw_control_config.duplicate(true) 26 | roll_control_config = roll_control_config.duplicate(true) 27 | throttle_control_config = throttle_control_config.duplicate(true) 28 | brake_control_config = brake_control_config.duplicate(true) 29 | collective_control_config = collective_control_config.duplicate(true) 30 | 31 | func _physics_process(delta : float) -> void: 32 | if Engine.is_editor_hint(): 33 | return 34 | 35 | update_controls(delta) 36 | update_flight_assist(delta) 37 | 38 | func update_controls(delta : float) -> void: 39 | aero_body.control_command.x = pitch_control_config.update(delta) 40 | aero_body.control_command.y = yaw_control_config.update(delta) 41 | aero_body.control_command.z = roll_control_config.update(delta) 42 | aero_body.throttle_command = throttle_control_config.update(delta) 43 | aero_body.brake_command = brake_control_config.update(delta) 44 | aero_body.collective_command = collective_control_config.update(delta) 45 | 46 | func update_flight_assist(delta : float) -> void: 47 | if not flight_assist: 48 | return 49 | 50 | #input and pids 51 | flight_assist.control_input = get_control_command() 52 | flight_assist.throttle_command = throttle_control_config.command 53 | flight_assist.air_speed = aero_body.air_speed 54 | flight_assist.air_density = aero_body.air_density 55 | flight_assist.angle_of_attack = aero_body.angle_of_attack 56 | flight_assist.altitude = aero_body.altitude 57 | flight_assist.heading = aero_body.heading 58 | flight_assist.bank_angle = aero_body.bank_angle 59 | flight_assist.inclination = aero_body.inclination 60 | flight_assist.linear_velocity = aero_body.linear_velocity 61 | flight_assist.local_angular_velocity = aero_body.local_angular_velocity 62 | flight_assist.global_transform = aero_body.global_transform 63 | flight_assist.update(delta) 64 | 65 | aero_body.control_command = flight_assist.control_command 66 | aero_body.throttle_command = flight_assist.throttle_command 67 | 68 | func set_control_input(value : Vector3) -> void: 69 | pitch_control_config.input = value.x 70 | yaw_control_config.input = value.y 71 | roll_control_config.input = value.z 72 | 73 | func get_control_command() -> Vector3: 74 | return Vector3(pitch_control_config.command, yaw_control_config.command, roll_control_config.command) 75 | 76 | @onready var updated_properties := get_property_conversion_info() 77 | func get_property_conversion_info() -> Dictionary: 78 | #super.get_property_conversion_info().merge() 79 | 80 | return { 81 | #input 82 | "control_input" : [TYPE_VECTOR3, \ 83 | func(value) -> void: 84 | pitch_control_config.input = value.x 85 | yaw_control_config.input = value.y 86 | roll_control_config.input = value.z 87 | ], 88 | 89 | "throttle_input": [TYPE_FLOAT, \ 90 | func(value) -> void: 91 | throttle_control_config.input = value 92 | ], 93 | 94 | "brake_input": [TYPE_FLOAT, \ 95 | func(value) -> void: 96 | brake_control_config.input = value 97 | ], 98 | 99 | #limits 100 | "min_control": [TYPE_VECTOR3], 101 | "max_control": [TYPE_VECTOR3], 102 | "min_throttle": [TYPE_FLOAT, \ 103 | func(value) -> void: 104 | throttle_control_config.min_limit = value 105 | ], 106 | "max_throttle": [TYPE_FLOAT, \ 107 | func(value) -> void: 108 | throttle_control_config.max_limit = value 109 | ], 110 | "min_brake": [TYPE_FLOAT, \ 111 | func(value) -> void: 112 | brake_control_config.min_limit = value 113 | ], 114 | "max_brake": [TYPE_FLOAT, \ 115 | func(value) -> void: 116 | brake_control_config.max_limit = value 117 | ], 118 | 119 | #bindings 120 | "use_bindings": [TYPE_BOOL, \ 121 | func(value : bool) -> void: 122 | pitch_control_config.use_bindings = value 123 | yaw_control_config.use_bindings = value 124 | roll_control_config.use_bindings = value 125 | throttle_control_config.use_bindings = value 126 | brake_control_config.use_bindings = value 127 | collective_control_config.use_bindings = value 128 | ], 129 | 130 | #pitch control 131 | "pitch_up_event": [TYPE_STRING_NAME, \ 132 | func(value) -> void: 133 | pitch_control_config.positive_event = value 134 | ], 135 | "pitch_down_event": [TYPE_STRING_NAME, \ 136 | func(value) -> void: 137 | pitch_control_config.negative_event = value 138 | ], 139 | "enable_pitch_smoothing": [TYPE_BOOL, \ 140 | func(value) -> void: 141 | pitch_control_config.enable_smoothing = value 142 | ], 143 | "pitch_smoothing_rate": [TYPE_FLOAT, \ 144 | func(value) -> void: 145 | pitch_control_config.smoothing_rate = value 146 | ], 147 | "cumulative_pitch_up_event": [TYPE_STRING_NAME, \ 148 | func(value) -> void: 149 | pitch_control_config.cumulative_positive_event = value 150 | ], 151 | "cumulative_pitch_down_event": [TYPE_STRING_NAME, \ 152 | func(value) -> void: 153 | pitch_control_config.cumulative_negative_event = value 154 | ], 155 | "cumulative_pitch_rate": [TYPE_FLOAT, \ 156 | func(value) -> void: 157 | pitch_control_config.cumulative_rate = value 158 | ], 159 | "pitch_easing": [TYPE_FLOAT, \ 160 | func(value) -> void: 161 | pitch_control_config.easing = value 162 | ], 163 | 164 | #yaw control 165 | "yaw_left_event": [TYPE_STRING_NAME, \ 166 | func(value) -> void: 167 | yaw_control_config.positive_event = value 168 | ], 169 | "yaw_right_event": [TYPE_STRING_NAME, \ 170 | func(value) -> void: 171 | yaw_control_config.negative_event = value 172 | ], 173 | "enable_yaw_smoothing": [TYPE_BOOL, \ 174 | func(value) -> void: 175 | yaw_control_config.enable_smoothing = value 176 | ], 177 | "yaw_smoothing_rate": [TYPE_FLOAT, \ 178 | func(value) -> void: 179 | yaw_control_config.smoothing_rate = value 180 | ], 181 | "cumulative_yaw_left_event": [TYPE_STRING_NAME, \ 182 | func(value) -> void: 183 | yaw_control_config.cumulative_positive_event = value 184 | ], 185 | "cumulative_yaw_right_event": [TYPE_STRING_NAME, \ 186 | func(value) -> void: 187 | yaw_control_config.cumulative_negative_event = value 188 | ], 189 | "cumulative_yaw_rate": [TYPE_FLOAT, \ 190 | func(value) -> void: 191 | yaw_control_config.cumulative_rate = value 192 | ], 193 | "yaw_easing": [TYPE_FLOAT, \ 194 | func(value) -> void: 195 | yaw_control_config.easing = value 196 | ], 197 | 198 | #roll control 199 | "roll_left_event": [TYPE_STRING_NAME, \ 200 | func(value) -> void: 201 | roll_control_config.positive_event = value 202 | ], 203 | "roll_right_event": [TYPE_STRING_NAME, \ 204 | func(value) -> void: 205 | roll_control_config.negative_event = value 206 | ], 207 | "enable_roll_smoothing": [TYPE_BOOL, \ 208 | func(value) -> void: 209 | roll_control_config.enable_smoothing = value 210 | ], 211 | "roll_smoothing_rate": [TYPE_FLOAT, \ 212 | func(value) -> void: 213 | roll_control_config.smoothing_rate = value 214 | ], 215 | "cumulative_roll_left_event": [TYPE_STRING_NAME, \ 216 | func(value) -> void: 217 | roll_control_config.cumulative_positive_event = value 218 | ], 219 | "cumulative_roll_right_event": [TYPE_STRING_NAME, \ 220 | func(value) -> void: 221 | roll_control_config.cumulative_negative_event = value 222 | ], 223 | "cumulative_roll_rate": [TYPE_FLOAT, \ 224 | func(value) -> void: 225 | roll_control_config.cumulative_rate = value 226 | ], 227 | "roll_easing": [TYPE_FLOAT, \ 228 | func(value) -> void: 229 | roll_control_config.easing = value 230 | ], 231 | 232 | #throttle control 233 | "throttle_up_event": [TYPE_STRING_NAME, \ 234 | func(value) -> void: 235 | throttle_control_config.positive_event = value 236 | ], 237 | "throttle_down_event": [TYPE_STRING_NAME, \ 238 | func(value) -> void: 239 | throttle_control_config.negative_event = value 240 | ], 241 | "enable_throttle_smoothing": [TYPE_BOOL, \ 242 | func(value) -> void: 243 | throttle_control_config.enable_smoothing = value 244 | ], 245 | "throttle_smoothing_rate": [TYPE_FLOAT, \ 246 | func(value) -> void: 247 | throttle_control_config.smoothing_rate = value 248 | ], 249 | "cumulative_throttle_up_event": [TYPE_STRING_NAME, \ 250 | func(value) -> void: 251 | throttle_control_config.cumulative_positive_event = value 252 | ], 253 | "cumulative_throttle_down_event": [TYPE_STRING_NAME, \ 254 | func(value) -> void: 255 | throttle_control_config.cumulative_negative_event = value 256 | ], 257 | "cumulative_throttle_rate": [TYPE_FLOAT, \ 258 | func(value) -> void: 259 | throttle_control_config.cumulative_rate = value 260 | ], 261 | "throttle_easing": [TYPE_FLOAT, \ 262 | func(value) -> void: 263 | throttle_control_config.easing = value 264 | ], 265 | 266 | #brake control 267 | "brake_up_event": [TYPE_STRING_NAME, \ 268 | func(value) -> void: 269 | brake_control_config.positive_event = value 270 | ], 271 | "brake_down_event": [TYPE_STRING_NAME, \ 272 | func(value) -> void: 273 | brake_control_config.negative_event = value 274 | ], 275 | "enable_brake_smoothing": [TYPE_BOOL, \ 276 | func(value) -> void: 277 | brake_control_config.enable_smoothing = value 278 | ], 279 | "brake_smoothing_rate": [TYPE_FLOAT, \ 280 | func(value) -> void: 281 | brake_control_config.smoothing_rate = value 282 | ], 283 | "cumulative_brake_up_event": [TYPE_STRING_NAME, \ 284 | func(value) -> void: 285 | brake_control_config.cumulative_positive_event = value 286 | ], 287 | "cumulative_brake_down_event": [TYPE_STRING_NAME, \ 288 | func(value) -> void: 289 | brake_control_config.cumulative_negative_event = value 290 | ], 291 | "cumulative_brake_rate": [TYPE_FLOAT, \ 292 | func(value) -> void: 293 | brake_control_config.cumulative_rate = value 294 | ], 295 | "brake_easing": [TYPE_FLOAT, \ 296 | func(value) -> void: 297 | brake_control_config.easing = value 298 | ], 299 | } 300 | 301 | func _get_property_list() -> Array[Dictionary]: 302 | var array : Array[Dictionary] = [] 303 | 304 | for property in updated_properties.keys(): 305 | var property_info : Array = updated_properties[property] 306 | array.append({ 307 | "name" : property, 308 | "type" : property_info[0], 309 | "usage" : 0, 310 | }) 311 | 312 | return array 313 | 314 | func _set(property: StringName, value: Variant) -> bool: 315 | if property in updated_properties.keys(): 316 | var property_info : Array = updated_properties[property] 317 | if property_info.size() >= 2: 318 | property_info[1].call(value) 319 | 320 | return true 321 | return false 322 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/flight_assist.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Resource 3 | class_name FlightAssist 4 | 5 | var air_speed : float = 0.0 6 | var air_density : float = 1.0 7 | 8 | var angle_of_attack : float = 0.0 9 | var bank_angle : float = 0.0 10 | var heading : float = 0.0 11 | var inclination : float = 0.0 12 | 13 | var altitude : float = 0.0 14 | 15 | var linear_velocity : Vector3 = Vector3.ZERO 16 | var local_angular_velocity : Vector3 = Vector3.ZERO 17 | var global_transform : Transform3D = Transform3D() 18 | 19 | var control_input := Vector3.ZERO 20 | var control_command := Vector3.ZERO 21 | var throttle_command : float = 0.0 22 | @export var enable_flight_assist : bool = true 23 | ## Maximum turn rate (in radians per second) the flight assist will command. 24 | @export var max_angular_rates := Vector3(2, 1, 5.0) 25 | var angular_rate_error := Vector3.ZERO 26 | 27 | @export_group("Flight Assist") 28 | @export_subgroup("Angular rate tracking") 29 | ## If enabled, flight assist is enabled on the pitch (X) axis. 30 | @export var enable_flight_assist_x : bool = true 31 | ## PID controller used to evaluate appropriate control response. 32 | @export var pitch_assist_pid : aero_PID = aero_PID.new(0.5, 3, 0, true, -0.2, 0.2) 33 | ## If enabled, flight assist is enabled on the yaw (Y) axis. 34 | @export var enable_flight_assist_y : bool = true 35 | ## PID controller used to evaluate appropriate control response. 36 | @export var yaw_assist_pid : aero_PID = aero_PID.new(1, 0, 0) 37 | ## If enabled, flight assist is enabled on the roll (Z) axis. 38 | @export var enable_flight_assist_z : bool = true 39 | ## PID controller used to evaluate appropriate control response. 40 | @export var roll_assist_pid : aero_PID = aero_PID.new(0.2, 0, 0) 41 | @export_subgroup("G Limiter") 42 | ## In enabled, the flight assist resource will attempt to keep felt G-forces below the g_limit. 43 | @export var enable_g_limiter : bool = true 44 | ## Maximum allowable G-force. 45 | @export var g_limit : float = 9.0 46 | ## Maximum allowable negative G-force. 47 | @export var negative_g_limit : float = 3.0 48 | @export_subgroup("AOA Limiter") 49 | ## If enabled, the flight assist resource will attempt to prevent angle of attack from exceeding this limit. 50 | @export var enable_aoa_limiter : bool = true 51 | ## Angle of attack (degrees) that control authority begins to decrease. 52 | @export var aoa_limit_start : float = 22.0 53 | ## Angle of attack (degrees) that control authority is reduced to 0. 54 | @export var aoa_limit_end : float = 25.0 55 | @export_subgroup("Tuning") 56 | ## If enabled, the flight assist resource will adjust the control authority in an attempt to maintain a consistent control respsonse, despite changing airspeed or air density. 57 | @export var enable_control_adjustment : bool = false 58 | ## Airspeed that control adjustments are tuned at. 59 | @export var tuned_airspeed : float = 100.0 60 | ## Minimum limit for airspeed adjustment. This prevents controls from losing authority at low speeds due to control surface stalls. 61 | @export var min_accounted_airspeed : float = 75.0 62 | ## Air density that control adjustments are tuned at. 63 | @export var tuned_density : float = 1.222 64 | ## Minimum limit for air density adjustment. This prevents controls from losing authority at low air density due to control surface stalls. 65 | @export var min_accounted_air_density : float = 0.1 66 | 67 | @export_group("Autopilot") 68 | @export_subgroup("Bank Angle Target") 69 | ## If enabled, the flight assist resource will take control of the bank angle (roll), and attempt to drive it towards bank_angle_target. 70 | @export var enable_bank_angle_assist : bool = false 71 | ## Target bank angle that bank_angle_pid will attempt to maintain. 72 | @export var bank_angle_target : float = 0.0 73 | ## PID controller used to evaluate appropriate control response. 74 | @export var bank_angle_pid : aero_PID = aero_PID.new(1, 0.05, 0.1) 75 | 76 | @export_subgroup("Heading Target") 77 | ## If enabled, the flight assist resource will attempt to maintain heading at heading_target. 78 | @export var enable_heading_hold : bool = false: 79 | set(x): 80 | enable_heading_hold = x 81 | heading_target = heading 82 | ## Target heading that heading_pid will attempt to maintain. 83 | @export var heading_target : float = 0.0 84 | ## PID controller used to evaluate appropriate control response. 85 | @export var heading_pid : aero_PID = aero_PID.new(10, 0, 0) 86 | 87 | @export_subgroup("Inclination Target") 88 | ## If enabled, the flight assist resource will attempt to maintain heading at heading_target. 89 | @export var enable_inclination_hold : bool = false: 90 | set(x): 91 | enable_inclination_hold = x 92 | inclination_target = inclination 93 | ## Target heading that heading_pid will attempt to maintain. 94 | @export var inclination_target : float = 0.0 95 | ## PID controller used to evaluate appropriate control response. 96 | @export var inclination_pid : aero_PID = aero_PID.new(10, 0, 0) 97 | 98 | @export_subgroup("Speed Target") 99 | ## If enabled, the flight assist resource will control throttle, and attempt to maintain airspeed at speed_target. 100 | @export var enable_speed_hold : bool = false: 101 | set(x): 102 | enable_speed_hold = x 103 | speed_target = air_speed 104 | ## Target speed that speed_pid will attempt to maintain. 105 | @export var speed_target : float = 0.0 106 | ## PID controller used to evaluate appropriate control response. 107 | @export var speed_pid : aero_PID = aero_PID.new(0, 0.4, 0) 108 | 109 | @export_subgroup("Altitude Target") 110 | ## If enabled, the flight assist resource will attempt to maintain altitude at altitude_target. 111 | @export var enable_altitude_hold : bool = false: 112 | set(x): 113 | enable_altitude_hold = x 114 | altitude_target = altitude 115 | ## Target altitude that altitude_pid will attempt to maintain. 116 | @export var altitude_target : float = 0.0 117 | ## PID controller used to evaluate appropriate control response. 118 | @export var altitude_pid : aero_PID = aero_PID.new(0.001, 0, 0.01) 119 | 120 | @export_subgroup("Target Direction") 121 | ## If enabled, the flight assist resource will attempt to maintain linear_velocity pointing in the direction of direction_target. 122 | @export var enable_target_direction : bool = false 123 | ## Target direction that direction PIDs will attempt to maintain. 124 | @export var direction_target : Vector3 = Vector3.ZERO 125 | ## PID controller used to evaluate appropriate control response. 126 | @export var direction_pitch_pid : aero_PID = aero_PID.new(10, 0.3, 4) 127 | ## PID controller used to evaluate appropriate control response. 128 | @export var direction_yaw_pid : aero_PID = aero_PID.new(25, 0, 0) 129 | ## PID controller used to evaluate appropriate control response. 130 | @export var direction_roll_pid : aero_PID = aero_PID.new(0.8, 0, 0) 131 | 132 | @export_subgroup("") 133 | @export_category("") 134 | 135 | func update(delta : float) -> void: 136 | control_command = control_input 137 | 138 | if not enable_flight_assist: 139 | return 140 | 141 | bank_angle_hold(delta) 142 | heading_hold(delta) 143 | inclination_hold(delta) 144 | altitude_hold(delta) 145 | speed_hold(delta) 146 | target_direction(delta) 147 | flight_assist(delta) 148 | 149 | func bank_angle_hold(delta : float) -> void: 150 | if not enable_bank_angle_assist and not enable_altitude_hold and not enable_heading_hold: 151 | return 152 | bank_angle_pid.update(delta, bank_angle_target - bank_angle) 153 | control_input.z += bank_angle_pid.output 154 | 155 | func heading_hold(delta : float) -> void: 156 | if not enable_heading_hold: 157 | return 158 | #shit but works. Also has -180/+180 wrapping issues 159 | heading_pid.update(delta, heading_target - heading) 160 | control_input.y += heading_pid.output 161 | 162 | func inclination_hold(delta : float) -> void: 163 | if not enable_inclination_hold: 164 | return 165 | #shit but works. Also has -180/+180 wrapping issues 166 | inclination_pid.update(delta, inclination_target - inclination) 167 | control_input.x += inclination_pid.output 168 | 169 | func speed_hold(delta : float) -> void: 170 | if not enable_speed_hold: 171 | return 172 | speed_pid.update(delta, speed_target - air_speed) 173 | throttle_command = speed_pid.output 174 | 175 | func altitude_hold(delta : float) -> void: 176 | if not enable_altitude_hold: 177 | return 178 | #shit but works 179 | altitude_pid.update(delta, altitude_target - altitude) 180 | control_input.x += altitude_pid.output 181 | 182 | 183 | func target_direction(delta : float) -> void: 184 | if not enable_target_direction: 185 | return 186 | if linear_velocity.is_equal_approx(Vector3.ZERO): 187 | return 188 | var local_velocity_direction : Vector3 = linear_velocity.normalized() * global_transform.basis 189 | var angles_to_local_velocity_direction := Vector3( 190 | atan2(local_velocity_direction.y, -local_velocity_direction.z), 191 | atan2(-local_velocity_direction.x, -local_velocity_direction.z), 192 | 0.0 193 | ) 194 | 195 | var local_direction_target : Vector3 = direction_target * global_transform.basis 196 | var angles_to_local_direction_target := Vector3( 197 | atan2(local_direction_target.y, -local_direction_target.z), 198 | atan2(-local_direction_target.x, -local_direction_target.z), 199 | 0.0#atan2(-local_direction_target.x, local_direction_target.y) 200 | ) 201 | 202 | var error := angles_to_local_direction_target - angles_to_local_velocity_direction 203 | 204 | var local_desired_acceleration : Vector3 = local_direction_target * linear_velocity.length() - local_velocity_direction * linear_velocity.length() + Vector3(0, 9.8, 0) * global_transform.basis 205 | var roll_error : float = -local_desired_acceleration.x 206 | 207 | error.z = roll_error 208 | 209 | direction_pitch_pid.update(delta, error.x) 210 | direction_yaw_pid.update(delta, error.y) 211 | direction_roll_pid.update(delta, error.z) 212 | 213 | control_input.x += direction_pitch_pid.output 214 | control_input.y += direction_yaw_pid.output 215 | control_input.z += direction_roll_pid.output 216 | 217 | control_input = control_input.clamp(Vector3(-1, -1, -1), Vector3(1, 1, 1)) 218 | 219 | #var normal_acceleration = local_acceleration_vector.z 220 | 221 | #this should be standardized. 222 | func flight_assist(delta : float) -> void: 223 | #prevent crashing the aero_PID when airspeed is 0 224 | #if is_equal_approx(air_speed, 0.0): 225 | #return 226 | 227 | if air_speed < 5.0: 228 | pitch_assist_pid._integral_error = 0.0 229 | yaw_assist_pid._integral_error = 0.0 230 | roll_assist_pid._integral_error = 0.0 231 | return #prevents unnecessary input commands 232 | 233 | var angular_rates := max_angular_rates 234 | 235 | if enable_g_limiter: 236 | #g limit 237 | var g_limited_angular_rate := max_angular_rates.x 238 | if !is_zero_approx(air_speed): 239 | var gravity_direction := Vector3(0, -1, 0) 240 | var zero_effort_g_force : float = global_transform.basis.y.dot(gravity_direction) 241 | 242 | g_limited_angular_rate = (g_limit + zero_effort_g_force) * 9.81 / air_speed 243 | #for negative g limit 244 | if control_input.x <= 0.0: 245 | g_limited_angular_rate = (negative_g_limit - zero_effort_g_force) * 9.81 / air_speed 246 | 247 | angular_rates.x = min(angular_rates.x, g_limited_angular_rate) 248 | #g limit 249 | 250 | angular_rate_error = angular_rates * control_input - local_angular_velocity 251 | 252 | #flight assist 253 | #these should be adjusted when the reference frame changes. 254 | if enable_flight_assist_x: 255 | control_command.x = pitch_assist_pid.update(delta, angular_rate_error.x) 256 | if enable_flight_assist_y: 257 | control_command.y = yaw_assist_pid.update(delta, angular_rate_error.y) 258 | if enable_flight_assist_z: 259 | control_command.z = roll_assist_pid.update(delta, angular_rate_error.z) 260 | 261 | if enable_aoa_limiter: 262 | control_command *= clamp(remap(angle_of_attack, deg_to_rad(aoa_limit_start), deg_to_rad(aoa_limit_end), 1, 0), 0, 1) 263 | 264 | #adjust for changing airspeed and density 265 | if enable_control_adjustment: 266 | control_command *= FlightAssist.get_control_adjustment_factor(air_speed, air_density, tuned_airspeed, tuned_density, min_accounted_airspeed, min_accounted_air_density) 267 | 268 | control_command = control_command.clamp(Vector3(-1, -1, -1), Vector3(1, 1, 1)) 269 | 270 | static func get_control_adjustment_factor(speed : float, density : float, tuned_speed : float = 100.0, _tuned_density : float = 1.222, min_accounted_speed : float = 0.75, min_accounted_density : float = 0.2) -> float: 271 | var control_adjustment : float = 1.0 272 | #adjust for airspeed 273 | var accounted_speed : float = max(min_accounted_speed, speed) 274 | control_adjustment /= (accounted_speed * accounted_speed) / (tuned_speed * tuned_speed) 275 | 276 | #adjust for air density 277 | var accounted_density : float = max(min_accounted_density, density) 278 | control_adjustment /= accounted_density / _tuned_density 279 | 280 | return control_adjustment 281 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_influencer_3d.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Node3D 3 | class_name AeroInfluencer3D 4 | 5 | const AeroMathUtils = preload("../../utils/math_utils.gd") 6 | const AeroNodeUtils = preload("../../utils/node_utils.gd") 7 | 8 | ## If true, this AeroInfluencer3D will not have any effect on the simulation. 9 | @export var disabled : bool = false 10 | ## Allows the current AeroInfluencer to prevent/interrupt the AeroBody's sleep. This is useful for thrust-providing 11 | ## nodes like AeroMovers or Propellers. Sleep is only interrupted if the AeroInfluencer sub-class triggers it. 12 | @export var can_override_body_sleep : bool = true 13 | 14 | var control_command := Vector3.ZERO 15 | var throttle_command : float = 0.0 16 | var brake_command : float = 0.0 17 | var collective_command : float = 0.0 18 | 19 | @export_group("Actuation Control") 20 | @export var actuation_config : AeroInfluencerControlConfig 21 | ## Rotation order used when doing control rotations. 22 | @export_enum("XYZ", "XZY", "YXZ", "YZX", "ZXY", "ZYX") var control_rotation_order : int = 0 23 | @export_subgroup("") 24 | 25 | @export_group("Debug") 26 | ## If enabled, this AeroInfluencer3D is omitted from AeroBody3D debug calculations. 27 | @export var omit_from_debug : bool = false 28 | var debug_scale : float = 0.1 29 | var debug_width : float = 0.05 30 | var show_debug : bool = false 31 | 32 | ## Controls visibility of the AeroInfluencer3D's force debug vector. 33 | @export var show_force : bool = true 34 | ## Controls visibility of the AeroInfluencer3D's torque debug vector. 35 | @export var show_torque : bool = true 36 | 37 | var aero_body : AeroBody3D 38 | var aero_influencers : Array[AeroInfluencer3D] = [] 39 | 40 | @onready var default_transform := transform 41 | var world_air_velocity := Vector3.ZERO 42 | var linear_velocity := Vector3.ZERO: 43 | get = get_linear_velocity 44 | var angular_velocity := Vector3.ZERO: 45 | get = get_angular_velocity 46 | @onready var last_linear_velocity : Vector3 = linear_velocity 47 | @onready var last_angular_velocity : Vector3 = angular_velocity 48 | var air_density := 0.0 49 | var relative_position := Vector3.ZERO 50 | var altitude := 0.0 51 | 52 | #Calculating air velocity relative to the surface's coordinate system. 53 | var local_air_velocity := Vector3.ZERO 54 | var air_speed := 0.0 55 | 56 | var mach : float = 0.0 57 | var dynamic_pressure : float = 0.0 58 | 59 | var _current_force : Vector3 = Vector3.ZERO 60 | var _current_torque : Vector3 = Vector3.ZERO 61 | 62 | var force_debug_vector : AeroDebugVector3D 63 | var torque_debug_vector : AeroDebugVector3D 64 | 65 | func _init(): 66 | #initialize debug vectors 67 | force_debug_vector = AeroDebugVector3D.new(Color(1, 1, 1), debug_width, true) 68 | force_debug_vector.visible = false 69 | force_debug_vector.sorting_offset = 0.02 70 | add_child(force_debug_vector, INTERNAL_MODE_FRONT) 71 | 72 | torque_debug_vector = AeroDebugVector3D.new(Color(0, 0, 0)) 73 | torque_debug_vector.visible = false 74 | torque_debug_vector.sorting_offset = 0.01 75 | add_child(torque_debug_vector, INTERNAL_MODE_FRONT) 76 | 77 | func _ready() -> void: 78 | if not Engine.is_editor_hint(): 79 | if actuation_config: 80 | actuation_config = actuation_config.duplicate(true) 81 | 82 | func _enter_tree() -> void: 83 | AeroNodeUtils.connect_signal_safe(self, "child_entered_tree", on_child_enter_tree, 0, true) 84 | AeroNodeUtils.connect_signal_safe(self, "child_exiting_tree", on_child_exit_tree, 0, true) 85 | 86 | func on_child_enter_tree(node : Node) -> void: 87 | if node is AeroInfluencer3D: 88 | aero_influencers.append(node) 89 | node.aero_body = aero_body 90 | 91 | func on_child_exit_tree(node : Node) -> void: 92 | if node is AeroInfluencer3D and aero_influencers.has(node): 93 | aero_influencers.erase(node) 94 | node.aero_body = null 95 | 96 | var last_transform : Transform3D = Transform3D() 97 | func _physics_process(delta : float) -> void: 98 | if Engine.is_editor_hint(): 99 | return 100 | if not transform == last_transform: 101 | aero_body.interrupt_sleep() 102 | last_transform = transform 103 | 104 | func _calculate_forces(substep_delta : float = 0.0) -> PackedVector3Array: 105 | linear_velocity = get_linear_velocity() 106 | angular_velocity = get_angular_velocity() 107 | 108 | relative_position = get_relative_position() 109 | world_air_velocity = get_world_air_velocity() 110 | air_speed = world_air_velocity.length() 111 | air_density = aero_body.air_density 112 | altitude = aero_body.altitude 113 | local_air_velocity = global_transform.basis.inverse() * world_air_velocity 114 | if has_node("/root/AeroUnits"): 115 | var _AeroUnits : Node = $"/root/AeroUnits" 116 | mach = _AeroUnits.speed_to_mach_at_altitude(world_air_velocity.length(), altitude) 117 | dynamic_pressure = 0.5 * _AeroUnits.get_density_at_altitude(altitude) * (air_speed * air_speed) 118 | 119 | var force : Vector3 = Vector3.ZERO 120 | var torque : Vector3 = Vector3.ZERO 121 | 122 | for influencer : AeroInfluencer3D in aero_influencers: 123 | var force_and_torque : PackedVector3Array = influencer._calculate_forces(substep_delta) 124 | 125 | force += force_and_torque[0] 126 | torque += force_and_torque[1] 127 | 128 | torque += relative_position.cross(force) 129 | 130 | _current_force = force 131 | _current_torque = torque 132 | 133 | return PackedVector3Array([force, torque]) 134 | 135 | #virtual 136 | func _update_transform_substep(substep_delta : float) -> void: 137 | for influencer : AeroInfluencer3D in aero_influencers: 138 | influencer._update_transform_substep(substep_delta) 139 | 140 | _update_control_transform(substep_delta) 141 | 142 | func _update_control_transform(substep_delta : float) -> void: 143 | control_command = get_parent().control_command 144 | throttle_command = get_parent().throttle_command 145 | brake_command = get_parent().brake_command 146 | collective_command = get_parent().collective_command 147 | 148 | # TODO: Make it easier to manually control AeroInfluencers. 149 | var actuation_value := Vector3.ZERO 150 | if actuation_config: 151 | actuation_value = apply_control_commands_to_config(substep_delta, actuation_config) 152 | 153 | basis = default_transform.basis * Basis().from_euler(actuation_value, control_rotation_order) 154 | 155 | #override 156 | func is_overriding_body_sleep() -> bool: 157 | if not can_override_body_sleep: 158 | return false 159 | 160 | var overriding : bool = false 161 | 162 | for influencer : AeroInfluencer3D in aero_influencers: 163 | overriding = overriding or influencer.is_overriding_body_sleep() 164 | 165 | return overriding 166 | 167 | func get_relative_position() -> Vector3: 168 | return get_parent().get_relative_position() + (get_parent().global_basis * position) 169 | 170 | func get_world_air_velocity() -> Vector3: 171 | return -get_linear_velocity() 172 | 173 | func get_linear_velocity() -> Vector3: 174 | return get_parent().linear_velocity + get_parent().angular_velocity.cross(get_parent().global_basis * position) 175 | 176 | func get_angular_velocity() -> Vector3: 177 | return get_parent().angular_velocity 178 | 179 | #virtual 180 | func get_centrifugal_offset() -> Vector3: 181 | return position 182 | 183 | func get_linear_acceleration() -> Vector3: 184 | var position_in_global_rotation : Vector3 = get_parent().global_basis * position 185 | 186 | var centrifugal_offset : Vector3 = get_parent().global_basis * get_centrifugal_offset() 187 | 188 | var axis : Vector3 = angular_velocity.normalized() 189 | var nearest_point_on_line : Vector3 = axis * centrifugal_offset.dot(axis.normalized()) 190 | var rotation_radius : float = nearest_point_on_line.distance_to(centrifugal_offset) 191 | var acceleration_axis : Vector3 = nearest_point_on_line - centrifugal_offset 192 | 193 | var centripetal_acceleration_force : float = get_parent().get_angular_velocity().length_squared() * rotation_radius 194 | var centripetal_acceleration_vector : Vector3 = centripetal_acceleration_force * acceleration_axis 195 | 196 | return get_parent().get_linear_acceleration() + get_parent().get_angular_acceleration().cross(centrifugal_offset) + centripetal_acceleration_vector 197 | 198 | func get_angular_acceleration() -> Vector3: 199 | return get_parent().get_angular_acceleration() 200 | 201 | 202 | 203 | #debug 204 | 205 | 206 | func update_debug_visibility(_show_debug : bool = false) -> void: 207 | for i : AeroInfluencer3D in aero_influencers: 208 | i.update_debug_visibility(_show_debug) 209 | 210 | show_debug = _show_debug 211 | 212 | force_debug_vector.visible = show_debug and show_force 213 | torque_debug_vector.visible = show_debug and show_torque 214 | 215 | func update_debug_scale(_scale : float, _width : float) -> void: 216 | for i : AeroInfluencer3D in aero_influencers: 217 | i.update_debug_scale(_scale, _width) 218 | 219 | debug_scale = _scale 220 | debug_width = _width 221 | 222 | force_debug_vector.width = debug_width 223 | torque_debug_vector.width = debug_width 224 | 225 | func update_debug_vectors() -> void: 226 | for i : AeroInfluencer3D in aero_influencers: 227 | i.update_debug_vectors() 228 | 229 | #don't update invisible vectors 230 | if force_debug_vector.visible: 231 | force_debug_vector.value = global_transform.basis.inverse() * AeroMathUtils.v3log_with_base(_current_force, 2.0) * debug_scale 232 | if torque_debug_vector.visible: 233 | torque_debug_vector.value = global_transform.basis.inverse() * AeroMathUtils.v3log_with_base(_current_torque, 2.0) * debug_scale 234 | 235 | func apply_control_commands_to_config(delta : float, control_config : AeroInfluencerControlConfig) -> Vector3: 236 | control_config.pitch_command = control_command.x 237 | control_config.yaw_command = control_command.y 238 | control_config.roll_command = control_command.z 239 | control_config.brake_command = brake_command 240 | control_config.throttle_command = throttle_command 241 | control_config.collective_command = collective_command 242 | 243 | return control_config.update(delta) 244 | 245 | var updated_properties := get_property_conversion_info() 246 | func get_property_conversion_info() -> Dictionary: 247 | # 248 | 249 | #return super.get_property_conversion_info().merged({ 250 | return { 251 | #input 252 | "enable_automatic_control" : [TYPE_BOOL, \ 253 | func(value) -> void: 254 | if value and not is_instance_valid(actuation_config): 255 | actuation_config = AeroInfluencerControlConfig.new() 256 | actuation_config.enable_control = value 257 | ], 258 | "max_actuation" : [TYPE_VECTOR3, \ 259 | func(value) -> void: 260 | if not is_instance_valid(actuation_config): 261 | actuation_config = AeroInfluencerControlConfig.new() 262 | actuation_config.max_value = value 263 | ], 264 | "limit_actuation_speed" : [TYPE_BOOL, \ 265 | func(value) -> void: 266 | if not is_instance_valid(actuation_config): 267 | actuation_config = AeroInfluencerControlConfig.new() 268 | actuation_config.limit_movement_speed = value 269 | ], 270 | "actuation_speed" : [TYPE_FLOAT, \ 271 | func(value) -> void: 272 | if not is_instance_valid(actuation_config): 273 | actuation_config = AeroInfluencerControlConfig.new() 274 | actuation_config.movement_speed = value 275 | ], 276 | "pitch_contribution" : [TYPE_VECTOR3, \ 277 | func(value) -> void: 278 | if not is_instance_valid(actuation_config): 279 | actuation_config = AeroInfluencerControlConfig.new() 280 | if not is_instance_valid(actuation_config.pitch_config): 281 | actuation_config.pitch_config = AeroInfluencerControlAxisConfig.new() 282 | actuation_config.pitch_config.contribution = value 283 | ], 284 | "pitch_easing" : [TYPE_FLOAT, \ 285 | func(value) -> void: 286 | if not is_instance_valid(actuation_config): 287 | actuation_config = AeroInfluencerControlConfig.new() 288 | if not is_instance_valid(actuation_config.pitch_config): 289 | actuation_config.pitch_config = AeroInfluencerControlAxisConfig.new() 290 | actuation_config.pitch_config.easing = value 291 | ], 292 | "yaw_contribution" : [TYPE_VECTOR3, \ 293 | func(value) -> void: 294 | if not is_instance_valid(actuation_config): 295 | actuation_config = AeroInfluencerControlConfig.new() 296 | if not is_instance_valid(actuation_config.yaw_config): 297 | actuation_config.yaw_config = AeroInfluencerControlAxisConfig.new() 298 | actuation_config.yaw_config.contribution = value 299 | ], 300 | "yaw_easing" : [TYPE_FLOAT, \ 301 | func(value) -> void: 302 | if not is_instance_valid(actuation_config): 303 | actuation_config = AeroInfluencerControlConfig.new() 304 | if not is_instance_valid(actuation_config.yaw_config): 305 | actuation_config.yaw_config = AeroInfluencerControlAxisConfig.new() 306 | actuation_config.yaw_config.easing = value 307 | ], 308 | "roll_contribution" : [TYPE_VECTOR3, \ 309 | func(value) -> void: 310 | if not is_instance_valid(actuation_config): 311 | actuation_config = AeroInfluencerControlConfig.new() 312 | if not is_instance_valid(actuation_config.roll_config): 313 | actuation_config.roll_config = AeroInfluencerControlAxisConfig.new() 314 | actuation_config.roll_config.contribution = value 315 | ], 316 | "roll_easing" : [TYPE_FLOAT, \ 317 | func(value) -> void: 318 | if not is_instance_valid(actuation_config): 319 | actuation_config = AeroInfluencerControlConfig.new() 320 | if not is_instance_valid(actuation_config.roll_config): 321 | actuation_config.roll_config = AeroInfluencerControlAxisConfig.new() 322 | actuation_config.roll_config.easing = value 323 | ], 324 | "brake_contribution" : [TYPE_VECTOR3, \ 325 | func(value) -> void: 326 | if not is_instance_valid(actuation_config): 327 | actuation_config = AeroInfluencerControlConfig.new() 328 | if not is_instance_valid(actuation_config.brake_config): 329 | actuation_config.brake_config = AeroInfluencerControlAxisConfig.new() 330 | actuation_config.brake_config.contribution = value 331 | ], 332 | "brake_easing" : [TYPE_FLOAT, \ 333 | func(value) -> void: 334 | if not is_instance_valid(actuation_config): 335 | actuation_config = AeroInfluencerControlConfig.new() 336 | if not is_instance_valid(actuation_config.brake_config): 337 | actuation_config.brake_config = AeroInfluencerControlAxisConfig.new() 338 | actuation_config.brake_config.easing = value 339 | ], 340 | "throttle_contribution" : [TYPE_VECTOR3, \ 341 | func(value) -> void: 342 | if not is_instance_valid(actuation_config): 343 | actuation_config = AeroInfluencerControlConfig.new() 344 | if not is_instance_valid(actuation_config.throttle_config): 345 | actuation_config.throttle_config = AeroInfluencerControlAxisConfig.new() 346 | actuation_config.throttle_config.contribution = value 347 | ], 348 | "throttle_easing" : [TYPE_FLOAT, \ 349 | func(value) -> void: 350 | if not is_instance_valid(actuation_config): 351 | actuation_config = AeroInfluencerControlConfig.new() 352 | if not is_instance_valid(actuation_config.throttle_config): 353 | actuation_config.throttle_config = AeroInfluencerControlAxisConfig.new() 354 | actuation_config.throttle_config.easing = value 355 | ], 356 | 357 | }#) 358 | 359 | func _get_property_list() -> Array[Dictionary]: 360 | var array : Array[Dictionary] = [] 361 | var updated_properties := get_property_conversion_info() 362 | for property in updated_properties.keys(): 363 | var property_info : Array = updated_properties[property] 364 | array.append({ 365 | "name" : property, 366 | "type" : property_info[0], 367 | "usage" : 0, 368 | }) 369 | 370 | return array 371 | 372 | func _set(property: StringName, value: Variant) -> bool: 373 | if property in updated_properties.keys(): 374 | var property_info : Array = updated_properties[property] 375 | if property_info.size() >= 2: 376 | #print("Calling %s with value %s" %[property, value]) 377 | property_info[1].call(value) 378 | 379 | return true 380 | return false 381 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/demo/demo_plane.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=49 format=3 uid="uid://dx0v1i14fyw8c"] 2 | 3 | [ext_resource type="Script" uid="uid://3iji867c81k5" path="res://addons/godot_aerodynamic_physics/core/aero_body_3d.gd" id="1_ddp7w"] 4 | [ext_resource type="Script" uid="uid://cc5dwx5pqkaur" path="res://addons/godot_aerodynamic_physics/components/AeroControlComponent.gd" id="2_x2dhw"] 5 | [ext_resource type="Script" uid="uid://d1725od371u4a" path="res://addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_3d.gd" id="2_ygpq6"] 6 | [ext_resource type="Script" uid="uid://ctbwb7xlhft6t" path="res://addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_config.gd" id="3_dhflf"] 7 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="3_hyo05"] 8 | [ext_resource type="Script" uid="uid://cdp8dc63ytxx1" path="res://addons/godot_aerodynamic_physics/core/flight_assist.gd" id="4_a0mgf"] 9 | [ext_resource type="Script" uid="uid://crkbhmxpjff4o" path="res://addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/aero_surface_config.gd" id="4_ht3i8"] 10 | [ext_resource type="Script" uid="uid://cbgqb7g7mmwtu" path="res://addons/godot_aerodynamic_physics/core/control_configs/aero_control_config.gd" id="5_x51t6"] 11 | [ext_resource type="Script" uid="uid://bjesvxrrkoocs" path="res://addons/godot_aerodynamic_physics/components/aero_thruster_component/AeroJetThrusterComponent.gd" id="8_e4glj"] 12 | [ext_resource type="Script" uid="uid://c46td0go5kvp6" path="res://addons/godot_aerodynamic_physics/core/control_configs/aero_influencer_control_axis_config.gd" id="11_1g5my"] 13 | [ext_resource type="Script" uid="uid://cblxodmmwvpt6" path="res://addons/godot_aerodynamic_physics/core/control_configs/aero_influencer_control_config.gd" id="12_njgyo"] 14 | 15 | [sub_resource type="PhysicsMaterial" id="PhysicsMaterial_m08mi"] 16 | friction = 0.0 17 | 18 | [sub_resource type="Resource" id="Resource_6l1yh"] 19 | script = ExtResource("3_hyo05") 20 | p = 0.001 21 | i = 0.0 22 | d = 0.01 23 | clamp_integral = false 24 | min_integral = -1.0 25 | max_integral = 1.0 26 | 27 | [sub_resource type="Resource" id="Resource_rsy8r"] 28 | script = ExtResource("3_hyo05") 29 | p = 1.0 30 | i = 0.05 31 | d = 0.1 32 | clamp_integral = false 33 | min_integral = -1.0 34 | max_integral = 1.0 35 | 36 | [sub_resource type="Resource" id="Resource_6l2fv"] 37 | script = ExtResource("3_hyo05") 38 | p = 10.0 39 | i = 0.3 40 | d = 4.0 41 | clamp_integral = false 42 | min_integral = -1.0 43 | max_integral = 1.0 44 | 45 | [sub_resource type="Resource" id="Resource_wyxpf"] 46 | script = ExtResource("3_hyo05") 47 | p = 0.8 48 | i = 0.0 49 | d = 0.0 50 | clamp_integral = false 51 | min_integral = -1.0 52 | max_integral = 1.0 53 | 54 | [sub_resource type="Resource" id="Resource_wruso"] 55 | script = ExtResource("3_hyo05") 56 | p = 25.0 57 | i = 0.0 58 | d = 0.0 59 | clamp_integral = false 60 | min_integral = -1.0 61 | max_integral = 1.0 62 | 63 | [sub_resource type="Resource" id="Resource_x3iit"] 64 | script = ExtResource("3_hyo05") 65 | p = 10.0 66 | i = 0.0 67 | d = 0.0 68 | clamp_integral = false 69 | min_integral = -1.0 70 | max_integral = 1.0 71 | 72 | [sub_resource type="Resource" id="Resource_n4pj1"] 73 | script = ExtResource("3_hyo05") 74 | p = 10.0 75 | i = 0.0 76 | d = 0.0 77 | clamp_integral = false 78 | min_integral = -1.0 79 | max_integral = 1.0 80 | 81 | [sub_resource type="Resource" id="Resource_piqeh"] 82 | script = ExtResource("3_hyo05") 83 | p = 0.5 84 | i = 5.0 85 | d = 0.0 86 | clamp_integral = true 87 | min_integral = -0.1 88 | max_integral = 0.1 89 | 90 | [sub_resource type="Resource" id="Resource_t6xgy"] 91 | script = ExtResource("3_hyo05") 92 | p = 0.2 93 | i = 0.0 94 | d = 0.0 95 | clamp_integral = false 96 | min_integral = -1.0 97 | max_integral = 1.0 98 | 99 | [sub_resource type="Resource" id="Resource_1qfso"] 100 | script = ExtResource("3_hyo05") 101 | p = 0.0 102 | i = 0.4 103 | d = 0.0 104 | clamp_integral = false 105 | min_integral = -1.0 106 | max_integral = 1.0 107 | 108 | [sub_resource type="Resource" id="Resource_bvmql"] 109 | script = ExtResource("3_hyo05") 110 | p = 1.0 111 | i = 0.0 112 | d = 0.0 113 | clamp_integral = false 114 | min_integral = -1.0 115 | max_integral = 1.0 116 | 117 | [sub_resource type="Resource" id="Resource_qpum4"] 118 | script = ExtResource("4_a0mgf") 119 | enable_flight_assist = true 120 | max_angular_rates = Vector3(2, 1, 5) 121 | enable_flight_assist_x = true 122 | pitch_assist_pid = SubResource("Resource_piqeh") 123 | enable_flight_assist_y = true 124 | yaw_assist_pid = SubResource("Resource_bvmql") 125 | enable_flight_assist_z = true 126 | roll_assist_pid = SubResource("Resource_t6xgy") 127 | enable_g_limiter = true 128 | g_limit = 9.0 129 | negative_g_limit = 3.0 130 | enable_aoa_limiter = true 131 | aoa_limit_start = 22.0 132 | aoa_limit_end = 25.0 133 | enable_control_adjustment = false 134 | tuned_airspeed = 100.0 135 | min_accounted_airspeed = 75.0 136 | tuned_density = 1.222 137 | min_accounted_air_density = 0.1 138 | enable_bank_angle_assist = false 139 | bank_angle_target = 0.0 140 | bank_angle_pid = SubResource("Resource_rsy8r") 141 | enable_heading_hold = false 142 | heading_target = 0.0 143 | heading_pid = SubResource("Resource_x3iit") 144 | enable_inclination_hold = false 145 | inclination_target = 0.0 146 | inclination_pid = SubResource("Resource_n4pj1") 147 | enable_speed_hold = false 148 | speed_target = 0.0 149 | speed_pid = SubResource("Resource_1qfso") 150 | enable_altitude_hold = false 151 | altitude_target = 0.0 152 | altitude_pid = SubResource("Resource_6l1yh") 153 | enable_target_direction = false 154 | direction_target = Vector3(0, 0, 0) 155 | direction_pitch_pid = SubResource("Resource_6l2fv") 156 | direction_yaw_pid = SubResource("Resource_wruso") 157 | direction_roll_pid = SubResource("Resource_wyxpf") 158 | 159 | [sub_resource type="Resource" id="Resource_ry778"] 160 | script = ExtResource("5_x51t6") 161 | use_bindings = true 162 | input = 0.0 163 | min_limit = -1.0 164 | max_limit = 1.0 165 | positive_event = &"ui_down" 166 | negative_event = &"ui_up" 167 | enable_smoothing = false 168 | smoothing_rate = 1.0 169 | cumulative_positive_event = &"" 170 | cumulative_negative_event = &"" 171 | cumulative_rate = 1.0 172 | easing = 1.0 173 | 174 | [sub_resource type="Resource" id="Resource_f2e3i"] 175 | script = ExtResource("5_x51t6") 176 | use_bindings = true 177 | input = 0.0 178 | min_limit = -1.0 179 | max_limit = 1.0 180 | positive_event = &"" 181 | negative_event = &"" 182 | enable_smoothing = false 183 | smoothing_rate = 1.0 184 | cumulative_positive_event = &"" 185 | cumulative_negative_event = &"" 186 | cumulative_rate = 1.0 187 | easing = 1.0 188 | 189 | [sub_resource type="Resource" id="Resource_7svdm"] 190 | script = ExtResource("5_x51t6") 191 | use_bindings = true 192 | input = 0.0 193 | min_limit = -1.0 194 | max_limit = 1.0 195 | positive_event = &"ui_left" 196 | negative_event = &"ui_right" 197 | enable_smoothing = false 198 | smoothing_rate = 1.0 199 | cumulative_positive_event = &"" 200 | cumulative_negative_event = &"" 201 | cumulative_rate = 1.0 202 | easing = 1.0 203 | 204 | [sub_resource type="Resource" id="Resource_x0pkh"] 205 | script = ExtResource("5_x51t6") 206 | use_bindings = true 207 | input = 0.0 208 | min_limit = 0.0 209 | max_limit = 1.0 210 | positive_event = &"" 211 | negative_event = &"" 212 | enable_smoothing = false 213 | smoothing_rate = 1.0 214 | cumulative_positive_event = &"ui_page_up" 215 | cumulative_negative_event = &"ui_page_down" 216 | cumulative_rate = 1.0 217 | easing = 1.0 218 | 219 | [sub_resource type="Resource" id="Resource_8exs8"] 220 | script = ExtResource("5_x51t6") 221 | use_bindings = true 222 | input = 0.0 223 | min_limit = 0.0 224 | max_limit = 1.0 225 | positive_event = &"" 226 | negative_event = &"" 227 | enable_smoothing = false 228 | smoothing_rate = 1.0 229 | cumulative_positive_event = &"" 230 | cumulative_negative_event = &"" 231 | cumulative_rate = 1.0 232 | easing = 1.0 233 | 234 | [sub_resource type="Resource" id="Resource_8vsie"] 235 | script = ExtResource("5_x51t6") 236 | use_bindings = true 237 | input = 0.0 238 | min_limit = -1.0 239 | max_limit = 1.0 240 | positive_event = &"" 241 | negative_event = &"" 242 | enable_smoothing = false 243 | smoothing_rate = 1.0 244 | cumulative_positive_event = &"" 245 | cumulative_negative_event = &"" 246 | cumulative_rate = 1.0 247 | easing = 1.0 248 | 249 | [sub_resource type="BoxShape3D" id="BoxShape3D_nchkm"] 250 | size = Vector3(7.3728, 0.215118, 4.76475) 251 | 252 | [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_6onr1"] 253 | radius = 0.446985 254 | 255 | [sub_resource type="Curve" id="Curve_v5kpe"] 256 | _data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.25, 1), 0.0, 0.0, 0, 0, Vector2(0.5, 0), 0.0, 0.0, 0, 0, Vector2(0.75, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] 257 | point_count = 5 258 | 259 | [sub_resource type="Curve" id="Curve_ob7c7"] 260 | _limits = [0.99, 1.69, 0.0, 1.0] 261 | bake_resolution = 23 262 | _data = [Vector2(0.07, 1), 0.0, 0.0, 0, 0, Vector2(0.088, 1.519), 0.0, 0.0, 0, 0, Vector2(0.119, 1), 0.0, 0.0, 0, 0] 263 | point_count = 3 264 | 265 | [sub_resource type="Curve" id="Curve_cmcrd"] 266 | _limits = [-1.0, 1.0, 0.0, 1.0] 267 | _data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.25, -0.571), 0.0, 0.0, 0, 0, Vector2(0.375, -0.4), 0.0, 0.0, 0, 0, Vector2(0.403, -1), 0.0, 0.0, 0, 0, Vector2(0.597, 1), 0.0, 0.0, 0, 0, Vector2(0.625, 0.4), 0.0, 0.0, 0, 0, Vector2(0.75, 0.571), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] 268 | point_count = 8 269 | 270 | [sub_resource type="Curve" id="Curve_l1ihd"] 271 | bake_resolution = 4 272 | _data = [Vector2(0, 1), 0.0, -2.09824, 0, 0, Vector2(0.406542, 0.490909), -0.666575, -0.666575, 0, 0, Vector2(1, 0.290909), 0.0, 0.0, 0, 0] 273 | point_count = 3 274 | 275 | [sub_resource type="Resource" id="Resource_raxm3"] 276 | script = ExtResource("3_dhflf") 277 | min_lift_coefficient = -1.5 278 | max_lift_coefficient = 1.5 279 | lift_aoa_curve = SubResource("Curve_cmcrd") 280 | min_drag_coefficient = 0.02 281 | max_drag_coefficient = 0.5 282 | drag_aoa_curve = SubResource("Curve_v5kpe") 283 | sweep_drag_multiplier_curve = SubResource("Curve_l1ihd") 284 | drag_at_mach_multiplier_curve = SubResource("Curve_ob7c7") 285 | 286 | [sub_resource type="Resource" id="Resource_b87jk"] 287 | script = ExtResource("4_ht3i8") 288 | chord = 1.0 289 | span = 3.0 290 | auto_aspect_ratio = true 291 | aspect_ratio = 2.0 292 | 293 | [sub_resource type="Resource" id="Resource_4upop"] 294 | script = ExtResource("11_1g5my") 295 | contribution = Vector3(-1, 0, 0) 296 | easing = 2.63902 297 | 298 | [sub_resource type="Resource" id="Resource_11wlp"] 299 | script = ExtResource("12_njgyo") 300 | enable_control = true 301 | max_value = Vector3(0.2, 0, 0) 302 | limit_movement_speed = false 303 | movement_speed = 1.0 304 | pitch_config = SubResource("Resource_4upop") 305 | 306 | [sub_resource type="Resource" id="Resource_7jeqs"] 307 | script = ExtResource("4_ht3i8") 308 | chord = 1.0 309 | span = 1.4 310 | auto_aspect_ratio = true 311 | aspect_ratio = 1.4 312 | 313 | [sub_resource type="Resource" id="Resource_kcr2m"] 314 | script = ExtResource("11_1g5my") 315 | contribution = Vector3(-1, 0, 0) 316 | easing = 1.0 317 | 318 | [sub_resource type="Resource" id="Resource_u1ahl"] 319 | script = ExtResource("12_njgyo") 320 | enable_control = true 321 | max_value = Vector3(0.1, 0, 0) 322 | limit_movement_speed = false 323 | movement_speed = 1.0 324 | yaw_config = SubResource("Resource_kcr2m") 325 | 326 | [sub_resource type="Resource" id="Resource_658jb"] 327 | script = ExtResource("4_ht3i8") 328 | chord = 1.0 329 | span = 3.55 330 | auto_aspect_ratio = true 331 | aspect_ratio = 3.55 332 | 333 | [sub_resource type="Resource" id="Resource_tjhsd"] 334 | script = ExtResource("11_1g5my") 335 | contribution = Vector3(-1, 0, 0) 336 | easing = 1.0 337 | 338 | [sub_resource type="Resource" id="Resource_kwcql"] 339 | script = ExtResource("12_njgyo") 340 | enable_control = true 341 | max_value = Vector3(0.1, 0, 0) 342 | limit_movement_speed = false 343 | movement_speed = 1.0 344 | roll_config = SubResource("Resource_tjhsd") 345 | 346 | [sub_resource type="Resource" id="Resource_tnj5l"] 347 | script = ExtResource("11_1g5my") 348 | contribution = Vector3(1, 0, 0) 349 | easing = 1.0 350 | 351 | [sub_resource type="Resource" id="Resource_hl3vl"] 352 | script = ExtResource("12_njgyo") 353 | enable_control = true 354 | max_value = Vector3(0.1, 0, 0) 355 | limit_movement_speed = false 356 | movement_speed = 1.0 357 | roll_config = SubResource("Resource_tnj5l") 358 | 359 | [node name="AeroBody3D" type="VehicleBody3D"] 360 | mass = 850.0 361 | physics_material_override = SubResource("PhysicsMaterial_m08mi") 362 | center_of_mass_mode = 1 363 | linear_damp_mode = 1 364 | angular_damp_mode = 1 365 | script = ExtResource("1_ddp7w") 366 | 367 | [node name="AeroControlComponent" type="Node" parent="."] 368 | script = ExtResource("2_x2dhw") 369 | flight_assist = SubResource("Resource_qpum4") 370 | pitch_control_config = SubResource("Resource_ry778") 371 | yaw_control_config = SubResource("Resource_f2e3i") 372 | roll_control_config = SubResource("Resource_7svdm") 373 | throttle_control_config = SubResource("Resource_x0pkh") 374 | brake_control_config = SubResource("Resource_8exs8") 375 | collective_control_config = SubResource("Resource_8vsie") 376 | 377 | [node name="CollisionShape3D" type="CollisionShape3D" parent="."] 378 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.00468441, 1.50782) 379 | shape = SubResource("BoxShape3D_nchkm") 380 | 381 | [node name="CollisionShape3D2" type="CollisionShape3D" parent="."] 382 | transform = Transform3D(-4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0, 1, 0, -0.434142, -0.41004) 383 | shape = SubResource("CapsuleShape3D_6onr1") 384 | 385 | [node name="Elevator" type="Node3D" parent="."] 386 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -3.57628e-07, 3.05994) 387 | script = ExtResource("2_ygpq6") 388 | manual_config = SubResource("Resource_raxm3") 389 | wing_config = SubResource("Resource_b87jk") 390 | actuation_config = SubResource("Resource_11wlp") 391 | 392 | [node name="CSGBox3D2" type="CSGBox3D" parent="Elevator"] 393 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.57628e-07, 0.29494) 394 | size = Vector3(2.0332, 0.113525, 1.14014) 395 | 396 | [node name="Rudder" type="Node3D" parent="."] 397 | transform = Transform3D(-4.37114e-08, -1, 3.55271e-15, 0.835693, -3.65293e-08, -0.549198, 0.549198, -2.40062e-08, 0.835693, -2.96035e-17, 0.742918, 3.32475) 398 | script = ExtResource("2_ygpq6") 399 | manual_config = SubResource("Resource_raxm3") 400 | wing_config = SubResource("Resource_7jeqs") 401 | actuation_config = SubResource("Resource_u1ahl") 402 | 403 | [node name="CSGBox3D3" type="CSGBox3D" parent="Rudder"] 404 | transform = Transform3D(-4.37114e-08, 0.999742, -0.0227224, -1, -4.37001e-08, 9.93232e-10, 3.55271e-15, 0.0227224, 0.999742, 0.0151641, -6.6283e-10, 0.239923) 405 | size = Vector3(0.141602, 1.41797, 1.14014) 406 | 407 | [node name="WingL" type="Node3D" parent="."] 408 | transform = Transform3D(0.996195, 0.0871557, 0, -0.0871558, 0.996194, 0, 0, 0, 1, -1.90776, 0, -0.473555) 409 | script = ExtResource("2_ygpq6") 410 | manual_config = SubResource("Resource_raxm3") 411 | wing_config = SubResource("Resource_658jb") 412 | actuation_config = SubResource("Resource_kwcql") 413 | 414 | [node name="CSGBox3D4" type="CSGBox3D" parent="WingL"] 415 | transform = Transform3D(1, 0, 5.96046e-08, 0, 1, 0, -5.96046e-08, 0, 1, 0.0698701, 0, 0.269345) 416 | size = Vector3(3.67285, 0.113525, 1.14014) 417 | 418 | [node name="WingR" type="Node3D" parent="."] 419 | transform = Transform3D(0.996195, -0.0871557, 0, 0.0871558, 0.996194, 0, 0, 0, 1, 1.908, 0, -0.473945) 420 | script = ExtResource("2_ygpq6") 421 | manual_config = SubResource("Resource_raxm3") 422 | wing_config = SubResource("Resource_658jb") 423 | actuation_config = SubResource("Resource_hl3vl") 424 | 425 | [node name="CSGBox3D5" type="CSGBox3D" parent="WingR"] 426 | transform = Transform3D(1, 0, -5.96046e-08, 0, 1, 0, 5.96046e-08, 0, 1, -0.0699999, 0, 0.269945) 427 | size = Vector3(3.67285, 0.113525, 1.14014) 428 | 429 | [node name="Camera3D" type="Camera3D" parent="."] 430 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.55771, 9.0562) 431 | 432 | [node name="AeroThrusterComponent" type="Marker3D" parent="."] 433 | script = ExtResource("8_e4glj") 434 | 435 | [node name="Label3D" type="Label3D" parent="."] 436 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.11, 3.133, 6.07) 437 | text = "Use arrow keys for pitch and roll. 438 | Use page up/page down for throttle control." 439 | -------------------------------------------------------------------------------- /aero_body_3d.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=58 format=3 uid="uid://c3hcp4hfgwyr4"] 2 | 3 | [ext_resource type="Script" uid="uid://3iji867c81k5" path="res://addons/godot_aerodynamic_physics/core/aero_body_3d.gd" id="1_rsftu"] 4 | [ext_resource type="Script" uid="uid://d1725od371u4a" path="res://addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_3d.gd" id="2_xdoe4"] 5 | [ext_resource type="Script" uid="uid://b4xqlunpiyivk" path="res://addons/godot_aerodynamic_physics/utils/pid.gd" id="3_lqxip"] 6 | [ext_resource type="Script" uid="uid://ctbwb7xlhft6t" path="res://addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/manual_aero_surface_3d/manual_aero_surface_config.gd" id="3_unwgd"] 7 | [ext_resource type="Script" uid="uid://cdp8dc63ytxx1" path="res://addons/godot_aerodynamic_physics/core/flight_assist.gd" id="4_iy6eo"] 8 | [ext_resource type="Script" uid="uid://crkbhmxpjff4o" path="res://addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_surface_3d/aero_surface_config.gd" id="4_tfar6"] 9 | [ext_resource type="Script" uid="uid://cc5dwx5pqkaur" path="res://addons/godot_aerodynamic_physics/components/AeroControlComponent.gd" id="5_ayst7"] 10 | [ext_resource type="Script" uid="uid://c46td0go5kvp6" path="res://addons/godot_aerodynamic_physics/core/control_configs/aero_influencer_control_axis_config.gd" id="5_skvh6"] 11 | [ext_resource type="Script" uid="uid://cblxodmmwvpt6" path="res://addons/godot_aerodynamic_physics/core/control_configs/aero_influencer_control_config.gd" id="6_vll1x"] 12 | [ext_resource type="Script" uid="uid://d1qklj3wtuuoc" path="res://addons/godot_aerodynamic_physics/core/aero_influencer_3d/aero_rotator_3d/aero_variable_propeller_3d.gd" id="8_og0ed"] 13 | [ext_resource type="Script" uid="uid://cbgqb7g7mmwtu" path="res://addons/godot_aerodynamic_physics/core/control_configs/aero_control_config.gd" id="10_dnrrx"] 14 | 15 | [sub_resource type="BoxShape3D" id="BoxShape3D_7lntn"] 16 | size = Vector3(6.23047, 1, 4.37244) 17 | 18 | [sub_resource type="Curve" id="Curve_6ojdt"] 19 | bake_resolution = 21 20 | _data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.25, 1), 0.0, 0.0, 0, 0, Vector2(0.5, 0), 0.0, 0.0, 0, 0, Vector2(0.75, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] 21 | point_count = 5 22 | 23 | [sub_resource type="Curve" id="Curve_jl3as"] 24 | _limits = [0.99, 1.69, 0.0, 1.0] 25 | bake_resolution = 23 26 | _data = [Vector2(0.07, 1), 0.0, 0.0, 0, 0, Vector2(0.088, 1.519), 0.0, 0.0, 0, 0, Vector2(0.119, 1), 0.0, 0.0, 0, 0] 27 | point_count = 3 28 | 29 | [sub_resource type="Curve" id="Curve_vwud1"] 30 | _limits = [-1.0, 1.0, 0.0, 1.0] 31 | bake_resolution = 36 32 | _data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.125, 1), 0.0, 0.0, 0, 0, Vector2(0.25, 1.45519e-11), 0.0, 0.0, 0, 0, Vector2(0.375, -0.4), 0.0, 0.0, 0, 0, Vector2(0.415, -1), 0.0, 0.0, 0, 0, Vector2(0.585, 1), 0.0, 0.0, 0, 0, Vector2(0.625, 0.4), 0.0, 0.0, 0, 0, Vector2(0.75, 1.45519e-11), 0.0, 0.0, 0, 0, Vector2(0.875, -1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] 33 | point_count = 10 34 | 35 | [sub_resource type="Curve" id="Curve_lqtsf"] 36 | bake_resolution = 4 37 | _data = [Vector2(0, 1), 0.0, -2.09824, 0, 0, Vector2(0.406542, 0.490909), -0.666575, -0.666575, 0, 0, Vector2(1, 0.290909), 0.0, 0.0, 0, 0] 38 | point_count = 3 39 | 40 | [sub_resource type="Resource" id="Resource_h5un7"] 41 | script = ExtResource("3_unwgd") 42 | min_lift_coefficient = -1.0 43 | max_lift_coefficient = 1.0 44 | lift_aoa_curve = SubResource("Curve_vwud1") 45 | min_drag_coefficient = 0.0 46 | max_drag_coefficient = 1.0 47 | drag_aoa_curve = SubResource("Curve_6ojdt") 48 | sweep_drag_multiplier_curve = SubResource("Curve_lqtsf") 49 | drag_at_mach_multiplier_curve = SubResource("Curve_jl3as") 50 | 51 | [sub_resource type="Resource" id="Resource_s7qy6"] 52 | script = ExtResource("4_tfar6") 53 | chord = 1.0 54 | span = 3.155 55 | auto_aspect_ratio = true 56 | aspect_ratio = 3.155 57 | 58 | [sub_resource type="Resource" id="Resource_csoy7"] 59 | script = ExtResource("5_skvh6") 60 | contribution = Vector3(-1, 0, 0) 61 | easing = 1.0 62 | 63 | [sub_resource type="Resource" id="Resource_m1kth"] 64 | script = ExtResource("6_vll1x") 65 | enable_control = true 66 | max_value = Vector3(0.2, 0, 0) 67 | limit_movement_speed = false 68 | movement_speed = 1.0 69 | roll_config = SubResource("Resource_csoy7") 70 | 71 | [sub_resource type="Resource" id="Resource_s5oyw"] 72 | script = ExtResource("5_skvh6") 73 | contribution = Vector3(1, 0, 0) 74 | easing = 1.0 75 | 76 | [sub_resource type="Resource" id="Resource_3vcci"] 77 | script = ExtResource("6_vll1x") 78 | enable_control = true 79 | max_value = Vector3(0.2, 0, 0) 80 | limit_movement_speed = false 81 | movement_speed = 1.0 82 | roll_config = SubResource("Resource_s5oyw") 83 | 84 | [sub_resource type="Resource" id="Resource_3csbm"] 85 | script = ExtResource("4_tfar6") 86 | chord = 1.0 87 | span = 1.89 88 | auto_aspect_ratio = true 89 | aspect_ratio = 1.89 90 | 91 | [sub_resource type="Resource" id="Resource_av1y7"] 92 | script = ExtResource("5_skvh6") 93 | contribution = Vector3(-1, 0, 0) 94 | easing = 1.0 95 | 96 | [sub_resource type="Resource" id="Resource_hq5d7"] 97 | script = ExtResource("6_vll1x") 98 | enable_control = true 99 | max_value = Vector3(0.2, 0, 0) 100 | limit_movement_speed = false 101 | movement_speed = 1.0 102 | pitch_config = SubResource("Resource_av1y7") 103 | 104 | [sub_resource type="Resource" id="Resource_t11qk"] 105 | script = ExtResource("4_tfar6") 106 | chord = 1.0 107 | span = 1.245 108 | auto_aspect_ratio = true 109 | aspect_ratio = 1.245 110 | 111 | [sub_resource type="Resource" id="Resource_s32jv"] 112 | script = ExtResource("5_skvh6") 113 | contribution = Vector3(1, 0, 0) 114 | easing = 1.0 115 | 116 | [sub_resource type="Resource" id="Resource_eq2as"] 117 | script = ExtResource("6_vll1x") 118 | enable_control = true 119 | max_value = Vector3(0.4, 0, 0) 120 | limit_movement_speed = false 121 | movement_speed = 1.0 122 | yaw_config = SubResource("Resource_s32jv") 123 | 124 | [sub_resource type="GDScript" id="GDScript_8p6bo"] 125 | script/source = "extends Camera3D 126 | 127 | @export var follow_distance : float = 6.0 128 | @export var transition_speed : float = 100.0 129 | @export var rotation_effect_multiplier : float = 3.0 130 | @export var look_ahead_lag_behind_toggle : bool = true 131 | 132 | @onready var last_pos : Vector3 = get_parent().global_position 133 | var dir : Vector3 134 | @onready var last_rotation : Basis = get_parent().global_basis 135 | var rot : Basis 136 | 137 | func _physics_process(delta): 138 | #calculate the direction of movement 139 | var movement_this_step = get_parent().global_position - last_pos 140 | dir = movement_this_step.normalized() 141 | var movement_speed : float = movement_this_step.length() / delta 142 | 143 | #smooths camera movement at lower speeds 144 | var lerp_factor : float = clamp(remap(movement_speed, 0, transition_speed, 0, 1), 0, 1) 145 | dir = lerp(Vector3(0, 0, -1) * get_parent().global_basis.inverse(), dir, lerp_factor) 146 | 147 | #get change in rotation 148 | var rotation_this_step = get_parent().global_basis.inverse() * last_rotation 149 | rot = rotation_this_step 150 | 151 | last_pos = get_parent().global_position 152 | last_rotation = get_parent().global_basis 153 | 154 | func _process(delta): 155 | #allows us to flip if the camera will lag behind due to movements, or if it will look ahead due to movements. 156 | if look_ahead_lag_behind_toggle: 157 | #position camera relative to movement direction 158 | #makes the camera appear to \"lag behind\" the airplane 159 | #in other words, the camera responds psudo-physically to the airplane's movements 160 | position = -dir * get_parent().global_basis * follow_distance 161 | 162 | #use change in rotation to make camera move in response to plane movements 163 | rotation = (rot).get_euler() * -rotation_effect_multiplier 164 | else: 165 | position = dir * get_parent().global_basis * follow_distance 166 | rotation = (rot).get_euler() * rotation_effect_multiplier 167 | 168 | 169 | " 170 | 171 | [sub_resource type="Resource" id="Resource_bqiga"] 172 | script = ExtResource("3_lqxip") 173 | p = 0.001 174 | i = 0.0 175 | d = 0.01 176 | clamp_integral = false 177 | min_integral = -1.0 178 | max_integral = 1.0 179 | 180 | [sub_resource type="Resource" id="Resource_21c0u"] 181 | script = ExtResource("3_lqxip") 182 | p = 1.0 183 | i = 0.05 184 | d = 0.1 185 | clamp_integral = false 186 | min_integral = -1.0 187 | max_integral = 1.0 188 | 189 | [sub_resource type="Resource" id="Resource_msv1l"] 190 | script = ExtResource("3_lqxip") 191 | p = 10.0 192 | i = 0.3 193 | d = 4.0 194 | clamp_integral = false 195 | min_integral = -1.0 196 | max_integral = 1.0 197 | 198 | [sub_resource type="Resource" id="Resource_7xquj"] 199 | script = ExtResource("3_lqxip") 200 | p = 0.8 201 | i = 0.0 202 | d = 0.0 203 | clamp_integral = false 204 | min_integral = -1.0 205 | max_integral = 1.0 206 | 207 | [sub_resource type="Resource" id="Resource_5hmoc"] 208 | script = ExtResource("3_lqxip") 209 | p = 25.0 210 | i = 0.0 211 | d = 0.0 212 | clamp_integral = false 213 | min_integral = -1.0 214 | max_integral = 1.0 215 | 216 | [sub_resource type="Resource" id="Resource_3p5e8"] 217 | script = ExtResource("3_lqxip") 218 | p = 10.0 219 | i = 0.0 220 | d = 0.0 221 | clamp_integral = false 222 | min_integral = -1.0 223 | max_integral = 1.0 224 | 225 | [sub_resource type="Resource" id="Resource_5cgdl"] 226 | script = ExtResource("3_lqxip") 227 | p = 10.0 228 | i = 0.0 229 | d = 0.0 230 | clamp_integral = false 231 | min_integral = -1.0 232 | max_integral = 1.0 233 | 234 | [sub_resource type="Resource" id="Resource_mxikr"] 235 | script = ExtResource("3_lqxip") 236 | p = 0.5 237 | i = 3.0 238 | d = 0.0 239 | clamp_integral = true 240 | min_integral = -0.2 241 | max_integral = 0.2 242 | 243 | [sub_resource type="Resource" id="Resource_ixe5b"] 244 | script = ExtResource("3_lqxip") 245 | p = 0.4 246 | i = 1.0 247 | d = 0.0 248 | clamp_integral = true 249 | min_integral = -0.1 250 | max_integral = 0.1 251 | 252 | [sub_resource type="Resource" id="Resource_m8s2u"] 253 | script = ExtResource("3_lqxip") 254 | p = 0.0 255 | i = 0.4 256 | d = 0.0 257 | clamp_integral = false 258 | min_integral = -1.0 259 | max_integral = 1.0 260 | 261 | [sub_resource type="Resource" id="Resource_66lwe"] 262 | script = ExtResource("3_lqxip") 263 | p = 1.0 264 | i = 0.0 265 | d = 0.0 266 | clamp_integral = false 267 | min_integral = -1.0 268 | max_integral = 1.0 269 | 270 | [sub_resource type="Resource" id="Resource_041xi"] 271 | script = ExtResource("4_iy6eo") 272 | enable_flight_assist = true 273 | max_angular_rates = Vector3(2, 1, 5) 274 | enable_flight_assist_x = true 275 | pitch_assist_pid = SubResource("Resource_mxikr") 276 | enable_flight_assist_y = true 277 | yaw_assist_pid = SubResource("Resource_66lwe") 278 | enable_flight_assist_z = true 279 | roll_assist_pid = SubResource("Resource_ixe5b") 280 | enable_g_limiter = true 281 | g_limit = 9.0 282 | negative_g_limit = 3.0 283 | enable_aoa_limiter = true 284 | aoa_limit_start = 22.0 285 | aoa_limit_end = 25.0 286 | enable_control_adjustment = false 287 | tuned_airspeed = 100.0 288 | min_accounted_airspeed = 75.0 289 | tuned_density = 1.222 290 | min_accounted_air_density = 0.1 291 | enable_bank_angle_assist = false 292 | bank_angle_target = 0.0 293 | bank_angle_pid = SubResource("Resource_21c0u") 294 | enable_heading_hold = false 295 | heading_target = 0.0 296 | heading_pid = SubResource("Resource_3p5e8") 297 | enable_inclination_hold = false 298 | inclination_target = 0.0 299 | inclination_pid = SubResource("Resource_5cgdl") 300 | enable_speed_hold = false 301 | speed_target = 0.0 302 | speed_pid = SubResource("Resource_m8s2u") 303 | enable_altitude_hold = false 304 | altitude_target = 0.0 305 | altitude_pid = SubResource("Resource_bqiga") 306 | enable_target_direction = false 307 | direction_target = Vector3(0, 0, 0) 308 | direction_pitch_pid = SubResource("Resource_msv1l") 309 | direction_yaw_pid = SubResource("Resource_5hmoc") 310 | direction_roll_pid = SubResource("Resource_7xquj") 311 | 312 | [sub_resource type="Resource" id="Resource_hlxhx"] 313 | script = ExtResource("10_dnrrx") 314 | use_bindings = true 315 | input = 0.0 316 | min_limit = -1.0 317 | max_limit = 1.0 318 | positive_event = &"ui_down" 319 | negative_event = &"ui_up" 320 | enable_smoothing = false 321 | smoothing_rate = 1.0 322 | cumulative_positive_event = &"" 323 | cumulative_negative_event = &"" 324 | cumulative_rate = 1.0 325 | easing = 1.0 326 | 327 | [sub_resource type="Resource" id="Resource_lwdgp"] 328 | script = ExtResource("10_dnrrx") 329 | use_bindings = true 330 | input = 0.0 331 | min_limit = -1.0 332 | max_limit = 1.0 333 | positive_event = &"" 334 | negative_event = &"" 335 | enable_smoothing = false 336 | smoothing_rate = 1.0 337 | cumulative_positive_event = &"" 338 | cumulative_negative_event = &"" 339 | cumulative_rate = 1.0 340 | easing = 1.0 341 | 342 | [sub_resource type="Resource" id="Resource_fnse0"] 343 | script = ExtResource("10_dnrrx") 344 | use_bindings = true 345 | input = 0.0 346 | min_limit = -1.0 347 | max_limit = 1.0 348 | positive_event = &"ui_left" 349 | negative_event = &"ui_right" 350 | enable_smoothing = false 351 | smoothing_rate = 1.0 352 | cumulative_positive_event = &"" 353 | cumulative_negative_event = &"" 354 | cumulative_rate = 1.0 355 | easing = 1.0 356 | 357 | [sub_resource type="Resource" id="Resource_uiyyl"] 358 | script = ExtResource("10_dnrrx") 359 | use_bindings = true 360 | input = 0.0 361 | min_limit = 0.0 362 | max_limit = 1.0 363 | positive_event = &"" 364 | negative_event = &"" 365 | enable_smoothing = false 366 | smoothing_rate = 1.0 367 | cumulative_positive_event = &"throttle_up" 368 | cumulative_negative_event = &"throttle_down" 369 | cumulative_rate = 1.0 370 | easing = 1.0 371 | 372 | [sub_resource type="Resource" id="Resource_m41ta"] 373 | script = ExtResource("10_dnrrx") 374 | use_bindings = true 375 | input = 0.0 376 | min_limit = 0.0 377 | max_limit = 1.0 378 | positive_event = &"" 379 | negative_event = &"" 380 | enable_smoothing = false 381 | smoothing_rate = 1.0 382 | cumulative_positive_event = &"" 383 | cumulative_negative_event = &"" 384 | cumulative_rate = 1.0 385 | easing = 1.0 386 | 387 | [sub_resource type="Resource" id="Resource_pgfhd"] 388 | script = ExtResource("10_dnrrx") 389 | use_bindings = true 390 | input = 0.0 391 | min_limit = -1.0 392 | max_limit = 1.0 393 | positive_event = &"" 394 | negative_event = &"" 395 | enable_smoothing = false 396 | smoothing_rate = 1.0 397 | cumulative_positive_event = &"" 398 | cumulative_negative_event = &"" 399 | cumulative_rate = 1.0 400 | easing = 1.0 401 | 402 | [sub_resource type="Resource" id="Resource_3r7bg"] 403 | script = ExtResource("5_skvh6") 404 | contribution = Vector3(1, 0, 0) 405 | easing = 1.0 406 | 407 | [sub_resource type="Resource" id="Resource_w2d5j"] 408 | script = ExtResource("6_vll1x") 409 | enable_control = true 410 | max_value = Vector3(0.5, 0, 0) 411 | limit_movement_speed = false 412 | movement_speed = 1.0 413 | collective_config = SubResource("Resource_3r7bg") 414 | 415 | [sub_resource type="Resource" id="Resource_dcl7k"] 416 | script = ExtResource("5_skvh6") 417 | contribution = Vector3(1, 1, 1) 418 | easing = 1.0 419 | 420 | [sub_resource type="Resource" id="Resource_chsa6"] 421 | script = ExtResource("6_vll1x") 422 | enable_control = true 423 | max_value = Vector3(0, 100, 0) 424 | limit_movement_speed = false 425 | movement_speed = 1.0 426 | throttle_config = SubResource("Resource_dcl7k") 427 | 428 | [sub_resource type="Curve" id="Curve_rfvuu"] 429 | bake_resolution = 21 430 | _data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.25, 1), 0.0, 0.0, 0, 0, Vector2(0.5, 0), 0.0, 0.0, 0, 0, Vector2(0.75, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] 431 | point_count = 5 432 | 433 | [sub_resource type="Curve" id="Curve_ofsw4"] 434 | _limits = [0.99, 1.69, 0.0, 1.0] 435 | bake_resolution = 23 436 | _data = [Vector2(0.07, 1), 0.0, 0.0, 0, 0, Vector2(0.088, 1.519), 0.0, 0.0, 0, 0, Vector2(0.119, 1), 0.0, 0.0, 0, 0] 437 | point_count = 3 438 | 439 | [sub_resource type="Curve" id="Curve_fngpt"] 440 | _limits = [-1.0, 1.0, 0.0, 1.0] 441 | bake_resolution = 36 442 | _data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.125, 1), 0.0, 0.0, 0, 0, Vector2(0.25, 1.45519e-11), 0.0, 0.0, 0, 0, Vector2(0.375, -0.4), 0.0, 0.0, 0, 0, Vector2(0.415, -1), 0.0, 0.0, 0, 0, Vector2(0.585, 1), 0.0, 0.0, 0, 0, Vector2(0.625, 0.4), 0.0, 0.0, 0, 0, Vector2(0.75, 1.45519e-11), 0.0, 0.0, 0, 0, Vector2(0.875, -1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] 443 | point_count = 10 444 | 445 | [sub_resource type="Curve" id="Curve_3sn0y"] 446 | bake_resolution = 4 447 | _data = [Vector2(0, 1), 0.0, -2.09824, 0, 0, Vector2(0.406542, 0.490909), -0.666575, -0.666575, 0, 0, Vector2(1, 0.290909), 0.0, 0.0, 0, 0] 448 | point_count = 3 449 | 450 | [sub_resource type="Resource" id="Resource_6jmqh"] 451 | script = ExtResource("3_unwgd") 452 | min_lift_coefficient = -1.0 453 | max_lift_coefficient = 1.0 454 | lift_aoa_curve = SubResource("Curve_fngpt") 455 | min_drag_coefficient = 0.0 456 | max_drag_coefficient = 1.0 457 | drag_aoa_curve = SubResource("Curve_rfvuu") 458 | sweep_drag_multiplier_curve = SubResource("Curve_3sn0y") 459 | drag_at_mach_multiplier_curve = SubResource("Curve_ofsw4") 460 | 461 | [sub_resource type="Resource" id="Resource_lqhqc"] 462 | script = ExtResource("4_tfar6") 463 | chord = 0.25 464 | span = 1.0 465 | auto_aspect_ratio = true 466 | aspect_ratio = 4.0 467 | 468 | [node name="AeroBody3D" type="VehicleBody3D"] 469 | mass = 600.0 470 | center_of_mass_mode = 1 471 | center_of_mass = Vector3(0, 0, 0.2) 472 | linear_damp_mode = 1 473 | angular_damp_mode = 1 474 | script = ExtResource("1_rsftu") 475 | show_lift_vectors = false 476 | show_drag_vectors = false 477 | show_linear_velocity = false 478 | show_center_of_thrust = false 479 | 480 | [node name="CollisionShape3D" type="CollisionShape3D" parent="."] 481 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.23993) 482 | visible = false 483 | shape = SubResource("BoxShape3D_7lntn") 484 | 485 | [node name="WingL" type="Node3D" parent="."] 486 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.624, 0, -0.212918) 487 | script = ExtResource("2_xdoe4") 488 | manual_config = SubResource("Resource_h5un7") 489 | wing_config = SubResource("Resource_s7qy6") 490 | actuation_config = SubResource("Resource_m1kth") 491 | 492 | [node name="CSGBox3D2" type="CSGBox3D" parent="WingL"] 493 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.238) 494 | size = Vector3(3.17578, 0.048584, 1) 495 | 496 | [node name="WingR" type="Node3D" parent="."] 497 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.62446, 0, -0.212918) 498 | script = ExtResource("2_xdoe4") 499 | manual_config = SubResource("Resource_h5un7") 500 | wing_config = SubResource("Resource_s7qy6") 501 | actuation_config = SubResource("Resource_3vcci") 502 | 503 | [node name="CSGBox3D" type="CSGBox3D" parent="WingR"] 504 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.238172) 505 | size = Vector3(3.17578, 0.048584, 1) 506 | 507 | [node name="Elevator" type="Node3D" parent="."] 508 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2.5542) 509 | script = ExtResource("2_xdoe4") 510 | manual_config = SubResource("Resource_h5un7") 511 | wing_config = SubResource("Resource_3csbm") 512 | actuation_config = SubResource("Resource_hq5d7") 513 | 514 | [node name="CSGBox3D" type="CSGBox3D" parent="Elevator"] 515 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.226665) 516 | size = Vector3(1.77051, 0.0627441, 1) 517 | 518 | [node name="Rudder" type="Node3D" parent="."] 519 | transform = Transform3D(-4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, 0, 1, 0, 0.643676, 2.45706) 520 | script = ExtResource("2_xdoe4") 521 | manual_config = SubResource("Resource_h5un7") 522 | wing_config = SubResource("Resource_t11qk") 523 | actuation_config = SubResource("Resource_eq2as") 524 | 525 | [node name="CSGBox3D" type="CSGBox3D" parent="Rudder"] 526 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.238376) 527 | size = Vector3(1.23145, 0.12793, 1) 528 | 529 | [node name="Camera3D" type="Camera3D" parent="."] 530 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.279, 6.922) 531 | visible = false 532 | script = SubResource("GDScript_8p6bo") 533 | 534 | [node name="AeroControlComponent" type="Node" parent="."] 535 | script = ExtResource("5_ayst7") 536 | flight_assist = SubResource("Resource_041xi") 537 | pitch_control_config = SubResource("Resource_hlxhx") 538 | yaw_control_config = SubResource("Resource_lwdgp") 539 | roll_control_config = SubResource("Resource_fnse0") 540 | throttle_control_config = SubResource("Resource_uiyyl") 541 | brake_control_config = SubResource("Resource_m41ta") 542 | collective_control_config = SubResource("Resource_pgfhd") 543 | 544 | [node name="AeroVariablePropeller3D" type="Node3D" parent="." node_paths=PackedStringArray("propeller_blade")] 545 | transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, -0.699653) 546 | script = ExtResource("8_og0ed") 547 | propeller_pitch = 45.0 548 | collective = 45.0 549 | propeller_collective_control_config = SubResource("Resource_w2d5j") 550 | propeller_amount = 3 551 | propeller_blade = NodePath("ManualAeroSurface3D") 552 | propeller_speed_control_config = SubResource("Resource_chsa6") 553 | angular_motor = Vector3(0, 150, 0) 554 | 555 | [node name="ManualAeroSurface3D" type="Node3D" parent="AeroVariablePropeller3D"] 556 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6, 0, 0) 557 | script = ExtResource("2_xdoe4") 558 | manual_config = SubResource("Resource_6jmqh") 559 | wing_config = SubResource("Resource_lqhqc") 560 | 561 | [node name="CSGBox3D" type="CSGBox3D" parent="AeroVariablePropeller3D/ManualAeroSurface3D"] 562 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.0611309) 563 | size = Vector3(1, 0.0332031, 0.25) 564 | 565 | [node name="CSGCylinder3D" type="CSGCylinder3D" parent="AeroVariablePropeller3D"] 566 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.214175, 0) 567 | radius = 0.0617059 568 | height = 0.456036 569 | -------------------------------------------------------------------------------- /addons/godot_aerodynamic_physics/core/aero_body_3d.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends VehicleBody3D 3 | class_name AeroBody3D 4 | 5 | const AeroMathUtils = preload("../utils/math_utils.gd") 6 | const AeroNodeUtils = preload("../utils/node_utils.gd") 7 | 8 | ## Overrides the amount of simulation substeps are used when calculating aerodynamic effects on this body. 9 | @export var substeps_override : int = -1: 10 | set(x): 11 | substeps_override = x 12 | PREDICTION_TIMESTEP_FRACTION = 1.0 / float(SUBSTEPS) 13 | 14 | @export_group("Control") 15 | ## Value used by AeroInfluencers to control the AeroBody3D. Represents rotational axes. 16 | ## X = Pitch, Y = Yaw, Z = Roll. 17 | @export var control_command : Vector3 = Vector3.ZERO 18 | ## Value used by AeroInfluencers to control the AeroBody3D. 19 | @export var throttle_command : float = 0.0 20 | ## Value used by AeroInfluencers to control the AeroBody3D. 21 | @export var brake_command : float = 0.0 22 | ## Value used by AeroInfluencers to control the AeroBody3D. 23 | @export var collective_command : float = 0.0 24 | 25 | @export_group("Debug") 26 | 27 | @export_subgroup("Visibility") 28 | ## Enables visibility of debug components. 29 | @export var show_debug : bool = false: 30 | set(x): 31 | show_debug = x 32 | _update_debug_visibility() 33 | ## Enables update of debug components. Debug is only updated when show_debug and update_debug are true. 34 | @export var update_debug : bool = true 35 | ## Enables visibility of wing debug components. 36 | @export var show_wing_debug_vectors : bool = true: 37 | set(x): 38 | show_wing_debug_vectors = x 39 | _update_debug_visibility() 40 | ## Controls visibility of total lift vector. 41 | @export var show_lift_vectors : bool = true: 42 | set(x): 43 | show_lift_vectors = x 44 | _update_debug_visibility() 45 | ## Controls visibility of total drag vector. 46 | @export var show_drag_vectors : bool = true: 47 | set(x): 48 | show_drag_vectors = x 49 | _update_debug_visibility() 50 | ## Controls visibility of linear velocity vector. 51 | @export var show_linear_velocity : bool = true: 52 | set(x): 53 | show_linear_velocity = x 54 | _update_debug_visibility() 55 | ## Controls visibility of angular velocity vector. 56 | @export var show_angular_velocity : bool = true: 57 | set(x): 58 | show_angular_velocity = x 59 | _update_debug_visibility() 60 | 61 | ## Controls visibility of the center of lift vector. 62 | @export var show_center_of_lift : bool = true: 63 | set(x): 64 | show_center_of_lift = x 65 | _update_debug_visibility() 66 | ## Controls visibility of the center of drag vector. 67 | @export var show_center_of_drag : bool = true: 68 | set(x): 69 | show_center_of_drag = x 70 | _update_debug_visibility() 71 | ## Controls visibility of the center of mass marker. 72 | @export var show_center_of_mass : bool = true: 73 | set(x): 74 | show_center_of_mass = x 75 | _update_debug_visibility() 76 | ## Controls visibility of the center of lift marker. (Unused) 77 | @export var show_center_of_thrust : bool = true: 78 | set(x): 79 | show_center_of_thrust = x 80 | 81 | @export_subgroup("Options") 82 | ## Linear velocity used for debug components calculations in the editor. 83 | @export var debug_linear_velocity := Vector3(0, -10, -100) 84 | ## Angular velocity used for debug components calculations in the editor. 85 | @export var debug_angular_velocity := Vector3.ZERO 86 | ## Controls the scale of debug components. 87 | @export var debug_scale : float = 0.1: 88 | set(x): 89 | debug_scale = x 90 | _update_debug_scale() 91 | ## Controls the width/thickness of debug vectors. 92 | @export var debug_width : float = 0.05: 93 | set(x): 94 | debug_width = x 95 | _update_debug_scale() 96 | ## Controls the width/thickness of debug center markers. 97 | @export var debug_center_width : float = 0.2: 98 | set(x): 99 | debug_center_width = x 100 | _update_debug_scale() 101 | 102 | _update_debug_visibility() 103 | 104 | @export_subgroup("") 105 | @export_group("") 106 | @export_category("") 107 | 108 | var SUBSTEPS : int = ProjectSettings.get_setting("physics/3d/aerodynamics/substeps", 1): 109 | set(x): 110 | SUBSTEPS = x 111 | PREDICTION_TIMESTEP_FRACTION = 1.0 / float(SUBSTEPS) 112 | get: 113 | if substeps_override > -1: 114 | return substeps_override 115 | return ProjectSettings.get_setting("physics/3d/aerodynamics/substeps", 1) 116 | var PREDICTION_TIMESTEP_FRACTION : float = 1.0 / float(SUBSTEPS) 117 | 118 | var aero_influencers : Array[AeroInfluencer3D] = [] 119 | var aero_surfaces : Array[AeroSurface3D] = [] 120 | 121 | var current_force := Vector3.ZERO 122 | var current_torque := Vector3.ZERO 123 | var current_gravity := Vector3.ZERO 124 | @onready var last_linear_velocity : Vector3 = linear_velocity 125 | @onready var last_angular_velocity : Vector3 = angular_velocity 126 | var wind := Vector3.ZERO: 127 | set(x): 128 | if not wind == x: interrupt_sleep() 129 | wind = x 130 | var air_velocity := Vector3.ZERO 131 | var local_air_velocity := Vector3.ZERO 132 | var local_angular_velocity := Vector3.ZERO 133 | var air_speed := 0.0 134 | var mach := 0.0 135 | var air_density : float = 0.0 136 | var air_pressure : float = 0.0 137 | var angle_of_attack := 0.0 138 | var sideslip_angle := 0.0 139 | var altitude := 0.0 140 | var bank_angle := 0.0 141 | var heading := 0.0 142 | var inclination := 0.0 143 | 144 | 145 | #override warning tests 146 | var test_enter_tree_override : bool = false 147 | var test_ready_override : bool = false 148 | var test_physics_process_override : bool = false 149 | var test_integrate_forces_override : bool = false 150 | 151 | func test_overrides() -> void: 152 | if not is_inside_tree() or not get_tree(): 153 | #push_error("Not inside tree, couldn't test method overrides.") 154 | return 155 | 156 | if not test_enter_tree_override: 157 | push_warning("_enter_tree() was overriden, but super._enter_tree() was not called. AeroBody3D may not work properly. " + get_script().get_path()) 158 | if not test_ready_override: 159 | push_warning("_ready() was overriden, but super._ready() was not called. AeroBody3D may not work properly." + get_script().get_path()) 160 | if can_process() and not test_physics_process_override: 161 | push_warning("_physics_process() was overriden, but super._physics_process() was not called. AeroBody3D may not work properly. " + get_script().get_path()) 162 | 163 | if not Engine.is_editor_hint(): 164 | await get_tree().physics_frame 165 | if can_process() and not test_integrate_forces_override: 166 | push_warning("_integrate_forces() was overriden, but super._integrate_forces() was not called. AeroBody3D may not work properly." + get_script().get_path()) 167 | 168 | 169 | 170 | #debug 171 | var linear_velocity_vector : AeroDebugVector3D 172 | var angular_velocity_vector : AeroDebugVector3D 173 | 174 | var lift_debug_vector : AeroDebugVector3D 175 | var drag_debug_vector : AeroDebugVector3D 176 | 177 | var mass_debug_point : AeroDebugPoint3D 178 | var thrust_debug_vector : AeroDebugVector3D 179 | 180 | func _init(): 181 | mass_debug_point = AeroDebugPoint3D.new(Color(1, 1, 0), debug_center_width, true) 182 | mass_debug_point.name = "MassDebugPoint" 183 | mass_debug_point.visible = false 184 | mass_debug_point.sorting_offset = 0.0 185 | 186 | lift_debug_vector = AeroDebugVector3D.new(Color(0, 1, 1), debug_center_width, true) 187 | lift_debug_vector.name = "LiftDebugVector" 188 | lift_debug_vector.visible = false 189 | lift_debug_vector.sorting_offset = 0.0 190 | 191 | drag_debug_vector = AeroDebugVector3D.new(Color(1, 0, 0), debug_width, true) 192 | drag_debug_vector.name = "DragDebugVector" 193 | drag_debug_vector.visible = false 194 | drag_debug_vector.sorting_offset = -0.01 195 | 196 | thrust_debug_vector = AeroDebugVector3D.new(Color(1, 0, 1), debug_width, true) 197 | thrust_debug_vector.name = "ThrustDebugVector" 198 | thrust_debug_vector.visible = false 199 | thrust_debug_vector.sorting_offset = -0.02 200 | 201 | linear_velocity_vector = AeroDebugVector3D.new(Color(0, 0.5, 0.5), debug_width, false) 202 | linear_velocity_vector.name = "LinearVelocityVector" 203 | linear_velocity_vector.visible = false 204 | linear_velocity_vector.sorting_offset = -0.03 205 | 206 | angular_velocity_vector = AeroDebugVector3D.new(Color(0, 0.333, 0), debug_width, false) 207 | angular_velocity_vector.name = "AngularVelocityVector" 208 | angular_velocity_vector.visible = false 209 | angular_velocity_vector.sorting_offset = -0.04 210 | 211 | linear_damp_mode = RigidBody3D.DAMP_MODE_REPLACE 212 | angular_damp_mode = RigidBody3D.DAMP_MODE_REPLACE 213 | 214 | center_of_mass_mode = RigidBody3D.CENTER_OF_MASS_MODE_CUSTOM 215 | 216 | #test that necessary functions (_ready(), _enter_tree(), _physics_process() 217 | test_overrides.call_deferred() 218 | 219 | func _enter_tree() -> void: 220 | test_enter_tree_override = true 221 | 222 | AeroNodeUtils.connect_signal_safe(self, "child_entered_tree", on_child_enter_tree, 0, true) 223 | AeroNodeUtils.connect_signal_safe(self, "child_exiting_tree", on_child_exit_tree, 0, true) 224 | 225 | if Engine.is_editor_hint(): 226 | update_configuration_warnings() 227 | 228 | func on_child_enter_tree(node : Node) -> void: 229 | if node is AeroInfluencer3D: 230 | aero_influencers.append(node) 231 | node.aero_body = self 232 | 233 | func on_child_exit_tree(node : Node) -> void: 234 | if node is AeroInfluencer3D and aero_influencers.has(node): 235 | aero_influencers.erase(node) 236 | node.aero_body = null 237 | 238 | func _ready() -> void: 239 | test_ready_override = true 240 | 241 | add_child(mass_debug_point, INTERNAL_MODE_FRONT) 242 | add_child(lift_debug_vector, INTERNAL_MODE_FRONT) 243 | add_child(drag_debug_vector, INTERNAL_MODE_FRONT) 244 | add_child(thrust_debug_vector, INTERNAL_MODE_FRONT) 245 | mass_debug_point.add_child(linear_velocity_vector, INTERNAL_MODE_FRONT) 246 | mass_debug_point.add_child(angular_velocity_vector, INTERNAL_MODE_FRONT) 247 | 248 | if Engine.is_editor_hint(): 249 | update_configuration_warnings() 250 | 251 | func _get_configuration_warnings() -> PackedStringArray: 252 | var warnings := PackedStringArray([]) 253 | var default_linear_damp : float = ProjectSettings.get_setting("physics/3d/default_linear_damp", 0.0) 254 | if linear_damp_mode == DAMP_MODE_COMBINE: 255 | if default_linear_damp + linear_damp > 0.0: 256 | warnings.append("Linear damping is greater than 0. Unexpected aerodynamic characteristics will be present.") 257 | else: 258 | if linear_damp > 0.0: 259 | warnings.append("Linear damping is greater than 0. Unexpected aerodynamic characteristics will be present.") 260 | 261 | var default_angular_damp : float = ProjectSettings.get_setting("physics/3d/default_angular_damp", 0.0) 262 | if angular_damp_mode == DAMP_MODE_COMBINE: 263 | if default_angular_damp + angular_damp > 0.0: 264 | warnings.append("Angular damping is greater than 0. Unexpected aerodynamic characteristics will be present.") 265 | else: 266 | if angular_damp > 0.0: 267 | warnings.append("Angular damping is greater than 0. Unexpected aerodynamic characteristics will be present.") 268 | 269 | return warnings 270 | 271 | func _physics_process(delta : float) -> void: 272 | test_physics_process_override = true 273 | 274 | if show_debug and update_debug: 275 | _update_debug() 276 | 277 | var _integrate_forces_time : float = 0.0 278 | func _integrate_forces(state : PhysicsDirectBodyState3D) -> void: 279 | test_integrate_forces_override = true 280 | 281 | if is_overriding_body_sleep(): 282 | interrupt_sleep() 283 | 284 | if state.sleeping or SUBSTEPS == 0: 285 | return 286 | 287 | var pre_time : int = Time.get_ticks_usec() 288 | integrator(state) 289 | var post_time : int = Time.get_ticks_usec() 290 | _integrate_forces_time = float(post_time - pre_time) * 0.001 291 | 292 | func integrator(state : PhysicsDirectBodyState3D) -> void: 293 | current_gravity = state.total_gravity 294 | var total_force_and_torque := calculate_forces(state) 295 | current_force = total_force_and_torque[0] 296 | current_torque = total_force_and_torque[1] 297 | state.apply_central_force(current_force) 298 | state.apply_torque(current_torque) 299 | 300 | var linear_velocity_prediction : Vector3 = linear_velocity 301 | var angular_velocity_prediction : Vector3 = angular_velocity 302 | var substep_delta : float = get_physics_process_delta_time() / SUBSTEPS 303 | 304 | func calculate_forces(state : PhysicsDirectBodyState3D) -> PackedVector3Array: 305 | #eventually implement wind 306 | #wind = Vector3.ZERO 307 | air_velocity = -linear_velocity + wind 308 | air_speed = air_velocity.length() 309 | 310 | if has_node("/root/AeroUnits"): 311 | var _AeroUnits : Node = $"/root/AeroUnits" 312 | altitude = _AeroUnits.get_altitude(self) 313 | mach = _AeroUnits.speed_to_mach_at_altitude(air_speed, altitude) 314 | air_density = _AeroUnits.get_density_at_altitude(altitude) 315 | air_pressure = _AeroUnits.get_pressure_at_altitude(altitude) 316 | 317 | local_air_velocity = global_transform.basis.inverse() * air_velocity 318 | local_angular_velocity = global_transform.basis.inverse() * angular_velocity 319 | angle_of_attack = global_basis.y.angle_to(-air_velocity) - (PI / 2.0) 320 | sideslip_angle = global_basis.x.angle_to(air_velocity) - (PI / 2.0) 321 | bank_angle = rotation.z 322 | heading = rotation.y 323 | inclination = rotation.x 324 | if not Engine.is_editor_hint(): 325 | center_of_mass = state.center_of_mass_local 326 | 327 | substep_delta = state.step / SUBSTEPS 328 | 329 | var last_force_and_torque := PackedVector3Array([Vector3.ZERO, Vector3.ZERO]) 330 | var total_force_and_torque := last_force_and_torque 331 | 332 | linear_velocity_prediction = linear_velocity 333 | angular_velocity_prediction = angular_velocity 334 | 335 | for substep : int in SUBSTEPS: 336 | last_linear_velocity = linear_velocity_prediction 337 | last_angular_velocity = angular_velocity_prediction 338 | 339 | #allow aeroinfluencers to update their own transforms before we calculate forces 340 | if not Engine.is_editor_hint(): 341 | for influencer : AeroInfluencer3D in aero_influencers: 342 | if influencer.disabled: 343 | continue 344 | influencer._update_transform_substep(substep_delta) 345 | 346 | linear_velocity_prediction = predict_linear_velocity(last_force_and_torque[0]) + state.total_gravity * PREDICTION_TIMESTEP_FRACTION 347 | angular_velocity_prediction = predict_angular_velocity(last_force_and_torque[1]) 348 | last_force_and_torque = calculate_aerodynamic_forces(linear_velocity_prediction, angular_velocity_prediction, air_density, substep_delta) 349 | 350 | #add to total forces 351 | total_force_and_torque[0] += last_force_and_torque[0] 352 | total_force_and_torque[1] += last_force_and_torque[1] 353 | 354 | total_force_and_torque[0] = total_force_and_torque[0] / SUBSTEPS 355 | total_force_and_torque[1] = total_force_and_torque[1] / SUBSTEPS 356 | return total_force_and_torque 357 | 358 | func calculate_aerodynamic_forces(_velocity : Vector3, _angular_velocity : Vector3, air_density : float, substep_delta : float = 0.0) -> PackedVector3Array: 359 | var force : Vector3 360 | var torque : Vector3 361 | 362 | #can we parallelize this for loop? 363 | for influencer : AeroInfluencer3D in aero_influencers: 364 | if influencer.disabled: 365 | continue 366 | 367 | #relative_position is the position of the surface, centered on the AeroBody's origin, with the global rotation 368 | var relative_position : Vector3 = global_basis * (influencer.transform.origin - center_of_mass) 369 | var force_and_torque : PackedVector3Array = influencer._calculate_forces(substep_delta) 370 | 371 | force += force_and_torque[0] 372 | torque += force_and_torque[1] 373 | 374 | return PackedVector3Array([force, torque]) 375 | 376 | func predict_linear_velocity(force : Vector3) -> Vector3: 377 | return linear_velocity + (force / mass * get_physics_process_delta_time() * PREDICTION_TIMESTEP_FRACTION) 378 | 379 | func predict_angular_velocity(torque : Vector3) -> Vector3: 380 | return angular_velocity + get_physics_process_delta_time() * PREDICTION_TIMESTEP_FRACTION * (get_inverse_inertia_tensor() * torque) 381 | 382 | func get_amount_of_active_influencers() -> int: 383 | var count : int = 0 384 | for influencer : AeroInfluencer3D in aero_influencers: 385 | if not influencer.disabled: 386 | count += 1 387 | 388 | return count 389 | 390 | func get_relative_position() -> Vector3: 391 | return global_basis * -center_of_mass 392 | 393 | func get_linear_velocity() -> Vector3: 394 | return linear_velocity_prediction 395 | 396 | func get_angular_velocity() -> Vector3: 397 | return angular_velocity_prediction 398 | 399 | func get_linear_acceleration() -> Vector3: 400 | return (linear_velocity_prediction - last_linear_velocity) / substep_delta 401 | 402 | func get_angular_acceleration() -> Vector3: 403 | return (angular_velocity_prediction - last_angular_velocity) / substep_delta 404 | 405 | func is_overriding_body_sleep() -> bool: 406 | var overriding : bool = false 407 | for influencer : AeroInfluencer3D in aero_influencers: 408 | overriding = overriding or influencer.is_overriding_body_sleep() 409 | 410 | return overriding 411 | 412 | func interrupt_sleep() -> void: 413 | sleeping = false 414 | 415 | #debug 416 | 417 | 418 | func _update_debug() -> void: 419 | for influencer : AeroInfluencer3D in aero_influencers: 420 | influencer.update_debug_vectors() 421 | 422 | aero_surfaces = [] 423 | for i : AeroInfluencer3D in aero_influencers: 424 | if i is AeroSurface3D: 425 | aero_surfaces.append(i) 426 | 427 | _update_debug_visibility() 428 | _update_debug_scale() 429 | 430 | mass_debug_point.position = center_of_mass 431 | #thrust_debug_vector.position = center of thrust 432 | 433 | 434 | var linear_velocity_to_use := linear_velocity 435 | var angular_velocity_to_use := angular_velocity 436 | if Engine.is_editor_hint(): 437 | linear_velocity_to_use = debug_linear_velocity 438 | angular_velocity_to_use = debug_angular_velocity 439 | 440 | linear_velocity_vector.value = global_transform.basis.inverse() * AeroMathUtils.v3log_with_base(linear_velocity_to_use, 2.0) 441 | angular_velocity_vector.value = global_transform.basis.inverse() * AeroMathUtils.v3log_with_base(angular_velocity_to_use, 2.0) 442 | 443 | #Godot doesn't run physics engine in-editor. 444 | #A consequence of this is that get_linear_velocity doesn't work. 445 | #Instead, we must access linear_velocity directly. 446 | #We don't want to overwrite user configured linear velocity, so we must use this workaround. 447 | 448 | if Engine.is_editor_hint(): 449 | var original_linear_velocity := linear_velocity 450 | var original_angular_velocity := angular_velocity 451 | linear_velocity = debug_linear_velocity 452 | angular_velocity = debug_angular_velocity 453 | var last_force_and_torque := calculate_aerodynamic_forces(linear_velocity_to_use, angular_velocity_to_use, air_density) 454 | linear_velocity = original_linear_velocity 455 | angular_velocity = original_angular_velocity 456 | 457 | #force and torque debug 458 | if aero_influencers.size() > 0: 459 | var amount_of_aero_influencers : int = aero_influencers.size() 460 | var force_sum := 0.0 461 | var force_vector_sum := Vector3.ZERO 462 | 463 | for influencer : AeroInfluencer3D in aero_influencers: 464 | if influencer.omit_from_debug or influencer.disabled: 465 | amount_of_aero_influencers -= 1 466 | continue 467 | force_vector_sum += influencer._current_force 468 | 469 | #lift and drag debug 470 | var amount_of_aero_surfaces : int = aero_surfaces.size() 471 | for surface : AeroSurface3D in aero_surfaces: 472 | if surface.omit_from_debug or surface.disabled: 473 | amount_of_aero_surfaces -= 1 474 | 475 | if amount_of_aero_surfaces > 0: 476 | var lift_sum := 0.0 477 | var lift_sum_vector := Vector3.ZERO 478 | var drag_sum := 0.0 479 | var drag_sum_vector := Vector3.ZERO 480 | var lift_position_sum := Vector3.ZERO 481 | var drag_position_sum := Vector3.ZERO 482 | for surface : AeroSurface3D in aero_surfaces: 483 | if surface.omit_from_debug: 484 | continue 485 | 486 | lift_sum += surface.lift_force 487 | lift_sum_vector += surface._current_lift 488 | drag_sum += surface.drag_force 489 | drag_sum_vector += surface._current_drag 490 | lift_position_sum += surface.transform.origin * surface.lift_force 491 | drag_position_sum += surface.transform.origin * surface.drag_force 492 | 493 | if lift_sum_vector.is_finite() and drag_sum_vector.is_finite(): 494 | lift_debug_vector.value = global_transform.basis.inverse() * AeroMathUtils.v3log_with_base(lift_sum_vector / amount_of_aero_surfaces, 2.0) 495 | drag_debug_vector.value = global_transform.basis.inverse() * AeroMathUtils.v3log_with_base(drag_sum_vector / amount_of_aero_surfaces, 2.0) 496 | 497 | if is_equal_approx(lift_sum, 0.0): 498 | lift_sum = 1.0 499 | lift_debug_vector.position = lift_position_sum / amount_of_aero_surfaces / (lift_sum / amount_of_aero_surfaces) 500 | if is_equal_approx(drag_sum, 0.0): 501 | drag_sum = 1.0 502 | drag_debug_vector.position = drag_position_sum / amount_of_aero_surfaces / (drag_sum / amount_of_aero_surfaces) 503 | 504 | for influencer : AeroInfluencer3D in aero_influencers: 505 | influencer.update_debug_vectors() 506 | 507 | func _update_debug_visibility() -> void: 508 | #update aerosurface visibility 509 | 510 | for influencer : AeroInfluencer3D in aero_influencers: 511 | influencer.update_debug_visibility(show_debug and show_wing_debug_vectors) 512 | 513 | #update self visibility 514 | linear_velocity_vector.visible = show_debug and show_linear_velocity 515 | angular_velocity_vector.visible = show_debug and show_angular_velocity 516 | 517 | lift_debug_vector.visible = show_debug and show_lift_vectors 518 | drag_debug_vector.visible = show_debug and show_drag_vectors 519 | 520 | mass_debug_point.visible = show_debug and show_center_of_mass 521 | thrust_debug_vector.visible = show_debug and show_center_of_thrust 522 | 523 | func _update_debug_scale() -> void: 524 | for influencer : AeroInfluencer3D in aero_influencers: 525 | influencer.update_debug_scale(debug_scale, debug_width) 526 | 527 | lift_debug_vector.width = debug_center_width 528 | mass_debug_point.width = debug_center_width 529 | thrust_debug_vector.width = debug_width 530 | 531 | linear_velocity_vector.width = debug_width 532 | angular_velocity_vector.width = debug_width 533 | drag_debug_vector.width = debug_width 534 | thrust_debug_vector.width = debug_width 535 | --------------------------------------------------------------------------------