三维图形几何变换与投影变换

一.实验目的

1)掌握4*4矩阵乘法运算的编程实现

2)掌握平移,比例,旋转三种基本三维几何变换矩阵生成

3)掌握正交投影图的生成和绘制方法

二.实验要求

1)三维坐标系的原点位于屏幕中心,X轴水平向右,X轴垂直向上,Z轴垂直

于坐标屏幕,指向屏幕外。

2)设计实现三维图形变换类,具有平移、比例、旋转三维几何变换功能,以

及正交投影变换功能.

3)使用第二章的直线类绘制正四面体的是三维线框模型,要求体心位于坐标原点,使正四面体同时绕Y轴匀速旋转,并相对于体心点来回缩放。

4)使用双缓冲机制,绘制正四面体三维线框模型的二维正交投影图,要求投

影到X0Y平面。

三.详细设计

注意:建议创建项目名为TTest.sln,这样可以直接使用本代码

1.本次实验使用直线扫描转换——中点Bresenham扫描换算法的MFC项目工程,可以参考文章:直线扫描转换——中点Bresenham扫描换算法(VS2022)_BaoTuxxl的博客-CSDN博客_bresenham直线扫描转换算法

2.首先创建CP3Edge.h和CP3Edge.cpp文件,建立三维齐次坐标顶点类和边表类:

CP3Edge.h:

#pragma once
class CP3
{
public :
	CP3();
	virtual ~CP3();
	CP3(double,double,double);
public:
	double x;
	double y;
	double z;
	double w;
};
//边表类
class CEdge
{
public:
	CEdge();
	virtual ~CEdge();
	void SetPointsIndex(int, int);
public:
	int Start;
	int End;
};

CP3Edge.cpp:

#include"pch.h"
#include"CP3Edge.h"
CP3::CP3()
{
	x = 0.0;
	y = 0.0;
	z = 0.0;
	w = 1.0;
}
CP3::~CP3()
{}
CP3::CP3(double x, double y, double z) {
	this->x = x;
	this->y = y;
	this->z = z;
	this->w = 1.0;
}
//边表类
CEdge::CEdge() {
	Start = 0;
	End = 0;
}
CEdge::~CEdge() 
{}
void CEdge::SetPointsIndex(int start, int end) {
	Start = start;
	End = end;
}

3.设计实现三维图形几何变换类

新建CTrans3D.h和CTrans3D.cpp

CTrans3D.h:

#pragma once
#include"Line.h"
#include"CP3Edge.h"
class CTrans3D//三维几何变换
{
public :
	CTrans3D();
	virtual~CTrans3D();
	void SetPoints(CP3*, int);
	void Identity();
	void Translate(double, double, double);
	void RotateX(double);
	void RotateY(double);
	void RotateZ(double);
	void Scale(double, double, double);
	void ProjXOY();
protected:
	void MultiMatrix();
public:
	double m_aT[4][4];
	CP3* m_p3Points;
	CP2* m_p2Screen;
	int m_iNum;
};

CTrans3D.cpp:

#include "pch.h"
#include"CTrans3D.h"
#include"math.h"
#define PI 3.14159

CTrans3D::CTrans3D()
{}
CTrans3D::~CTrans3D()
{
	if (m_p2Screen)
	{
		delete []m_p2Screen;
		m_p2Screen = NULL;
	}
	if (m_p3Points)
	{
		delete []m_p3Points;
		m_p3Points = NULL;
	}

}
void CTrans3D::SetPoints(CP3* p, int n)
{
	m_p3Points = new CP3[n];
	for (int i = 0; i < n; i++)
		m_p3Points[i] = p[i];
	m_iNum = n;
}

void CTrans3D::Identity() {
	m_aT[0][0] = 1.0; m_aT[0][1] = 0.0; m_aT[0][2] = 0.0; m_aT[0][3] = 0.0;
	m_aT[1][0] = 0.0; m_aT[1][1] = 1.0; m_aT[1][2] = 0.0; m_aT[1][3] = 0.0;
	m_aT[2][0] = 0.0; m_aT[2][1] = 0.0; m_aT[2][2] = 1.0; m_aT[2][3] = 0.0;
	m_aT[3][0] = 0.0; m_aT[3][1] = 0.0; m_aT[3][2] = 0.0; m_aT[3][3] = 1.0;
}
void CTrans3D::Translate(double tx, double ty, double tz)
{
	Identity();
	m_aT[3][0] = tx;
	m_aT[3][1] = ty;
	m_aT[3][2] = tz;
	MultiMatrix();
}

void CTrans3D::Scale(double sx, double sy, double sz) {
	Identity();
	m_aT[0][0] = sx;
	m_aT[1][1] = sy;
	m_aT[2][2] = sz;
	MultiMatrix();
}

void CTrans3D::RotateX(double beta) {
	Identity();
	double rad = beta * PI / 180;
	m_aT[1][1] = cos(rad);
	m_aT[1][2] = sin(rad);
	m_aT[2][1] = -sin(rad);
	m_aT[2][2] = cos(rad);
	MultiMatrix();
}

void CTrans3D::RotateY(double beta) {
	Identity();
	double rad = beta * PI / 180;
	m_aT[0][0] = cos(rad);
	m_aT[0][2] = -sin(rad);
	m_aT[2][0] = sin(rad);
	m_aT[2][2] = cos(rad);
	MultiMatrix();
}

void CTrans3D::RotateZ(double beta) {
	Identity();
	double rad = beta * PI / 180;
	m_aT[0][0] = cos(rad);
	m_aT[0][1] = sin(rad);
	m_aT[1][0] = -sin(rad);
	m_aT[1][1] = cos(rad);
	MultiMatrix();
}

void CTrans3D::ProjXOY() {
	m_p2Screen = new CP2[m_iNum];
	for (int i = 0; i < m_iNum; i++) {
		m_p2Screen[i].x = m_p3Points[i].x;
		m_p2Screen[i].y = m_p3Points[i].y;
	}
}

void CTrans3D::MultiMatrix() {
	CP3* PNew = new CP3[m_iNum];
	for (int i = 0; i < m_iNum; i++) {
		PNew[i] = m_p3Points[i];
	}

	for (int j = 0; j < m_iNum; j++) {
		m_p3Points[j].x = PNew[j].x * m_aT[0][0] + PNew[j].y * m_aT[1][0] + PNew[j].z * m_aT[2][0] + PNew[j].w * m_aT[3][0];
		m_p3Points[j].y = PNew[j].x * m_aT[0][1] + PNew[j].y * m_aT[1][1] + PNew[j].z * m_aT[2][1] + PNew[j].w * m_aT[3][1];
		m_p3Points[j].z = PNew[j].x * m_aT[0][2] + PNew[j].y * m_aT[1][2] + PNew[j].z * m_aT[2][2] + PNew[j].w * m_aT[3][2];
		m_p3Points[j].w = PNew[j].x * m_aT[0][3] + PNew[j].y * m_aT[1][3] + PNew[j].z * m_aT[2][3] + PNew[j].w * m_aT[3][3];
	}
	delete[]PNew;
}

4.建立正四面体的点表和边表

将TTestView.h更新为:


// TTestView.h: CTTestView 类的接口
//

#pragma once

#include "CP3Edge.h"
class CTTestView : public CView
{
protected: // 仅从序列化创建
	CTTestView() noexcept;
	DECLARE_DYNCREATE(CTTestView)

// 特性
public:
	CTTestDoc* GetDocument() const;

// 操作
public:
	void BuildPointEdge();
// 重写
public:
	virtual void OnDraw(CDC* pDC);  // 重写以绘制该视图
	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
	virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
	virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
	virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

// 实现
public:
	virtual ~CTTestView();
#ifdef _DEBUG
	virtual void AssertValid() const;
	virtual void Dump(CDumpContext& dc) const;
#endif

protected:
	CP3 P[4];
	CEdge E[6];
// 生成的消息映射函数
protected:
	afx_msg void OnFilePrintPreview();
	afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
	afx_msg void OnContextMenu(CWnd* pWnd, CPoint point);
	DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // TTestView.cpp 中的调试版本
inline CTTestDoc* CTTestView::GetDocument() const
   { return reinterpret_cast<CTTestDoc*>(m_pDocument); }
#endif

将TTestView.cpp更新为:


// TTestView.cpp: CTTestView 类的实现
//

#include "pch.h"
#include "framework.h"
// SHARED_HANDLERS 可以在实现预览、缩略图和搜索筛选器句柄的
// ATL 项目中进行定义,并允许与该项目共享文档代码。
#ifndef SHARED_HANDLERS
#include "TTest.h"
#endif
#include "CTrans3D.h"
#include "Line.h"
#include "TTestDoc.h"
#include "TTestView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CTTestView

IMPLEMENT_DYNCREATE(CTTestView, CView)

BEGIN_MESSAGE_MAP(CTTestView, CView)
	// 标准打印命令
	ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CTTestView::OnFilePrintPreview)
	ON_WM_CONTEXTMENU()
	ON_WM_RBUTTONUP()
