Hello everyone, I just wanted to demonstrate my math skills by making this physics simulator
Features
Takes air resistance, gravity, Euler integration methods and real-time 3D visualization into account to bring a full system into play
Simulates projectiles in motion in 2D
Prints trajectory data
Integration
Place this ModuleScript into ReplicatedStorage, and call it ProjectileMotion. Then paste this code, and you’re done! 
local Projectile = {}
Projectile.__index = Projectile
local AIR_DENSITY = 1.225
local DRAG_COEFFICIENT = 0.47
local PROJECTILE_AREA = 0.01
local PROJECTILE_MASS = 0.145
local function magnitude(v)
return math.sqrt(v.x * v.x + v.y * v.y)
end
local function normalize(v)
local mag = magnitude(v)
if mag == 0 then return {x=0, y=0} end
return {x = v.x / mag, y = v.y / mag}
end
local function gravityAtHeight(y)
local g0 = 9.81
local R = 6.371e6
local h = y
return g0 * (R / (R + h))^2
end
local function dragForce(velocity)
local vMag = magnitude(velocity)
return 0.5 * AIR_DENSITY * DRAG_COEFFICIENT * PROJECTILE_AREA * vMag * vMag
end
function Projectile.new(position, velocity)
local self = setmetatable({}, Projectile)
self.position = {x = position.x, y = position.y}
self.velocity = {x = velocity.x, y = velocity.y}
self.acceleration = {x = 0, y = 0}
self.mass = PROJECTILE_MASS
self.time = 0
self.trajectory = {}
return self
end
function Projectile:update(dt)
local g = gravityAtHeight(self.position.y)
local dragMag = dragForce(self.velocity)
local dragDir = normalize({x = -self.velocity.x, y = -self.velocity.y})
local drag = {x = dragDir.x * dragMag, y = dragDir.y * dragMag}
local ax = drag.x / self.mass
local ay = (-g) + (drag.y / self.mass)
self.acceleration.x = ax
self.acceleration.y = ay
self.velocity.x = self.velocity.x + ax * dt
self.velocity.y = self.velocity.y + ay * dt
self.position.x = self.position.x + self.velocity.x * dt
self.position.y = self.position.y + self.velocity.y * dt
self.time = self.time + dt
table.insert(self.trajectory, {
time = self.time,
pos = {x = self.position.x, y = self.position.y},
vel = {x = self.velocity.x, y = self.velocity.y},
acc = {x = self.acceleration.x, y = self.acceleration.y},
})
end
function Projectile:isLanded()
return self.position.y <= 0
end
function Projectile:simulate(maxTime, dt)
dt = dt or 0.01
maxTime = maxTime or 10
while self.time < maxTime and not self:isLanded() do
self:update(dt)
end
end
function Projectile:printTrajectory()
print("Time\tX\tY\tVx\tVy\tAx\tAy")
for _, point in ipairs(self.trajectory) do
print(string.format("%.3f\t%.3f\t%.3f\t%.3f\t%.3f\t%.4f\t%.4f",
point.time,
point.pos.x, point.pos.y,
point.vel.x, point.vel.y,
point.acc.x, point.acc.y
))
end
end
local p = Projectile.new({x=0, y=0}, {x=20, y=30})
p:simulate(10, 0.01)
p:printTrajectory()
return Projectile
Usage Policy
I really don’t care what you do with it, but if you do enjoy it, be sure to send me a DM telling me, other than that, no attribution needed, and no worries. Have a good day everyone! ![]()