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

2023-05-16

系列链接

  • 使用MFC的CDC类绘制二维坐标系及正余弦函数 / 源码

  • 使用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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 错误: Entry在LinkedHashMap中不是公共的; 无法从外部程序包中对其进行访问

    遇到了一个很奇怪的问题 xff0c 使用LinkedHashMap来做LRU缓存时 xff0c 重写protected boolean removeEldestEntry Entry lt String String gt eldest 方
  • 【安卓真机调试】较全面的Android真机调试详解

    目录 1 启动调试功能1 1 配置设备上的开发者选项1 2 运行可调试的 build 变体 2 开始调试2 1 设置断点2 2 选择设备2 3 在工具栏中点击Debug图标2 4 打开Debug窗口2 5 将调试程序连接到正在运行的应用上
  • 如何搭建ftp服务器实现文件共享

    这里以windows系统和linux系统为例 xff0c 简单介绍一下如何在这2种系统下搭建ftp服务器 xff0c 整个过程非常简单 xff0c 感兴趣的朋友可以自己尝试一下 xff1a windows windows系统自带有ftp服务
  • Rabbitmq—— 从入门到放弃

    文章目录 背景总体架构类与方法BlockingConnection init channel BlockingChannelqueue declarequeue deleteexchange declarebasic publishbasi
  • 使用electron-vue+go写一个处理excel表格小软件(2)

    目录 问题思路go部分主要流程遇到的坑 node部分主要流程遇到的坑 源码链接 问题 使用node xlsx处理excel一次最多能处理30M的文件 xff0c 所以来个80M的话就要手动拆成3个文件 xff0c 这看起来太蠢了 xff0c
  • Gradle project sync failed的解决方法

    开发工具android studio在运行项目的时候报如下错误 xff1a Error Gradle project sync failed Please fix your project and try again 编辑gradle wr
  • Mvp契约类实践

    MVP中关于契约的用法 契约类的好处 xff1a 低耦合 接口统一管理 业务逻辑清晰 易于后期维护 以最简单的登录为例 xff1a loginContract契约类 span class token comment 契约类 span spa
  • Ubuntu Wine deepin-Wechat生成方法

    转自https ywnz com linuxjc 5530 html 更新deepin中微信的方法 1 下载官方打包的xxx deb xff0c 放至 xff5e wine app文件夹中 2 创建文件夹extract xff0c 并在ex
  • 借助Redis Bitmap实现简单的布隆过滤器

    在之前的一篇文章中 xff0c 我们已经深入理解了布隆过滤器的基本原理 xff0c 并且了解到它在缓存系统中有较多的应用 Redis提供的Bitmap正好能够作为布隆过滤器所需要的位数组的基础 xff0c 本文先简要介绍Bitmap xff
  • AndroidStudio编写编译脚本Gradle文件时没有,没有代码提示,ctrl + 点击属性时提示Cannot find declaration to go to

    问题描述 AndroidStudio编写编译脚本Gradle文件时没有 xff0c 没有代码提示 xff0c ctrl 43 点击属性时提示Cannot find declaration to go to 原因分析 xff1a gradle
  • 在Ubuntu下最靠谱的键位修改方法 ,亲测有效

    本人刚入坑linux不久 我一直在windows下工作 同样linux我也当成windows来玩 也常有改键位的需求 我曾经百度无数改键位的方法 要么就是只能改左边的ctrol和大小写键交换 右边的alt和ctrol交换失败 有的教程能交换
  • svn怎么切换分支

    项目场景 xff1a svn切换不成功 问题描述 怎么切换都不成功 原因分析 xff1a 解决方案 xff1a 1查看当前所在的位置 2点击switch 3to path选中需要的路径 xff0c ok就可以了 重复1步骤就能查看当前路径是
  • android studio识别不到夜神模拟器怎么办

    问题描述 xff1a 正常运行情况下 xff0c 夜神模拟器突然找不到了 xff1b 解决方案 xff1a 1 找到夜神模拟器的目录bin目录下 xff0c 路径栏中输入cmd回车 xff0c 进入控制台页面 2 执行命令 nox adb
  • Android 动态设置padding跟margin的问题

    padding view setPadding int left int top int right int bottom margin LayoutParams lp 61 LayoutParams view getLayoutParam
  • svn如何合并代码

    1 先提交本地代码 2 切换至要汇总代码的目标枝干 3 在目标枝干选择最外层的文件夹 xff0c 然后右击文件夹 merge 4 选择需要合并的分支 xff0c 和需要合并的日志 xff1b 5可以先test merge xff0c 然后选
  • android studio logcat 无日志 No connect devices

    解决 xff1a 去sdk tools中找到 google use driver xff0c 下载 xff0c 然后重启编译器 成功 连接不上夜神模拟器可以去夜神对应的bin目录下 xff0c 在目录框中输入cmd回车 输入nox adb
  • android findviewbyid 返回null

    findViewById返回Null 转自 xff1a http blog sina com cn s blog 5e58565701012q2d html 错误 xff1a findViewById返回Null xff0c 报nullpo
  • nox夜神模拟器连接不上android studio,用bat脚本快速输入命令

    不知道为什么android studio老是会识别不到夜神模拟器 xff1b 之前都是通过cmd 到夜神的bin目录下面 然后输入命令 xff0c 连接模拟器 现在发现一种更简单的做法 xff1b 1 创建一个文本文档 xff0c 改名的时
  • 微策略2017年秋招线下笔试+技术面+在线测评+主管面总结

    1 前言 微策略可能在国内的知名度比较小 xff0c 它是一家总部在美国 xff0c 在杭州设立研发中心 xff0c 主要做智能商用软件的外企 更多的信息 xff0c 请自行搜索 我是17年10月份面试微策略 xff0c 然后拿到的开发 x
  • Gradle Wrapper是什么

    Gradle提供了内置的Wrapper task帮助我们自动生成Wrapper所需要的目录文件 在一个项目中的根目录下执行 gradle wrapper即可生成 工程结构介绍 xff1a gradlew xff1a Linux下的可执行脚本

随机推荐