END_MESSAGE_MAP()

// CTTestView 构造/析构

CTTestView::CTTestView() noexcept
{
	// TODO: 在此处添加构造代码
	BuildPointEdge();
}
void CTTestView::BuildPointEdge()
{
	double d = 400;
	P[0].x = d / 2; P[0].y = d / 2; P[0].z = d / 2;
	P[1].x = d / 2; P[1].y = -d / 2; P[1].z = -d / 2;
	P[2].x = -d / 2; P[2].y = -d / 2; P[2].z = d / 2;
	P[3].x = -d / 2; P[3].y = d / 2; P[3].z = -d / 2;
	E[0].SetPointsIndex(0, 1);
	E[1].SetPointsIndex(0, 2);
	E[2].SetPointsIndex(0, 3);
	E[3].SetPointsIndex(1, 2);
	E[4].SetPointsIndex(1, 3);
	E[5].SetPointsIndex(2, 3);
}

CTTestView::~CTTestView()
{
	
}


BOOL CTTestView::PreCreateWindow(CREATESTRUCT& cs)
{
	// TODO: 在此处通过修改
	//  CREATESTRUCT cs 来修改窗口类或样式

	return CView::PreCreateWindow(cs);
}

// CTTestView 绘图


void CTTestView::OnDraw(CDC* pDC)
{
	CTTestDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// TODO: 在此处为本机数据添加绘制代码
	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);
	//	rect.OffsetRect(-rect.Width() / 2, -rect.Height() / 2);

	CDC MemDC;
	CBitmap NewBitmap, * pOldBitmap;
	MemDC.CreateCompatibleDC(pDC);
	NewBitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
	pOldBitmap = MemDC.SelectObject(&NewBitmap);
	MemDC.FillSolidRect(rect, pDC->GetBkColor());
	MemDC.SetMapMode(MM_ANISOTROPIC);
	MemDC.SetWindowExt(rect.Width(), rect.Height());
	MemDC.SetViewportExt(rect.Width(), -rect.Height());
	MemDC.SetViewportOrg(rect.Width() / 2, rect.Height() / 2);

	CLine* line = new CLine;
	line->SetLineColor(RGB(0, 0, 0));
	line->MoveTo(CP2(-rect.Width() / 2, 0));
	line->LineTo(CP2(rect.Width() / 2, 0), &MemDC);
	line->MoveTo(CP2(0, -rect.Height() / 2));
	line->LineTo(CP2(0, rect.Height() / 2), &MemDC);

	CTrans3D tans;
	tans.SetPoints(P, 4);
	static float s = 1.0;
	static float step = 0.01f;
	if (s >= 2.0 || s <= 0.5)
		step = -step;
	s += step;
	tans.Scale(s, s, s);

	static float theta = 0.0;
	theta += 1.0;
	if (theta >= 360.0)
		theta = 0.0;
	tans.RotateY(theta);
	tans.ProjXOY();
	for (int i = 0; i < 6; i++)
	{
		line->SetLineColor(RGB(0, 255, 0));
		line->MoveTo(tans.m_p2Screen[E[i].Start]);
		line->LineTo(tans.m_p2Screen[E[i].End], &MemDC);
	}
	delete line;

	pDC->BitBlt(-rect.Width() / 2, -rect.Height() / 2, rect.Width(), rect.Height(), &MemDC, -rect.Width() / 2, -rect.Height() / 2, SRCCOPY);
	MemDC.SelectObject(pOldBitmap);
	NewBitmap.DeleteObject();
	Invalidate(FALSE);
}





// CTTestView 打印


void CTTestView::OnFilePrintPreview()
{
#ifndef SHARED_HANDLERS
	AFXPrintPreview(this);
#endif
}

BOOL CTTestView::OnPreparePrinting(CPrintInfo* pInfo)
{
	// 默认准备
	return DoPreparePrinting(pInfo);
}

void CTTestView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO: 添加额外的打印前进行的初始化过程
}

void CTTestView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO: 添加打印后进行的清理过程
}

void CTTestView::OnRButtonUp(UINT /* nFlags */, CPoint point)
{
	ClientToScreen(&point);
	OnContextMenu(this, point);
}

void CTTestView::OnContextMenu(CWnd* /* pWnd */, CPoint point)
{
#ifndef SHARED_HANDLERS
	theApp.GetContextMenuManager()->ShowPopupMenu(IDR_POPUP_EDIT, point.x, point.y, this, TRUE);
#endif
}


