Today I come to you seeking advice. I’ve tried my hand at this but I just can’t figure it out after spending quite a few hours on it.
THE PROBLEM:
There is an object “red X” which is at the end of a raycast from the camera to the mouse position. I collect this raycast by using the following variables: start position = camera CFrame end position = camera:ViewportPointToRay(mouseX, mouseY).Direction * 1000
I need to obtain the CFrame so the blue X will never be more than 50 studs from the player’s head CFrame. If the impact position of the camera->mouse ray is closer to the player’s head than 50 studs, then the blue X will be at the impact position (image 1 below). However if the impact point along the camera->mouse ray is further away from the player’s head than 50 studs, then I need to find the 3D point in space along that ray which is within that 50 stud limit (image 2 below). Does anyone have any suggestions on how to accomplish this?
I might very well be wrong, but I don’t think you can model an equation for that as there’s an infinite amount of combinations. Interpreting what you mean I think you are trying to get the one the farthest back but also close to the thing. You could apply a proportional closed loop system then just check if it is within some tolerance.
if distanceFromPlayerToX > 50 then
bias = sign(player-x:Dot(-direction))
out = 1
err = 50 - o+d*(bias*1.1)
while true {
pos = o+d*out
err = 50 - (player-pos).Magnitude
out = 0.1*err
if abs(err) < 1 {
print(o+d*out)
break
}
}
end
Thanks for the response! You’re correct, there are 0 or 2 intersection points in what I’m trying to do.
The player has an attack ability with a range of 50. I want to keep that range around the player’s head so they can’t angle their camera to use that ability further away from themselves than 50 studs. But I also want the ability to be where their mouse is (camera->mouse ray). So ideally the ability would happen along that camera->mouse ray, as close to the hit point as possible, but no further from the character’s head than 50 studs.
And ideally, if there happens to be 0 intersection points, it would just put the ability at 50 studs along a ray from your head to our mouse click. I can probably do that one relatively easily - the hard part is I can’t figure out the formula for the top-left square. Examples below:
Are you sure you don’t want the ray to just go from their head to the mouse in the first place? With this system they could angle their camera to shoot on the other side of a wall, even if there’s no line of sight between them and their mouse.
Just instead of +/- sqrt you’d use only the + (or maybe just the -?) so you get the second point.
That’s after checking for 0 intersections special case of course.
Edit: I guess there’s a few more special cases, like if you have 1 intersection you need to figure out if you’re inside or outside the sphere to start, because in the former case you should use the intersection point but in the latter you should use the ray hit pos, etc.
This seemed to work with me. The first function returns the two parameters, where one or more can be nil.
The second one just returns the further intersection point, or nil.
local function SphereLineIntersectionsU(sphereCenter: Vector3, sphereRadius: number, lineP1: Vector3, lineP2: Vector3)
local x1, y1, z1 = lineP1.X, lineP1.Y, lineP1.Z
local x2, y2, z2 = lineP2.X, lineP2.Y, lineP2.Z
local x3, y3, z3 = sphereCenter.X, sphereCenter.Y, sphereCenter.Z
local a = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1)*(z2-z1)
local b = 2*((x2-x1)*(x1-x3) + (y2-y1)*(y1-y3) + (z2-z1)*(z1-z3))
local c = x3*x3 + y3*y3 + z3*z3 + x1*x1 + y1*y1 + z1*z1 - 2*(x3*x1 + y3*y1 + z3*z1) - sphereRadius*sphereRadius
local radicand = b*b - 4*a*c
if radicand < 0 then return nil, nil end
if radicand < 0.1 then return -b / (2*a) end -- doesn't really happen in practice
return (-b + math.sqrt(radicand)) / (2*a), (-b - math.sqrt(radicand)) / (2*a)
end
local function GetFurthestSphereIntersection(sphereCenter: Vector3, sphereRadius: number, lineP1: Vector3, lineP2: Vector3)
local u1, u2 = SphereLineIntersectionsU(sphereCenter, sphereRadius, lineP1, lineP2)
if u1 then return lineP1 + (lineP2-lineP1)*u1 end
return nil
end
local function SphereLineIntersectionsU(sphereCenter: Vector3, sphereRadius: number, lineP1: Vector3, lineP2: Vector3)
local x1, y1, z1 = lineP1.X, lineP1.Y, lineP1.Z
local x2, y2, z2 = lineP2.X, lineP2.Y, lineP2.Z
local x3, y3, z3 = sphereCenter.X, sphereCenter.Y, sphereCenter.Z
local a = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1)*(z2-z1)
local b = 2*((x2-x1)*(x1-x3) + (y2-y1)*(y1-y3) + (z2-z1)*(z1-z3))
local c = x3*x3 + y3*y3 + z3*z3 + x1*x1 + y1*y1 + z1*z1 - 2*(x3*x1 + y3*y1 + z3*z1) - sphereRadius*sphereRadius
local radicand = b*b - 4*a*c
if radicand < 0 then return nil, nil end
if radicand < 0.1 then return -b / (2*a) end -- doesn't really happen in practice
return (-b + math.sqrt(radicand)) / (2*a), (-b - math.sqrt(radicand)) / (2*a)
end
local bias = sign(xToPlayer:Dot(-dir)
local u1, u2 = SphereLineIntersectionsU(sphereCenter, sphereRadius, lineP1, lineP2)
if u1 > u2 then
greatest = u1
least = u2
else
greatest = u2
least = u1
end
if bias > 0 then
return camera.CFrame.Position + dir*greatest
else
return camera.CFrame.Position + dir*least
end
I think he wants the dot to be closest to the wall is why
Yes, relative to the ray in which it cast. You need to do a check on your scalars so it is the closest to what you want relatively. In this case, the wall
sign is math.sign, dir is the ray direction from the camera to the point in the world, lineP1 is camera.CFrame.Position, lineP2 is lineP1+dir, xToPlayer is player.Position-x.Position. You probably need to sort the bias not by the angle but rather the closest distance to the x point now that I think of it