我想做三国志

努力学习做游戏的小白

用相机观察线条 银牌收录

程序截图

操作说明

鼠标拖动转换摄像机朝向,鼠标滚轮使摄像机往朝向方向前进或后退,中间那个圆圈是摄像机屏幕大小,再远的地方摄像机能看到的只有这么大的区域,线条不会超出这个圆圈的区域。这里观察的两条线条为互相垂直的两条线条。

编写步骤

① 坐标系转换,以相机绝对位置为原点 O 计算其他顶点的相对位置,然后以相机 X 轴,Y 轴方向的单位向量点乘顶点的相对位置,得到顶点在相机坐标系中的坐标。推荐相机坐标系用左手坐标系。

② 三维空间中设一条线 AB 的两端点  可以列出直线方程  当此直线中某点出现在屏幕边缘,则此点与 Z 轴正方向向量的夹角为视角的大小。
列出以下方程:

 

 

 

 

也就是一个简单的一元二次方程,求解得到 k 的值后完成第二步。

③ 对所求出来的结果进行分析,为了简化计算,这里进行了开平方的操作,所以实际上 k 的值包含了直线与 Z 轴方向夹角为 π - θ 的情况,过滤掉所有错误的情况后得到在屏幕内的唯二的两个点的三维坐标。此坐标 Z 轴的值应该为正值,这样比较好计算,然后令三维点变成原点到三维点的向量进行缩放操作得到一个 Z 坐标值为透视距离的三维向量,此向量的 X,Y 值即为该点透视投影后在屏幕上的坐标值。

代码实现

////////////////////////////////////////////
// 程序:用相机观察线条
// 作者:我想做三国志
// 编译环境:Visual Studio 2022,EasyX_20220901
// 编写日期:2023-5-7

#include <graphics.h>
#include <math.h>
#include <vector>
const double PI = 3.1415926536; // π
const int WIDTH = 640; 			// 屏幕宽度
const int HEIGHT = 480; 		// 屏幕高度
const int GAMEPAD = 60; 		// 游戏手柄,移动多少距离旋转 60
using std::vector;

// 三维向量
class Vec3
{
public:
	double xx, yy, zz;
	// 构造函数
	Vec3(double xx = 0, double yy = 0, double zz = 0) : xx(xx), yy(yy), zz(zz) {}
	// 向量相加
	Vec3	operator+(Vec3 num) { return Vec3(this->xx + num.xx, this->yy + num.yy, this->zz + num.zz); }
	// 向量乘法
	Vec3	operator*(double num) { return Vec3(this->xx * num, this->yy * num, this->zz * num); }
	// 向量点乘
	double	operator*(Vec3 num) { return this->xx * num.xx + this->yy * num.yy + this->zz * num.zz; }
	// 向量除法
	Vec3	operator/(double num) { return Vec3(this->xx / num, this->yy / num, this->zz / num); }
	// 向量相减
	Vec3	operator-(Vec3 num) { return Vec3(this->xx - num.xx, this->yy - num.yy, this->zz - num.zz); }
	// 得到此向量模长
	double	GetLength() { return sqrt(this->xx * this->xx + this->yy * this->yy + this->zz * this->zz); }
	// 得到两向量之间的 cos 值
	double	GetCosBetween(Vec3 num) { return (*this) * num / this->GetLength() / num.GetLength(); }
	// 得到此向量的单位向量
	Vec3	GetUnitVector() { return (*this) / this->GetLength(); }
	// 得到此向量在另一个向量上的投影
	Vec3	GetProjectionTo(Vec3 num) { return num.GetUnitVector() * (this->GetCosBetween(num) * this->GetLength()); }
	// 向量叉乘
	Vec3	MultiplicationCross(Vec3 num) { return Vec3(this->yy * num.zz - this->zz * num.yy, -this->xx * num.zz + this->zz * num.xx, this->xx * num.yy - this->yy * num.xx); }
	// 求将此向量关于 X 轴,Y 轴,Z 轴旋转 a、b、c 度后的向量
	Vec3	GetRotateVec(double a, double b, double c)
	{
		Vec3 result = this->GetUnitVector();
		result = Vec3(result.xx, result.yy * cos(a) - result.zz * sin(a), result.zz * cos(a) + result.yy * sin(a)).GetUnitVector();
		result = Vec3(result.xx * cos(b) - result.zz * sin(b), result.yy, result.zz * cos(b) + result.xx * sin(b)).GetUnitVector();
		result = Vec3(result.xx * cos(c) - result.yy * sin(c), result.yy * cos(c) + result.xx * sin(c), result.zz).GetUnitVector();
		return (result * this->GetLength());
	}
};

// 二维向量
class Vec2
{
public:
	double xx, yy;
	// 构造函数
	Vec2(double xx = 0, double yy = 0) : xx(xx), yy(yy) {}
	// 向量相加
	Vec2	operator+(Vec2 num) { return Vec2(this->xx + num.xx, this->yy + num.yy); }
	// 向量乘法
	Vec2	operator*(double num) { return Vec2(this->xx * num, this->yy * num); }
	// 向量点乘
	double	operator*(Vec2 num) { return this->xx * num.xx + this->yy * num.yy; }
	// 向量除法
	Vec2	operator/(double num) { return Vec2(this->xx / num, this->yy / num); }
	// 向量相减
	Vec2	operator-(Vec2 num) { return Vec2(this->xx - num.xx, this->yy - num.yy); }
	// 得到此向量模长
	double	GetLength() { return sqrt(this->xx * this->xx + this->yy * this->yy); }
	// 得到两向量之间的 cos 值
	double	GetCosBetween(Vec2 num) { return (*this) * num / this->GetLength() / num.GetLength(); }
	// 得到此向量的单位向量
	Vec2	GetUnitVector() { return (*this) / this->GetLength(); }
	// 得到此向量在另一个向量上的投影
	Vec2	GetProjectionTo(Vec2 num) { return num.GetUnitVector() * (this->GetCosBetween(num) * this->GetLength()); }
	// 得到此向量旋转 angle 后的向量
	Vec2	GetRotateVec(double angle) { return Vec2(this->xx * cos(angle) - this->yy * sin(angle), this->yy * cos(angle) + this->xx * sin(angle)); }
};

// Camera 得用左手坐标系,视角为 120°
class Camera
{
public:
	Vec3 XAcross, YAcross, position;
	double Angle;
	double focal_length;
	Camera()
	{
		XAcross = Vec3(1, 0, 0);
		YAcross = Vec3(0, 1, 0);
		position = Vec3(0, 0, 0);
		Angle = PI / 3;
		focal_length = 100;
		// 320/sqrt(3) 240/sqrt(3)
	}
	void HorizontalRotate(double angle)
	{
		Vec3 Z_Across = YAcross.MultiplicationCross(XAcross).GetUnitVector();
		XAcross = XAcross * cos(angle) + Z_Across * sin(angle);
		XAcross = XAcross.GetUnitVector();
	}
	void VerticalRotate(double angle)
	{
		Vec3 Z_Across = YAcross.MultiplicationCross(XAcross).GetUnitVector();
		YAcross = YAcross * cos(angle) + Z_Across * sin(angle);
		YAcross = YAcross.GetUnitVector();
	}
	void GoAhead(double times)
	{
		Vec3 Z_Across = YAcross.MultiplicationCross(XAcross).GetUnitVector();
		position = position + Z_Across * times;
	}
};

