[SMART Series Offshoot] Quick little 2D physics simulator (Free + Open Source)

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! :slight_smile:

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! :slight_smile:

6 Likes