前言
这是上一篇文章的详解版本,容我和大家详细地分享一下画线思路
一、前期准备
1.成员变量
// 背景网格的间隔
const float GRIDGAP = 0.1f;
// 记录起始点
CPoint startPoint;
CPoint endPoint;
// 判断绘图状态
bool isDrawing;
// 保存画好的线
int lineCount;
CPoint linePoints[1000][2];
2.鼠标响应事件
鼠标按下时,准备绘制当前线段。将isDrawing置为true,并记录起点位置。嗯……顺便把终点位置也放到那
void CMy22uCGv1View::OnLButtonDown(UINT nFlags, CPoint point)
{
isDrawing = true;
startPoint = point;
endPoint = point;
Invalidate(false);
COpenGLView::OnLButtonDown(nFlags, point);
}
鼠标移动时,只要在画图,就让当前线段的尾部跟着移动
void CMy22uCGv1View::OnMouseMove(UINT nFlags, CPoint point)
{
if (isDrawing)
{
endPoint = point;
Invalidate(false);
}
COpenGLView::OnMouseMove(nFlags, point);
}
鼠标抬起时,结束绘制,并保存当前线段
void CMy22uCGv1View::OnLButtonUp(UINT nFlags, CPoint point)
{
isDrawing = false;
endPoint = point;
linePoints[lineCount][0] = startPoint;
linePoints[lineCount][1] = endPoint;
lineCount++;
Invalidate(false);
COpenGLView::OnLButtonUp(nFlags, point);
}
二、正式画图
1. 调用Display()函数
我们所有的画图操作都会丢在这里面进行
void CMy22uCGv1View::Display()
{
// 清空屏幕
glClearColor(0.2f, 0.2f, 0.2f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 画网格
DrawGrids();
// 画线
DrawLines();
}
2. 画网格
总之先把背景的网格画出来
void CMy22uCGv1View::DrawGrids()
{
// 获取视口的宽高
CRect rc;
GetClientRect(rc);
float width = rc.right - rc.left;
float height = rc.bottom - rc.top;
// 计算画线数量
float wNum = (int)(width / height * 10) / 10.f;
// 调好颜色开画
// OpenGL坐标系见上一篇文章
glColor3f(0.5f, 0.5f, 0.5f);
glBegin(GL_LINES);
for (float i = -wNum; i < wNum + 0.0001f; i += GRIDGAP)
// 因为是float类型所以需要加上一个小值作误差
// GRIDGAP是之前定义的浮点型常量
{
// 竖线
glVertex2f(i, -1);
glVertex2f(i, 1);
}
for (float i = -1; i < 1.0001f; i += GRIDGAP)
{
// 横线
glVertex2f(-wNum, i);
glVertex2f(wNum, i);
}
glEnd();
}
画出来的效果就像这样:
3. 画线
用OpenGL的glvertex方法画线。注意要把当前正在画的线(isDrawing为true)和已经画好的线都画出来
void CMy22uCGv1View::DrawLines()
{
// 拖拽线
glColor3f(0.95f, 0.95f, 0.95f);
if (isDrawing)
{
glBegin(GL_LINES);
// 坐标转换
glVertex2f(ChangePos(startPoint.x, 1), ChangePos(startPoint.y, 2));
glVertex2f(ChangePos(endPoint.x, 1), ChangePos(endPoint.y, 2));
// 不转换就看不见
// glVertex2f(startPoint.x, startPoint.y);
// glVertex2f(endPoint.x, endPoint.y);
glEnd();
}
// 已画线
for (int i = 0; i < lineCount; i++)
{
float x0 = ChangePos(linePoints[i][0].x, 1);
float y0 = ChangePos(linePoints[i][0].y, 2);
float x1 = ChangePos(linePoints[i][1].x, 1);
float y1 = ChangePos(linePoints[i][1].y, 2);
// 把线画上
glBegin(GL_LINES);
glVertex2f(x0, y0);
glVertex2f(x1, y1);
glEnd();
// DDA算法画圆
DrawLine_DDA(x0, y0, x1, y1);
}
}
这里大的来了,它就是——坐标变换!
因为之前记录的startPoint和endPoint点都是屏幕坐标,随随便便就几十上百了,直接画会超出视口显示范围,导致啥也看不见,所以得先把它们转成视口坐标。这里是写了一个ChangePos()函数
float CMy22uCGv1View::ChangePos(float num, int mode)
// 用mode判断这是x坐标还是y坐标
{
float n = num;
CRect rc;
GetClientRect(rc);
float width = rc.right - rc.left;
float height = rc.top - rc.bottom;
if (mode == 1)
// n = n / width * 4.5376 - 2.2688;
// n = -(n / width * width / height * 2 - width / height);
n = (width - 2 * n) / height;
else
n = n / height * 2 + 1;
return n;
}
现在就能看到我们辛辛苦苦画的线了
4. DDA算法
最后是用DDA算法沿线画圆。关于算法本身的介绍可以看上篇文章里贴的链接
void CMy22uCGv1View::DrawLine_DDA(float x0, float y0, float x1, float y1)
{
float dx = x1 - x0;
float dy = y1 - y0;
float k = dy / dx;
bool flag = false;
if (k > 1 || k < -1) {
flag = true;
k = dx / dy;
Swap(x0, y0, x1, y1);
}
// 起点调整,把圆心放在网格交点上
float adjust = x0 >= 0 ? 1 : -1;
float x = int(x0 / GRIDGAP + adjust * GRIDGAP * 5) * GRIDGAP;
adjust = y0 >= 0 ? 1 : -1;
float y = int(y0 / GRIDGAP + adjust * GRIDGAP * 5) * GRIDGAP;
if (!flag)
DrawCircle(x, y);
else
DrawCircle(y, x);
if (x0 <= x1) // 往右
{
while (x < x1) {
x += GRIDGAP;
if (x > x1) {
// 终点调整
if (x1 < (x - 0.5f * GRIDGAP)) break;
}
y0 += k * GRIDGAP;
if (k >= 0) {
if (y0 >= y + GRIDGAP * 0.5f) {
y += GRIDGAP;
}
}
else {
if (y0 <= y - GRIDGAP * 0.5f) {
y -= GRIDGAP;
}
}
if (!flag)
DrawCircle(x, y);
else
DrawCircle(y, x);
}
}
else // 往左
{
while (x > x1) {
x -= GRIDGAP;
if (x < x1) {
// 终点调整
if (x1 > (x + 0.5f * GRIDGAP)) break;
}
y0 -= k * GRIDGAP;
if (k <= 0) {
if (y0 >= y + GRIDGAP * 0.5f) {
y += GRIDGAP;
}
}
else {
if (y0 <= y - GRIDGAP * 0.5f) {
y -= GRIDGAP;
}
}
if (!flag)
DrawCircle(x, y);
else
DrawCircle(y, x);
}
}
}
画圆的函数DrawCircle()长这样:
void CMy22uCGv1View::DrawCircle(float x, float y)
{
glBegin(GL_TRIANGLE_FAN);
for (int i = 0; i <= 360; i += 30) {
float p = (float)(i * 3.14 / 180);
glVertex2f(x + (float)sin(p) * GRIDGAP * 0.5f, y + (float)cos(p) * GRIDGAP * 0.5f);
}
glEnd();
}
交换坐标的函数Swap()长这样:
void CMy22uCGv1View::Swap(float &x0, float &x1, float &y0, float &y1)
{
float tmp = x0;
x0 = x1;
x1 = tmp;
tmp = y0;
y0 = y1;
y1 = tmp;
}
这下终于可以画出本文开头一样的效果了
总结
这就是我在MFC框架中用OpenGL画线的全过程了,希望对你有帮助~