BodyVelocity❌ BodyForce✅ for Airplanes (Aerodynamics)

Checked the AoA again, and it was the issue. After a takeoff, it repeatedly switched from positive to negative, which explained why it looked wobbly up-down before getting nullified.

I switched to your drag formula. It’s printing out huge numbers without the part even moving.

I went back to AoA calculation, which also uses your method, it does the same behavior. I notice that the wobble effect started from minuscule decimals, which gradually scales up and causes the large numbers.

Windex is using the cframe to find the relative airflow from the pitch AoA and Angle of Sideslip so you can apply drag in the opposite direction. Yes its a modified version, but it shouldn’t be causing the problem. If its happening regardless of the method you use to calculate AoA, that means something else is causing the weird AoA. What are you using as your plane? Is it a single mesh, or a rig with many parts? Also, how are you handling gravity? If you can, share a video of the wobble behavior.

I use a frictionless WedgePart, and my jet model with wheels and suspensions installed. Server-sided. This is how I set the constraints up for both models:

Screenshot 2025-03-08 132248

Thrust is relative to Attachment, while Drag and Lift to world. AlignOrientation is for testing AoA, and just how airplanes pitch up to lift.

WedgePart


Last time, it works for the first few seconds, where it manages to takeoff, then wobbles and disappear after. The values outputted large numbers. I used @Imperiector’s drag formula which worked for me.

Today, I gave another try with both drag models. Both WedgeParts disintegrated even without thrust. As how I remember Roblox physics is, there’s like a tiny amount of velocity on unanchored parts when the simulation starts.

I tried to print out both drag and lift value to check for large numbers. What’s weird is, there is no large numbers, other than decimals and decimals. It’s last print output when the WedgePart disappears is 0.

There was a Roblox update recently, and that’s when it started to happen even with @Imperiector’s drag formula. This is definitely something to do with their engine.

I cannot share you a clip of it’s wobbly motion, as the WedgePart itself disappears into thin air before doing anything.

Rigged Jet


Jet is pitched upwards 5 degrees.

Not far different, except it doesn’t immediately fling with @Imperiector’s formula.

I’ve tried with both suspensions enabled and disabled, they react the same way.

The worst of all, I did barely any modifications to the code and when I started to clip them, they don’t seem to react anymore. It’s my horrible part of programming when it comes to not knowing the root of an issue.

Latest version of my script:

local pt = script.Parent

local airDensity = 1.2
local liftPower = 100

local CD = 0.01
local S = 2 -- ¯\_(ツ)_/¯
local wingAR = 3
local oswald = 0.7

function calculateLift(velocity: Vector3, AoA)
	
	AoA = math.deg(AoA)
	print(AoA)
	local CL = math.clamp(math.sin(math.rad(3 * AoA)), -60, 60)
	
	return 0.5 * velocity.Magnitude^2 * CL * liftPower, CL
end

function calculateDrag(velocity: Vector3, pressure, CL, AoA)
	
	local CLAoA = CL * AoA
	local CD_i = CLAoA^2 / (3.14 * wingAR * oswald)
	
	return (CD + CD_i) * pressure * S
end

function calculateAOA(velocity, CF)
	local AoA
	if velocity.Magnitude > 0 then 
		-- Project the velocity vector onto the plane's vertical plane
		local projectedVelocity = velocity.Unit - CF.RightVector * velocity.Unit:Dot(CF.RightVector)

		-- Calculate the angle between the projected velocity and look vector
		local dotProduct = math.clamp(CF.LookVector:Dot(projectedVelocity.Unit), -1, 1)
		local cross = CF.LookVector:Cross(projectedVelocity.Unit)
		local sign = cross:Dot(CF.RightVector) > 0 and -1 or 1 -- Check if the aoa is pos/neg, if nose is above/below velocity vector

		-- Convert to degrees and apply neg or pos, clamp from -90 to 90 
		AoA = math.clamp(math.deg(math.acos(dotProduct)) * sign, -90, 90)
	else AoA = 0 end -- To avoid NaN AoAs
	return math.rad(AoA)
end


game["Run Service"].Heartbeat:Connect(function(dT)
	
	local cF = pt.CFrame
	local velocity = pt.AssemblyLinearVelocity
	if velocity.Magnitude == 0 then velocity = Vector3.new(0, 0.001, 0) end
	
	local localVelocity = cF:VectorToObjectSpace(velocity)
	
	local AoA = calculateAOA(velocity, cF)--math.atan2(-localVelocity.Y, -localVelocity.Z)
	local AoS = math.asin(localVelocity.X / localVelocity.Magnitude)
	
	local windex = cF * CFrame.Angles(-AoA, 0, 0) * CFrame.Angles(0, -AoS, 0)
	
	local dir_lift = windex.UpVector
	local dir_side = windex.RightVector
	local dir_drag = -windex.LookVector
	
	local pressure = airDensity * 0.5 * velocity.Magnitude^2
	
	local lift, CL = calculateLift(velocity, AoA)
	local drag = calculateDrag(localVelocity, pressure, CL, AoA)
	
	pt.Lift.Force = dir_lift * lift
	pt.Drag.Force = dir_drag * drag
	
end)

I am unable to determine what really causes the issue. But if I really have to, I’m saying it’s the Roblox engine itself.

Dont use align orientation. If your using constraints to get your plane to rotate, use angularVelocity. Handle inputs 60 times a second, and set the target angular velocity to be the current input on pitch/yaw:

function Controls.handleControls(flightControl, plane, stats, gun, velocity, AOA, deltaTime)
	local rollInput  = 0
	local pitchInput = 0
	local yawInput   = 0
	local degToRad = math.pi/180

	-- Process control inputs
	if UIS:IsKeyDown(Enum.KeyCode.LeftShift)   then throttle = math.min(throttle + 0.0025, 1) end
	if UIS:IsKeyDown(Enum.KeyCode.LeftControl) then throttle = math.max(throttle - 0.0025, 0) end
	if UIS:IsKeyDown(Enum.KeyCode.W) then pitchInput = -stats.TurnRate * 0.5 end
	if UIS:IsKeyDown(Enum.KeyCode.S) then pitchInput = stats.TurnRate  end 
	if UIS:IsKeyDown(Enum.KeyCode.A) then rollInput  = stats.RollRate  end
	if UIS:IsKeyDown(Enum.KeyCode.D) then rollInput  = -stats.RollRate end
	if UIS:IsKeyDown(Enum.KeyCode.Q) then yawInput   = stats.YawRate   end
	if UIS:IsKeyDown(Enum.KeyCode.E) then yawInput   = -stats.YawRate  end

	rollInput = rollInput + (yawInput * 1.75) -- Roll when the plane yaws
	local angularVelocity = Vector3.new(pitchInput * degToRad, yawInput * degToRad, rollInput * degToRad) -- Calculate base angular vel

	flightControl.AngularVelocity = angularVelocity
end

The max torque you set on the AngularVelocity constraint will be how quick the plane responds to rotational input. Align orientation is a weird way to handle it, kind of like setting cframe directly. Also, set thrust relative to WORLD

To mention that I did these server-sided. I thought it had something to do with the wobble behavior. I decided to try it on my client, and that’s when I couldn’t find out.

The jet doesn’t want to get past the speed of lifting off. I adjusted the liftPower, and the top speed is capped at when it tries to takeoff, noticed by a wobbly behavior.

It’s just a copy-pasted script, yet it works differently. This does not make sense.

If your plane cant take off, its because the gravity is too strong. That, or the forces are too weak relative to the mass of the plane they are effecting. The code im giving you relies on the mesh being the proper mass(you can change this by changing the density), proper game gravity(0 since i use a vector force to handle gravity separately), and a bit of fiddling with inertia. Roblox vector forces aren’t really proportional, so you have to manipulate the velocity vector directly to nudge it towards where the thrust is pointing. Heres what i did for that:

