Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

As many people have noted, the 2D graphics primitive Circle doesn't work in a Graphics3D environment. Several solutions to this problem have been proposed, both on this site and on StackOverflow.

They all have the disadvantage that they result in either rather ugly circles or highly inefficient ones because these circles were generated by a polygon with several hundreds of facets, making interactive graphics incredibly slow. Other alternatives involve the use of ParametricPlot which doesn't generate efficient graphics either or yield a primitive that can't be used with GeometricTransformation.

I would like to have a more elegant solution that creates a smooth circular arc in 3D without requiring zillions of coordinates. The resulting arc should be usable in combination with Tube and can be used with GeometricTransformation.

share|improve this question
1  
You know about Piegl and Tiller's book on NURBS, I presume? Their chapter on conic arcs is dandy. Alternatively, see their earlier article, which is more focused on NURBS circle arcs. Their example for drawing a full circle with NURBS is also in the docs for BSplineCurve[]. –  J. M. Sep 23 '12 at 1:24
1  
@J.M. No, I wasn't aware of those guys, but thanks for the reference. I knew the example from the doc page and the Wikipedia lemma I linked to, of course, but that actually gave no clue about the extension to a general arc, which I needed to answer the annular disk question. I used part of this page. I see that your ref derives the weight that I got from there in eq 7.33. –  Sjoerd C. de Vries Sep 23 '12 at 6:12
    
You can actually use the primitive from ParametricPlot with GeometricTransformation, see example. And, you can also replace Line with Tube and it works. –  VLC Sep 28 '12 at 10:57
1  
@VLC I wasn't saying you can't use Line with Tube, I was saying that making an arc with a few hundred Line segments is inefficient. And you can't use Tube in combination with ParametricPlot. –  Sjoerd C. de Vries Sep 28 '12 at 11:38

2 Answers 2

up vote 24 down vote accepted

In principle, Non-uniform rational B-splines (NURBS) can be used to represent conic sections. The difficulty is finding the correct set of control points and knot weights. The following function does this.


EDIT : Better handling of cases where end angle < start angle