// CTTestView 诊断

#ifdef _DEBUG
void CTTestView::AssertValid() const
{
	CView::AssertValid();
}

void CTTestView::Dump(CDumpContext& dc) const
{
	CView::Dump(dc);
}

CTTestDoc* CTTestView::GetDocument() const // 非调试版本是内联的
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CTTestDoc)));
	return (CTTestDoc*)m_pDocument;
}
#endif //_DEBUG


// CTTestView 消息处理程序

5.最后需要修改Line.h和Line.cpp的内容,将二维改成三维

Line.h:

#pragma once
class CP2
{
public:
	CP2();
	virtual~CP2();
	CP2(double, double);
public:
	double x;
	double y;
	double w;
};
class CLine
{
public:
	CLine();
	virtual~CLine();
	void SetLineColor(COLORREF);
	void MoveTo(CP2);
	void MoveTo(double, double);
	void LineTo(CP2, CDC*);
	void LineTo(double, double, CDC*);
public:
	CP2 P0;
	CP2 P1;
	COLORREF clr;
};

Line.cpp:

#include "pch.h"
#include "Line.h"
#include "math.h"
#define Round(d) int (floor(d+0.5))


CP2::CP2()
{
	x = 0.0;
	y = 0.0;
	w = 1.0;
}

CP2::~CP2()
{}

CP2::CP2(double x, double y)
{ /*齐次坐标*/
	this->x = x;
	this->y = y;
	this->w = 1;
}

CLine::CLine()
{}

CLine::~CLine()
{}

void CLine::SetLineColor(COLORREF color)
{
	clr = color;
}

void CLine::MoveTo(CP2 p0)
{
	P0 = p0;
}

void CLine::MoveTo(double x, double y)
{
	P0.x = x;
	P0.y = y;
}

void CLine::LineTo(double x, double y, CDC* pDC)
{
	CP2 p;
	p.x = x;
	p.y = y;
	LineTo(p, pDC);
}

void CLine::LineTo(CP2 p1, CDC* pDC)
{
	P1 = p1;
	CP2 p, t;
	if (fabs(P0.x - P1.x) < 1e-6)
	{
		if (P0.y > P1.y)
		{
			t = P0; P0 = P1; P1 = t;
		}
		for (p = P0; p.y < P1.y; p.y++)
		{
			pDC->SetPixelV(Round(p.x), Round(p.y), clr);
		}
	}
	else
	{
		double k, d;
		k = (P1.y - P0.y) / (P1.x - P0.x);
		if (k > 1.0)
		{
			if (P0.y > P1.y)
			{
				t = P0; P0 = P1; P1 = t;
			}
			d = 1 - 0.5 * k;
			for (p = P0; p.y < P1.y; p.y++)
			{
				pDC->SetPixelV(Round(p.x), Round(p.y), clr);
				if (d >= 0)
				{
					p.x++;
					d += 1 - k;
				}
				else
					d += 1;
			}
		}
		if (0.0 <= k && k <= 1.0)
		{
			if (P0.x > P1.x)
			{
				t = P0; P0 = P1; P1 = t;
			}
			d = 0.5 - k;
			for (p = P0; p.x < P1.x; p.x++)
			{
				pDC->SetPixelV(Round(p.x), Round(p.y), clr);
				if (d < 0)
				{
					p.y++;
					d += 1 - k;
				}
				else
					d -= k;
			}
		}
		if (k >= -1.0 && k < 0.0)
		{
			if (P0.x > P1.x)
			{
				t = P0; P0 = P1; P1 = t;
			}
			d = -0.5 - k;
			for (p = P0; p.x < P1.x; p.x++)
			{
				pDC->SetPixelV(Round(p.x), Round(p.y), clr);
				if (d > 0)
				{
					p.y--;
					d -= 1 + k;
				}
				else
					d -= k;
			}
		}
		if (k < -1.0)
		{
			if (P0.y < P1.y)
			{
				t = P0; P0 = P1; P1 = t;
			}
			d = -1 - 0.5 * k;
			for (p = P0; p.y > P1.y; p.y--)
			{
				pDC->SetPixelV(Round(p.x), Round(p.y), clr);
				if (d < 0)
				{
					p.x++;
					d -= 1 + k;
				}
				else
					d -= 1;
			}
		}
	}
	P0 = p1;
}

四.典型测试结果

 

猜你喜欢

转载自blog.csdn.net/m0_63975371/article/details/127554930