void DrawLine3D(Camera camera, Vec3 pos1, Vec3 pos2, Vec2 pericenter)
{
	bool flag0 = false, flag1 = false;
	double angle = camera.Angle;
	Vec3 ZAcross = camera.YAcross.MultiplicationCross(camera.XAcross).GetUnitVector();
	Vec3 XAcross = camera.XAcross.GetUnitVector();
	Vec3 YAcross = camera.YAcross.GetUnitVector();
	Vec3 p1 = pos1 - camera.position;
	Vec3 p2 = pos2 - camera.position;
	Vec3 transform_p1 = Vec3(p1 * XAcross, p1 * YAcross, p1 * ZAcross);		// 转化坐标系
	Vec3 transform_p2 = Vec3(p2 * XAcross, p2 * YAcross, p2 * ZAcross);		// 转化坐标系
	if (transform_p1.GetLength() < DBL_MIN || transform_p1.zz / transform_p1.GetLength() >= cos(angle))flag0 = true;	// transform_p1 在屏幕内
	if (transform_p2.GetLength() < DBL_MIN || transform_p2.zz / transform_p2.GetLength() >= cos(angle))flag1 = true;	// transform_p2 在屏幕内
	if (flag0 && flag1)
	{
		Vec3 lp0 = transform_p1 * (camera.focal_length / transform_p1.zz);
		Vec3 lp1 = transform_p2 * (camera.focal_length / transform_p2.zz);
		line(lp0.xx + pericenter.xx, lp0.yy + pericenter.yy, lp1.xx + pericenter.xx, lp1.yy + pericenter.yy);
		setfillcolor(RED);
		solidcircle(lp0.xx + pericenter.xx, lp0.yy + pericenter.yy, 3);
		setfillcolor(GREEN);
		solidcircle(lp1.xx + pericenter.xx, lp1.yy + pericenter.yy, 3);
		return;
	}
	// 这里事先过滤一下,避免计算复杂
	// 穿过圆心的应该过滤
	double del_z = transform_p2.zz - transform_p1.zz;
	double del_y = transform_p2.yy - transform_p1.yy;
	double del_x = transform_p2.xx - transform_p1.xx;
	double Cos = cos(angle) * cos(angle);
	double a = ((1 - Cos) * del_z * del_z - Cos * del_y * del_y - Cos * del_x * del_x);
	double b = 2 * ((1 - Cos) * del_z * transform_p1.zz - Cos * del_x * transform_p1.xx - Cos * del_y * transform_p1.yy);
	double c = ((1 - Cos) * transform_p1.zz * transform_p1.zz - Cos * transform_p1.xx * transform_p1.xx - Cos * transform_p1.yy * transform_p1.yy);
	double delta = b * b - 4 * a * c;
	if (delta < 0.0)return;
	else if (delta > DBL_MIN)
	{
		double k0 = (-b + sqrt(delta)) / 2 / a;
		double k1 = (-b - sqrt(delta)) / 2 / a;
		bool flag2 = false, flag3 = false;
		if (transform_p1.zz + del_z * k0 < 0)flag2 = true;	// 点三在相机背后
		if (transform_p1.zz + del_z * k1 < 0)flag3 = true;	// 点四在相机背后
		if (flag2 && flag3)return;
		if (!flag2 && !flag3 && (k0 < 0 && k1 < 0 || k0>1 && k1>1))return;
		// 至少有一个点在屏幕内
		double k2 = 0;
		double k3 = 0;
		if (!flag2 && !flag3 && (k0 >= 0 && k0 <= 1 && k1 >= 0 && k1 <= 1))
		{
			k2 = min(k0, k1);
			k3 = max(k0, k1);
		}
		else if (flag0)
		{
			k2 = 0;
			if (!flag2 && k0 >= 0 && k0 <= 1)k3 = k0;
			else if (!flag3 && k1 >= 0 && k1 <= 1)k3 = k1;
		}
		else if (flag1)
		{
			if (!flag2 && k0 >= 0 && k0 <= 1)k2 = k0;
			else if (!flag3 && k1 >= 0 && k1 <= 1)k2 = k1;
			k3 = 1;
		}
		else return;
		Vec3 lp0 = transform_p1 + (transform_p2 - transform_p1) * k2;
		Vec3 lp1 = transform_p1 + (transform_p2 - transform_p1) * k3;
		lp0 = lp0 * (camera.focal_length / lp0.zz);
		lp1 = lp1 * (camera.focal_length / lp1.zz);
		line(lp0.xx + pericenter.xx, lp0.yy + pericenter.yy, lp1.xx + pericenter.xx, lp1.yy + pericenter.yy);
		setfillcolor(RED);
		solidcircle(lp0.xx + pericenter.xx, lp0.yy + pericenter.yy, 3);
		setfillcolor(GREEN);
		solidcircle(lp1.xx + pericenter.xx, lp1.yy + pericenter.yy, 3);
	}
	else
	{
		double k = -b / 2 / a;
		bool flag = false;
		double k2 = 0, k3 = 0;
		if (transform_p1.zz + del_z * k < 0)flag = true;	// 此点在相机背后
		if (flag || !flag0 && !flag1)return;
		if (flag0)
		{
			k2 = 0;
			k3 = k;
		}
		else if (flag1)
		{
			k2 = k;
			k3 = 1;
		}
		else return;
		Vec3 lp0 = transform_p1 + (transform_p2 - transform_p1) * k2;
		Vec3 lp1 = transform_p1 + (transform_p2 - transform_p1) * k3;
		lp0 = lp0 * (camera.focal_length / lp0.zz);
		lp1 = lp1 * (camera.focal_length / lp1.zz);
		line(lp0.xx + pericenter.xx, lp0.yy + pericenter.yy, lp1.xx + pericenter.xx, lp1.yy + pericenter.yy);
		setfillcolor(RED);
		solidcircle(lp0.xx + pericenter.xx, lp0.yy + pericenter.yy, 3);
		setfillcolor(GREEN);
		solidcircle(lp1.xx + pericenter.xx, lp1.yy + pericenter.yy, 3);
	}
}

// 主函数
int main()
{
	initgraph(WIDTH, HEIGHT);
	BeginBatchDraw();
	Vec3 pos1(100, 100, 100), pos2(100, -100, 100), pos3(-100, -100, 100), pos4(-100, 100, 100);
	Vec3 pos5(100, 100, -100), pos6(100, -100, -100), pos7(-100, -100, -100), pos8(-100, 100, -100);
	Camera camera;
	camera.position = Vec3(0, 0, 200);
	bool isExit = false;
	bool isLPress = false;
	Vec2 ori_L;
	ExMessage msg;
	while (!isExit)
	{
		while (peekmessage(&msg, EM_KEY | EM_MOUSE))
		{
			if (msg.message == WM_KEYDOWN)
			{
				if (msg.vkcode == VK_ESCAPE) isExit = true;
			}
			else if (msg.message == WM_MOUSEMOVE || msg.message == WM_LBUTTONDOWN || msg.message == WM_LBUTTONUP)
			{
				if (!isLPress && msg.lbutton)
				{
					ori_L = Vec2(msg.x, msg.y);
					isLPress = true;
				}
				else if (isLPress && msg.lbutton)
				{
					Vec2 next = Vec2(msg.x, msg.y);
					ori_L = next - ori_L;
					double Th = 0;
					double Fi = 0;
					Th = ori_L.xx / GAMEPAD * PI / 3;
					Fi = ori_L.yy / GAMEPAD * PI / 3;
					camera.HorizontalRotate(Th);
					camera.VerticalRotate(Fi);
					ori_L = next;
				}
				else if (isLPress && !msg.lbutton)
				{
					isLPress = false;
				}
			}
			else if (msg.message == WM_MOUSEWHEEL)
			{
				camera.GoAhead(msg.wheel / 6);
			}
		}
		cleardevice();
		circle(WIDTH / 2, HEIGHT / 2, 100 * sqrt(3));
		DrawLine3D(camera, pos1, pos2, Vec2(WIDTH / 2, HEIGHT / 2));
		DrawLine3D(camera, pos2, pos3, Vec2(WIDTH / 2, HEIGHT / 2));
		DrawLine3D(camera, pos3, pos4, Vec2(WIDTH / 2, HEIGHT / 2));
		DrawLine3D(camera, pos4, pos1, Vec2(WIDTH / 2, HEIGHT / 2));

		DrawLine3D(camera, pos1, pos5, Vec2(WIDTH / 2, HEIGHT / 2));
		DrawLine3D(camera, pos2, pos6, Vec2(WIDTH / 2, HEIGHT / 2));
		DrawLine3D(camera, pos3, pos7, Vec2(WIDTH / 2, HEIGHT / 2));
		DrawLine3D(camera, pos4, pos8, Vec2(WIDTH / 2, HEIGHT / 2));

		DrawLine3D(camera, pos5, pos6, Vec2(WIDTH / 2, HEIGHT / 2));
		DrawLine3D(camera, pos6, pos7, Vec2(WIDTH / 2, HEIGHT / 2));
		DrawLine3D(camera, pos7, pos8, Vec2(WIDTH / 2, HEIGHT / 2));
		DrawLine3D(camera, pos8, pos5, Vec2(WIDTH / 2, HEIGHT / 2));
		FlushBatchDraw();
	}
	return 0;
}

添加评论