使用MFC的CDC类绘制三维坐标系及球面函数

系列链接

概述

本文使用MFC的CDC类绘制三维坐标系及球面函数。首先计算推导出三维坐标在二维平面显示的坐标变换方程(使用斜二测视图),使用球面的参数方程,然后定义图形缩放比例规模、坐标轴位移,变换坐标系和规模等,最后绘制坐标轴及球面函数。

如果对绘制二维坐标系还不太熟悉可以先看上面系列链接的:使用MFC的CDC类绘制二维坐标系及正余弦函数,本文对二维绘制及绘制函数部分不再赘述。因为二维坐标系的博文已经分模块讲解地比较清楚了,而与三维坐标系的基本思路相同,所以本文大部分直接使用注释讲解。

三维转二维的推导

Transform3Dto2D

上图可知,只要使用Transform3Dto2D()函数,即可方便的把三维坐标转化为二维坐标(斜二测视图)。

球面参数方程

在三维空间直角坐标系中,以原点为球心、半径为 r 的球面的方程为 x^2 + y^2 + z^2 = r^2,其参数方程为

SphericalParameterEquation

新建项目

Visual Studio- 新建项目 - MFC应用程序 - 命名为GraphicsExercise3D - 确定 - 下一步 - 应用程序类型选择单个文档 - 完成

GraphicsExercise3DView.h

GraphicsExercise3DView.h添加以下内容

// 操作
public:
	void SetScale(int scale);
	void SetTransformOrigin(float transformOriginX, float transformOriginY);
	void SetPlotSphere(float radius, float stepPhi, float stepTheta);
  	void SetSlantRadian(float slant);

	float TransformScale(float num);
	float TransformOriginX(float x);
	float TransformOriginY(float y);
	float TransformOriginScaleX(float x);
	float TransformOriginScaleY(float y);
	void Transform3Dto2D(float &x, float &y, float z);

private:
    int scale;
    float radius, stepPhi, stepTheta, slant, transformOriginX, transformOriginY;

GraphicsExercise3DView.cpp

引入数学函数库

#include <math.h>

定义π

#ifndef PI
#define PI 3.14159
#endif // !PI

在构造函数初始化

CGraphicsExercise3DView::CGraphicsExercise3DView()
{
	// TODO: 在此处添加构造代码

	// 设置斜二测视图倾斜角度(弧度制)
	SetSlantRadian(PI / 4);

	// 设置规模比例
	SetScale(70);

	// 设置坐标系在x、y方向的位移(不改变规模情况下,即移动像素)
	SetTransformOrigin(300, 350);

	// 设置球面半径radius、取样步长stepPhi、stepTheta
	SetPlotSphere(2.0, 0.01, 0.1);
}

设置初始化参数的Set函数

// 设置规模
void CGraphicsExercise3DView::SetScale(int scale)
{
	this->scale = scale;
}

// 设置坐标系原点在x、y方向的位移(不改变规模情况下,即移动像素)
void CGraphicsExercise3DView::SetTransformOrigin(float transformOriginX, float transformOriginY)
{
	this->transformOriginX = transformOriginX;
	this->transformOriginY = transformOriginY;
}

// 设置球面半径radius、取样步长stepPhi、stepTheta
void CGraphicsExercise3DView::SetPlotSphere(float radius, float stepPhi, float stepTheta)
{
	this->radius = radius;
	this->stepPhi = stepPhi;
	this->stepTheta = stepTheta;
}

// 设置斜二测视图的倾斜角(单位弧度)
void CGraphicsExercise3DView::SetSlantRadian(float slant)
{
	this->slant = slant;
}

坐标及规模变换

// 变换规模
float CGraphicsExercise3DView::TransformScale(float num)
{
	return num * scale;
}

// 坐标系X轴方向位移
float CGraphicsExercise3DView::TransformOriginX(float x)
{
	return x + transformOriginX / scale;
}

// 坐标系y轴方向位移
float CGraphicsExercise3DView::TransformOriginY(float y)
{
	return y - transformOriginY / scale;
}

// 变换坐标系X和规模
float CGraphicsExercise3DView::TransformOriginScaleX(float x)
{
	return TransformScale(TransformOriginX(x));
}

// 变换坐标系Y和规模
float CGraphicsExercise3DView::TransformOriginScaleY(float y)
{
	return -TransformScale(TransformOriginY(y));
}

三维坐标转化为二维坐标

// 使用斜二测视图,把三维坐标点转化为二维平面上的点
void CGraphicsExercise3DView::Transform3Dto2D(float &x, float &y, float z)
{
	x = x - (z * cos(slant)) / 2;
	y = y - (z * sin(slant)) / 2;
}

绘制坐标轴及函数图形

// CGraphicsExercise2View 绘制

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

	// TODO: 在此处为本机数据添加绘制代码

	float x, y, z;

	// -------------------- 绘制坐标系 -------------------------

	// 坐标x轴
	pDC->MoveTo(TransformOriginScaleX(0), TransformOriginScaleY(0));
	pDC->LineTo(TransformOriginScaleX(radius + 2), TransformOriginScaleY(0));

	// 坐标y轴
	pDC->MoveTo(TransformOriginScaleX(0), TransformOriginScaleY(0));
	pDC->LineTo(TransformOriginScaleX(0), TransformOriginScaleY(radius + 2));

	// 坐标z轴
	x = 0, y = 0;
	Transform3Dto2D(x, y, radius + 5);
	pDC->MoveTo(TransformOriginScaleX(0), TransformOriginScaleY(0));
	pDC->LineTo(TransformOriginScaleX(x), TransformOriginScaleY(y));

	// 坐标x轴的箭头
	pDC->MoveTo(TransformOriginScaleX(radius + 1.8), TransformOriginScaleY(0.2));
	pDC->LineTo(TransformOriginScaleX(radius + 2), TransformOriginScaleY(0));
	pDC->LineTo(TransformOriginScaleX(radius + 1.8), TransformOriginScaleY(-0.2));

	// 坐标y轴的箭头
	pDC->MoveTo(TransformOriginScaleX(-0.2), TransformOriginScaleY(radius + 1.8));
	pDC->LineTo(TransformOriginScaleX(0), TransformOriginScaleY(radius + 2));
	pDC->LineTo(TransformOriginScaleX(0.2), TransformOriginScaleY(radius + 1.8));

	// 坐标z轴的箭头
	x = 0, y = 0.2;
	Transform3Dto2D(x, y, radius + 5 - 0.2);
	pDC->MoveTo(TransformOriginScaleX(x), TransformOriginScaleY(y));
	x = 0, y = 0;
	Transform3Dto2D(x, y, radius + 5);
	pDC->LineTo(TransformOriginScaleX(x), TransformOriginScaleY(y));
	x = 0.2, y = 0;
	Transform3Dto2D(x, y, radius + 5 - 0.2);
	pDC->LineTo(TransformOriginScaleX(x), TransformOriginScaleY(y));

	// -------------------- 绘制刻度线 -------------------------

	// 绘制x轴刻度线
	for (float scaleX = 0.2; scaleX < radius + 1; scaleX += 0.2)
	{
		pDC->MoveTo((int)TransformOriginScaleX(scaleX), (int)TransformOriginScaleY(0));
		pDC->LineTo((int)TransformOriginScaleX(scaleX), (int)TransformOriginScaleY(0.1));
	}

	// 绘制y轴刻度线
	for (float scaleY = 0.2; scaleY <= radius + 1; scaleY += 0.2)
	{
		pDC->MoveTo((int)TransformOriginScaleX(0), (int)TransformOriginScaleY(scaleY));
		pDC->LineTo((int)TransformOriginScaleX(0.1), (int)TransformOriginScaleY(scaleY));
	}

	// 绘制z轴刻度线
	for (float x = 0, y = 0, scaleZ = 0.2; scaleZ <= radius + 4; scaleZ += 0.2, x = 0, y = 0)
	{
		Transform3Dto2D(x, y, scaleZ);
		pDC->MoveTo((int)TransformOriginScaleX(x), (int)TransformOriginScaleY(y));
		pDC->LineTo((int)TransformOriginScaleX(x + 0.1), (int)TransformOriginScaleY(y));
	}


	// -------------------- 绘制文字 -------------------------

	// 绘制x轴的x
	pDC->TextOutW(TransformOriginScaleX(radius + 1.6), TransformOriginScaleY(-0.2), CString("x"));
	// 绘制y轴的y
	pDC->TextOutW(TransformOriginScaleX(-0.2), TransformOriginScaleY(radius + 1.6), CString("y"));
	// 绘制z轴的z
	x = 0.2, y = 0;
	Transform3Dto2D(x, y, radius + 5 - 0.4);
	pDC->TextOutW(TransformOriginScaleX(x), TransformOriginScaleY(y), CString("z"));

	CString s;
	// 绘制x轴刻度文字
	for (float ScaleTextX = 0.4; ScaleTextX < radius + 1; ScaleTextX += 0.4)
	{
		s.Format(_T("%.1f"), ScaleTextX);
		pDC->TextOutW(TransformOriginScaleX(ScaleTextX - 0.1), TransformOriginScaleY(-0.1), s);
	}

	// 绘制y轴刻度文字
	for (float ScaleTextY = 0.4; ScaleTextY <= radius + 1; ScaleTextY += 0.4)
	{
		s.Format(_T("%.1f"), ScaleTextY);
		pDC->TextOutW(TransformOriginScaleX(-0.4), TransformOriginScaleY(ScaleTextY + 0.1), s);
	}

	// 绘制z轴刻度文字
	for (float ScaleTextZ = 0.6; ScaleTextZ <= radius + 4; ScaleTextZ += 0.6)
	{
		s.Format(_T("%.1f"), ScaleTextZ);
		x = 0, y = 0;
		Transform3Dto2D(x, y, ScaleTextZ);
		pDC->TextOutW(TransformOriginScaleX(x + 0.15), TransformOriginScaleY(y + 0.12), s);
	}

	// 绘制函数图的Title
	x = 0, y = 0;
	Transform3Dto2D(x, y, radius + 5);
	pDC->TextOutW(TransformOriginScaleX(x + 3), TransformOriginScaleY(y), CString("x^2 + y^2 + z^2 = r^2"));

	// -------------------- 绘制函数 -------------------------

	// 球面
	float phi, theta;
	for (phi = 0; phi < 2 * PI; phi += stepPhi)
	{
		for (theta = 0; theta < PI; theta += stepTheta)
		{
			x = radius * sin(phi) * cos(theta);
			y = radius * sin(phi) * sin(theta);
			z = radius * cos(phi);

			Transform3Dto2D(x, y, z);

			srand(z);
			pDC->SetPixel(TransformOriginScaleX(x), TransformOriginScaleY(y), RGB(rand() % 255, rand() % 255, rand() % 255));
		}
	}


	 三棱锥(测试用)
	//x = 1, y = 0, z = 0;
	//Transform3Dto2D(x, y, z);
	//pDC->MoveTo((int)TransformOriginScaleX(x), (int)TransformOriginScaleY(y));

	//x = 0, y = 1, z = 0;
	//Transform3Dto2D(x, y, z);
	//pDC->LineTo((int)TransformOriginScaleX(x), (int)TransformOriginScaleY(y));

	//x = 0, y = 0, z = 1;
	//Transform3Dto2D(x, y, z);
	//pDC->LineTo((int)TransformOriginScaleX(x), (int)TransformOriginScaleY(y));

	//x = 1, y = 0, z = 0;
	//Transform3Dto2D(x, y, z);
	//pDC->LineTo((int)TransformOriginScaleX(x), (int)TransformOriginScaleY(y));

}

效果图

GraphicsExercise3DCapture

/*=============================================== 作者:LXZ-2008 FROM:CUMT 计08级 时间:2012-04-22 功能:能在SDK、MFC编程中实现笛卡尔 坐标系统的绘制,以及曲线,点的绘制。 特性:1.本程序采用面向对象思想设计; 2.具备很好的独立性,随时可以把这两个文件应 用在任意SDK、MFC开发中; 3.有良好的灵活性,扩展性,易用性,在稍微扩 展一下可以绘制任意曲线,图形; 4.具备良好的组合性,符合模块内高内聚,模块 外低耦合的思路; 5.整个程序仅有1300行左右代码,如果嫌代码过 多,可以把原先变量的PROTECTED保护型打开, 换成PUBLIC,这样去掉GET和SET函数,这个思路 起源于我对J2EE中STRUTS2框架的学习以及对COM 组件技术的了解,它们也是这种思路这时可以省 下几百行代码。 6.当然也会有设计模式的思路在里面。 个人说明: 本程序花了我将近2天的时间编写,尽管开始有点 不想,但是还是觉得有意义,能给广大网友提供益处。 本系统的雏形来自2010年下半年的程序,当时花了 10天时间,弄了3千行代码。在现在看来当时的程序的 执行效率未必比现在的低,但是可维护性糟糕,可拓展 性糟糕,不具备良好的灵活性。需求改变了,代码会大 幅改变。换句话说,现在看来当时的程序是十分糟糕的, 生命周期已经结束。 而在用了面向对象的思想和设计模式,以及一些数 据结构去重新搭建这个系统的时候,代码其实1千多行就 搞定了,时间3-4天,不需要那么多(现在我来弄的话)。 主要起源于自己参与真实的有数十万代码的项目的开发, 这样提高了对程序开发的认识,以及商业程序应该如何 开发。同时也是自己面向对象思想和设计模式学习,对 自身思想的提高。 希望阅读代码的人觉得这些代码是优雅的,这就满 足了,尽管注释少了些,你们自己加吧。 QQ:706625262 E-MAIL:706625262@qq.com 不做商业和技术支持。 声明: 本程序代码未经本人同意,或者未给我MONEY的前提下, 不得用于商业目的,别让我鄙视你。在非商业目的使用 下请注明本人是原创,表学腾讯。 ==================================================*/
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值