MFC绘制键盘控制动画
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/6766ddd02dba7d38df7cc2b54fcdd4e8.gif)
参考孔令德老师的《计算几何算法与实现》
使用双缓冲机制绘制中心位于窗口客户区中心的立方体线框模型,可以使用键盘上的方向键旋转立方体。
1、创建MFC单文档项目;
2、添加三维点类CP3
#pragma once
class CP3
{
public:
CP3(void);
~CP3(void);
CP3(double x,double y,double z);
public:
double x;
double y;
double z;
double w;
};
#include "StdAfx.h"
#include "P3.h"
CP3::CP3(void)
{
w=1;
}
CP3::~CP3(void)
{
}
CP3::CP3(double x,double y,double z)
{
this->x=x;
this->y=y;
this->z=z;
}
3、添加几何变换算法类CTransform3,实现物体顶点的平移和旋转变换
#pragma once
#include"P3.h"
#include"math.h"
#define PI 3.1415926
class CTransform3
{
public:
CTransform3(void);
~CTransform3(void);
void SetMatrix(CP3*P,int ptNumber);
void Identity();//初始变换单位矩阵
void Translate(double tx,double ty,double tz);//平移变换矩阵
void RotateX(double beta);//绕x轴旋转变换矩阵
void RotateY(double beta);//绕x轴旋转变换矩阵
void RotateZ(double beta);//绕x轴旋转变换矩阵
void MultiplyMatrix();//变换矩阵左乘顶点列阵
public:
double T[4][4];//三维变换矩阵
CP3*P;//动态数组定义顶点列阵
int ptNumber;//顶点个数
};
#include "StdAfx.h"
#include "Transform3.h"
CTransform3::CTransform3(void)
{
}
CTransform3::~CTransform3(void)
{
}
void CTransform3::SetMatrix(CP3*P,int ptNumber)
{
this->P=P;
this->ptNumber=ptNumber;
}
void CTransform3::Identity()
{
T[0][0]=1.0;T[0][1]=0;T[0][2]=0;T[0][3]=0;
T[1][0]=0;T[1][1]=1.0;T[1][2]=0;T[1][3]=0;
T[2][0]=0;T[2][1]=0;T[2][2]=1.0;T[2][3]=0;
T[3][0]=0;T[3][1]=0;T[3][2]=0;T[3][3]=1.0;
}
void CTransform3::Translate(double tx,double ty,double tz)
{
Identity();
T[0][3]=tx;
T[1][3]=ty;
T[2][3]=tz;
MultiplyMatrix();
}
void CTransform3::RotateX(double beta)
{
Identity();
double rad=beta*PI/180;
T[1][1]=cos(rad);T[1][2]=-sin(rad);
T[2][1]=sin(rad);T[1][2]=cos(rad);
MultiplyMatrix();
}
void CTransform3::RotateY(double beta)
{
Identity();
double rad=beta*PI/180;
T[0][0]=cos(rad);T[0][2]=sin(rad);
T[2][0]=-sin(rad);T[2][2]=cos(rad);
MultiplyMatrix();
}
void CTransform3::RotateZ(double beta)
{
Identity();
double rad=beta*PI/180;
T[0][0]=cos(rad);T[0][1]=-sin(rad);
T[1][0]=sin(rad);T[1][1]=cos(rad);
MultiplyMatrix();
}
void CTransform3::MultiplyMatrix()
{
CP3*PTemp=new CP3[ptNumber];
for(int i=0;i<ptNumber;i++)
PTemp[i]=P[i];
for(int j=0;j<ptNumber;j++)
{
P[j].x=T[0][0]*PTemp[j].x+T[0][1]*PTemp[j].y+T[0][2]*PTemp[j].z+T[0][3]*PTemp[j].w;
P[j].y=T[1][0]*PTemp[j].x+T[1][1]*PTemp[j].y+T[1][2]*PTemp[j].z+T[1][3]*PTemp[j].w;
P[j].z=T[2][0]*PTemp[j].x+T[2][1]*PTemp[j].y+T[2][2]*PTemp[j].z+T[2][3]*PTemp[j].w;
P[j].w=T[3][0]*PTemp[j].x+T[3][1]*PTemp[j].y+T[3][2]*PTemp[j].z+T[3][3]*PTemp[j].w;
}
delete [] PTemp;
}
4、立方体共有8顶点,6个面,在View类中添加函数读取立方体网格的点表和面表
首先添加面表类CFacet
#pragma once
class CFacet
{
public:
CFacet(void);
~CFacet(void);
public:
int pNumber;//表面顶点数
int pIndex[4];//表面顶点索引号
};
#include "StdAfx.h"
#include "Facet.h"
CFacet::CFacet(void)
{
}
CFacet::~CFacet(void)
{
}
在View类中包含头文件
#include"P3.h"
#include"Facet.h"
#include"Transform3.h"
添加成员变量
CP3 P[8];//点表
CFacet F[6];//面表
double Alpha,Beta;//绕x轴旋转角α,绕y轴旋转角β
CTransform3 tran;//变换对象
添加正方体点表、面表函数
void CtestView::ReadPoint(void)
{
double a=160;//立方体边长为2a,中心点在几何体心
P[0].x=-a;P[0].y=-a;P[0].z=-a;//定义所有的点坐标,注意点要按照顺序进行赋值连接
P[1].x=+a;P[1].y=-a;P[1].z=-a;
P[2].x=+a;P[2].y=+a;P[2].z=-a;
P[3].x=-a;P[3].y=+a;P[3].z=-a;
P[4].x=-a;P[4].y=-a;P[4].z=+a;
P[5].x=+a;P[5].y=-a;P[5].z=+a;
P[6].x=+a;P[6].y=+a;P[6].z=+a;
P[7].x=-a;P[7].y=+a;P[7].z=+a;
}
void CtestView::ReadFacet(void)
{
F[0].pIndex[0]=4;F[0].pIndex[1]=5;F[0].pIndex[2]=6;F[0].pIndex[3]=7;//每个面按照序号最小顶点依次逆时针连接
F[1].pIndex[0]=0;F[1].pIndex[1]=3;F[1].pIndex[2]=2;F[1].pIndex[3]=1;
F[2].pIndex[0]=0;F[2].pIndex[1]=4;F[2].pIndex[2]=7;F[2].pIndex[3]=3;
F[3].pIndex[0]=1;F[1].pIndex[1]=2;F[3].pIndex[2]=6;F[3].pIndex[3]=5;
F[4].pIndex[0]=2;F[4].pIndex[1]=3;F[4].pIndex[2]=7;F[4].pIndex[3]=6;
F[5].pIndex[0]=0;F[5].pIndex[1]=1;F[5].pIndex[2]=5;F[5].pIndex[3]=4;
}
构造函数初始化:
// TODO: add construction code here
Alpha=0;
Beta=0;
ReadPoint();
ReadFacet();
tran.SetMatrix(P,8);
添加双缓冲函数DoubleBuffer:
void CTestView::DoubleBuffer(CDC* pDC)//双缓冲
{
CRect rect;//定义客户区矩形
GetClientRect(&rect);//获得客户区的大小
pDC->SetMapMode(MM_ANISOTROPIC);//pDC自定义坐标系
pDC->SetWindowExt(rect.Width(),rect.Height());//设置窗口范围
pDC->SetViewportExt(rect.Width(),-rect.Height());//设置视区范围,x轴水平向右,y轴垂直向上
pDC->SetViewportOrg(rect.Width()/2,rect.Height()/2);//客户区中心为原点
CDC memDC;//内存DC
memDC.CreateCompatibleDC(pDC);//创建一个与显示pDC兼容的内存memDC
CBitmap NewBitmap,*pOldBitmap;//内存中承载的临时位图
NewBitmap.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());//创建兼容位图
pOldBitmap=memDC.SelectObject(&NewBitmap);//将兼容位图选入memDC
memDC.FillSolidRect(rect,pDC->GetBkColor());//按原来背景填充客户区,否则是黑色
memDC.SetMapMode(MM_ANISOTROPIC);//memDC自定义坐标系
memDC.SetWindowExt(rect.Width(),rect.Height());
memDC.SetViewportExt(rect.Width(),-rect.Height());
memDC.SetViewportOrg(rect.Width()/2,rect.Height()/2);
rect.OffsetRect(-rect.Width()/2,-rect.Height()/2);
DrawGraph(&memDC);//向memDC绘制图形
pDC->BitBlt(rect.left,rect.top,rect.Width(),rect.Height(),&memDC,-rect.Width()/2,-rect.Height()/2,SRCCOPY);//将内存memDC中的位图拷贝到显示pDC中
memDC.SelectObject(pOldBitmap);//恢复位图
NewBitmap.DeleteObject();//删除位图
memDC.DeleteDC();//删除memDC
}
添加绘制立方体线框函数DrawGraph:
void CTestView::DrawGraph(CDC* pDC)//绘制立方体线框
{
CPoint ScreenP[4];
for(int nFacet=0;nFacet<6;nFacet++)//面循环
{
for(int nPoint=0;nPoint<4;nPoint++)//顶点循环
{
ScreenP[nPoint].x=P[F[nFacet].pIndex[nPoint]].x;
ScreenP[nPoint].y=P[F[nFacet].pIndex[nPoint]].y;
}
pDC->MoveTo(ScreenP[0].x,ScreenP[0].y);
pDC->LineTo(ScreenP[1].x,ScreenP[1].y);
pDC->LineTo(ScreenP[2].x,ScreenP[2].y);
pDC->LineTo(ScreenP[3].x,ScreenP[3].y);
pDC->LineTo(ScreenP[0].x,ScreenP[0].y);//闭合多边形
}
}
添加鼠标事件:
void CTestView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
switch(nChar)
{
case VK_UP:
Alpha=+5;
tran.RotateX(Alpha);
break;
case VK_DOWN:
Alpha=-5;
tran.RotateX(Alpha);
break;
case VK_LEFT:
Beta=-5;
tran.RotateY(Beta);
break;
case VK_RIGHT:
Beta=5;
tran.RotateY(Beta);
break;
default:
break;
}
Invalidate(false);
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
最后,在OnDraw函数中调用DoubleBuffer函数:
DoubleBuffer(pDC);