Hi. I am currently trying to make pathfinding service generate smooth turns for sharp corners, however I am not smart enough with math to figure out how to do this. I have already incorporated logic to find out whether or not the angle is too sharp, but I cannot for the life of me figure out how to use a Bezier or lerp or whatever to make it work. Here’s an image describing shortly what I want to achieve here?
EDIT: No I do not want to toy with agent radius.
I am sure there is a way to do this properly, I just can’t quite put my finger on it. Any help would be appreciated!
local function CalculateWaypoints()
local Path = PathfindingService:CreatePath(AgentParameters)
Path:ComputeAsync(Alternative.HumanoidRootPart.Position, Target.HumanoidRootPart.Position)
Path.Blocked:Connect(function(blockedWaypointIndex)
Path:ComputeAsync(Alternative.HumanoidRootPart.Position, Target.HumanoidRootPart.Position) -- Recompute the path if blocked
end)
local Waypoints = Path:GetWaypoints()
local AdvancedWaypoints = {}
for Index = 2, #Waypoints - 1 do
local current = Waypoints[Index]
local previous = Waypoints[Index - 1]
local next = Waypoints[Index + 1]
local currentDirection = (current.Position - previous.Position).unit
local nextDirection = (next.Position - current.Position).unit
local averageDirection = (currentDirection + nextDirection).unit
local cFrame = CFrame.lookAt(current.Position, current.Position + averageDirection)
AdvancedWaypoints[Index] = {
["Position"] = current.Position,
["CFrame"] = cFrame,
["Corner"] = false
}
local dotProduct = currentDirection:Dot(nextDirection)
local angle = math.deg(math.acos(dotProduct))
if angle > 45 then
AdvancedWaypoints[Index]["Corner"] = true
end
end
return AdvancedWaypoints
end
I found the solution for anyone who comes here in the future. Basically, you want to re-calculate the path and only select the points which have an angle threshold above 3 degrees or so. Then you want to use the SimpleSpline module:
To calculate a path between those points, and you have a smooth path!
Here’s a snipped from my code for anyone who stumbles upon this in the future:
function SeekTarget(Target)
States["Seeking"] = false
local function CalculateWaypoints()
local Path = PathfindingService:CreatePath(AgentParameters)
Path:ComputeAsync(Alternative.HumanoidRootPart.Position, Target.HumanoidRootPart.Position)
Path.Blocked:Connect(function(blockedWaypointIndex)
Path:ComputeAsync(Alternative.HumanoidRootPart.Position, Target.HumanoidRootPart.Position) -- Recompute the path if blocked
end)
local Waypoints = Path:GetWaypoints()
local AdvancedWaypoints = {}
for Index = 2, #Waypoints - 1 do
local current = Waypoints[Index]
local previous = Waypoints[Index - 1]
local next = Waypoints[Index + 1]
local currentDirection = (current.Position - previous.Position).unit
local nextDirection = (next.Position - current.Position).unit
local averageDirection = (currentDirection + nextDirection).unit
local cFrame = CFrame.lookAt(current.Position, current.Position + averageDirection)
AdvancedWaypoints[Index] = {
["Position"] = current.Position,
["CFrame"] = cFrame,
}
local dotProduct = currentDirection:Dot(nextDirection)
local angle = math.deg(math.acos(dotProduct))
if angle > 45 then
AdvancedWaypoints[Index]["Corner"] = true
end
AdvancedWaypoints[Index]["Angle"] = angle
end
return AdvancedWaypoints
end
local function NavWaypoints(Waypoints)
local function Center(From)
local rayOrigin = From
local NewCFrame = CFrame.new(From,RootPart.CFrame.LookVector)
local rayDirectionLeft = NewCFrame.RightVector * -10
local rayDirectionRight = NewCFrame.RightVector * 10
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {Alternative}
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.IgnoreWater = true
local rayResultLeft = workspace:Raycast(rayOrigin, rayDirectionLeft, raycastParams)
local rayResultRight = workspace:Raycast(rayOrigin, rayDirectionRight, raycastParams)
local leftPoint = rayResultLeft and rayResultLeft.Position or (rayOrigin + rayDirectionLeft)
local rightPoint = rayResultRight and rayResultRight.Position or (rayOrigin + rayDirectionRight)
local centerPoint = (leftPoint + rightPoint) * 0.5
return centerPoint
end
local function CheckCorner(Current)
for Index, WaypointData in pairs(Waypoints) do
if Index > Current and Index - Current <= 5 and WaypointData["Corner"] == true then
return Index
end
end
return false
end
local Points = {}
for Index,Waypoint in pairs(Waypoints) do
if ((Waypoint["Angle"] > 3) or Index == 2 or Index == #Waypoints) then
local P = Instance.new("Part")
P.Size = Vector3.new(.5,.5,.5)
P.Material = Enum.Material.Neon
P.CanCollide = false
P.Parent = workspace
P.CFrame = Waypoint["CFrame"]
P.Anchored = true
table.insert(Points,Waypoint.CFrame * CFrame.new(0,0,0))
end
end
if #Points ~= 0 then
if Path then
Path:Stop()
Path = nil
end
Spline = SimpleSpline.new(Points, 0.5, 0)
Spline:SetOptions({
Uniform = true,
Offset = CFrame.new(),
Loop = false,
Reverse = false
})
Spline:Visualize()
local Object = Alternative
local Speed = 16
Path = Spline:FollowPath(Object, Speed)
end
end
if tick() - LastSeek > SeekDebounce then
LastSeek = tick()
AdvancedWaypoints = CalculateWaypoints()
NavWaypoints(AdvancedWaypoints)
end
end