commit 907695442fbb26a05c14bfef53eb17467e100a2d Author: Patrick Marsee Date: Wed Sep 3 18:24:08 2025 -0400 Initial commit. diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f28239b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*] +charset = utf-8 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0af181c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Godot 4+ specific ignores +.godot/ +/android/ diff --git a/demo/airfoils/symmetric.tres b/demo/airfoils/symmetric.tres new file mode 100644 index 0000000..ed3c658 --- /dev/null +++ b/demo/airfoils/symmetric.tres @@ -0,0 +1,26 @@ +[gd_resource type="Resource" script_class="Airfoil" load_steps=5 format=3 uid="uid://dpk3bgq54ajul"] + +[ext_resource type="Script" uid="uid://jts0c0xrwc8q" path="res://scripts/aircraft/resources/airfoil.gd" id="1_bnq8l"] + +[sub_resource type="Curve" id="Curve_bnq8l"] +_limits = [0.0, 0.25, -20.0, 20.0] +_data = [Vector2(-20, 0.25), 0.0, -0.0451077, 0, 0, Vector2(-15.2, 0.106742), -0.0196947, -0.0196947, 0, 0, Vector2(-10.2128, 0.0337079), -0.00684561, -0.00684561, 0, 0, Vector2(0, 0.011236), 0.0, 0.0, 0, 0, Vector2(10, 0.0337079), 0.00560095, 0.00560095, 0, 0, Vector2(14.8936, 0.0983146), 0.0198034, 0.0198034, 0, 0, Vector2(20, 0.25), 0.0490369, 0.0, 0, 0] +point_count = 7 + +[sub_resource type="Curve" id="Curve_fbf5p"] +_limits = [-1.5, 1.5, -20.0, 20.0] +bake_resolution = 80 +_data = [Vector2(-13, -0.7), 0.0, 0.0, 0, 0, Vector2(-12, -1.1), 0.0, 0.0, 0, 0, Vector2(12, 1.1), 0.0, 0.0, 0, 0, Vector2(13, 0.7), 0.0, 0.0, 0, 0] +point_count = 4 + +[sub_resource type="Curve" id="Curve_iphra"] +_limits = [0.0, 1.0, -20.0, 20.0] +_data = [Vector2(-20, 0), 0.0, 0.0, 0, 0, Vector2(20, 0), 0.0, 0.0, 0, 0] +point_count = 2 + +[resource] +script = ExtResource("1_bnq8l") +lift_curve = SubResource("Curve_fbf5p") +drag_curve = SubResource("Curve_bnq8l") +moment_curve = SubResource("Curve_iphra") +metadata/_custom_type_script = "uid://jts0c0xrwc8q" diff --git a/demo/demo.tscn b/demo/demo.tscn new file mode 100644 index 0000000..a162ea0 --- /dev/null +++ b/demo/demo.tscn @@ -0,0 +1,138 @@ +[gd_scene load_steps=18 format=3 uid="uid://d20jwlre5rc5m"] + +[ext_resource type="Texture2D" uid="uid://bruwqvb6qrsfn" path="res://demo/textures/untexture.png" id="1_ytiva"] +[ext_resource type="Script" uid="uid://bddnsq0h2tpf" path="res://scripts/aircraft/nodes/fixed_wing_aircraft.gd" id="2_dar0o"] +[ext_resource type="Resource" uid="uid://dpk3bgq54ajul" path="res://demo/airfoils/symmetric.tres" id="3_u10mr"] +[ext_resource type="Script" uid="uid://qjdma7j2qrns" path="res://scripts/aircraft/resources/fixed_wing_aircraft_performance.gd" id="4_onu7k"] +[ext_resource type="Resource" uid="uid://p2i7a806s6gs" path="res://demo/propulsion/electric_motor.tres" id="4_s1s6j"] +[ext_resource type="Script" uid="uid://pc3j1c6e1ra7" path="res://scripts/aircraft/nodes/flight_sim_controller.gd" id="5_s1s6j"] +[ext_resource type="Script" uid="uid://tgj0rvn5wj8t" path="res://demo/hud.gd" id="6_onu7k"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_j5lb4"] +albedo_texture = ExtResource("1_ytiva") +uv1_scale = Vector3(100, 100, 100) + +[sub_resource type="PlaneMesh" id="PlaneMesh_5hjng"] +material = SubResource("StandardMaterial3D_j5lb4") +size = Vector2(2000, 2000) + +[sub_resource type="Curve" id="Curve_s1s6j"] +_limits = [0.0, 0.5, 0.0, 20.0] +_data = [Vector2(0, 0.5), 0.0, 0.0, 0, 0, Vector2(20, 0), -0.082397, 0.0, 0, 0] +point_count = 2 + +[sub_resource type="Curve" id="Curve_onvmd"] +_limits = [0.0, 2.0, 0.0, 30.0] +_data = [Vector2(0, 2), 0.0, 0.0, 0, 0, Vector2(30, 0), -0.199316, 0.0, 0, 0] +point_count = 2 + +[sub_resource type="Curve" id="Curve_oeqrt"] +_limits = [0.0, 0.202247, 0.0, 10.0] +_data = [Vector2(0, 0.202247), 0.0, 0.0, 0, 0, Vector2(10, 0), -0.0393259, 0.0, 0, 0] +point_count = 2 + +[sub_resource type="Resource" id="Resource_onu7k"] +script = ExtResource("4_onu7k") +horizontal_surface = ExtResource("3_u10mr") +horizontal_area = 21.8 +horizontal_aspect_ratio = 5.83 +horizontal_sweep = 0.0 +vertical_surface = ExtResource("3_u10mr") +vertical_area = 5.0 +vertical_aspect_ratio = 5.0 +vertical_sweep = 0.0 +propultion = ExtResource("4_s1s6j") +base_thrust = 100000.0 +base_drag = 0.0 +empty_mass = 3463.0 +yaw_axis = Vector3(0, -1, 0) +roll_axis = Vector3(0, 0.1, -1) +pitch_stability = 0.0 +yaw_stability = 0.0 +roll_stability = 0.0 +reference_ias_mps = 100.0 +braking_power = 0.0 +pitch_power = SubResource("Curve_s1s6j") +yaw_power = SubResource("Curve_oeqrt") +roll_power = SubResource("Curve_onvmd") +metadata/_custom_type_script = "uid://qjdma7j2qrns" + +[sub_resource type="SphereShape3D" id="SphereShape3D_u10mr"] +radius = 8.0 + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_onu7k"] + +[sub_resource type="Sky" id="Sky_s1s6j"] +sky_material = SubResource("ProceduralSkyMaterial_onu7k") + +[sub_resource type="Environment" id="Environment_onvmd"] +background_mode = 2 +sky = SubResource("Sky_s1s6j") +ambient_light_source = 3 +ambient_light_color = Color(1, 1, 1, 1) + +[node name="Demo" type="Node3D"] + +[node name="Ground" type="MeshInstance3D" parent="."] +mesh = SubResource("PlaneMesh_5hjng") + +[node name="FixedWingAircraft" type="CharacterBody3D" parent="." node_paths=PackedStringArray("controller")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 10, 0) +script = ExtResource("2_dar0o") +performance = SubResource("Resource_onu7k") +controller = NodePath("FlightSimController") +initial_speed = 100.0 +metadata/_custom_type_script = "uid://bddnsq0h2tpf" + +[node name="CollisionShape3D" type="CollisionShape3D" parent="FixedWingAircraft"] +shape = SubResource("SphereShape3D_u10mr") + +[node name="FlightSimController" type="Node" parent="FixedWingAircraft"] +script = ExtResource("5_s1s6j") +pitch_speed = 4.0 +yaw_speed = 4.0 +roll_speed = 3.0 +metadata/_custom_type_script = "uid://pc3j1c6e1ra7" + +[node name="Camera3D" type="Camera3D" parent="FixedWingAircraft"] + +[node name="CanvasLayer" type="CanvasLayer" parent="."] + +[node name="HUD" type="Control" parent="CanvasLayer" node_paths=PackedStringArray("source")] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("6_onu7k") +source = NodePath("../../FixedWingAircraft") + +[node name="TAS_Label" type="Label" parent="CanvasLayer/HUD"] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 32.0 + +[node name="AOA_Label" type="Label" parent="CanvasLayer/HUD"] +layout_mode = 0 +offset_top = 32.0 +offset_right = 40.0 +offset_bottom = 64.0 + +[node name="SideslipLabel" type="Label" parent="CanvasLayer/HUD"] +layout_mode = 0 +offset_top = 64.0 +offset_right = 40.0 +offset_bottom = 96.0 + +[node name="RelativeVelocityLabel" type="Label" parent="CanvasLayer/HUD"] +layout_mode = 0 +offset_top = 96.0 +offset_right = 40.0 +offset_bottom = 128.0 + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_onvmd") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(-4.37114e-08, -0.984808, 0.173648, 0, 0.173648, 0.984808, -1, 4.30473e-08, -7.5904e-09, 0, 0, 0) diff --git a/demo/hud.gd b/demo/hud.gd new file mode 100644 index 0000000..e8b1c5b --- /dev/null +++ b/demo/hud.gd @@ -0,0 +1,15 @@ +class_name HUD +extends Control + +@export var source: FixedWingAircraft + +@onready var tas_label := $TAS_Label as Label +@onready var aoa_label := $AOA_Label as Label +@onready var sideslip_label := $SideslipLabel as Label +@onready var relative_velocity_label := $RelativeVelocityLabel as Label + +func _process(_delta: float) -> void: + tas_label.text = "TAS: %s" % source.m_tas + aoa_label.text = "AOA: %s" % rad_to_deg(source.m_aoa) + sideslip_label.text = "Sideslip: %s" % rad_to_deg(source.m_sideslip) + relative_velocity_label.text = "V: %s" % source.m_relative_velocity diff --git a/demo/hud.gd.uid b/demo/hud.gd.uid new file mode 100644 index 0000000..7fea0f8 --- /dev/null +++ b/demo/hud.gd.uid @@ -0,0 +1 @@ +uid://tgj0rvn5wj8t diff --git a/demo/propulsion/electric_motor.tres b/demo/propulsion/electric_motor.tres new file mode 100644 index 0000000..9a64a9c --- /dev/null +++ b/demo/propulsion/electric_motor.tres @@ -0,0 +1,22 @@ +[gd_resource type="Resource" script_class="Propultion" load_steps=5 format=3 uid="uid://p2i7a806s6gs"] + +[ext_resource type="Script" uid="uid://8fsi1mq555uj" path="res://scripts/aircraft/resources/propultion.gd" id="1_idr77"] + +[sub_resource type="Curve" id="Curve_idr77"] +_data = [Vector2(0, 0), 0.0, 2.81648, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] +point_count = 2 + +[sub_resource type="Curve" id="Curve_sw002"] +_data = [Vector2(0, 1), 0.0, -2.57766, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] +point_count = 2 + +[sub_resource type="Curve" id="Curve_jnvlh"] +_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] +point_count = 2 + +[resource] +script = ExtResource("1_idr77") +propultion_speed_curve = SubResource("Curve_sw002") +propultion_density_curve = SubResource("Curve_idr77") +propultion_temperature_curve = SubResource("Curve_jnvlh") +metadata/_custom_type_script = "uid://8fsi1mq555uj" diff --git a/demo/textures/untexture.png b/demo/textures/untexture.png new file mode 100644 index 0000000..63b3576 Binary files /dev/null and b/demo/textures/untexture.png differ diff --git a/demo/textures/untexture.png.import b/demo/textures/untexture.png.import new file mode 100644 index 0000000..dfaf68e --- /dev/null +++ b/demo/textures/untexture.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bruwqvb6qrsfn" +path.s3tc="res://.godot/imported/untexture.png-e9b4c6dd107bfceece0daf5f5dd4fe66.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://demo/textures/untexture.png" +dest_files=["res://.godot/imported/untexture.png-e9b4c6dd107bfceece0daf5f5dd4fe66.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..9d8b7fa --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..e2f5ee3 --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bw3c6f8nrg5q7" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..22d44f4 --- /dev/null +++ b/project.godot @@ -0,0 +1,80 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="SparkyGD" +run/main_scene="uid://d20jwlre5rc5m" +config/features=PackedStringArray("4.4", "Forward Plus") +config/icon="res://icon.svg" + +[autoload] + +Atmosphere="*res://scripts/aircraft/nodes/atmosphere.gd" + +[debug] + +gdscript/warnings/untyped_declaration=2 +gdscript/warnings/unsafe_property_access=2 +gdscript/warnings/unsafe_method_access=2 +gdscript/warnings/unsafe_cast=1 +gdscript/warnings/unsafe_call_argument=2 +gdscript/warnings/return_value_discarded=1 + +[input] + +pitch_down={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null) +] +} +pitch_up={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null) +] +} +yaw_left={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":4,"axis_value":1.0,"script":null) +] +} +yaw_right={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":5,"axis_value":1.0,"script":null) +] +} +roll_left={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null) +] +} +roll_right={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null) +] +} +throttle_down={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":true,"script":null) +] +} +throttle_up={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":true,"script":null) +] +} diff --git a/scenes/aircraft/fixed_wing_aircraft.tscn b/scenes/aircraft/fixed_wing_aircraft.tscn new file mode 100644 index 0000000..d42f391 --- /dev/null +++ b/scenes/aircraft/fixed_wing_aircraft.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://duwkogb01nnwa"] + +[ext_resource type="Script" uid="uid://bddnsq0h2tpf" path="res://scripts/aircraft/nodes/fixed_wing_aircraft.gd" id="1_6x31w"] + +[node name="FixedWingAircraft" type="CharacterBody3D"] +script = ExtResource("1_6x31w") diff --git a/scripts/aircraft/nodes/aircraft_controller.gd b/scripts/aircraft/nodes/aircraft_controller.gd new file mode 100644 index 0000000..749d885 --- /dev/null +++ b/scripts/aircraft/nodes/aircraft_controller.gd @@ -0,0 +1,8 @@ +class_name AircraftController +extends Node + +@export var pitch: float # Negative: down, positive: up +@export var yaw: float # Negative: left, positive: right +@export var roll: float # Negative: left, positive: right +@export var throttle: float # 0: minimum, 1: maximum +@export var brake: float # 0: fully released, 1: fully applied diff --git a/scripts/aircraft/nodes/aircraft_controller.gd.uid b/scripts/aircraft/nodes/aircraft_controller.gd.uid new file mode 100644 index 0000000..3d35be3 --- /dev/null +++ b/scripts/aircraft/nodes/aircraft_controller.gd.uid @@ -0,0 +1 @@ +uid://b2k35mmv3g82l diff --git a/scripts/aircraft/nodes/atmosphere.gd b/scripts/aircraft/nodes/atmosphere.gd new file mode 100644 index 0000000..acd90a6 --- /dev/null +++ b/scripts/aircraft/nodes/atmosphere.gd @@ -0,0 +1,55 @@ +extends Node + +@export var pressure_ASL := 101.325 # 1 atm. in kPa +@export var density_ASL := 1.225 # at 288.15k in kg/m^3 +@export var temperature_ASL := 288.15 # surface temperature in Kelvin (288.15K = 15C) +@export var tropopause_altitude := 11000.0 # meters above ASL +@export var tropopause_temp := 216.65 # (216.65K = -56.5C) +@export var gravity := 9.8 # here until I figure out the best way to get the project's default gravity. + +const AIR_MOLAR_MASS := 0.029 # kg/mol +const GAS_CONSTANT := 8.31447 +const MACH_COEFF := 20.02948 + +var pressure_constant: float +var density_constant: float +var temp_lapse_at_tropo: float # temperature lapse at troposphere, in K or C + +# Use this for initialization +func _ready() -> void: + _recalculate() + +func _recalculate() -> void: + pressure_constant = (gravity * AIR_MOLAR_MASS) / (GAS_CONSTANT * temperature_ASL) + density_constant = 1 / density_ASL + temp_lapse_at_tropo = (temperature_ASL - tropopause_temp) / tropopause_altitude + +func pressure_by_alt(altitude: float, atm := false) -> float: # returns the pressure at altitude in kPa (default) or atm. + var ret := exp(pressure_constant * altitude) + if !atm: + ret *= pressure_ASL + return ret + +func density_by_alt(altitude: float, relative := false) -> float: # returns the density at altitude in kg/m^3 (default), or relative. + var ret := pressure_by_alt(altitude) / (0.287058 * temperature_by_alt(altitude)) + if relative: + ret *= density_constant + return ret + +func temperature_by_alt(altitude: float) -> float: + if altitude < tropopause_altitude: + return temperature_ASL - altitude * temp_lapse_at_tropo + else: # mesopause coming soon! + return tropopause_temp + +func speed_of_sound_by_alt(altitude: float) -> float: + return sqrt(temperature_by_alt(altitude)) * MACH_COEFF + +func mach_by_alt(altitude: float, speed: float) -> float:# a little bit expensive - use with caution + return speed / speed_of_sound_by_alt(altitude) + +func atm_to_kPa(pressure: float) -> float: + return pressure * pressure_ASL + +func kPa_to_atm(pressure: float) -> float: + return pressure / pressure_ASL diff --git a/scripts/aircraft/nodes/atmosphere.gd.uid b/scripts/aircraft/nodes/atmosphere.gd.uid new file mode 100644 index 0000000..1d2fac4 --- /dev/null +++ b/scripts/aircraft/nodes/atmosphere.gd.uid @@ -0,0 +1 @@ +uid://0nphoxplw624 diff --git a/scripts/aircraft/nodes/fixed_wing_aircraft.gd b/scripts/aircraft/nodes/fixed_wing_aircraft.gd new file mode 100644 index 0000000..c4b03e6 --- /dev/null +++ b/scripts/aircraft/nodes/fixed_wing_aircraft.gd @@ -0,0 +1,201 @@ +class_name FixedWingAircraft +extends CharacterBody3D + +@export var performance: FixedWingAircraftPerformance +@export var controller: AircraftController + +@export var initial_speed: float + +#var acceleration := Vector3 +#var angular_velocity := Vector3 +#var angular_acceleration := Vector3 +var m_aoa: float +var m_sideslip: float +var m_tas: float +var m_ias: float +var m_relative_velocity: Vector3 + +var m_is_landed := false + +func _ready() -> void: + velocity = -global_basis.z * initial_speed + m_relative_velocity = transform.basis.inverse() * velocity + m_tas = _tas() + m_ias = _ias() + +func _physics_process(delta: float) -> void: + + m_relative_velocity = transform.basis.inverse() * velocity + m_aoa = -atan2(m_relative_velocity.y, -m_relative_velocity.z) + if m_aoa > PI: + m_aoa -= TAU + m_sideslip = -atan2(m_relative_velocity.x, -m_relative_velocity.z) + if m_sideslip > PI: + m_sideslip -= TAU + m_tas = _tas() + m_ias = _ias() + + # aero + var vel_forward := m_relative_velocity.normalized() + var vel_right := vel_forward.cross(Vector3.UP).normalized() + var vel_up := vel_right.cross(vel_forward).normalized() + #var vel_basis := Basis(vel_right, vel_up, vel_forward) + var linear_forces := Vector3.ZERO + + var horiz_lift := _lift(performance.horizontal_surface, + performance.horizontal_area, + performance.horizontal_aspect_ratio, + performance.horizontal_sweep, + m_aoa, m_tas) + var horiz_drag := _drag(performance.horizontal_surface, + performance.horizontal_area, + performance.horizontal_aspect_ratio, + performance.horizontal_sweep, + m_aoa, m_tas) + var vert_lift := _lift(performance.vertical_surface, + performance.vertical_area, + performance.vertical_aspect_ratio, + performance.vertical_sweep, + m_sideslip, m_tas) + var vert_drag := _drag(performance.vertical_surface, + performance.vertical_area, + performance.vertical_aspect_ratio, + performance.vertical_sweep, + m_sideslip, m_tas) + + linear_forces += vel_up * horiz_lift + linear_forces += vel_right * vert_lift + linear_forces -= vel_forward * horiz_drag + linear_forces -= vel_forward * vert_drag + linear_forces += Vector3.FORWARD * _thrust() + m_relative_velocity += linear_forces * delta / performance.empty_mass + velocity = (transform.basis * m_relative_velocity) + (get_gravity() * delta) + + # steering + var steering_axis := _get_steering_axis() * delta + if (steering_axis.length_squared() > 0.0): + rotate_object_local(steering_axis.normalized(), steering_axis.length()) + + move_and_slide() + #if !is_landed: + # _fly() + #else: + # taxi() + #ResetControls() + +#func _sync() + +func _get_steering_axis() -> Vector3: + var pitch_effect := Vector3.RIGHT * performance.pitch_power.sample(absf(rad_to_deg(m_aoa))) * controller.pitch + var yaw_effect := performance.yaw_axis.normalized() * performance.yaw_power.sample(absf(rad_to_deg(m_sideslip))) * controller.yaw + var roll_effect := performance.roll_axis.normalized() * performance.roll_power.sample(absf(rad_to_deg(m_aoa))) * controller.roll + return (pitch_effect + yaw_effect + roll_effect) * m_ias / performance.reference_ias_mps + +func _lift(foil: Airfoil, + area: float, + aspect_ratio: float, + _sweep: float, + aoa: float, + tas: float) -> float: + # https://www1.grc.nasa.gov/beginners-guide-to-aeronautics/downwash-effects-on-lift/ + var Cl0: float + if absf(aoa) < PI / 9: + Cl0 = foil.lift_curve.sample(rad_to_deg(aoa)) + else: + Cl0 = sin(aoa * 2.0) * 2.0 + var Cl := Cl0 / (1.0 + Cl0 / (PI * aspect_ratio)) + return Cl * area * tas * tas * Atmosphere.density_by_alt(position.y) * 0.5 + +func _drag(foil: Airfoil, + area: float, + _aspect_ratio: float, + _sweep: float, + aoa: float, + tas: float) -> float: + var Dl0: float + if absf(aoa) < PI / 9: + Dl0 = foil.drag_curve.sample(rad_to_deg(aoa)) + else: + Dl0 = sin(aoa) * sin(aoa) * 10.0 + #var Dl := Dl0 / (1.0 + Dl0 / (PI * aspect_ratio)) + return Dl0 * area * tas * tas * Atmosphere.density_by_alt(position.y) * 0.5 + +func _moment(foil: Airfoil, + area: float, + aspect_ratio: float, + _sweep: float, + aoa: float, + tas: float) -> float: + var Ml0 := foil.lift_curve.sample(rad_to_deg(aoa)) + #var Ml := Ml0 / (1.0 + Ml0 / (PI * aspect_ratio)) + var mean_chord := sqrt(area / aspect_ratio) + return Ml0 * area * tas * tas * Atmosphere.density_by_alt(position.y) * 0.5 * mean_chord + +func _thrust() -> float: + var speed_contrib := performance.propultion.propultion_speed_curve.sample(Atmosphere.mach_by_alt(position.y, m_tas)) + var density_contrib := performance.propultion.propultion_density_curve.sample(Atmosphere.density_by_alt(position.y, true)) + var temp_contrib := performance.propultion.propultion_temperature_curve.sample(Atmosphere.temperature_by_alt(position.y)) + return speed_contrib * density_contrib * temp_contrib * performance.base_thrust * controller.throttle + +#func _fly() -> void: + #acceleration = Vector3.DOWN * global_basis * gravity + #if controller.brake > 0.0: + # mThrust -= performance.braking_power * _ias() + #acceleration += Vector3.FORWARD * mThrust / mass; + #lift(); + #control(); + #velocity += mAcceleration * Time.fixedDeltaTime; + #transform.Translate(velocity * Time.fixedDeltaTime * iScale); + #Vector3 prevVel = transform.TransformDirection(velocity); + #transform.Rotate(mAngularVelocity * Time.fixedDeltaTime); + #velocity = transform.InverseTransformDirection(prevVel); + # Check to see if we've landed + #if (isLandingGearDeployed) + #{ + # RaycastHit ground; + # Physics.Raycast(transform.position, -transform.up, out ground, rideHeight, 0xFF, QueryTriggerInteraction.Ignore); + # if (ground.collider != null && ground.distance < rideHeight) + # { + # AttemptLanding(ref ground); + # } + #} + +#func lift() -> void: +# var speed_sqr_YZ := velocity.y * velocity.y + velocity.z * velocity.z +# var speed_sqr_XZ := velocity.x * velocity.x + velocity.z * velocity.z +# var altitude := Atmosphere.altitude(position.y) +# float horizLiftPerCoeff = speed_sqr_YZ * horizWingArea * Atmosphere.Density(altitude, true) * 0.5f / mass; +# float vertLiftPerCoeff = speed_sqr_XZ * vertWingArea * Atmosphere.Density(altitude, true) * 0.5f / mass; +# float degAoA = AoA * Mathf.Rad2Deg; +# float degSideslip = sideslip * Mathf.Rad2Deg; +# float wingDrag = horizAirfoil.getDrag(degAoA) * horizWingArea + vertAirfoil.getDrag(degSideslip) * vertWingArea; +# float mach = Atmosphere.Mach(altitude, velocity.magnitude); +# if (mach > 0.7f) # No need to do expensive calculations unless we're going fast enough for them to matter. +# { +# wingDrag += 3 * wingDrag * Mathf.Min(Mathf.Exp(16 * (mach - 1.0f + Mathf.Log10(1 - wingSweep / 90.0f))), Mathf.Exp(-mach + 1.0f)); +# } +# float totalDrag = indicatedVelocity.sqrMagnitude * Atmosphere.Density(altitude, true) * 0.5f * (wingDrag + bodyDragCoeff * frontalArea) / mass; +# Vector3 relativeAccel = new Vector3(-vertAirfoil.getLift(degSideslip) * vertLiftPerCoeff, horizAirfoil.getLift(degAoA) * horizLiftPerCoeff, -totalDrag); +# Vector3 fixedAcceleration = TransformR(relativeAccel); +# if ((velocity.x + mAcceleration.x * Time.fixedDeltaTime) * velocity.x < 0.0f) +# { +# fixedAcceleration.x = -velocity.x / Time.fixedDeltaTime; +# } +# if ((velocity.y + mAcceleration.y * Time.fixedDeltaTime) * velocity.y < 0.0f) +# { +# fixedAcceleration.y = -velocity.y / Time.fixedDeltaTime; +# } +# acceleration += fixedAcceleration; +# mAngularVelocity = new Vector3(horizAirfoil.getMoment(degAoA) * horizLiftPerCoeff, vertAirfoil.getMoment(degSideslip) * vertLiftPerCoeff, 0f); +# mAngularVelocity += new Vector3(-moment * Mathf.Cos(Vector3.Angle(Physics.gravity, transform.up) * Mathf.Deg2Rad) * horizLiftPerCoeff, 0.0f, 0.0f) + +func _tas() -> float: + return sqrt(m_relative_velocity.z * m_relative_velocity.z + m_relative_velocity.y * m_relative_velocity.y) * -sign(m_relative_velocity.z) + +func _ias() -> float: + return _tas() * sqrt(Atmosphere.density_by_alt(position.y, true)) + +#func _ + +#func _g_force() -> Vector3: +# return (acceleration - transform.inverse(get_gravity())) / get_gravity().length() diff --git a/scripts/aircraft/nodes/fixed_wing_aircraft.gd.uid b/scripts/aircraft/nodes/fixed_wing_aircraft.gd.uid new file mode 100644 index 0000000..1716831 --- /dev/null +++ b/scripts/aircraft/nodes/fixed_wing_aircraft.gd.uid @@ -0,0 +1 @@ +uid://bddnsq0h2tpf diff --git a/scripts/aircraft/nodes/flight_sim_controller.gd b/scripts/aircraft/nodes/flight_sim_controller.gd new file mode 100644 index 0000000..78a16e3 --- /dev/null +++ b/scripts/aircraft/nodes/flight_sim_controller.gd @@ -0,0 +1,37 @@ +class_name FlightSimController +extends AircraftController + +@export var pitch_down := &"pitch_down" +@export var pitch_up := &"pitch_up" +@export var yaw_left := &"yaw_left" +@export var yaw_right := &"yaw_right" +@export var roll_left := &"roll_left" +@export var roll_right := &"roll_right" +@export var throttle_down := &"throttle_down" +@export var throttle_up := &"throttle_up" + +@export var pitch_speed := 1.0 +@export var yaw_speed := 1.0 +@export var roll_speed := 1.0 +@export var throttle_speed := 1.0 + +func _physics_process(delta: float) -> void: + if get_multiplayer_authority() == multiplayer.get_unique_id(): + var pitch_target := Input.get_axis(pitch_down, pitch_up) + var pitch_diff := pitch_target - pitch + pitch += signf(pitch_diff) * minf(absf(pitch_diff), pitch_speed * delta) + + var yaw_target := Input.get_axis(yaw_left, yaw_right) + var yaw_diff := yaw_target - yaw + yaw += signf(yaw_diff) * minf(absf(yaw_diff), yaw_speed * delta) + + var roll_target := Input.get_axis(roll_left, roll_right) + var roll_diff := roll_target - roll + roll += signf(roll_diff) * minf(absf(roll_diff), roll_speed * delta) + + var throttle_target := Input.get_axis(throttle_down, throttle_up) * 0.5 + 0.5 + if throttle_target > throttle: + throttle = minf(throttle_target, throttle + throttle_speed * delta) + elif throttle_target < throttle: + throttle = maxf(throttle_target, throttle - throttle_speed * delta) + brake = 1.0 if throttle < 0.05 else 0.0 diff --git a/scripts/aircraft/nodes/flight_sim_controller.gd.uid b/scripts/aircraft/nodes/flight_sim_controller.gd.uid new file mode 100644 index 0000000..1c9f83b --- /dev/null +++ b/scripts/aircraft/nodes/flight_sim_controller.gd.uid @@ -0,0 +1 @@ +uid://pc3j1c6e1ra7 diff --git a/scripts/aircraft/resources/airfoil.gd b/scripts/aircraft/resources/airfoil.gd new file mode 100644 index 0000000..bf34734 --- /dev/null +++ b/scripts/aircraft/resources/airfoil.gd @@ -0,0 +1,6 @@ +class_name Airfoil +extends Resource + +@export var lift_curve: Curve +@export var drag_curve: Curve +@export var moment_curve: Curve diff --git a/scripts/aircraft/resources/airfoil.gd.uid b/scripts/aircraft/resources/airfoil.gd.uid new file mode 100644 index 0000000..aa248a6 --- /dev/null +++ b/scripts/aircraft/resources/airfoil.gd.uid @@ -0,0 +1 @@ +uid://jts0c0xrwc8q diff --git a/scripts/aircraft/resources/fixed_wing_aircraft_performance.gd b/scripts/aircraft/resources/fixed_wing_aircraft_performance.gd new file mode 100644 index 0000000..db2911f --- /dev/null +++ b/scripts/aircraft/resources/fixed_wing_aircraft_performance.gd @@ -0,0 +1,27 @@ +class_name FixedWingAircraftPerformance +extends Resource + +@export var horizontal_surface: Airfoil +@export var horizontal_area: float +@export var horizontal_aspect_ratio: float +@export var horizontal_sweep: float +@export var vertical_surface: Airfoil +@export var vertical_area: float +@export var vertical_aspect_ratio: float +@export var vertical_sweep: float +@export var propultion: Propultion +@export var base_thrust: float +@export var base_drag: float +@export var empty_mass: float +@export var yaw_axis: Vector3 = Vector3.DOWN +@export var roll_axis: Vector3 = Vector3.FORWARD +@export var pitch_stability: float +@export var yaw_stability: float +@export var roll_stability: float + +# Below are factors designed around the reference IAS. +@export var reference_ias_mps: float = 100.0 +@export var braking_power: float +@export var pitch_power: Curve +@export var yaw_power: Curve +@export var roll_power: Curve diff --git a/scripts/aircraft/resources/fixed_wing_aircraft_performance.gd.uid b/scripts/aircraft/resources/fixed_wing_aircraft_performance.gd.uid new file mode 100644 index 0000000..c19d3db --- /dev/null +++ b/scripts/aircraft/resources/fixed_wing_aircraft_performance.gd.uid @@ -0,0 +1 @@ +uid://qjdma7j2qrns diff --git a/scripts/aircraft/resources/propultion.gd b/scripts/aircraft/resources/propultion.gd new file mode 100644 index 0000000..6386a65 --- /dev/null +++ b/scripts/aircraft/resources/propultion.gd @@ -0,0 +1,6 @@ +class_name Propultion +extends Resource + +@export var propultion_speed_curve: Curve +@export var propultion_density_curve: Curve +@export var propultion_temperature_curve: Curve diff --git a/scripts/aircraft/resources/propultion.gd.uid b/scripts/aircraft/resources/propultion.gd.uid new file mode 100644 index 0000000..8741a7c --- /dev/null +++ b/scripts/aircraft/resources/propultion.gd.uid @@ -0,0 +1 @@ +uid://8fsi1mq555uj