├── .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 |
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  AeroBody to your scene, and add one or more  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 |
--------------------------------------------------------------------------------