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?
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)
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.
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!