在本文中,我们将看到如何将椭圆(或圆形)离散化为多边形。由于圆只是椭圆的一种特殊情况,我们将不单独讨论如何将圆离散化为多边形!
为什么将椭圆离散化为多边形?
离散化有几个应用程序,其中两个最重要的是:
- 渲染曲线:曲线不能直接在屏幕上渲染。首先需要将它们近似为多边形(在“圆形”和椭圆形的情况下)或链线段(在“贝塞尔曲线”和样条曲线的情况下),离散化可以达到这个目的! *
- 碰撞检测:虽然检查诸如Circle和Bezier曲线之类的曲线的交点很简单,但是诸如Ellipses之类的某些曲线却太复杂了,而精确地检查它们的交点是非常低效的!因此,它们首先被离散化为简单的形状,例如矩形,多边形等,可以有效地检测到它们的碰撞!
* While one can render curves pixel by pixel with Bresengham’s algorithm, it is often not the best practice! Since modern GPUs have become much more powerful they can render a series of the pixel without any bloat! Also drawing any curve pixel by pixel strips any possibility of batching and GPU “memorization”! And because of the way modern CPUs work trigonometric functions are no longer “computationally expensive” (atleast most of the time)!
概念验证:因此,将椭圆离散化的主要思想是将椭圆分解为表示多边形的顶点,从而可以自动计算多边形的段数,也可以由客户用户指定多边形的段数!在此示例中,我们不会自动计算细分数!
我们将使用C Borland Graphics API,但相同的原理可以应用于任何图形库! Linux用户可能想使用SDL libgraph替代Borland图形库!同样,我们将使用C++而不是C来使用STL提供的内置pair
数据结构(以及稍后会派上用场的函数重载)!
渲染多边形:
事实证明,Borland Graphics API实际上不像现代图形库那样具有多格式的多边形渲染函数!有drawPoly和fillPoly,但是多边形的表示对于某些人来说可能没有意义,并且也可能导致我们遇到一些指针问题。因此,我们将实现自己的多边形渲染函数!我们将多边形表示为代表多边形顶点的整数对的向量!这样做的主要好处是,向量总是知道其大小,这与数组在幕后只是其边界未知的指针不同。
无论如何,以下是我们在Borland图形库中渲染多边形的代码:-
#include
#include
using namespace std;
typedef pair vertex;
void polygon(vector& vertices)
{
for (int i = 0, n = vertices.size(); i < n; i++) {
vertex current = vertices[i], next;
next = vertices[(i == n - 1) ? 0 : i + 1];
int x1 = current.first, y1 = current.second;
int x2 = next.first, y2 = next.second;
line(x1, y1, x2, y2);
}
}
// Driver code
int main()
{
int gd = DETECT, gm;
// initialize graphics library
initgraph(&gd, &gm, "");
vector vertices;
vertices.push_back(vertex(340, 150));
vertices.push_back(vertex(220, 250));
vertices.push_back(vertex(340, 350));
polygon(vertices);
delay(5000);
}
输出:
将椭圆离散化为多边形:
现在我们可以渲染多边形了,我们准备将椭圆离散化为多边形了!
因此,离散化椭圆的关键是要有一个移动点以相等的间隔在椭圆上移动,并在每个这样的点(移动点经过的每个点)上创建一个顶点!为此,必须知道椭圆的参数形式为:
根据上述公式,以下是将椭圆离散化为多边形的代码段:-
#define TWO_PI 44 / 7.0f
vector discretizeEllipse(
int x, int y,
int a, int b,
int seg)
{
float angle_shift = TWO_PI / seg, phi = 0;
vector vertices;
for (int i = 0; i < seg; ++i) {
phi += angle_shift;
vertices
.push_back(
vertex(
x + a * cos(phi),
y + b * sin(phi)));
}
return vertices;
}
剩下要做的最后一件事就是重载该函数,这样就不需要最后一个参数了!我们可以将segments
设置为某个默认值,但我们希望根据椭圆的尺寸来计算它!所以这是第二个超载:
vector discretizeEllipse(
int x, int y,
int a, int b)
{
int segments
= max((int)floor(
sqrt(((a + b) / 2) * 20)),
8);
return discretizeEllipse(
x, y,
a, b,
segments);
}
结束本文,下面是完整的源代码:
#include
#include
#define TWO_PI 44 / 7.0f
typedef pair vertex;
void polygon(vector vertices)
{
for (int i = 0, n = vertices.size(); i < n; i++) {
vertex current = vertices[i], next;
next = vertices[(i == n - 1) ? 0 : i + 1];
int x1 = current.first, y1 = current.second;
int x2 = next.first, y2 = next.second;
line(x1, y1, x2, y2);
}
}
vector discretizeEllipse(
int x, int y, int a,
int b, int seg)
{
float angle_shift = TWO_PI / seg, phi = 0;
vector vertices;
for (int i = 0; i < seg; ++i) {
phi += angle_shift;
vertices.push_back(
vertex(
x + a * cos(phi),
y + b * sin(phi)));
}
return vertices;
}
vector discretizeEllipse(
int x, int y, int a, int b)
{
int segments
= max((int)floor(
sqrt(((a + b) / 2) * 20)),
8);
return discretizeEllipse(
x, y, a, b, segments);
}
int main()
{
int gd = DETECT, gm;
// initialize graphics library
initgraph(&gd, &gm, "");
polygon(discretizeEllipse(320, 240, 200, 100));
polygon(discretizeEllipse(320, 240, 200, 100, 8));
delay(5000);
}
输出: