Finding a arbitrary point on a Rope Constraint

Hello again, recently figured a way to find a arbitrary point on a Rope Constraint, as of right now I have not seen anyone accomplish this as there’s no native way of doing so, so I felt the need to challenge myself. I’ve provided a game link to demo it if you’re interested, and a video.

If you’re interested in this sort of tech you can check out the followings below.

https://devforum.roblox.com/t/bypassing-particle-lod-rate-throttle/2446042 - Removes the LOD from Particle Rate property

https://devforum.roblox.com/t/tracking-roblox-particles/2425825 - Can track actual particles from Particle Emitters in-engine.

https://devforum.roblox.com/t/bypassing-forced-beam-lod/2426888/1 - Removes the segment LOD for beams


:sparkles: The Code :sparkles:
Scroll down to where you find EvalPointOnRC function to see usage

-- credits to nurokoi
local function IsNan(value)
	return type(value) == `number` and value ~= value
end

local function HyperSolver(t, d)
	local N = 1e6 
	local EAppx = (1 + 1/N) ^ N 
	local PE = EAppx ^ t 
	local PEIn = 1 / PE 
	local TCt = PE + PEIn - 2
	local TSt = t * (PE - PEIn)

	return t * (2 * TCt - TSt) / (TCt - TSt + (t * t) * d)
end

local function UnitizeDist(dist)
	local CatA = (dist < 0.1 and ((2.3e-1 * dist + 1.344e-1) * (dist ^ 1.15)) 
		or (dist < 0.9 and ((9.071e-2 + 1.462e-1 * dist - 1.962e-1 * dist * dist) * (dist / (1 - dist))) 
			or (math.exp(-1.243) * (dist * dist) / math.sqrt(1 - dist * dist))))
	
	local ApproxT, InverseSq = dist / CatA, 1 / (dist * dist)

	for _ = 1, 4 do -- 4 guess attempts should be more than enough 
		ApproxT = HyperSolver(ApproxT, InverseSq)
	end

	return dist / ApproxT
end

local function EvalCurve(x, l, a)
	return a * (math.cosh((x - l) / a) - 1)
end

local function EvalPointOnRC(ropeConstraint : RopeConstraint, alpha : number)	
	assert(ropeConstraint.Attachment0 and ropeConstraint.Attachment1, `RC Attachments not prepared for {ropeConstraint}`)

	local A0 = (ropeConstraint.Attachment0 :: Attachment).WorldCFrame
	local A1 = (ropeConstraint.Attachment1 :: Attachment).WorldCFrame
	local Length = ropeConstraint.Length

	local A0Pos = A0:ToObjectSpace(A0).Position
	local A1Pos = A0:ToObjectSpace(A1).Position
	local AVec = A1Pos - A0Pos
	
	-- welcome to where i spent a few days working on, enjoy & expect more down the line.
	-- would provide the papers i went by and desmos playgrounds to make sense of all this 
	-- but it's completely idiosyncratic in workflow, evidently...

	local D = math.sqrt(AVec.X ^ 2 + AVec.Z ^ 2)
	local H = AVec.Y
	local S = math.sqrt(Length * Length - H * H)
	local CatA  = S * UnitizeDist(D / S)
	local XL = CatA * math.log(CatA * (math.exp(D / CatA) - 1) / (H + Length))	
	local ADs = A0Pos.Y - EvalCurve(0, XL, CatA)
	local ADe = A1Pos.Y - EvalCurve(D, XL, CatA)
	local X = D * alpha
	local Y = EvalCurve(X, XL, CatA) + ADs + (ADe - ADs) * alpha
	
	local Trans = A0Pos + X * Vector3.new(AVec.X, 0, AVec.Z).Unit + Y * Vector3.yAxis
	Trans = A0:ToWorldSpace(CFrame.new(Trans))

	local IsLinear = IsNan(Trans.X) or IsNan(Trans.Y) or IsNan(Trans.Z)

	return (IsLinear and A0:Lerp(A1, alpha) or Trans).Position
end

local function CPointSpacing(ropeConstraint : RopeConstraint, numPoints : number)
	local Result = {}
	local PreP = EvalPointOnRC(ropeConstraint, 0)
	local PreH = PreP.Y

	for i = 1, numPoints do
		local CTrans = EvalPointOnRC(ropeConstraint, i / numPoints)
		local CHeight = CTrans.Y

		local HDiff = math.abs(CHeight - PreH)
		local PDiff = CTrans - PreP
		local LA = HDiff ^ 2 + PDiff.Magnitude ^ 2

		PreH = CHeight
		PreP = CTrans
		table.insert(Result, math.sqrt(LA))
	end

	return Result
end

local function EvalFixedDistPointsOnRC(ropeConstraint : RopeConstraint, intervalDistance : number, resolution : number?)
	assert(ropeConstraint.Attachment0 and ropeConstraint.Attachment1, `RC Attachments not prepared for {ropeConstraint}`)

	local A0 = (ropeConstraint.Attachment0 :: Attachment).WorldPosition
	local A1 = (ropeConstraint.Attachment1 :: Attachment).WorldPosition
	
	local Result = {}
	local PointRes = math.max(resolution or 1e3, ropeConstraint.Length / intervalDistance) -- increased for better precision
	
	local Accu = 0
	local LastA = 0

	for i, distance in ipairs(CPointSpacing(ropeConstraint, PointRes)) do
		Accu += distance

		if Accu >= intervalDistance then
			local Alpha = i / PointRes
			local Trans = EvalPointOnRC(ropeConstraint, (Alpha + LastA) / 2)

			if (Trans - A0).Magnitude + (Trans - A1).Magnitude <= ropeConstraint.Length then
				table.insert(Result, Trans)
			end

			Accu = 0
			LastA = Alpha
		end
	end
	
	return Result
end

(Multiple attachments sliding up & down)
Game Link : Ropeconstraint Point Finder - Roblox (Uncopylocked)

35 Likes

New feature added : Equidistant points
What does it do : You can find points that are a equal distant from each other on the catenary rope, returns a list of points

You can find the function being used in the place link that I’ve mentioned or in the code snippet where it’s called EvalFixedDistPointsOnRC

Can be used for light fixtures as such that require precise equal distance from one another.

9 Likes

This is really cool, making wires and such in games will be so much easier

Especially as you noted, christmas lights, so many uses