More accurate hit detection for kinematic projectile physics

I am using a modified version of the displacement formula to simulate projectile physics.

local function x(x0, v0, a, t)
	return x0 + v0 * t + 0.5 * a * t^2
end

local function v(v0, a, t)
	return v0 + a * t
end

function physics:step(time)
	self.position = Vector3.new(
		x(self.position.X, self.velocity.X, self.acceleration.X, time),
		x(self.position.Y, self.velocity.Y, self.acceleration.Y, time),
		x(self.position.Z, self.velocity.Z, self.acceleration.Z, time)
	)
	
	self.velocity = Vector3.new(
		v(self.velocity.X, self.acceleration.X, time),
		v(self.velocity.Y, self.acceleration.Y, time),
		v(self.velocity.Z, self.acceleration.Z, time)
	)
end

i have a wrapper object which is supposed to add more functionality to the physics module.

local _physics = self._physics
	
	_physics.acceleration = self.acceleration
	_physics.position = self.position
	_physics.velocity = self.velocity
	
	_physics:step(time)
	
	local raycastResult = workspace:Raycast(self.position, _physics.position - self.position, self.raycastParams)
	
	if raycastResult then
		
	end

initially, for collisions i thought about using raycasts but then i realised this would not work if the time i step by is too large.
The obvious solution is to break down a large step to smaller increments and update the physics that way.
however, I want to know if there is any way that I could skip this? is there any way I can simulate collisions without having to raycast tiny steps all the time?

1 Like

Try raycasting backwards from the tip of the projectile the amount it would move in one increment.

Say it travels 4 studs/increment. If you raycast backwards from the tip of the bullet 5 studs (to be safe) then if the bullet passes through an item it should register the hit fairly accurately.

that is still raycasting multiple times. I was thinking to break things up into simply their volumes and checking the position values for the physics object to check at what time would it reach an intersection. this however sounds very complicated and so im wondering if it even is feasible at all.

Yes, since if you have moving objects it’d be hard to predict where their volumes should be when they might intersect.
For example if you had a fast moving object that changed direction suddenly into the path of the projectile before it was expected then it’d miss the object.
You could do GetPartsInPart, but again, you’d have to check any one of these methods each step to see if there would be, or is, a hit.

it doesnt matter that. i just need the projectile to bounce accordingly. its simply just a point being pushed through 3d space.

I have a feeling we could maybe implement “curved raycasts”. Currently, raycasts are limited to linear motion due to how much less complex those math equations are to compute. We will have to define the plane/s in which we will be checking for intersections. The issue is that we will be very limited to what we can do since roblox doesn’t allow us (but should) to see further information into the geometry of objects and meshes (WHICH THEY SHOULD).

Your can design your functions to work with vectors instead of numbers. This would allow you to adjust the position and velocity of the object in two lines of code, with zero code duplication

i dont understand what you mean. the way the code is right now is fine.

i have a lastposition variable that is at first the initial position of the projectile and is set at the end of every frame that the projectile is updated so that the direction argument passed into the raycast function is just currentposition - lastposition which also happens to account for any skipped/laggy frames

this is stripped boilerplate code from a recent project that you can probably see what’s going on

--- time, bullet, collisions are made in-house and can be found here
--- https://github.com/00826/konbini
local lastposition = start
local alreadyhit = table.create(Players.MaxPlayers)
local c; c = Time.framerule(Bullet.Framerate, function()
	local framedata = Bullet.ballisticsolve(linear, Bullet.Framerate)

	do --- player
		
	end

	do --- env
		local maphit = Collisions.raycast("Whitelist", lastposition, framedata.Position - lastposition, {workspace.Map})
		if maphit then
			if c then c:Disconnect() end

			--- ... 
		end
	end

	lastposition = framedata.Position

	if framedata.Elapsed > Config.time then
		if c then c:Disconnect() end
	end
end)

also the t^2 can be unwrapped to t*t and if you do sincerely care about squeezing performance then you wouldn’t be using oop because it doesn’t benefit from luau optimizations

1 Like

In your code you are doing what I intend to avoid. You are using a small increment and raycasting between the last and next position. My intention isn’t to cut down on the number of raycasts but to further improve the accuracy of the kinematics. You can’t model a projectile’s motion with a linear ray perfectly unless the acceleration is 0.

In your code I did find an interesting method I could maybe utilize, BasePart:GetClosestPointOnSurface().

if you really care about the most perfectly accurate position of the impact, then you could do curved raycasting(idk anything about that) or:
you could use the stepped raycast method, but only to determine the part that is hit(so you don’t have to check every part as a volume), and then solve for the time which the bullet enters the bounds

At this point I think that trying to implement raycast physics is no good. It has too many issues with accuracy and only works at the fixed time step rate of 60hz. I think I will go with a physics approach using my physics/trajectory module for validating positions on the server while the ball is simulated on the clients.

So far i am having some issues with a truncation error that seems to be an engine bug. i will make a post on this later.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.