So this answer is part of the story, given that the code is in Mathematica rather than C#, but certainly all of the maths (with perhaps one minor exception) should all be relatively easy to translate to any language.
The basic approach presented is to:
- Project the three points (A, B, C) onto the plane that those points lie in. It should have a normal AB x BC. This reduces the problem from three dimensions to two.
- Use your favourite technique for finding the center of the circle that passes through the three projected points.
- Unproject the center of the circle back into three dimensions.
- Use an appropriate spherical interpolation strategy (slerp is used in the sample, but I believe it would have been better to use quaternions).
The one caveat is that you need to work out which direction you are rotating in, I'm sure there are smarter ways, but with only two alternatives, rejection testing is sufficient. I'm using reduce
, but you'd probably need to do something slightly different to do this in C#
No guarantees that this is the most numerically stable or robust way to do this, or that there are any corner cases that have been missed.
(* Perpendicular vector in 2 dimensions *)
Perp2d[v_] := {-v[[2]], v[[1]]};
(* Spherical linear interpolation. From wikipedia \
http://en.wikipedia.org/wiki/Slerp *)
slerp[p0_, p1_, t_, rev_] :=
Module[{\[CapitalOmega], v},
\[CapitalOmega] = ArcCos[Dot[p0, p1]];
\[CapitalOmega] =
If[rev == 0, 2 Pi - \[CapitalOmega], \[CapitalOmega]];
v = (Sin[(1 - t) \[CapitalOmega]]/
Sin[\[CapitalOmega]]) p0 + (Sin[t \[CapitalOmega]]/
Sin[\[CapitalOmega]]) p1;
Return[v]
];
(* Based on the expressions from mathworld \
http://mathworld.wolfram.com/Line-LineIntersection.html *)
IntersectionLineLine[{x1_, y1_}, {x2_, y2_}, {x3_, y3_}, {x4_, y4_}] :=
Module[{x, y, A, B, C},
A = Det[{{x1, y1}, {x2, y2}}];
B = Det[{{x3, y3}, {x4, y4}}];
C = Det[{{x1 - x2, y1 - y2}, {x3 - x4, y3 - y4}}];
x = Det[{{A, x1 - x2}, {B, x3 - x4}}]/C;
y = Det[{{A, y1 - y2}, {B, y3 - y4}}]/C;
Return[{x, y}]
]
(* Based on Paul Bourke's Notes \
http://local.wasp.uwa.edu.au/~pbourke/geometry/circlefrom3/ *)
CircleFromThreePoints2D[v1_, v2_, v3_] :=
Module[{v12, v23, mid12, mid23, v12perp, v23perp, c, r},
v12 = v2 - v1;
v23 = v3 - v2;
mid12 = Mean[{v1, v2}];
mid23 = Mean[{v2, v3}];
c = IntersectionLineLine[
mid12, mid12 + Perp2d[v12],
mid23, mid23 + Perp2d[v23]
];
r = Norm[c - v1];
Assert[r == Norm[c - v2]];
Assert[r == Norm[c - v3]];
Return[{c, r}]
]
(* Projection from 3d to 2d *)
CircleFromThreePoints3D[v1_, v2_, v3_] :=
Module[{v12, v23, vnorm, b1, b2, va, vb, vc, xc, xr, yc, yr},
v12 = v2 - v1;
v23 = v3 - v2;
vnorm = Cross[v12, v23];
b1 = Normalize[v12];
b2 = Normalize[Cross[v12, vnorm]];
va = {0, 0};
vb = {Dot[v2, b1], Dot[v2, b2]};
vc = {Dot[v3, b1], Dot[v3, b2]};
{xc, xr} = CircleFromThreePoints2D[va, vb, vc];
yc = xc[[1]] b1 + xc[[2]] b2;
yr = Norm[v1 - yc];
Return[{yc, yr, b1, b2}]
]
v1 = {0, 0, 0};
v2 = {5, 3, 7};
v3 = {6, 4, 2};
(* calculate the center of the circle, radius, and basis vectors b1 \
and b2 *)
{yc, yr, b1, b2} = CircleFromThreePoints3D[v1, v2, v3];
(* calculate the path of motion, given an arbitrary direction *)
path = Function[{t, d},
yc + yr slerp[(v1 - yc)/yr, (v3 - yc)/yr, t, d]];
(* correct the direction of rotation if necessary *)
dirn = If[
TrueQ[Reduce[{path[t, 1] == v2, t >= 0 && t <= 1}, t] == False], 0,
1]
(* Plot Results *)
gr1 = ParametricPlot3D[path[t, dirn], {t, 0.0, 1.0}];
gr2 = ParametricPlot3D[Circle3d[b1, b2, yc, yr][t], {t, 0, 2 Pi}];
Show[
gr1,
Graphics3D[Line[{v1, v1 + b1}]],
Graphics3D[Line[{v1, v1 + b2}]],
Graphics3D[Sphere[v1, 0.1]],
Graphics3D[Sphere[v2, 0.1]],
Graphics3D[{Green, Sphere[v3, 0.1]}],
Graphics3D[Sphere[yc, 0.2]],
PlotRange -> Transpose[{yc - 1.2 yr, yc + 1.2 yr}]
]
Which looks something like this:
