一、实验项目要求
1.设计物体线框模型类;
2.调用三维变换类将物体导入三维场景(CTgestView类)中显示;
3.使用鼠标或者键盘方向键,与图形进行交互。
二、理论分析或算法分析
1.设计物体线框模型类:
在这个实验中,设计一个金字塔线框模型,在Pyramid.h中定义一个面表(void CPyramid::ReadFacet(void)//面表)和点表(void CPyramid::ReadPoint(void)//点表),(void Draw(CDC* pDC);//绘制金字塔线框)来进行对线框模型的绘制;在绘制图形的时候采用正交投影的方式(ScreenPoint.x = P[F[nFacet].Index[nPoint]].x;,ScreenPoint.y = P[F[nFacet].Index[nPoint]].y;),绘制的时候采用一点到另一个点的绘制方式(pDC->MoveTo(ROUND(ScreenPoint.x),ROUND(ScreenPoint.y));,pDC->LineTo(ROUND(ScreenPoint.x), ROUND(ScreenPoint.y));)最后闭合多边形(pDC->LineTo(ROUND(temp.x), ROUND(temp.y));//闭合多边形);
2.使用三维变换类CTransform3类来进行三维的变换,在这个类中定义了三维顶点数组初始化(void CTransform3::SetMatrix(CP3* P, int ptNumber)//顶点数组初始化)和单位矩阵初始化(void CTransform3::Identity(void)//单位矩阵初始化)和平移变换(void CTransform3::Translate(double tx,double ty,double tz)//平移变换)和相对于任意点的比例变换(void CTransform3::Scale(double sx, double sy, double sz, CP3 p)//相对于任意点的缩放变换)和绕X轴旋转变换(void CTransform3::RotateX(double beta)//绕X轴旋转变换)和绕Y轴旋转变换(void CTransform3::RotateY(double beta)//绕Y轴旋转变换)和绕Z轴旋转变换(void CTransform3::RotateZ(double beta)//绕Z轴旋转变换)和相对于任意点的绕X轴旋转变换(void CTransform3::RotateX(double beta, CP3 p)//相对于任意点的X轴旋转变换)和相对于任意点的绕Y轴旋转变换(void CTransform3::RotateY(double beta, CP3 p)//相对于任意点的Y轴旋转变换)和相对于任意点的绕Z轴旋转变换(void CTransform3::RotateZ(double beta, CP3 p)//相对于任意点的Z轴旋转变换)和关于X轴反射变换(void CTransform3::ReflectX(void)//X轴反射变换)和关于Y轴反射变换(void CTransform3::ReflectY(void)//Y轴反射变换)和关于Z轴反射变换(void CTransform3::ReflectZ(void)//Z轴反射变换)和关于XOY面反射变换(void CTransform3::ReflectXOY(void)//XOY面反射变换)和关于YOZ面反射变换(void CTransform3::ReflectYOZ(void)//YOZ面反射变换)和关于ZOX面反射变换(void CTransform3::ReflectZOX(void)//XOZ面反射变换)和沿X方向错切变换(void CTransform3::ShearX(double b, double c)//X方向错切变换)和沿Y方向错切变换(void CTransform3::ShearY(double d, double f)//Y方向错切变换)和沿Z方向错切变换(void CTransform3::ShearZ(double g, double h)//Z方向错切变换)和矩阵相乘(void CTransform3::MultiplyMatrix(void)//矩阵相乘);
3.使用鼠标或者键盘方向键,与图形进行交互:
使用键盘方向键:当按动左键时,可以转动5,按动右键时,又可以转动5;
使用鼠标键:设置一个定时器,当按动动画按钮(OnGraphAnimation)的时候,可以进行旋转,当再次按动动画按钮(OnUpdateGraphAnimation)的时候,可以停下来。
实现了动画的交互。
三、实现方法
1.设计物体线框模型类:
Pyramid.h:
#pragma once
#include"P3.h"
#include "Facet.h"
class CPyramid
{
public:
CPyramid(void);
virtual ~CPyramid(void);
CP3* GetVertexArrayName(void);//得到顶点数组名
void ReadPoint(void);//读入点表
void ReadFacet(void);//读入面表
void Draw(CDC* pDC);//绘制金字塔线框
private:
CP3 P[5];//点表
CFacet F[5];//面表
};
Pyramid.cpp:
#include "pch.h"
#include "Pyramid.h"
#define ROUND(d) int(d + 0.5)
CPyramid::CPyramid(void)
{
}
CPyramid::~CPyramid(void)
{
}
CP3* CPyramid::GetVertexArrayName(void)
{
return P;
}
void CPyramid::ReadPoint(void)//点表
{
P[0].x = 0.0, P[0].y = 0.0, P[0].z = 1.0;
P[1].x = 1.0, P[1].y = 0.0, P[1].z = 1.0;
P[2].x = 1.0, P[2].y = 0.0, P[2].z = 0.0;
P[3].x = 0.0, P[3].y = 0.0, P[3].z = 0.0;
P[4].x = 0.5, P[4].y = 1.0, P[4].z = 0.5;
}
void CPyramid::ReadFacet(void)//面表
{
F[0].Number = 3, F[0].Index[0] = 0, F[0].Index[1] = 1, F[0].Index[2] = 4;
F[1].Number = 3, F[1].Index[0] = 1, F[1].Index[1] = 2, F[1].Index[2] = 4;
F[2].Number = 3, F[2].Index[0] = 2, F[2].Index[1] = 3, F[2].Index[2] = 4;
F[3].Number = 3, F[3].Index[0] = 0, F[3].Index[1] = 4, F[3].Index[2] = 3;
F[4].Number = 4, F[4].Index[0] = 0, F[4].Index[1] = 3, F[4].Index[2] = 2, F[4].Index[3] = 1;
}
void CPyramid::Draw(CDC* pDC)
{
CP2 ScreenPoint, temp;
for (int nFacet = 0; nFacet < 5; nFacet++)//面循环
{
for (int nPoint = 0; nPoint < F[nFacet].Number; nPoint++)//顶点循环
{
ScreenPoint.x = P[F[nFacet].Index[nPoint]].x;
ScreenPoint.y = P[F[nFacet].Index[nPoint]].y;
if (0 == nPoint)
{
pDC->MoveTo(ROUND(ScreenPoint.x), ROUND(ScreenPoint.y));
temp = ScreenPoint;
}
else
{
pDC->LineTo(ROUND(ScreenPoint.x), ROUND(ScreenPoint.y));
}
}
pDC->LineTo(ROUND(temp.x), ROUND(temp.y));//闭合多边形
}
}
2.使用三维变换类CTransform3类来进行三维的变换
CTransform3.h:
#pragma once
#include"P3.h"
class CTransform3
{
public:
CTransform3(void);
virtual ~CTransform3(void);
void Identity(void);//单位矩阵初始化
void SetMatrix(CP3* P, int ptNumber);//三维顶点数组初始化
void Translate(double tx, double ty, double tz);//平移变换
void Scale(double sx,double sy, double sz);//缩放变换
void Scale(double sx,double sy, double sz,CP3 p);//相对于任意点的缩放变换
void Scale(double s);//整体缩放变换
void Scale(double s, CP3 p);//相对于任意点的整体缩放变换
void RotateX(double beta);//绕X轴旋转变换
void RotateY(double beta);//绕Y轴旋转变换
void RotateZ(double beta);//绕Z轴旋转变换
void RotateX(double beta, CP3 p);//相对于任意点的绕X轴旋转变换
void RotateY(double beta, CP3 p);//相对于任意点的绕Y轴旋转变换
void RotateZ(double beta, CP3 p);//相对于任意点的绕Z轴旋转变换
void ReflectX(void);//关于X轴反射变换
void ReflectY(void);//关于Y轴反射变换
void ReflectZ(void);//关于Z轴反射变换
void ReflectXOY(void);//关于XOY面反射变换
void ReflectYOZ(void);//关于YOZ面反射变换
void ReflectZOX(void);//关于ZOX面反射变换
void ShearX(double b, double c);//沿X方向错切变换
void ShearY(double d, double f);//沿Y方向错切变换
void ShearZ(double g, double h);//沿Z方向错切变换
void MultiplyMatrix(void);//矩阵相乘
private:
double T[4][4];//三维变换矩阵
CP3* P;//三维顶点数组名
int ptNumber;//三维顶点个数
};
CTransform3.cpp:
#include "pch.h"
#include "Transform3.h"
#include<math.h>
#define PI 3.1415926
CTransform3::CTransform3(void)
{
}
CTransform3::~CTransform3(void)
{
}
void CTransform3::Identity(void)//单位矩阵初始化
{
T[0][0] = 1.0, T[0][1] = 0.0, T[0][2] = 0.0, T[0][3] = 0.0;
T[1][0] = 0.0, T[1][1] = 1.0, T[1][2] = 0.0, T[1][3] = 0.0;
T[2][0] = 0.0, T[2][1] = 0.0, T[2][2] = 1.0, T[2][3] = 0.0;
T[3][0] = 0.0, T[3][1] = 0.0, T[3][2] = 0.0, T[3][3] = 1.0;
}
void CTransform3::SetMatrix(CP3* P, int ptNumber)//顶点数组初始化
{
this->P = P;
this->ptNumber = ptNumber;
}
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::Scale(double sx, double sy, double sz)//缩放变换
{
Identity();
T[0][0] = sx, T[1][1] = sy, T[2][2] = sz;
MultiplyMatrix();
}
void CTransform3::Scale(double sx, double sy, double sz, CP3 p)//相对于任意点的缩放变换
{
Translate(-p.x, -p.y, -p.z);
Scale(sx, sy, sz);
Translate(p.x, p.y, p.z);
}
void CTransform3::Scale(double s)//整体缩放变换
{
Identity();
T[0][0] = s, T[1][1] = s, T[2][2] = s;
MultiplyMatrix();
}
void CTransform3::Scale(double s, CP3 p)//相对于任意点的整体缩放变换
{
Translate(-p.x, -p.y, -p.z);
Scale(s);
Translate(p.x, p.y, p.z);
}
void CTransform3::RotateX(double beta)//绕X轴旋转变换
{
Identity();
beta = beta * PI / 180;
T[1][1] = cos(beta), T[1][2] = -sin(beta);
T[2][1] = sin(beta), T[2][2] = cos(beta);
MultiplyMatrix();
}
void CTransform3::RotateY(double beta)//绕Y轴旋转变换
{
Identity();
beta = beta * PI/180;
T[0][0] = cos(beta), T[0][2] = sin(beta);
T[2][0] =-sin(beta), T[2][2] = cos(beta);
MultiplyMatrix();
}
void CTransform3::RotateZ(double beta)//绕Z轴旋转变换
{
Identity();
beta = beta * PI/180;
T[0][0] = cos(beta), T[0][1] = -sin(beta);
T[1][0] = sin(beta), T[1][1] = cos(beta);
MultiplyMatrix();
}
void CTransform3::RotateX(double beta, CP3 p)//相对于任意点的X轴旋转变换
{
Translate(-p.x, -p.y, -p.z);
RotateX(beta);
Translate(p.x, p.y, p.z);
}
void CTransform3::RotateY(double beta, CP3 p)//相对于任意点的Y轴旋转变换
{
Translate(-p.x, -p.y, -p.z);
RotateY(beta);
Translate(p.x, p.y, p.z);
}
void CTransform3::RotateZ(double beta, CP3 p)//相对于任意点的Z轴旋转变换
{
Translate(-p.x, -p.y, -p.z);
RotateZ(beta);
Translate(p.x, p.y, p.z);
}
void CTransform3::ReflectX(void)//X轴反射变换
{
Identity();
T[1][1] = -1, T[2][2] = -1;
MultiplyMatrix();
}
void CTransform3::ReflectY(void)//Y轴反射变换
{
Identity();
T[0][0] = -1, T[2][2] = -1;
MultiplyMatrix();
}
void CTransform3::ReflectZ(void)//Z轴反射变换
{
Identity();
T[0][0] = -1, T[1][1] = -1;
MultiplyMatrix();
}
void CTransform3::ReflectXOY(void)//XOY面反射变换
{
Identity();
T[2][2] = -1;
MultiplyMatrix();
}
void CTransform3::ReflectYOZ(void)//YOZ面反射变换
{
Identity();
T[0][0] = -1;
MultiplyMatrix();
}
void CTransform3::ReflectZOX(void)//XOZ面反射变换
{
Identity();
T[1][1] = -1;
MultiplyMatrix();
}
void CTransform3::ShearX(double b, double c)//X方向错切变换
{
Identity();
T[0][1] = b, T[0][2] = c;
MultiplyMatrix();
}
void CTransform3::ShearY(double d, double f)//Y方向错切变换
{
Identity();
T[1][0] = d, T[1][2] = f;
MultiplyMatrix();
}
void CTransform3::ShearZ(double g, double h)//Z方向错切变换
{
Identity();
T[2][0] = g;T[2][1] = h;
MultiplyMatrix();
}
void CTransform3::MultiplyMatrix(void)//矩阵相乘
{
CP3* PTemp = new CP3[ptNumber];
for(int i = 0;i < ptNumber;i++)
PTemp[i] = P[i];
for(int i = 0;i < ptNumber;i++)
{
P[i].x = T[0][0] * PTemp[i].x + T[0][1] * PTemp[i].y + T[0][2] * PTemp[i].z + T[0][3] * PTemp[i].w;
P[i].y = T[1][0] * PTemp[i].x + T[1][1] * PTemp[i].y + T[1][2] * PTemp[i].z + T[1][3] * PTemp[i].w;
P[i].z = T[2][0] * PTemp[i].x + T[2][1] * PTemp[i].y + T[2][2] * PTemp[i].z + T[2][3] * PTemp[i].w;
P[i].w = T[3][0] * PTemp[i].x + T[3][1] * PTemp[i].y + T[3][2] * PTemp[i].z + T[3][3] * PTemp[i].w;
}
delete []PTemp;
}
3.使用鼠标或者键盘方向键,与图形进行交互:
void CTestView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if (!bPlay)
{
switch (nChar)
{
case VK_UP:
Alpha = -5;
transform.RotateX(Alpha);
break;
case VK_DOWN:
Alpha = +5;
transform.RotateX(Alpha);
break;
case VK_LEFT:
Beta = -5;
transform.RotateY(Beta);
break;
case VK_RIGHT:
Beta = +5;
transform.RotateY(Beta);
break;
default:
break;
}
Invalidate(FALSE);
}
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
void CTestView::OnGraphAnimation()
{
// TODO: 在此添加命令处理程序代码
bPlay = !bPlay;
if (bPlay)//设置定时器
SetTimer(1, 300, NULL);
else
KillTimer(1);
}
void CTestView::OnUpdateGraphAnimation(CCmdUI *pCmdUI)
{
// TODO: 在此添加命令更新用户界面处理程序代码
if (bPlay)
pCmdUI->SetCheck(TRUE);
else
pCmdUI->SetCheck(FALSE);
}
在TestView.h中定义:
protected:
CPyramid pyramid;
CTransform3 transform;//变换对象
double Alpha, Beta;//x方向旋转α角,y方向旋转β角
BOOL bPlay;//动画开关
public:
void DoubleBuffer(CDC* pDC);//双缓冲绘图
void DrawObject(CDC* pDC);//绘制金字塔线框
在TestView.cpp中绘制三维变换:
CTestView::CTestView() noexcept
{
// TODO: 在此处添加构造代码
bPlay = FALSE;
pyramid.ReadPoint();
pyramid.ReadFacet();
transform.SetMatrix(pyramid.GetVertexArrayName(), 5);
double nEdge = 400;
transform.Scale(nEdge, nEdge, nEdge);//比例变换
transform.Translate(-nEdge / 2, -nEdge / 2, -nEdge / 2);//平移变换
}
绘制图形:
void CTestView::OnDraw(CDC* pDC)
{
CTestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: 在此处为本机数据添加绘制代码
DoubleBuffer(pDC);//绘制图形
}
DoubleBuffer函数:
void CTestView::DoubleBuffer(CDC* pDC)//双缓冲绘图
{
CRect rect;
GetClientRect(&rect);
pDC->SetMapMode(MM_ANISOTROPIC);
pDC->SetWindowExt(rect.Width(), rect.Height());
pDC->SetViewportExt(rect.Width(), -rect.Height());
pDC->SetViewportOrg(rect.Width() / 2, rect.Height() / 2);
CDC memDC;//声明内存DC
memDC.CreateCompatibleDC(pDC);//创建一个与显示DC兼容的内存DC
CBitmap NewBitmap, *pOldBitmap;
NewBitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());//创建兼容内存位图
pOldBitmap = memDC.SelectObject(&NewBitmap);//将兼容位图选入内存DC
memDC.FillSolidRect(rect, pDC->GetBkColor());//设置客户区背景色
rect.OffsetRect(-rect.Width() / 2, -rect.Height() / 2);
memDC.SetMapMode(MM_ANISOTROPIC);//内存DC自定义坐标系
memDC.SetWindowExt(rect.Width(), rect.Height());
memDC.SetViewportExt(rect.Width(), -rect.Height());
memDC.SetViewportOrg(rect.Width() / 2, rect.Height() / 2);
DrawObject(&memDC);//绘制图形
pDC->BitBlt(rect.left, rect.top, rect.Width(), rect.Height(), &memDC, -rect.Width() / 2, -rect.Height() / 2, SRCCOPY); //将内存DC中的位图拷贝到显示DC
memDC.SelectObject(pOldBitmap);
NewBitmap.DeleteObject();
memDC.DeleteDC();
}
四、实验结果分析
实验结果
实验参考
计算机图形学
实验总结
在这次实验中,使用三维变换可以使物体看起来有立体感,刚开始进行代码编译的时候,感觉不是很熟悉就会感觉很陌生,在进行多次编译后,慢慢自己找代码错误,就开始熟悉自己的编译过程,自己建文档,自己建代码的开始,还是感觉有意思,但是经常出错就会很烦解决不了,但是最终都解决啦。