So, this answer is part of the story, given that the code is in Mathematica, and not in C #, but, of course, all mathematical data (perhaps one small exception) should be relatively easy to translate into any language.
The main approach presented:
- Project three points ( A , B , C ) onto the plane where these points are. It should have normal AB x BC . This reduces the problem from three dimensions to two.
- Use your favorite method to find the center of a circle that passes through three projected points.
- Do not project the center of the circle back into three dimensions.
- Use the appropriate spherical interpolation strategy (slerp is used in the sample, but I think it would be better to use quaternions).
One caveat is that you need to decide which direction you are heading in, I'm sure there are more reasonable ways, but with only two alternatives, testing rejection is sufficient. I use reduce
, but you probably need to do something a little different in order to do this in C #
There is no guarantee that this is the most numerically stable or reliable way to do this, or that there are any corner cases that were 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 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: