If your want the ball to reflect you should use the formula mentioned in the post @Qinrir shared. I’ve used it before and it works like a charm. As for calculating the direction of the ball while under the force of gravity, that one is a bit trickier. However, I’ve done it before when needing to get an estimate for my players position, so I should be able to help.
Here’s part of the code I used for getting the future position:
local RunService: RunService = game:GetService("RunService")
local rootPart: BasePart = character.PrimaryPart or character:WaitForChild("HumanoidRootPart")
local x0: Vector3 = rootPart.Position -- initial position
local v0: Vector3 = rootPart.AssemblyLinearVelocity -- initial velocity
RunService.Heartbeat:Connect(function()
local linearVelocity: Vector3 = rootPart.AssemblyLinearVelocity
local characterVelocity: Vector3 = character:GetPivot():VectorToObjectSpace(linearVelocity)
local v1: Vector3 = (0.5*(-Vector3.yAxis * workspace.Gravity)*(deltaTime^2) + v0*deltaTime)
local v2: Vector3 = (v1 * horizontalVector) -- the horizontal velocity
local x1: CFrame = character:GetPivot() + (v2 * 0.5) -- the position
x0 = rootPart.Position
v0 = linearVelocity
fallHeight = if (characterVelocity.Y < 0) and (math.abs(characterVelocity.Y) > 0.00001) then math.max(math.abs(v1.Y) * 2, fallHeight) else 0
local querySize: Vector3 = floorSize + (Vector3.yAxis * fallHeight)
local queryFrame: CFrame = x1:ToWorldSpace(floorFrame - (Vector3.yAxis * querySize.Y * 0.5))
end)
You should be able to adapt it to your needs. There are a few variables in there that you don’t need, like floorSize and floorFrame. You can just ignore these, as I left them to help understand the code better.