Better methods for generating a (semi) physically accurate fire hose

I’m attempting to set up a system for rendering a fire hose that travels from the connection point on the wall to the player in a semi-realistic manner (not just clipping through whatever is in the way in a straight line). What I’m trying currently is to use pathfinding, which although giving decent results sometimes generates ridiculous, unrealistic paths that exceed the limit I set on the number of waypoints, and this is the biggest problem for me.

Unfortunately, the Pathfinding service is not very flexible and doesn’t contain many options besides a few agent parameters for defining your path, so I’m not sure what else I can do to make the result more favorable. The code I’m using for the above system is:

local pathfinding = game:GetService("PathfindingService")
local player = game:GetService("Players").LocalPlayer
local run = game:GetService("RunService")
local path = pathfinding:CreatePath({AgentRadius = 1, AgentHeight = 1, AgentCanJump = true})
local bracket = workspace.HoseConnectionRL.Bracket

function SetEndpoints(part, point1, point2)
	part.Size = Vector3.new(part.Size.X, (point1 - point2).magnitude, part.Size.Z)
	part.CFrame = CFrame.new(point1:Lerp(point2, 0.5), point2) * CFrame.Angles(math.rad(90), 0, 0)
end

wait(1)
while true do
	wait()
	path:ComputeAsync(bracket.Position, player.Character.PrimaryPart.Position)
	local wp = path:GetWaypoints()
	if #wp < 100 then
		workspace.HoseParts:ClearAllChildren()
		local last = bracket
		for _, waypoint in pairs(wp) do
			local part = workspace.HoseTemplate:Clone()
			part.Parent = workspace.HoseParts
			SetEndpoints(part, last.Position + (-last.CFrame.UpVector * (last.Size.Y/2)), waypoint.Position)
			last = part
		end
	end
end

(this code uses some really awful scripting practices which I’m aware of, but it was just lazily written to test the idea)

In this example, the hose template part I had created is a cylinder which I was unable to flip in a way that the forward face was actually the front face of the cylinder mesh, so I’m using the bottom face instead, hence -CFrame.UpVector.

I’ve been trying to look for ways of either improving this method or using another method that generates better results. The only other way of doing this that I’ve recognized so far is actually creating the hose out of physics parts attached by ball in socket joints with a limited angle, but I was hoping there would be alternative solutions before relying on physics. Does anyone have any ideas/advice?

5 Likes

actually its a nice hose system

do u still have the same issue?

One approach you could try is using raycasting to determine the path of the fire hose. You can cast rays from the starting point of the hose to the player’s position and check if any obstacles intersect with the ray. If an obstacle is found, you can calculate a new endpoint for the hose that avoids the obstacle. You can then repeat this process along the length of the hose until you reach the player.

Below, I provided some sample code you could use to see if that helps.

local player = game:GetService("Players").LocalPlayer
local hoseStart = workspace.HoseConnectionRL.Bracket
local hoseEnd = player.Character.PrimaryPart
local hoseTemplate = workspace.HoseTemplate

local function createHosePart(startPos, endPos)
    local hosePart = hoseTemplate:Clone()
    hosePart.CFrame = CFrame.new(startPos, endPos)
    hosePart.Size = Vector3.new(1, (startPos - endPos).Magnitude, 1)
    hosePart.Parent = workspace.HoseParts
    return hosePart
end

local function raycastForHose(startPos, endPos)
    local ray = Ray.new(startPos, endPos - startPos)
    local hitPart, hitPosition, hitNormal = workspace:FindPartOnRayWithIgnoreList(ray, {hoseStart, hoseEnd, workspace.HoseParts})

    if hitPart then
        -- Calculate a new endpoint that avoids the obstacle
        local newEndPos = hitPosition - (hitNormal * hoseTemplate.Size.Y/2)
        return true, newEndPos
    else
        return false, endPos
    end
end

local function generateHose()
    workspace.HoseParts:ClearAllChildren()
    local currentPos = hoseStart.Position
    local endPos = hoseEnd.Position
    while (endPos - currentPos).Magnitude > 0.1 do
        local success, newEndPos = raycastForHose(currentPos, endPos)
        if success then
            createHosePart(currentPos, newEndPos)
            currentPos = newEndPos
        else
            break
        end
    end
end

while true do
    wait()
    generateHose()
end

This code casts a ray from the current position of the hose to the player’s position, and checks if the ray intersects with any parts in the workspace. If it does, it calculates a new endpoint for the hose that avoids the obstacle. The code then creates a new hose part and sets its position and orientation based on the endpoints.

This method should be more flexible than pathfinding, as it allows the hose to move around obstacles instead of being limited to a pre-defined path. However, it may be less efficient than pathfinding if you have many obstacles in the workspace, as it needs to cast a ray for each segment of the hose. You can optimize this by increasing the distance between raycasts or by using more efficient collision detection methods.