ClearAll[splineCircle];
splineCircle[m_List, r_, angles_List: {0, 2 \[Pi]}] :=
 Module[{seg, \[Phi], start, end, pts, w, k},
   {start, end} = Mod[angles // N, 2 \[Pi]];
   If[ end <= start, end += 2 \[Pi]];
   seg = Quotient[end - start // N, \[Pi]/2];
   \[Phi] = Mod[end - start // N, \[Pi]/2];
   If[seg == 4, seg = 3; \[Phi] = \[Pi]/2];
   pts = r RotationMatrix[start ].# & /@ 
     Join[Take[{{1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1,0}, {-1, -1}, {0, -1}}, 2 seg + 1], 
      RotationMatrix[seg \[Pi]/2 ].# & /@ {{1, Tan[\[Phi]/2]}, {Cos[ \[Phi]], Sin[ \[Phi]]}}];
   If[Length[m] == 2, 
    pts = m + # & /@ pts, 
    pts = m + # & /@ Transpose[Append[Transpose[pts], ConstantArray[0, Length[pts]]]]
   ];
   w = Join[
        Take[{1, 1/Sqrt[2], 1, 1/Sqrt[2], 1, 1/Sqrt[2], 1}, 2 seg + 1], 
        {Cos[\[Phi]/2 ], 1}
       ];
   k = Join[{0, 0, 0}, Riffle[#, #] &@Range[seg + 1], {seg + 1}];
   BSplineCurve[pts, SplineDegree -> 2, SplineKnots -> k, SplineWeights -> w]
 ] /; Length[m] == 2 || Length[m] == 3

This looks rather complex, and it is. However, the output (the only thing that ends up in the final graphics) is clean and simple:

splineCircle[{0, 0}, 1, {0, 3/2 \[Pi]}]

Mathematica graphics

Just a single BSplineCurve with a few control points.

It can be used both in 2D and 3D Graphics (the dimensionality of the center point location is used to select this):

DynamicModule[{sc},
 Manipulate[
  Graphics[
    {FaceForm[], EdgeForm[Black], 
     Rectangle[{-1, -1}, {1, 1}], Circle[], 
      {Thickness[0.02], Blue, 
       sc = splineCircle[m, r, {start Degree, end Degree}]
      }, 
      Green, Line[sc[[1]]], Red, PointSize[0.02], Point[sc[[1]]]
    }
  ],
  {{m, {0, 0}}, {-1, -1}, {1, 1}},
  {{r, 1}, 0.5, 2},
  {{start, 45}, 0, 360},
  {{end, 180}, 0, 360}
  ]
 ] 

Mathematica graphics

Manipulate[
 Graphics3D[{FaceForm[], EdgeForm[Black], 
   Cuboid[{-1, -1, -1}, {1, 1, 1}], Blue, 
   sc = splineCircle[{x, y, z}, r, {start Degree, end Degree}], Green,
    Line[sc[[1]]], Red, PointSize[0.02], Point[sc[[1]]]}, 
  Boxed -> False],
 {{x, 0}, -1, 1},
 {{y, 0}, -1, 1},
 {{z, 0}, -1, 1},
 {{r, 1}, 0.5, 2},
 {{start, 45}, 0, 360},
 {{end, 180}, 0, 360}
 ]

Mathematica graphics

With Tube and various transformation functions:

Graphics3D[
  Table[
   {
    Hue@Random[],
    GeometricTransformation[
     Tube[splineCircle[{0, 0, 0}, RandomReal[{0.5, 4}], 
       RandomReal[{\[Pi]/2, 2 \[Pi]}, 2]], RandomReal[{0.2, 1}]], 
     TranslationTransform[RandomReal[{-10, 10}, 3]].RotationTransform[
       RandomReal[{0, 2 \[Pi]}], {0, 0, 1}].RotationTransform[
       RandomReal[{0, 2 \[Pi]}], {0, 1, 0}]]
    },
   {50}
   ], Boxed -> False
  ]

enter image description here


Additional uses

I used this code to make the partial disk with annular hole asked for in this question.

share|improve this answer
    
Excellent!!! Looks like a copy-paste error in the first Manipulate; the rectangle and the circle are fixed. Changing those to Rectangle[m + {-r, -r}, m + {r, r}], Circle[m, r] and setting PlotRange->... works beautifully.(++1) –  kguler Sep 22 '12 at 23:30
    
@kguler That wasn't an error. I used the square and circle as a reference. If all is well, the NURBS circle should perfectly cover the fixed circle. I made them fixed so that you can see the effect of manipulating the centre of the NURBS circle. –  Sjoerd C. de Vries Sep 22 '12 at 23:36
    
I see.. Thank you for a great question and answer btw. –  kguler Sep 22 '12 at 23:43
    
Sign@{Cos@#, Sin@#} & /@ (Pi/4 Range[0, 6]) :) –  belisarius Sep 22 '12 at 23:54
1  
@Reb.Cabin You might want to check this question. –  Sjoerd C. de Vries Sep 25 '12 at 21:29

Might as well... what follows is a routine that isn't as general as the routine Sjoerd gave, but gives simpler results in some cases. This is based on work by Piegl and Tiller (see their nice book as well).

arc[center_?VectorQ, {start_?VectorQ, end_?VectorQ}] := Module[{ang, co, r},
  ang = VectorAngle[start - center, end - center];
  co = Cos[ang/2]; r = EuclideanDistance[center, start];
  BSplineCurve[{start, center + r/co Normalize[(start + end)/2 - center], end}, 
   SplineDegree -> 2, SplineKnots -> {0, 0, 0, 1, 1, 1}, SplineWeights -> {1, co, 1}]]

For example:

{Graphics[arc[{0, 0}, {{1, 1}, {-1, 1}}]],
 Graphics3D[arc[{0, 0, 0}, {{1, 1, 1}, {-1, 1, 1}}]]} // GraphicsRow

arc[] example

This routine works as long as the angle determined by the arc lies in the open interval $(0,\pi)$ (an inherent limitation of the simple method), and that EuclideanDistance[center, start] == EuclideanDistance[center, end] (otherwise, it draws an elliptical arc). For reflex angles (that is, angles in the interval $(\pi,2\pi)$), you will have to stitch together two of these arc[]s properly.

(A little note: though Piegl and Tiller show in their work that one can use negative weights to generate an arc corresponding to a reflex angle, BSplineCurve[] handles negative weights poorly:

Graphics[BSplineCurve[{{-1/Sqrt[2], 1/Sqrt[2]}, {0, Sqrt[2]}, {1/Sqrt[2], 1/Sqrt[2]}},
 SplineDegree -> 2, SplineKnots -> {0, 0, 0, 1, 1, 1}, SplineWeights -> {1, -1/Sqrt[2], 1}], 
 PlotRange -> {{-1, 1}, {-1, 1}}]

bad rendering of BSplineCurve[]

but maybe it might get fixed in a future version of Mathematica.

)

Finally, here's how to render a unit semicircle with BSplineCurve[] (the generalization to the three-dimensional case is left to the reader):

Graphics[BSplineCurve[{{1, 0}, {1, 1}, {-1, 1}, {-1, 0}}, 
  SplineDegree -> 2, SplineKnots -> {0, 0, 0, 1/2, 1, 1, 1}, 
  SplineWeights -> {1, 1/2, 1/2, 1}]]

Again, see Piegl and Tiller's work if you want to learn more about these things.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.