function Physics.alignVV(velocity, lookVector, throttle, plane, dt)
	local velocityUnit = velocity.Unit
	local lookVectorUnit = lookVector.Unit

	local dotProduct = velocityUnit:Dot(lookVectorUnit)
	local angleBetween = math.acos(math.clamp(dotProduct, -1, 1)) 

	-- Scale correction angle with delta time to decouple from frame rate
	local correctionAngle = math.clamp(0.1 + (throttle * (0.5 - 0.01)), 0.01, 0.5) * (dt/(1/60)) 
	local correctionMagnitude = math.rad(correctionAngle)  

	-- If the angle is greater than the threshold, do the correction
	if angleBetween > correctionMagnitude then
		local correctionAxis = velocityUnit:Cross(lookVectorUnit)  
		local correctionDirection = correctionAxis.Unit * correctionMagnitude

		-- Gradually change the velocity direction using the correction.
		local correctedVelocity = velocityUnit:Lerp(lookVectorUnit, correctionMagnitude)  
		correctedVelocity = correctedVelocity * velocity.Magnitude
		plane.AssemblyLinearVelocity = correctedVelocity 
	end
end

It just directly changes the direction of the velocity vector based on the current thrust, and interpolates to make it smooth.

Use airfoils, search them up there great they simulate lift and drag for a part it custom airfoil characteristics that’s the most realistic physics your gonna get, i only know 3 utilize airfoil physics, hostile skies, airx 2020 and Novus flight simulator

I’ve re-examined every calculation made. From AoA, coefficients, and stuff. They all make sense, except one thing: lift.

The fling behavior is caused by the lift force. As I said before, it increases and decreases repeatedly from little decimals to large numbers. The liftCoefficient is fine, so I doubt it’s something to do with the airspeed/velocity.

I use .Heartbeat to run the physics. The RunService constantly updates the force. I came up with the idea, that the latency between the updates causes the velocity to be not perfect, hence why the wobble behavior started from tiny decimals. However, that might not be the case.

That’s the server side. On the client side, I would say that it already tries to fling itself at low speeds, cancelled by the solid surface below, and declining it from exceeding said speed.

Knowing that the lift is the problem here, I thought of using the zero gravity formula, by multiplying .AssemblyMass with .Gravity, but this time is affected by my desired stallSpeed. It didn’t go well, and is cancelled.


Other than lift, there’s still issue with drag. If it the part reaches zero velocity, it becomes non-existent. That’s when they check if it’s zero, it would be replaced with something like 0.001. My jet model has suspensions on, preventing this from happening since the velocity never reaches zero.

What do I do?

One time, I gave up my own physics simulation. I’ve went to the Marketplace and think of using existing aerodynamics model made.

I don’t understand how most are done, as they are done in different ways—in ways I’ve never seen. They don’t work as intended, either. That’s the con of free models, you don’t fully understand them, at least to me. Which is why I avoid using them. It’s all on me again.


Since I couldn’t do more to the script and constraints, I tried going after one passive stuff: physical properties.

I’ve noticed that @xPoIarity has their jets set with large mass, which is why I gave another try here.

I tried playing with .Mass. There is quite significant change with the reactions of the aerodynamics. The heavier it gets, the longer it endures before eventually glitching out. Although it is not perfect, it could still be improved.

I thought it is time to shrink the world—giving the illusion of a large map, while preventing you from reaching extreme world distances.

However, my suspension chassis doesn’t tolerate this. The SpringConstraint causes it to fling off the world, not any different from the issue I’ve been experiencing. I’ve tried adjusting stuff, and it results in no difference.

I removed the SpringConstraint, and keep the PrismConstraint on, as it also plays as the wheel hinges. It wobbles a lot, but eventually eases off. Not until it gets forced to move, causing it to fling off.

Alright. No suspensions, no wheels. Just welds. I’ve tried this long before, that’s when I’m issuing about setting the speed value to 0.001 when it is read zero. It doesn’t do the job. It completely disappears when it has reached zero velocity. The springs and wheels is what keeps this from intervening.

I’ve always planned of shrinking it down to do the illusion thing, but these issues stopped the jet from demonstrating aerodynamics.


I couldn’t think of anything more wrong, either the active or passive stuffs. It is likely that I have failed to understand how most stuff works. But again, I’m sure it’s all the fault of Roblox physics.

I could not fix this, nor have I achieved functioning aerodynamics.

You can avoid using Roblox physics and integrating the kinematics and dynamics yourself to avoid any numerical problems.

Here’s an example of utilizing a nonlinear parametric model from Morelli, with the goal of keeping it as simple as possible (ie; capturing entire flight envelope without having to special case different regimes) : GitHub - Jody7/6DOF_Nonlinear_F16_Control: lua implementation of nonlinear f16 controlled with linearized LQR

Share the place and maybe i or someone else can fix the problems your having.

While increasing mass helps with its lifespan, it is also down forced by the suspension and wheel behavior, which affects the lift force that puts struggle on the thrust.


aero_samples.rbxl (145.6 KB)

I’ve made it into different models:

  1. Mk6A - @Imperiector’s formula.
  2. Mk6B - @xPoIarity’s formula, no induced drag.

Variants:

  • W - All weld, no suspensions + no wheels
  • P - Springless suspension—just PrismaticConstraints.
  • M - More mass + more aerodynamic power + stronger suspension.
  • Sh - Shrunk, 1:5 scale.

The formula difference is only with lift and drag calculations, hence similar results.
I use Polar’s AoA formula, as Imperiector’s had errors.
I disabled Polar’s air density decrease.


BodyMovers are found in RootPart.
AeroForce is the aerodynamic mover.
AlignOrientation is used to gain lift, pitched up 3 degrees.

For the car & suspension rig, I use Suphi Kaner’s guide.
Chassis parts are WheelChassis, N, R, & L.
Constraints and welds can be found in Costraints folder.

You may use logResults() at the bottom to get value printouts.
You can try adjusting liftPower and see significant results, as lift is the original problem.

Do not attempt to enable all models at once.

Shaking was happening because of the suspension. You were also using a single vector force and setting it to the combined plane forces. You shouldn’t do that, i fixed that by making these forces:
Thrust, Lift, Drag, SideForce. Also, the shaking was caused by your prismatic constraint’s limits(they are disabled for now, and i just welded the gear system to the gear hatches, not sure how to fix them). I assume your trying to retract and extend the gears using that, as well as make suspension, but you shouldn’t work on that yet. Make sure you have your flight physics the way you want it before you work on suspension. I also made basic wasd controllers so that you can increase throttle, decrease throttle, and pitch/roll/yaw the plane using an angular velocity constraint. Angular velocity is just a torque object but will just target a certain angular velocity you specify with the strength of maxTorque. Currently, there is no aoa limiter, and you accelerate until terminal velocity. I fixed the problems, you just need to scale it down, probably tweak some variables. It works though. Gravity is set to 10 right now, if you kept 196, the plane would be glued to the ground. What your asking for here is either no realism at all, or compromise, because if you want to keep default gravity, you cant really use vector forces to simulate the nicer physics. You are pretty much constrained to naval warfare physics. Sorry if this explanation is poor, its really late where i live right now, so ill help you later if there is anything else you need.
aerodynamic_samples_FIXED.rbxl (152.0 KB)

Tried to fly it. It appears that the plane couldn’t really align its velocity to the direction it’s facing. When I try to steer like 15 degrees, it aligns in an extremely slow speed. If I try to fly perpendicular and parallel, it stops aligning its velocity and decides to give me the FTL treatment—accelerates in higher and higher rate, and the black screen in just few seconds. Doesn’t look much different to the flinging issue, just in sync with the velocity. I assume this is what you meant by the absent AoA limiter and infinite acceleration?


Still wondering why the previous full-rigid model caused it to completely glitch out on zero velocity…


It had multiple VectorForces. I thought there would be a problem handling multiple of them. They resulted in the same behavior.

Never planned on making animated landing gears… just the old-fashioned opacity-collision.

I’ve forgotten about customizing the world gravity. I now notice the low-gravity wobble I feel when playing this particular game: Aeronautica.


This doesn’t work.
Also, are you sure that lift and side force are relative to local attachment?

Drag was too low, thats why you were accelerating indefinitely. I added a simple aoa limiter that decreases the pitch input when you get near the specified aoa limit. The gravity problem i was talking about earlier can be solved by just making the player’s gravity the classical gravity, and everything else realistic gravity. Also, the mass you were setting seemed arbitrary, the masses i chose were the plane’s intended weight/9.81(the workspace gravity). When you set your plane mass in Roblox, thats the TRUE MASS. That means, when gravity is applied, your weight = TRUE MASS * GRAVITY. If you want to keep a proper thrust to weight ratio for your plane, you want to set your true mass to your weight/9.81. The thrust force cares about your WEIGHT, not the true mass. So what happens here? What happens now is that lift/drag/side forces, their equations, can be properly made according to the real life equations and you can just get a more realistic simulation for normal gravity. So the equations that NASA gives us we can just use instead, like air density, and wing area etc. Your basically solving a proportional problem, sorry for the wait. I added tons of comments to explain my edits too, so hopefully you get a better understand for how it works:
aerodynamic_samples_FIXED_v2.rbxl (157.2 KB)

