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.