3D graphics geometric transformation and projective transformation

1. Purpose of experiment

1) Master the programming implementation of 4*4 matrix multiplication operation

2) Master the generation of three basic three-dimensional geometric transformation matrices of translation, scale and rotation

3) Master the generation and drawing methods of orthographic projections

2. Experimental requirements

1) The origin of the three-dimensional coordinate system is located in the center of the screen, the X-axis is horizontal to the right, the X-axis is vertically upward, and the Z-axis is vertical

On the coordinate screen, point out of the screen.

2) Design and realize the three-dimensional graphics transformation class, which has the functions of translation, proportion and rotation three-dimensional geometric transformation, to

and orthogonal projection transformation functions.

3) It is a three-dimensional wireframe model to draw a regular tetrahedron using the straight line class in Chapter 2. The body center is required to be located at the origin of the coordinates, so that the regular tetrahedron rotates at a constant speed around the Y axis at the same time, and scales back and forth relative to the body center point.

4) Use the double buffering mechanism to draw the 2D orthogonal projection of the 3D wireframe model of the regular tetrahedron.

shadow to the X0Y plane.

3. Detailed design

Note: It is recommended to create a project named TTest.sln, so that this code can be used directly

1. This experiment uses the MFC project of linear scan conversion - midpoint Bresenham scan conversion algorithm, you can refer to the article: Linear scan conversion - midpoint Bresenham scan conversion algorithm (VS2022)_BaoTuxxl's blog-CSDN blog_bresenham linear scan conversion algorithm

2. First create CP3Edge.h and CP3Edge.cpp files, and create three-dimensional homogeneous coordinate vertex classes and edge table classes:

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. Design and realize the geometric transformation class of 3D graphics

Create new CTrans3D.h and 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. Establish the point table and edge table of the regular tetrahedron

Update TTestView.h to:


// 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

Update TTestView.cpp to:


// 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. Finally, you need to modify the content of Line.h and Line.cpp, and change the two-dimensional to three-dimensional

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;
}

4. Typical test results

 

 

Guess you like

Origin blog.csdn.net/m0_63975371/article/details/127554930