I feel close. Hereâ€™s where Iâ€™m at:

##
GetCircle

```
-- given two points on a circle and a tangent to p1, returns the center of
-- the circle.
-- returns: success, center
local function GetCenter(p1: Vector3, tangent: Vector3, p2: Vector3): (boolean, Vector3)
local diff = p2 - p1
local norm = diff:Cross(tangent)
local len = norm.Magnitude
local avg = p1:Lerp(p2, 0.5) -- faster than (p1 + p2)/2
if len < 0.001 then
-- basically straight line
return false, avg
end
norm /= len
-- must solve these three equations where c = the center of the circle
--[[
(1) (p1 - c):Dot(tangent) = 0 <-- c->p1 must be perpendicular to the tangent
(2) (p2 - c):Dot(norm) = 0 <-- c->p2 must be perpendicular to the normal
(3) (avg - c):Dot(diff) = 0 <-- c->avg must be perpendicular to p1->p2 (perpendicular bisector)
so
A c = B
where
A = {
{tangent.X, tangent.Y, tangent.Z),
{norm.X, norm.Y, norm.Z),
(diff.X, diff.Y, diff.Z)
}
and
B = {
{p1:Dot(tangent)},
{p2:Dot(norm)},
{avg:Dot(diff)}
}
]]
-- using cframes to solve the system:
local transform = CFrame.new(
0, 0, 0, -- just care about the rotation matrix
tangent.X, tangent.Y, tangent.Z, -- (1)
norm.X, norm.Y, norm.Z, -- (2)
diff.X, diff.Y, diff.Z -- (3)
):Inverse()
local vec = Vector3.new(
p1:Dot(tangent),
p2:Dot(norm),
avg:Dot(diff)
)
return true, transform*vec
end
```

And if you want to try to test it you can throw this in a script and move the balls around:

##
Test Script

local function CreateBall(name, color, pos, shape)

shape = shape or Enum.PartType.Ball

local ball = Instance.new(â€śPartâ€ť)

ball.Name = name

ball.Shape = shape

ball.Massless = true

ball.Anchored = true

ball.BrickColor = color

ball.Size = Vector3.new(3, 3, 3)

ball.Position = pos

ball.Parent = workspace

return ball

end

local circlePart = CreateBall(â€śCircleâ€ť, BrickColor.new(â€śWhiteâ€ť), Vector3.new(), Enum.PartType.Cylinder)

local originPart = CreateBall(â€śOriginâ€ť, BrickColor.new(â€śReally redâ€ť), Vector3.new(0, 0, 0)) â€“ p1

local tangentPart = CreateBall(â€śTangentâ€ť, BrickColor.new(â€śBright yellowâ€ť), Vector3.new(0, 10, 10)) â€“ tangent dir relative to p1

local targetPart = CreateBall(â€śTargetâ€ť, BrickColor.new(â€śBright greenâ€ť), Vector3.new(10, 10, 0)) â€“ p2

â€“ given two points on a circle and a tangent to p1, returns the center of

â€“ the circle.

â€“ returns: success, center

local function GetCenter(p1: Vector3, tangent: Vector3, p2: Vector3): (boolean, Vector3)

local diff = p2 - p1

local norm = diff:Cross(tangent)

local len = norm.Magnitude

local avg = p1:Lerp(p2, 0.5) â€“ faster than (p1 + p2)/2

```
if len < 0.001 then
-- basically straight line
return false, avg
end
norm /= len
-- must solve these three equations where c = the center of the circle
--[[
(1) (p1 - c):Dot(tangent) = 0 <-- c->p1 must be perpendicular to the tangent
(2) (p2 - c):Dot(norm) = 0 <-- c->p2 must be perpendicular to the normal
(3) (avg - c):Dot(diff) = 0 <-- c->avg must be perpendicular to p1->p2 (perpendicular bisector)
so
A c = B
where
A = {
{tangent.X, tangent.Y, tangent.Z),
{norm.X, norm.Y, norm.Z),
(diff.X, diff.Y, diff.Z)
}
and
B = {
{p1:Dot(tangent)},
{p2:Dot(norm)},
{avg:Dot(diff)}
}
]]
-- using cframes to solve the system:
local transform = CFrame.new(
0, 0, 0, -- just care about the rotation matrix
tangent.X, tangent.Y, tangent.Z, -- (1)
norm.X, norm.Y, norm.Z, -- (2)
diff.X, diff.Y, diff.Z -- (3)
):Inverse()
local vec = Vector3.new(
p1:Dot(tangent),
p2:Dot(norm),
avg:Dot(diff)
)
return true, transform*vec
```

end

â€“ given a point on a circle, a tangent, and a center, stretches a given Cylinder Part to match that circle

â€“ note: (point-center) and tangent must be orthogonal

local function RenderCircle(part, point, tangent, center)

local diff = point - center

local radius = diff.Magnitude

local radius2 = radius*2

local forward = diff / radius;

local right = forward:Cross(tangent)

```
circlePart.Size = Vector3.new(1, radius2, radius2)
circlePart.CFrame = CFrame.fromMatrix(
center,
right,
tangent,
-forward
)
```

end

game:GetService(â€śRunServiceâ€ť).Heartbeat:Connect(function()

local origin = originPart.Position

local tangentDir = (tangentPart.Position - origin).Unit

local target = targetPart.Position

```
local straight, center = GetCenter(origin, tangentDir, target)
RenderCircle(circlePart, origin, tangentDir, center)
```

end)

Itâ€™s not quite working, though. The circle is aligned to the right plane at least, but the size and center are wayyyy off.

Current theory is that that matrix is still singular. Iâ€™ll need to figure that out, though.