Beam for a projectile trajectory

I’m trying to use a Beam to visually represent a projectile’s trajectory, showing where it will land. The Beam is dynamically updated between two attachments (start position and target).

The issue I’m facing is that the CurveSize doesn’t accurately reflect the expected parabolic trajectory. Depending on the distance or angle, the curve generated by the Beam doesn’t align with the physics of the projectile.

I’m looking for advice on how to calculate CurveSize so that the Beam always matches the actual trajectory, considering factors like initial velocity, gravity, and direction.

Any guidance or suggestions for adjusting the calculation would be greatly appreciated!

local function onMaxDistance(v, g, d, h)
	local m = h / d
	local v2 = v * v
	return (v2*math.sqrt(m*m + 1) - m*v2) / g
end

local function onLaunchAngle(v: number, g: number, d: number, h: number, higherArc: boolean): (boolean, number)
	local max_x = onMaxDistance(v, g, d, h)
	local v2 = v * v
	
	if d > max_x then
		return false, math.atan(v2 / (g * max_x))
	end

	local v4 = v2 * v2
	local humanoidRootPart = math.sqrt(v4 - g*(g*d*d + 2*h*v2))
	if not higherArc then humanoidRootPart = -humanoidRootPart end
	return true, math.atan((v2 + humanoidRootPart) / (g * d))
end

local function onLaunchDirection(start, target, v, g, higherArc: boolean)
	local horizontal = Vector3.new(target.X - start.X, 0, target.Z - start.Z)

	local h = target.Y - start.Y
	local d = horizontal.Magnitude
	local inRange, a = onLaunchAngle(v, g, d, h, higherArc)
	
	local vec = horizontal.Unit * v
	local rotAxis = Vector3.new(-horizontal.Z, 0, horizontal.X)
	return inRange, CFrame.fromAxisAngle(rotAxis, a) * vec
end

local function onUpdateTrajectory()
	local start = humanoidRootPart.CFrame
	--local target = workspace:WaitForChild("Target").Position
	local target = mouse.Hit.Position
	
	local horizontal = Vector3.new(target.X - start.X, 0, target.Z - start.Z)
	local gravity = workspace.Gravity
	
	local h = target.Y - start.Y
	local d = horizontal.Magnitude
	
	local IsRange, test = onLaunchAngle(velocity, gravity, d, h, higher)
	local IsRange, dir = onLaunchDirection(start, target, velocity, gravity, higher)
	
	local curveSize = workspace.Adjust.Value * test * dir.Magnitude
	
	Beam.CurveSize0 = curveSize > 0 and curveSize or 1
	Beam.CurveSize1 = curveSize > 0 and -curveSize or .5
	
	Attach0.CFrame = start * CFrame.Angles(0,0,math.rad(90))
	Attach1.CFrame = mouse.Hit * CFrame.new(0, workspace.StudsY.Value, 0) * CFrame.Angles(0,0,math.rad(90))
	
	local p = Instance.new("Part")
	p.Size = Vector3.new(1, 1, 1)
	p.CanCollide = false
	p.BrickColor = IsRange and BrickColor.Green() or BrickColor.Red()
	p.Position = humanoidRootPart.Position
	p.Velocity = dir
	p.Parent = workspace.Framework.Parts
	Debris:AddItem(p, 1)
end

image

you at least need to use the LaunchDirection for the beam attach0 cframe
beam uses cubic bezier curve and it has 4 points (start, control1, control2, end).
a parabola is quadratic, it is possible to convert a cubic bezier into quadratic through some maths (you can find on wiki if you are interested)

here is a piece of code I used, see if you can fit the necessary info from part of your calculations

-- position and shootVelocity should be in world space
-- gravity (downward is positive) should account for all forces on the projectile,
-- for example, workspace.Gravity
module.Visualize = function(beam: Beam, position: Vector3, shootVelocity: Vector3, gravity: number, t: number)
	local beamRotation = CFrame.Angles(0, math.rad(90), 0)
	local p2 = module.GetPosition(position, shootVelocity, gravity, t)
	local v2 = module.GetVelocity(position, shootVelocity, gravity, t)
	
	beam.Attachment0.WorldCFrame = CFrame.lookAlong(position, shootVelocity) * beamRotation
	beam.Attachment1.WorldCFrame = CFrame.lookAlong(p2, v2) * beamRotation
	
	-- quadratic bezier control point is at half the duration of the flight
	local t1 = t / 2
	local p1 = position + shootVelocity*t1

	-- change from quadratic to cubic bezier
	local curveSizeMultiplier = 2 / 3
	beam.CurveSize0 = (p1-position).Magnitude * curveSizeMultiplier
	beam.CurveSize1 = (p2-p1).Magnitude * curveSizeMultiplier 
end