Get Exact Position with BasePart.Touched?

Sorry for the ambiguous title, don’t want it to be too wordy.

Here is the problem. The transparent floating red ball represents the Projectile.Position at the moment it touches a surface—or rather, when Roblox says the projectile made contact, which is always late (at differing degrees, depending on your framerate). This has been an issue for a long time, and is one of a multitude of reasons many developers have put aside using BasePart.Touched entirely. For reasons not pertinent to this thread, that is not an option here.

What I ultimately want here is the exact position of the fired ball at the moment of contact.

The reason I need this is so that the server can verify that the ball’s X component did not change relative to its direction (beyond floating point errors) from when the ball was launched to when it first touched a part. Here’s a good example of this verification happening on a schedule rather than on each touch, wherein the ball is forcefully moved by the client to a location outside of the server’s calculated path, and thus prevented from continued replication (notice the warnings in the output).

Know that the ball in this case exists only on the client, and that any info the server knows about the ball relates only to its initial parameters (initial position, initial velocity, weapon firer, etc.) In this birds-eye view illustration you can see

  1. the character firing the ball in the bottom-left corner,
  2. the target position in the upper-right corner,
  3. the square green perimeter that symbolizes the coordinate frame for the ball’s direction, and
  4. the shaded green path that the ball will be allowed to travel and hit surfaces in during its flight.

When the ball hits a part, the server will ensure that ball hit the part inside this shaded green space using some highly classified CFrame tricks. Again, the server needs an exact point of touch for this security check to be reliable. Because BasePart.Touched doesn’t provide even the bare minimum for info about the point of contact, it’s up to the developer to figure out how to get that.

Upon contact, I cannot try some resource-heavy trick with raycasting backwards and curved, looping through frames and running GetTouchingParts, rotating Region3’s, or any other hacky attempt to find the contact point. This is because the ball is designed to damage players, who are constantly moving. Between the actual time of contact and the reported time (when BasePart.Touched fires), the targeted character will likely have moved enough for these methods to yield nothing valuable.

Luckily, it’s actually possible to calculate the exact point of contact. That’s where I need your help. Because we have the ball’s vector (both position and velocity) from both before the moment of contact and after, we are able to find the point of intersection just from these two vectors alone. Here you can see a rough sketch of the problem we’re dealing with here, with

  1. the [color=#FF7FB4]pink[/color] circle representing the ball’s [color=#00AA1C]initial[/color] [color=#AA55FF]position[/color],
  2. the [color=#FF7FB4]pink[/color] arrow representing the ball’s [color=#00AA1C]initial[/color] [color=#CD0000]velocity[/color],
  3. the [color=#FF9600]orange[/color] circle representing the ball’s [color=#007FFF]latest[/color] [color=#AA55FF]position[/color], and
  4. the [color=#FF9600]orange[/color] arrow representing the ball’s [color=#007FFF]latest[/color] [color=#CD0000]velocity[/color].

The surface isn’t particularly important here. So how can I find the intersection point of two vectors? I went to Google and quickly found this thread, which conveniently had a C++ code sample ready to use. I modified the syntax so Roblox Lua could read it, and then promptly realized it only worked in 2D. “Oh whatever,” I thought, “I’m not going to be using the Y component in the security check anyways. It can be any value, doesn’t matter.” So I changed every “Y” reference to a “Z” and made it return a Vector3 whose Y component was just equal to the late touch position’s Y component.

function GetPointOfIntersection(p1, p2, n1, n2)
    local p1End = p1 + n1; -- another point in line p1->n1
    local p2End = p2 + n2; -- another point in line p2->n2
    
    local m1 = (p1End.Z - p1.Z) / (p1End.X - p1.X); -- slope of line p1->n1
    local m2 = (p2End.Z - p2.Z) / (p2End.X - p2.X); -- slope of line p2->n2
    
    local b1 = p1.Z - m1 * p1.X; -- z-intercept of line p1->n1
    local b2 = p2.Z - m2 * p2.X; -- z-intercept of line p2->n2
    
    local px = (b2 - b1) / (m1 - m2); -- collision x
    local pz = m1 * px + b1; -- collision z
    
    return Vector3.new(px, p2.Y, pz); -- return statement
end

Then, upon touching a surface, I had it run the function with the right parameters, except for the ball’s latest velocity component being flipped negative so the lines will cross:

local PostRicochetPosition = projectile.Position
local PostRicochetVelocity = projectile.Velocity
local IntersectionPoint = GetPointOfIntersection(PF.InitialPosition.Value, PostRicochetPosition, PF.InitialVelocity.Value.Unit, -PostRicochetVelocity.Unit)

PF is just a folder that contains some of the initial values when the ball was fired.

And hey, it works like a charm! The red ball represents the reported intersection point between the initial (pink) vector and the latest (orange) vector like in the image posted earlier. It looks perfect, except for the Y component—again note that the Y component of the red ball is level with the orange ball only because I didn’t think the Y component had any significance beyond being aesthetic, so I didn’t bother trying to figure it out, having to consider gravity and all.

Well, it turns out that the Y component actually is important. That .gif only showed me firing at a wall, perfectly perpendicular to the ground—meaning the surface normal of the wall had a Y component of zero, so we didn’t have to adjust for gravity at all and we could just stick to finding the point between two straight lines. That’s also the only condition under which the bird’s eye view graphic with the two vectors shows the situation correctly. When the ball is bouncing on the ground, one of the axes between X and Z doesn’t change, while the Y component is changing, and because of gravity, it’s changing at a non-constant rate. If we still calculate everything looking down from above, this can cause some interesting unintended behavior, as shown in that .gif. Here’s something close to the proper perspective. Related, but don’t know where to put this.

In this case, we have to not view from above but rather from the side. Even trickier too is that we have to cover the case of one of these bad boys. Or a sphere. Preferably the function wouldn’t even differentiate between a rotated surface, the ground, or a surface perpendicular to the ground. My friend calls this viewing angle the ‘plane of projection.’

I don’t know how to get the right plane to find the intersection on. Harder still, I don’t know how to find the intersection point between the two curved trajectories.

If you can answer this thread with a working solution, please message me in private afterward. Solving this will allow me to institute much more thorough and accurate security in lieu of easily exploitable weapons. This will have patched a major security hole that has existed in the brick battle community forever, one that’s literally thwarted the ability for potentially promising games to become popular.

I don’t know where to put this, but I figured I’d leave it here as well.

4 Likes

off topic but, how did you change the text color?

They said they’ll get rid of it but it’s been a while so I used it anyway.

I don’t get why you cannot use raycasting, it returns a position where it was intersecting with a part, so, if the part was touched at 0, 0, 0 then you could use that, you can also try using the SurfaceNormal, which is also a return of the raycast; this topic might help:

Read the rest of the paragraph:

This is because the ball is designed to damage players, who are constantly moving. Between the actual time of contact and the reported time (when BasePart.Touched fires), the targeted character will likely have moved enough for these methods to yield nothing valuable.