You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
338 lines
20 KiB
Plaintext
338 lines
20 KiB
Plaintext
<!DOCTYPE shtml PUBLIC "-//W3C//DTD shtml 4.01 Transitional//EN" "http://www.w3.org/TR/shtml4/loose.dtd">
|
|
<html><head>
|
|
|
|
|
|
|
|
<title>An Efficient Way to Draw Approximate Circles in OpenGL - SiegeLord's Abode</title>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
<link rel="stylesheet" type="text/css" href="circle_draw-Dateien/style.css">
|
|
<link rel="SHORTCUT ICON" href="http://slabode.exofire.net/images/sl.ico">
|
|
</head><body>
|
|
|
|
<div class="button_how_pre"></div><div class="button_up_how_pre"></div><div class="button_back_how_pre"></div><div class="button_home_how_pre"></div>
|
|
|
|
<p class="title_image"><img src="circle_draw-Dateien/title_computer.jpg"></p>
|
|
|
|
<hr color="#6699ff">
|
|
|
|
<div class="link_bar">
|
|
<a class="link_bar" href="http://slabode.exofire.net/index.shtml">Home</a>
|
|
<a class="link_bar" href="http://slabode.exofire.net/progress_reports.shtml">Progress</a>
|
|
<a class="link_bar" href="http://slabode.exofire.net/computer_programming.shtml">Computers</a>
|
|
<a class="link_bar" href="http://slabode.exofire.net/various_art.shtml">Various Art</a>
|
|
<a class="link_bar" href="http://slabode.exofire.net/building.shtml">Building</a>
|
|
<a class="link_bar" href="http://slabode.exofire.net/about.shtml">About</a>
|
|
</div>
|
|
|
|
|
|
<hr color="#6699ff">
|
|
|
|
<h1>An Efficient Way to Draw Approximate Circles in OpenGL</h1>
|
|
|
|
<br>
|
|
|
|
<h2>Introduction</h2>
|
|
<p>OpenGL is well known to not posses any
|
|
method to rasterize non-straight curves. As such, whenever one is
|
|
required to draw a curve, one has to either rasterize it himself
|
|
(through GL_POINTS) which is slow, or approximate it using a number
|
|
of line segments by drawing a many sided regular polygon. The latter method shall be explored here for
|
|
drawing circles.
|
|
</p>
|
|
<p>It is a well known fact that a regular
|
|
polygon with a large number of sides looks approximately like a
|
|
circle when rasterized on the screen. As such, when given a challenge
|
|
to draw a circle in OpenGL, people generally come up with something
|
|
like this:
|
|
</p>
|
|
<hr color="#6699ff">
|
|
<p class="code">
|
|
|
|
</p><pre><span class="type">void</span> DrawCircle<span class="operator">(</span><span class="type">float</span> cx<span class="operator">,</span><span class="type"> float</span> cy<span class="operator">,</span><span class="type"> float</span> r<span class="operator">,</span><span class="type"> int</span> num_segments<span class="operator">)
|
|
{</span>
|
|
glBegin<span class="operator">(</span>GL_LINE_LOOP<span class="operator">);</span><span class="flow">
|
|
for</span><span class="operator">(</span><span class="type">int</span> ii<span class="operator"> =</span><span class="int"> 0</span><span class="operator">;</span> ii<span class="operator"> <</span> num_segments<span class="operator">;</span> ii<span class="operator">++)
|
|
{</span><span class="type">
|
|
float</span> theta<span class="operator"> =</span><span class="float"> 2.0f</span><span class="operator"> *</span><span class="float"> 3.1415926f</span><span class="operator"> *</span><span class="type"> float</span><span class="operator">(</span>ii<span class="operator">) /</span><span class="type"> float</span><span class="operator">(</span>num_segments<span class="operator">);</span><span class="comment">//get the current angle
|
|
|
|
</span><span class="type"> float</span> x<span class="operator"> =</span> r<span class="operator"> *</span> cosf<span class="operator">(</span>theta<span class="operator">);</span><span class="comment">//calculate the x component
|
|
</span><span class="type"> float</span> y<span class="operator"> =</span> r<span class="operator"> *</span> sinf<span class="operator">(</span>theta<span class="operator">);</span><span class="comment">//calculate the y component
|
|
|
|
</span> glVertex2f<span class="operator">(</span>x<span class="operator"> +</span> cx<span class="operator">,</span> y<span class="operator"> +</span> cy<span class="operator">);</span><span class="comment">//output vertex
|
|
|
|
</span><span class="operator"> }</span>
|
|
glEnd<span class="operator">();
|
|
}</span>
|
|
</pre>
|
|
|
|
<p></p>
|
|
<hr color="#6699ff">
|
|
|
|
<p>There are some simple optimizations
|
|
that one can do, but nonetheless the general algorithm is as stated.
|
|
This is very bad algorithm however, since for every point we have to
|
|
perform expensive calls to the trigonometric functions. I propose a
|
|
method that requires only the basic arithmetic operations for every
|
|
point to obtain the same result.
|
|
</p>
|
|
<h2>The Algorithm</h2>
|
|
<p>The inspiration came to me from
|
|
physics. As we know, when an object moves in a circle it does so
|
|
entirely because of a constant centripetal acceleration acting on it.
|
|
When this is the case the object moves at a constant radial speed.
|
|
Note that that means that we have two constant length terms (radial
|
|
speed and centripetal acceleration) which only change direction, but
|
|
not length. Since direction is easy to compute, it is just a vector,
|
|
we are in business.
|
|
</p>
|
|
|
|
<p class="title_image"><img src="circle_draw-Dateien/circle_diag.png"></p>
|
|
|
|
<p>Here is the general motion of the point
|
|
whose position we use to calculate the position of the vertices of
|
|
our circle. During each calculation, we move the point tangentially
|
|
to the circle for a fixed distance (segments AK and BL) and then back
|
|
towards the centre of the circle for a fixed distance (segments KB
|
|
and LC). At this point we store the location of our point (perhaps
|
|
through a call to glVertex) and then repeat the process. Our problem,
|
|
thus, is to calculate the magnitude of the tangential motion, the
|
|
direction of the tangential motion, the magnitude of the radial
|
|
motion and the direction of the radial motion.
|
|
</p>
|
|
<p>We are helped in this task by the
|
|
following vectors that we know. At the beginning of our calculation
|
|
we know the vector AO. We can easily turn this vector 90 degrees so
|
|
it becomes tangent to the circle by flipping the coordinates, while
|
|
negating one of them. Now, we have a vector that is tangent to the
|
|
circle, but still the length of the radius. To make be the length of
|
|
AK, we multiply it by a factor, which is simply the tangent of the
|
|
theta. This factor is the same for every calculation, so it can be
|
|
precalculated before entering the loop.
|
|
</p>
|
|
<p>Now, we add the tangential vector to
|
|
the vector AO and get the vector KO. We need to get the vector BO
|
|
from that. Again, we can simply multiply it by a factor which turns
|
|
out to be the cosine of theta. Again, this factor is constant and can
|
|
be precalculated. At this point we are back where we started, so we
|
|
can just repeat this process again, and continue to do so until we
|
|
fill the entire circle. Thus, here is the code for this
|
|
implementation:
|
|
</p>
|
|
<hr color="#6699ff">
|
|
<p class="code">
|
|
</p><pre><span class="type">void</span> DrawCircle<span class="operator">(</span><span class="type">float</span> cx<span class="operator">,</span><span class="type"> float</span> cy<span class="operator">,</span><span class="type"> float</span> r<span class="operator">,</span><span class="type"> int</span> num_segments<span class="operator">)
|
|
{</span><span class="type">
|
|
float</span> theta<span class="operator"> =</span><span class="int"> 2</span><span class="operator"> *</span><span class="float"> 3.1415926</span><span class="operator"> /</span><span class="type"> float</span><span class="operator">(</span>num_segments<span class="operator">);</span><span class="type">
|
|
float</span> tangetial_factor<span class="operator"> =</span> tanf<span class="operator">(</span>theta<span class="operator">);</span><span class="comment">//calculate the tangential factor
|
|
|
|
</span><span class="type"> float</span> radial_factor<span class="operator"> =</span> cosf<span class="operator">(</span>theta<span class="operator">);</span><span class="comment">//calculate the radial factor
|
|
</span><span class="type">
|
|
float</span> x<span class="operator"> =</span> r<span class="operator">;</span><span class="comment">//we start at angle = 0
|
|
|
|
</span><span class="type"> float</span> y<span class="operator"> =</span><span class="int"> 0</span><span class="operator">;</span>
|
|
|
|
glBegin<span class="operator">(</span>GL_LINE_LOOP<span class="operator">);</span><span class="flow">
|
|
for</span><span class="operator">(</span><span class="type">int</span> ii<span class="operator"> =</span><span class="int"> 0</span><span class="operator">;</span> ii<span class="operator"> <</span> num_segments<span class="operator">;</span> ii<span class="operator">++)
|
|
{</span>
|
|
glVertex2f<span class="operator">(</span>x<span class="operator"> +</span> cx<span class="operator">,</span> y<span class="operator"> +</span> cy<span class="operator">);</span><span class="comment">//output vertex
|
|
|
|
//calculate the tangential vector
|
|
//remember, the radial vector is (x, y)
|
|
//to get the tangential vector we flip those coordinates and negate one of them
|
|
|
|
</span><span class="type"> float</span> tx<span class="operator"> = -</span>y<span class="operator">;</span><span class="type">
|
|
float</span> ty<span class="operator"> =</span> x<span class="operator">;</span><span class="comment">
|
|
|
|
//add the tangential vector
|
|
|
|
</span> x<span class="operator"> +=</span> tx<span class="operator"> *</span> tangetial_factor<span class="operator">;</span>
|
|
y<span class="operator"> +=</span> ty<span class="operator"> *</span> tangetial_factor<span class="operator">;</span><span class="comment">
|
|
|
|
//correct using the radial factor
|
|
|
|
</span> x<span class="operator"> *=</span> radial_factor<span class="operator">;</span>
|
|
y<span class="operator"> *=</span> radial_factor<span class="operator">;
|
|
}</span>
|
|
glEnd<span class="operator">();
|
|
}</span></pre>
|
|
|
|
<p></p>
|
|
<hr color="#6699ff">
|
|
|
|
The algorithm can be modified to draw arcs instead of circles. We first calculate the starting point from the starting angle.
|
|
Then we calculate the theta parameter based not on the full circle as before, but rather the angular span of the arc.
|
|
<br>
|
|
Here is one way to do it:
|
|
|
|
<hr color="#6699ff">
|
|
<p class="code">
|
|
|
|
</p><pre><span class="type">void</span> DrawArc<span class="operator">(</span><span class="type">float</span> cx<span class="operator">,</span><span class="type"> float</span> cy<span class="operator">,</span><span class="type"> float</span> r<span class="operator">,</span><span class="type"> float</span> start_angle<span class="operator">,</span><span class="type"> float</span> arc_angle<span class="operator">,</span><span class="type"> int</span> num_segments<span class="operator">)
|
|
{</span><span class="type">
|
|
float</span> theta<span class="operator"> =</span> arc_angle<span class="operator"> /</span><span class="type"> float</span><span class="operator">(</span>num_segments<span class="operator"> -</span><span class="int"> 1</span><span class="operator">);</span><span class="comment">//theta is now calculated from the arc angle instead, the - 1 bit comes from the fact that the arc is open
|
|
|
|
</span><span class="type"> float</span> tangetial_factor<span class="operator"> =</span> tanf<span class="operator">(</span>theta<span class="operator">);</span><span class="type">
|
|
|
|
float</span> radial_factor<span class="operator"> =</span> cosf<span class="operator">(</span>theta<span class="operator">);</span><span class="type">
|
|
|
|
|
|
float</span> x<span class="operator"> =</span> r<span class="operator"> *</span> cosf<span class="operator">(</span>start_angle<span class="operator">);</span><span class="comment">//we now start at the start angle
|
|
</span><span class="type"> float</span> y<span class="operator"> =</span> r<span class="operator"> *</span> sinf<span class="operator">(</span>start_angle<span class="operator">);</span>
|
|
|
|
glBegin<span class="operator">(</span>GL_LINE_STRIP<span class="operator">);</span><span class="comment">//since the arc is not a closed curve, this is a strip now
|
|
</span><span class="flow"> for</span><span class="operator">(</span><span class="type">int</span> ii<span class="operator"> =</span><span class="int"> 0</span><span class="operator">;</span> ii<span class="operator"> <</span> num_segments<span class="operator">;</span> ii<span class="operator">++)
|
|
{</span>
|
|
glVertex2f<span class="operator">(</span>x<span class="operator"> +</span> cx<span class="operator">,</span> y<span class="operator"> +</span> cy<span class="operator">);</span><span class="type">
|
|
|
|
float</span> tx<span class="operator"> = -</span>y<span class="operator">;</span><span class="type">
|
|
float</span> ty<span class="operator"> =</span> x<span class="operator">;</span>
|
|
|
|
x<span class="operator"> +=</span> tx<span class="operator"> *</span> tangetial_factor<span class="operator">;</span>
|
|
y<span class="operator"> +=</span> ty<span class="operator"> *</span> tangetial_factor<span class="operator">;</span>
|
|
|
|
x<span class="operator"> *=</span> radial_factor<span class="operator">;</span>
|
|
y<span class="operator"> *=</span> radial_factor<span class="operator">;
|
|
}</span>
|
|
glEnd<span class="operator">();
|
|
}</span></pre>
|
|
|
|
|
|
<p></p>
|
|
<hr color="#6699ff">
|
|
|
|
<h2>Other Considerations</h2>
|
|
<p></p>
|
|
<p>Both this and the other algorithms can
|
|
be greately sped up by using a vertex array, that is then passed as
|
|
an argument to the glDrawArrays, I leave it up to the reader to
|
|
implement this trivial optimisation.
|
|
</p>
|
|
<p>You will note that to draw the circle
|
|
you need to pass the number of segments to draw. Is it possible to
|
|
create a function to give a good estimate of that number so that you
|
|
get a reasonably smooth circle? Yes there is. One way to do this is
|
|
to limit to the magnitude of the KB segment in the diagram above. The
|
|
length of that segment is easily derived to be:
|
|
</p>
|
|
<hr color="#6699ff">
|
|
<p class="code">
|
|
|
|
</p><pre><span class="operator">(</span><span class="int">1</span><span class="operator"> -</span> cos<span class="operator">(</span>theta<span class="operator">)) *</span> r</pre>
|
|
|
|
<p></p>
|
|
<hr color="#6699ff">
|
|
<p>We set that to some small value and
|
|
solve for theta. I find that setting it to 0.25 (i.e. a quarter of a
|
|
pixel) generally produces acceptable results. You can vary that
|
|
number as appropriate. The actual formula that you get involves inverse trigonometric functions,
|
|
but I found that the following approximation matches their prediction quite well:
|
|
</p>
|
|
<hr color="#6699ff">
|
|
<p class="code">
|
|
|
|
</p><pre><span class="type">int</span> GetNumCircleSegments<span class="operator">(</span><span class="type">float</span> r<span class="operator">)
|
|
{</span><span class="flow">
|
|
return</span><span class="int"> 10</span><span class="operator"> *</span> sqrtf<span class="operator">(</span>r<span class="operator">);</span><span class="comment">//change the 10 to a smaller/bigger number as needed
|
|
</span><span class="operator">}</span></pre>
|
|
|
|
<p></p>
|
|
<hr color="#6699ff">
|
|
|
|
<p>Lastly, this code can be adapted to
|
|
draw ellipses as well. Simply
|
|
scale the x and y variables by appropriate factors, either using a
|
|
matrix, or perhaps by modifying the algorithm itself. This extension is
|
|
trivial.
|
|
</p>
|
|
<h2>Conclusion</h2>
|
|
<p></p>
|
|
<p>This algorithm is significantly faster
|
|
than any other circle approximation algorithm I have personally
|
|
encountered on the web. I believe that if one chooses to draw a
|
|
circle using OpenGL and using a polygonal approximation then my
|
|
method is optimal. In theory, with a large number of segments the
|
|
true circle, i.e. compatible with the diamond-exit specification, can
|
|
be drawn, although at that point it may be preferable to rasterize
|
|
the circle using GL_POINTS manually.
|
|
</p>
|
|
<h2>Recasting the Algorithm as a Repeated Rotation</h2>
|
|
<p>
|
|
Someone suggested to me that this algorithm can be recast as a simple repeated rotation.
|
|
That is indeed correct. All you have to do is multiply the whole algorithm by a few trig functions,
|
|
and obtain something that just looks like the repeated application of the rotation matrix. I leave it
|
|
as the excersize to the reader to see how you can transform the above algorithm into this one:
|
|
</p>
|
|
|
|
<p class="code">
|
|
</p><pre><span class="type">void</span> DrawCircle<span class="operator">(</span><span class="type">float</span> cx<span class="operator">,</span><span class="type"> float</span> cy<span class="operator">,</span><span class="type"> float</span> r<span class="operator">,</span><span class="type"> int</span> num_segments<span class="operator">)
|
|
{</span><span class="type">
|
|
float</span> theta<span class="operator"> =</span><span class="int"> 2</span><span class="operator"> *</span><span class="float"> 3.1415926</span><span class="operator"> /</span><span class="type"> float</span><span class="operator">(</span>num_segments<span class="operator">);</span><span class="type">
|
|
float</span> c<span class="operator"> =</span> cosf<span class="operator">(</span>theta<span class="operator">);</span><span class="comment">//precalculate the sine and cosine
|
|
</span><span class="type"> float</span> s<span class="operator"> =</span> sinf<span class="operator">(</span>theta<span class="operator">);</span><span class="type">
|
|
float</span> t<span class="operator">;</span><span class="type">
|
|
|
|
float</span> x<span class="operator"> =</span> r<span class="operator">;</span><span class="comment">//we start at angle = 0
|
|
</span><span class="type"> float</span> y<span class="operator"> =</span><span class="int"> 0</span><span class="operator">;</span>
|
|
|
|
glBegin<span class="operator">(</span>GL_LINE_LOOP<span class="operator">);</span><span class="flow">
|
|
for</span><span class="operator">(</span><span class="type">int</span> ii<span class="operator"> =</span><span class="int"> 0</span><span class="operator">;</span> ii<span class="operator"> <</span> num_segments<span class="operator">;</span> ii<span class="operator">++)
|
|
{</span>
|
|
glVertex2f<span class="operator">(</span>x<span class="operator"> +</span> cx<span class="operator">,</span> y<span class="operator"> +</span> cy<span class="operator">);</span><span class="comment">//output vertex
|
|
|
|
//apply the rotation matrix
|
|
</span> t<span class="operator"> =</span> x<span class="operator">;</span>
|
|
x<span class="operator"> =</span> c<span class="operator"> *</span> x<span class="operator"> -</span> s<span class="operator"> *</span> y<span class="operator">;</span>
|
|
y<span class="operator"> =</span> s<span class="operator"> *</span> t<span class="operator"> +</span> c<span class="operator"> *</span> y<span class="operator">;
|
|
}</span>
|
|
glEnd<span class="operator">();
|
|
}</span></pre>
|
|
<p></p>
|
|
|
|
<p>
|
|
The advantages of this version of the algorithm is that it is easier to understand
|
|
(repeated rotation instead of the physics inspired mumbo jumbo) and at a glance it seems to be
|
|
a little more numerically stable. I use this version in my current projects, but in principle its your
|
|
choice which one you like more.
|
|
</p>
|
|
<p>This code is released as public domain,
|
|
but I will appreciate an email if you do use it somewhere. Feel free
|
|
to email me for clarifications of the algorithm and to report bugs
|
|
and whatnot.</p>
|
|
|
|
<br>
|
|
|
|
<hr color="#6699ff">
|
|
|
|
<div class="bottom_bar">
|
|
<a class="back_button" href="http://slabode.exofire.net/computer_programming.shtml"></a>
|
|
<a class="home_button" href="http://slabode.exofire.net/index.shtml"></a>
|
|
<a class="up_button" href="#"></a>
|
|
</div>
|
|
|
|
<hr color="#6699ff">
|
|
|
|
<p class="copy_note">Original content Copyright SiegeLord's Abode 2006-2009. All rights reserved.
|
|
|
|
<a href="http://my.statcounter.com/project/standard/stats.php?project_id=4033591&guest=1">♫</a>
|
|
|
|
<!-- Start of StatCounter Code -->
|
|
<script type="text/javascript">
|
|
var sc_project=4033591;
|
|
var sc_invisible=1;
|
|
var sc_partition=31;
|
|
var sc_click_stat=1;
|
|
var sc_security="c1dd1a20";
|
|
var sc_text=1;
|
|
</script>
|
|
|
|
<script type="text/javascript" src="circle_draw-Dateien/counter.js"></script><noscript><div
|
|
class="statcounter"><a title="web analytics"
|
|
href="http://www.statcounter.com/" target="_blank"><img
|
|
class="statcounter"
|
|
src="http://c.statcounter.com/4033591/0/c1dd1a20/1/" alt="web
|
|
analytics" ></a></div></noscript>
|
|
<!-- End of StatCounter Code -->
|
|
|
|
|
|
</p>
|
|
</body></html> |