Try and combine anything you dont need as a separate object. Too many meshparts make your game laggy, and harder to work with. Also dont scale the plane down, theres no need unless your making a massive map for your game. If you do scale down, you will need to set network ownership, work with unreliable streaming because all of that revolves around the position of the player’s character. I hope this finally fixes your problems.

Also, if your looking at the speed outputs and find it hard to believe thats your actual speed, its because your plane is huge. The numbers we are using are metric, and your f15 model is not to scale. The problem you will have is the size of the player relative to the plane when you do have it at its proper metric proportions, and your going to have to scale your player down. That’s another problem i have also run into and i solved it by just scaling down the character. Roblox engine actually handles the settings, and the character will feel the same after being scaled. All you need to do is change the gravity for the player and you will have that fixed. I didnt add it because i wasnt sure what you wanted to do with this next. But in short, it DOES have costs. Of course, If you have any questions just ask.

Tested it multiple times, it’s great!

I notice that the aircraft slowly pitches down, and it’s caused by the players weight addon you made. I won’t be needing player characters, so it’d be fine. I’m sorry if you had to code that :sweat_smile:


There’s some stuff I wish to achieve. I seem to have trouble with drag and thrust. I just think that my plane takes a really long time to deaccelerate when I set to zero throttle. I tried increasing my drag coefficient. But then this is when I have a problem with thrust. Even if I keep setting my thrust power higher, it seems to be unable to match it’s real-life speed (1,600+ kts) on high altitudes. Oh, and by altitude, I don’t really know how to accurately achieve max ceiling of a plane (60,000 ft for the F-15), where they would start to stall.

Setting my thrust power too high would also lead to this particular issue. When it comes to certain planes, example like a B-52, they shouldn’t be able to fly vertically, or even after surpassing certain attitude angle. Is this what they meant by thrust-to-weight ratio?

Speaking of stall, do you happen to know how to indicate if your plane is stalling? Other than getting stall warning feature, I could use it to help define my stall speed, which would be done by adjusting wing area. I assume this is how I could do it to adjust max ceiling. I tried a simple way to indicate stall using only AoA and airspeed, but it’s inaccurate.


I happen to plan to optimize this game by a lot., with the first one being to scale the world down. Not only boosting performance since objects are smaller to Roblox, but it also helps with rendering distance. The game will be 5 times smaller, what should I apply the changes onto?

For the F-15 model, it’s a low-poly model I made several months ago. Had some mistakes as I was quite new to Blender and 3D modelling. I plan to revamp and optimize it. I don’t- can’t model high-poly models :woozy_face:

Oh and, I was wondering with what metric unit you were using. I just assume that Roblox studs is equal to feet (according to my thoughts on the default character size, and later Avatarology). I made sure that my F-15 has the correct size. Also, 1 ft/s is 0.592+ knots, so I use it to get my speed in knots.

For this aerodynamic knowledge of mine, it’s mostly based on my experience in Aeronautica.

Maybe I missed it in this thread somewhere, but is there a reason why you aren’t using Roblox’s new aerodynamics physics? This will allow you to skip the complexities of coding it yourself.

They work for basic planes, and I think he wants a much better simulation. I dont think Roblox has disclosed the equations they are using for lift and drag, but as far as i know, it gets the air density at the current altitude the parts geometry, and calculates some drag or lift equation. Even with Roblox’s native aerodynamic tools, I dont think you can get around coding. Regardless, these are new features that aren’t fully understood (they are experimental), so until they get much better, I dont think using them beyond basic planes is worth it.

I’m assuming 1 stud = 1 meter, 1rmu = 1kg etc. Roblox units are consistent and this checks out for me. If you want more help, end this topic and just message me instead.