在本文中,我们将看到如何将椭圆(或圆)离散为多边形。由于圆只是椭圆的一个特例,我们不会单独讨论如何将圆离散化为多边形!
为什么将椭圆离散化为多边形?
离散化有多种应用,其中最重要的两个是:
- 渲染曲线:不能直接在屏幕上渲染曲线。它们首先需要近似为多边形(在圆形和椭圆的情况下)或链线段(在贝塞尔曲线和样条的情况下)并且离散化服务于这个目的! *
- 碰撞检测:虽然检查圆和贝塞尔曲线等曲线的交点很简单,但像椭圆这样的曲线太复杂了,精确地检查它们的交点效率非常低!因此,它们首先被离散化为简单的形状,如矩形、多边形等,可以有效地检测碰撞!
* 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);
}
输出: