Curve along Sphere

Hello. How would I go about making a curved line (in 3D of course) to connect 2 points on a sphere (which is the globe in this case) but to also follow it’s curvature?

1 Like

Hello, a fairly simple (maybe not the most mathematical) way of achieving this would be through using a line, then offsetting the line based on the direction from the origin and the sphere’s radius.

Basically, if you have a circle at an origin and a randomly placed point, you can easily get the vector to the point from the circle’s origin with direction = point - origin. This means that origin + direction = point. We know that we can make a vector have a magnitude of any value by simply taking the unit vector of the target vector (always has magnitude of 1), then multiplying it by the target magnitude. We also know that the magnitude of a vector from a circle’s origin to a point on the circle’s surface is equal to its radius. Since we have the direction from the origin to the random point, we can simply convert it to a unit vector (in this case just direction.Unit) and multiply it by the circle’s radius (newDirection = direction.Unit*radius). To get the final position, all we have to do is add newDirection to the origin position and get:
finalPosition = origin + newDirection = origin + (point - origin).Unit*radius

Using this information, we can now convert an arbitrary point to the closest point on a circle/sphere. Since a line is being drawn in this case, we can just linearly interpolate between point A and B (assuming points A and B are already on the surface of the sphere) and convert each point on the line to a point on the sphere.

Here’s how the code would look like:

local delta = 0.1 -- ratio of line to draw per step (#segments = 1/delta)
local thickness = 0.1 -- thickness of line visually

local lineTemplate = Instance.new("Part")
lineTemplate.Anchored = true
lineTemplate.CanCollide = false

function drawLine(pointA: Vector3,pointB: Vector3,origin: Vector3,radius: number)
	local points = {}
	local index = 0
	for i = 0,1,delta do
		local line = lineTemplate:Clone()
		local p = pointA:Lerp(pointB,i)
		local direction = (p - origin).Unit
		local targetP = origin + direction*radius
		
		local prevP = points[index - 1] or pointA
		local prevDistance = (targetP-prevP).Magnitude
		line.Size = Vector3.new(thickness,thickness,prevDistance)
		line.CFrame = CFrame.new((targetP+prevP)/2,targetP)
		
		line.Parent = workspace
		points[index] = targetP
		index += 1
	end
end

Here’s an example image of the code in use (green = final line, yellow = converted vector from line point, red = original line)

image

Edit:
The main downside to this method is that as the angle between the two points increases, the line gets more and more “segmented” in the center due to most moved line points ending up closer to the points. I suggest trying to keep the angle below 135 degrees to mitigate this. You could also just draw two lines after getting the half-way point on the circle as well.

Hope this helps! Let me know if you have any questions.

1 Like

Hi, this works really well. Only issue is that depending on the delta, the final segment doesn’t perfectly reach the end point. It cuts off a little before if the delta is rather high. If it’s like 0.01 then it’s not as noticeable but there are a lot of parts in that case.

Ah, that issue happens if 1/delta has a decimal place. You could fix this issue by defining the segment count instead of delta, then set delta to equal to 1/segments. However, if you plan to do it this way, I suggest basing the for loop off of segments instead of delta.

Hello, I know I had helped to solve this earlier, but while I was experimenting with this, I found a much more reliable solution to this problem with evenly spaced points using CFrames.

Here’s the new code I came up with:

function drawLineOnSphere(origin: Vector3, pointA: Vector3, pointB: Vector3,radius: number,segments: number)
	local aNormal = (pointA - origin).Unit
	local bNormal = (pointB - origin).Unit
	local angle = math.acos(aNormal:Dot(bNormal))
	
	local crossed = aNormal:Cross(bNormal)
	
	local originalCFrame = CFrame.lookAlong(origin,aNormal,crossed)
	
	local prevPoints = {}
	
	for i = 1,segments do
		local alpha = i/segments
		local rotated = angle * alpha
		
		local nextDirection = (originalCFrame*CFrame.Angles(0,rotated,0)).LookVector
		local nextP = origin + nextDirection*radius
		
		local prevPoint = prevPoints[i-1] or pointA
		local line = lineTemplate:Clone()
		local lineD = (nextP - prevPoint).Magnitude
		line.Size = Vector3.new(thickness,thickness,lineD)
		line.CFrame = CFrame.new((prevPoint + nextP)/2,nextP)
		line.Parent = workspace.holder
		
		prevPoints[i] = nextP
	end
end

Hopefully this code may be able to help you with drawing more accurate lines around spheres. Let me know if you need any explanations!