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 @onready var m_region_transform: RegionTransform = $RegionTransform 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() m_region_transform.sync_from_transform() #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()