C语言画一个正方体

news2024/11/24 19:27:06

程序截图

 

操作方法

鼠标拖动。左键拖动及滚轮能看到不同角度下正方体的形状,右键拖动能将最近的正方体顶点挪到这个投影面的相应位置。

按键控制。wasd 控制投影面旋转,ws 关于 x 轴旋转,ad 关于 y 轴旋转。

个人思路

首先投影面的确立需要两个量,一个 x 轴方向的单位向量,一个 y 轴方向的单位向量,求原点与三维空间中的点的连线到这两个单位向量的投影就能得到三维空间中的点在二维投影面中的坐标。记 x 轴方向单位向量为 X,y 轴方向单位向量为 Y,X 与 Y 互相垂直,模都为 1。

正方体的八个顶点位置随意,X,Y 两个单位向量只需是互相垂直的非零向量即可。

鼠标横向拖动时,X 关于 Y 旋转,这是怎么做到的呢。要做到这一点,就需要一个新的向量,也就是投影面的法向量,投影面的法向量可以根据两向量叉乘得到。叉乘的推法用的是线性代数的方法,但是不好理解,我用我的方法推出来,希望能方便理解。

设投影面法向量为 Z(x2, y2, z2),X(x0, y0, z0) 和 Y(x1, y1, z1) 与 Z 的点乘为 0,这就能列出两个式子:

① x0x2+y0y2+z0z2 = 0

② x1x2+y1y2+z1z2 = 0

将①式转化为以 x2 和 y2 表示的 z2 得

③ z2 = - (x0x2 + y0y2) / z0

将②式和③式结合转化为以 x2 表示的 y2 得

④ y2 = (x0z1 - x1z0) / (y1z0 - y0z1) * x2

④式代回③式得

⑤ z2 = (x1y0 - x0y1) / (y1z0 - y0z1) * x2 (推这一步时负号别忘了看)

也就是 Z = [x2, (x0z1 - x1z0) / (y1z0 - y0z1) * x2, (x1y0 - x0y1) / (y1z0 - y0z1) * x2]

仔细观察 Z 只有一个变量 x2,不妨先放大(y1z0 - y0z1)倍,得到

Z = [(y1z0 - y0z1) * x2, (x0z1 - x1z0) * x2, (x1y0 - x0y1) * x2]

把 x2 提取出来,Z 就是 Z = x2 * [(y1z0 - y0z1), (x0z1 - x1z0), (x1y0 - x0y1)]

令 x2 = 1,Z[(y1z0 - y0z1), (x0z1 - x1z0), (x1y0 - x0y1)] 就是投影面的法向量。到这一步还没有结束,因为垂直于一个面的法向量有两个,一个符合右手坐标系,一个符合左手坐标系,将 X(1, 0, 0),Y(0, 1, 0) 代入得 Z(0, 0, -1),所以这个 Z 是符合左手坐标系的投影面法向量,要转换成右手坐标系只需乘个 -1 就行,也就是 Z 最终为 (y0z1 - y1z0, x1z0 - x0z1, x0y1 - x1y0)。

由于 X,Y 都是单位向量,这个 Z 还是投影面的单位法向量。

Z 求出来了,X 关于 Y 旋转可以看做 X 在 XOZ 平面上旋转,问题转化成了求平面中某个向量转过θ度后的向量,如下图,将 X 看做下图中的红色向量,Z 看做下图中的绿色向量,虚线为向量旋转后θ度后的向量,可以发现 cos(θ)X - sin(θ)Z,就能求出 X 顺时针转动θ度后的向量,而 cos(θ)Z + sin(θ)X 就能求出 Z 顺时针转动θ度后的向量。

 

其它的旋转方式皆可以此类推。

代码实现

TCW_GUI.h:

#pragma once
#include<graphics.h>
#include<string>
#include<list>
#include<functional>
#define TCW_GUI_BUTTON_MYSELF 0


namespace TCW_GUI
{
	enum class State
	{
		general = 0,
		touch = 1,
		press = 2,
		release = 3,
		forbidden = 4
	};

	class Vec2
	{
	public:
		double x, y;
		Vec2() :x(0), y(0) {}
		Vec2(double xx, double yy) :x(xx), y(yy) {};
		Vec2 operator+(Vec2 num)
		{
			return Vec2(x + num.x, y + num.y);
		}
		Vec2 operator-(Vec2 num)
		{
			return Vec2(x - num.x, y - num.y);
		}
		Vec2 operator/(double num)
		{
			return Vec2(x / num, y / num);
		}
		Vec2 operator*(double num)
		{
			return Vec2(x * num, y * num);
		}
	};

	class Rect
	{
	public:
		Rect() :size(), position() {}
		Rect(Vec2 position, Vec2 size) :size(size), position(position) {}
		Vec2 size;
		Vec2 position;
		bool isInRect(Vec2 point)
		{
			Vec2 left_top = position - size / 2.0;
			Vec2 right_buttom = position + size / 2.0;
			if (point.x >= left_top.x && point.y >= left_top.y &&
				point.x <= right_buttom.x && point.y <= right_buttom.y)return true;
			return false;
		}
	};

	class Button
	{
	private:
		double textsize = 20;
		double textareasize = 0.9;
		Vec2 defaultsize = Vec2(textwidth(L"...") / textareasize, textheight(L"...") / textareasize);
		Vec2 defaulttext = Vec2(textwidth(L"..."), textheight(L"..."));
		State nowstate = State::general;
		void DrawButton_General();
		void DrawButton_Touch();
		void DrawButton_Press();
		void DrawButton_Forbidden();
		bool isPress = false;
	public:
		Button() :boundingbox(), buttontext() {}
		Button(Rect boundingbox, std::wstring buttontext, std::function<int(void*)> releaseFunc, void* releaseParam) :
			boundingbox(boundingbox), buttontext(buttontext), releaseFunc(releaseFunc), releaseParam(releaseParam) {}
		std::wstring buttontext;
		Rect boundingbox;
		std::function<int(void*)> releaseFunc = nullptr;
		void* releaseParam = nullptr;
		void DrawButton();
		void receiver(ExMessage* msg);
		void ForbidButton() { this->nowstate = State::forbidden; }	// 禁用按钮
		void RefreshButton() { this->nowstate = State::general; }	// 恢复按钮
		void SetTextSize(double size)
		{
			textsize = size;
			defaultsize = Vec2(textsize * 1.5 / textareasize, textsize / textareasize);
			defaulttext = Vec2(textsize * 1.5, textsize);
		}
		void SetTextAreaSize(double size)
		{
			textareasize = size;
			defaultsize = Vec2(textsize * 1.5 / textareasize, textsize / textareasize);
			defaulttext = Vec2(textsize * 1.5, textsize);
		}
	};

	class ButtonManager
	{
		std::list<Button> buttonlist;
	public:
		Button* AddButton(Button button);
		void ReceiveMessage(ExMessage* msg);
		void DrawButton();
	};

	void Rectangle_TCW(Vec2 left_top, Vec2 right_buttom)
	{
		rectangle(left_top.x, left_top.y, right_buttom.x, right_buttom.y);
	}

	void Fillrectangle_TCW(Vec2 left_top, Vec2 right_buttom)
	{
		fillrectangle(left_top.x, left_top.y, right_buttom.x, right_buttom.y);
	}

	void Outtextxy_TCW(Vec2 position, const WCHAR* str)
	{
		outtextxy(position.x, position.y, str);
	}

	void Button::DrawButton_General()
	{
		LOGFONT log;
		COLORREF textcol;
		COLORREF linecol;
		COLORREF fillcol;
		int bkmode;
		gettextstyle(&log);
		bkmode = getbkmode();
		textcol = gettextcolor();
		linecol = getlinecolor();
		fillcol = getfillcolor();

		settextstyle(textsize, 0, TEXT("微软雅黑"));
		settextcolor(BLACK);
		setbkmode(TRANSPARENT);
		setlinecolor(BLACK);
		setfillcolor(WHITE);
		Vec2 size_button = Vec2(this->boundingbox.size * textareasize);
		Vec2 size_text = Vec2(textwidth(this->buttontext.c_str()), textsize);

		if (boundingbox.size.x > defaultsize.x && boundingbox.size.y > defaultsize.y)
		{
			Rectangle_TCW(this->boundingbox.position - this->boundingbox.size / 2.0,
				this->boundingbox.position + this->boundingbox.size / 2.0);
			Fillrectangle_TCW(this->boundingbox.position - this->boundingbox.size / 2.0,
				this->boundingbox.position + this->boundingbox.size / 2.0);
			if (size_button.x >= size_text.x && size_button.y >= size_text.y)
			{
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, buttontext.c_str());
			}
			else
			{
				int wordnum = size_button.x / textwidth(buttontext.c_str()) * buttontext.size() - 2;
				std::wstring realstr = buttontext.substr(0, wordnum);
				realstr += L"...";
				size_text = Vec2(textwidth(realstr.c_str()), textsize);
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, realstr.c_str());
			}
		}
		else
		{
			Rectangle_TCW(this->boundingbox.position - this->defaultsize / 2.0,
				this->boundingbox.position + this->defaultsize / 2.0);
			Fillrectangle_TCW(this->boundingbox.position - this->defaultsize / 2.0,
				this->boundingbox.position + this->defaultsize / 2.0);
			if (defaulttext.x >= size_text.x && defaulttext.y >= size_text.y)
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, buttontext.c_str());
			else Outtextxy_TCW(this->boundingbox.position - defaulttext / 2.0, TEXT("..."));
		}

		settextstyle(&log);
		settextcolor(textcol);
		setbkmode(bkmode);
		setlinecolor(linecol);
		setfillcolor(fillcol);
	}

	void Button::DrawButton_Touch()
	{
		LOGFONT log;
		COLORREF textcol;
		COLORREF linecol;
		COLORREF fillcol;
		int bkmode;
		gettextstyle(&log);
		bkmode = getbkmode();
		textcol = gettextcolor();
		linecol = getlinecolor();
		fillcol = getfillcolor();

		settextstyle(textsize, 0, TEXT("微软雅黑"));
		settextcolor(BLACK);
		setbkmode(TRANSPARENT);
		setlinecolor(BLACK);
		setfillcolor(RGB(240, 240, 240));
		Vec2 size_button = Vec2(this->boundingbox.size * textareasize);
		Vec2 size_text = Vec2(textwidth(this->buttontext.c_str()), textsize);

		if (boundingbox.size.x > defaultsize.x && boundingbox.size.y > defaultsize.y)
		{
			Rectangle_TCW(this->boundingbox.position - this->boundingbox.size / 2.0,
				this->boundingbox.position + this->boundingbox.size / 2.0);
			Fillrectangle_TCW(this->boundingbox.position - this->boundingbox.size / 2.0,
				this->boundingbox.position + this->boundingbox.size / 2.0);
			if (size_button.x >= size_text.x && size_button.y >= size_text.y)
			{
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, buttontext.c_str());
			}
			else
			{
				int wordnum = size_button.x / textwidth(buttontext.c_str()) * buttontext.size() - 2;
				std::wstring realstr = buttontext.substr(0, wordnum);
				realstr += L"...";
				size_text = Vec2(textwidth(realstr.c_str()), textsize);
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, realstr.c_str());
			}
		}
		else
		{
			Rectangle_TCW(this->boundingbox.position - this->defaultsize / 2.0,
				this->boundingbox.position + this->defaultsize / 2.0);
			Fillrectangle_TCW(this->boundingbox.position - this->defaultsize / 2.0,
				this->boundingbox.position + this->defaultsize / 2.0);
			if (defaulttext.x >= size_text.x && defaulttext.y >= size_text.y)
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, buttontext.c_str());
			else Outtextxy_TCW(this->boundingbox.position - defaulttext / 2.0, TEXT("..."));
		}

		settextstyle(&log);
		settextcolor(textcol);
		setbkmode(bkmode);
		setlinecolor(linecol);
		setfillcolor(fillcol);
	}

	void Button::DrawButton_Press()
	{
		LOGFONT log;
		COLORREF textcol;
		COLORREF linecol;
		COLORREF fillcol;
		int bkmode;
		gettextstyle(&log);
		bkmode = getbkmode();
		textcol = gettextcolor();
		linecol = getlinecolor();
		fillcol = getfillcolor();

		settextstyle(textsize, 0, TEXT("微软雅黑"));	// 设置字体为宽高 20 的字,有一些字体的中文宽度为字母的两倍
		settextcolor(BLACK);
		setbkmode(TRANSPARENT);
		setlinecolor(BLACK);
		setfillcolor(RGB(240, 240, 240));
		Vec2 size_button = Vec2(this->boundingbox.size * textareasize);
		Vec2 size_text = Vec2(textwidth(this->buttontext.c_str()), textsize);

		if (boundingbox.size.x > defaultsize.x && boundingbox.size.y > defaultsize.y)
		{
			Rectangle_TCW(this->boundingbox.position - this->boundingbox.size / 2.0,
				this->boundingbox.position + this->boundingbox.size / 2.0);
			Fillrectangle_TCW(this->boundingbox.position - this->boundingbox.size / 2.0,
				this->boundingbox.position + this->boundingbox.size / 2.0);
			if (size_button.x >= size_text.x && size_button.y >= size_text.y)
			{
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0 + Vec2(2, 2), buttontext.c_str());
			}
			else
			{
				int wordnum = size_button.x / textwidth(buttontext.c_str()) * buttontext.size() - 2;
				std::wstring realstr = buttontext.substr(0, wordnum);
				realstr += L"...";
				size_text = Vec2(textwidth(realstr.c_str()), textsize);
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, realstr.c_str());
			}
		}
		else
		{
			Rectangle_TCW(this->boundingbox.position - this->defaultsize / 2.0,
				this->boundingbox.position + this->defaultsize / 2.0);
			Fillrectangle_TCW(this->boundingbox.position - this->defaultsize / 2.0,
				this->boundingbox.position + this->defaultsize / 2.0);
			if (defaulttext.x >= size_text.x && defaulttext.y >= size_text.y)
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0 + Vec2(2, 2), buttontext.c_str());
			else Outtextxy_TCW(this->boundingbox.position - defaulttext / 2.0 + Vec2(2, 2), TEXT("..."));
		}

		settextstyle(&log);
		settextcolor(textcol);
		setbkmode(bkmode);
		setlinecolor(linecol);
		setfillcolor(fillcol);
	}
	
	void Button::DrawButton_Forbidden()
	{
		LOGFONT log;
		COLORREF textcol;
		COLORREF linecol;
		COLORREF fillcol;
		int bkmode;
		gettextstyle(&log);
		bkmode = getbkmode();
		textcol = gettextcolor();
		linecol = getlinecolor();
		fillcol = getfillcolor();

		settextstyle(textsize, 0, TEXT("微软雅黑"));
		settextcolor(RGB(128, 128, 128));
		setbkmode(TRANSPARENT);
		setlinecolor(BLACK);
		setfillcolor(WHITE);
		Vec2 size_button = Vec2(this->boundingbox.size * textareasize);
		Vec2 size_text = Vec2(textwidth(this->buttontext.c_str()), textsize);

		if (boundingbox.size.x > defaultsize.x && boundingbox.size.y > defaultsize.y)
		{
			Rectangle_TCW(this->boundingbox.position - this->boundingbox.size / 2.0,
				this->boundingbox.position + this->boundingbox.size / 2.0);
			Fillrectangle_TCW(this->boundingbox.position - this->boundingbox.size / 2.0,
				this->boundingbox.position + this->boundingbox.size / 2.0);
			if (size_button.x >= size_text.x && size_button.y >= size_text.y)
			{
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, buttontext.c_str());
			}
			else
			{
				int wordnum = size_button.x / textwidth(buttontext.c_str()) * buttontext.size() - 2;
				std::wstring realstr = buttontext.substr(0, wordnum);
				realstr += L"...";
				size_text = Vec2(textwidth(realstr.c_str()), textsize);
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, realstr.c_str());
			}
		}
		else
		{
			Rectangle_TCW(this->boundingbox.position - this->defaultsize / 2.0,
				this->boundingbox.position + this->defaultsize / 2.0);
			Fillrectangle_TCW(this->boundingbox.position - this->defaultsize / 2.0,
				this->boundingbox.position + this->defaultsize / 2.0);
			if (defaulttext.x >= size_text.x && defaulttext.y >= size_text.y)
				Outtextxy_TCW(this->boundingbox.position - size_text / 2.0, buttontext.c_str());
			else Outtextxy_TCW(this->boundingbox.position - defaulttext / 2.0, TEXT("..."));
		}

		settextstyle(&log);
		settextcolor(textcol);
		setbkmode(bkmode);
		setlinecolor(linecol);
		setfillcolor(fillcol);
	}

	void Button::DrawButton()
	{
		switch (this->nowstate)
		{
		case State::general:
			DrawButton_General();
			break;
		case State::touch:
			DrawButton_Touch();
			break;
		case State::press:
			DrawButton_Press();
			break;
		case State::release:
			DrawButton_Touch();
			if (releaseFunc != nullptr)
			{
				if (releaseParam == TCW_GUI_BUTTON_MYSELF)releaseFunc(this);
				else releaseFunc(releaseParam);
			}
			this->nowstate = State::touch;
			break;
		case State::forbidden:
			DrawButton_Forbidden();
			break;
		default:
			break;
		}
	}

	void Button::receiver(ExMessage* msg)
	{
		if (this->nowstate == State::forbidden)return;
		// 先 general 后 touch 再 press 一个 release 后重新 general
		if (!isPress && !this->boundingbox.isInRect(Vec2(msg->x, msg->y)))
		{
			this->nowstate = State::general;
		}
		else if (!isPress && this->boundingbox.isInRect(Vec2(msg->x, msg->y)))
		{
			if (!msg->lbutton)
				this->nowstate = State::touch;
			else if (this->nowstate == State::touch)
			{
				isPress = true;
				this->nowstate = State::press;
			}
		}
		else if (isPress && this->boundingbox.isInRect(Vec2(msg->x, msg->y)))
		{
			if (!msg->lbutton)
			{
				isPress = false;
				this->nowstate = State::release;
			}
			else this->nowstate = State::press;
		}
		else if (isPress && !this->boundingbox.isInRect(Vec2(msg->x, msg->y)))
		{
			if (!msg->lbutton)
			{
				isPress = false;
				this->nowstate = State::general;
			}
			else this->nowstate = State::press;
		}
	}

	Button* ButtonManager::AddButton(Button button)
	{
		this->buttonlist.push_back(button);
		return &buttonlist.back();
	}

	void ButtonManager::ReceiveMessage(ExMessage* msg)
	{
		for (Button& button : this->buttonlist)
		{
			button.receiver(msg);
		}
	}

	void ButtonManager::DrawButton()
	{
		for (Button& button : this->buttonlist)
		{
			button.DrawButton();
		}
	}
}

main.cpp:


// 程序:一个正方体
// 编译环境:Visual Studio 2019,EasyX_20211109
// 

#include<math.h>
#include<conio.h>
#include"TCW_GUI.h"
#define WIDTH 640						// 窗口宽度
#define HEIGHT 480						// 窗口高度
#define PI 3.14159265					// π
#define SIDE (min(WIDTH, HEIGHT) / 4)	// 正方体边长
#define GAMEPAD (SIDE / 2)				// 手柄,控制面旋转幅度的量
#define DISPLAY 3						// 展示出来顶点的尺寸
#define ARROWS 3						// 箭头尺寸
#define PIECE 360
double FocalLength = 6;					// 观察点到投影面的距离

// 8 个顶点的颜色,用于分辨 8 个不同的点
COLORREF VertexColor[8] =
{
	RED, YELLOW, BLUE, GREEN, BROWN, MAGENTA, CYAN, WHITE
};

struct Vec2
{
	double x, y;
};

Vec2 operator*(Vec2 a, double num)
{
	return { a.x * num, a.y * num };
}

Vec2 operator+(Vec2 a, Vec2 b)
{
	return { a.x + b.x, a.y + b.y };
}

Vec2 operator-(Vec2 a, Vec2 b)
{
	return { a.x - b.x, a.y - b.y };
}

double operator*(Vec2 a, Vec2 b)
{
	return a.x * b.x + a.y * b.y;
}

Vec2 operator/(Vec2 a, double num)
{
	return { a.x / num, a.y / num };
}

// 三维向量,也可以表示一个坐标
struct Vec3
{
	double x, y, z;
};
typedef struct Vec3;

// 求两向量相减
Vec3 operator-(Vec3 a, Vec3 b)
{
	return { a.x - b.x, a.y - b.y, a.z - b.z };
}

// 求两向量相加
Vec3 operator+(Vec3 a, Vec3 b)
{
	return { a.x + b.x, a.y + b.y, a.z + b.z };
}

// 得到两向量点乘的值
double operator*(Vec3 a, Vec3 b)
{
	return a.x * b.x + a.y * b.y + a.z * b.z;
}

// 得到向量缩短 num 倍后的向量
Vec3 operator/(Vec3 a, long double num)
{
	Vec3 result;
	result.x = a.x / num;
	result.y = a.y / num;
	result.z = a.z / num;
	return result;
}

// 得到向量延长 num 倍后的向量
Vec3 operator*(Vec3 a, long double num)
{
	Vec3 result;
	result.x = a.x * num;
	result.y = a.y * num;
	result.z = a.z * num;
	return result;
}

// 得到一个向量的模长
double GetVec3Length(Vec3 vec)
{
	return sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);
}

// 得到向量 a 与向量 b 的夹角余弦值
double GetCosineOfTheAngle(Vec3 a, Vec3 b)
{
	return a * b / GetVec3Length(a) / GetVec3Length(b);
}

// 得到向量 A 在向量 B 上的投影
Vec3 GetProjectionAOntoB(Vec3 A, Vec3 B)
{
	double num = GetCosineOfTheAngle(A, B);				// 得到向量 A,B 的夹角余弦值
	double length = GetVec3Length(A) * num;					// 向量 A 的模长乘 num 为向量 A 在向量 B 上投影的模长
	Vec3 result = B * (abs(length) / GetVec3Length(B));	// 向量 B 延长 length 倍再缩短 B 的模长倍就是向量 A 在向量 B 上的投影
	// 如果 length 比 0 小说明 num 小于 0,也就是两向量夹角大于 90 度,结果要变为相反向量
	if (length > 0)return result;
	return result * (-1.0);
}

// 根据投影面 x,y 轴正方向向量求出投影面法向量
Vec3 getVerticalAxis(Vec3 AuxiliaryVector[2])
{
	double x0 = AuxiliaryVector[0].x;
	double y0 = AuxiliaryVector[0].y;
	double z0 = AuxiliaryVector[0].z;
	double x1 = AuxiliaryVector[1].x;
	double y1 = AuxiliaryVector[1].y;
	double z1 = AuxiliaryVector[1].z;
	Vec3 result = { y0 * z1 - y1 * z0, x1 * z0 - x0 * z1, x0 * y1 - x1 * y0 };
	return result;
}

// 将三维的点的值转换为在对应 xoy 面上的投影的坐标
typedef Vec3 DoubleVec3[2];
Vec2 Transform3DTo2D(Vec3 vertex, DoubleVec3 AuxiliaryVector, bool isParallel)
{
	Vec2 result;
	Vec3 tempX = GetProjectionAOntoB(vertex, AuxiliaryVector[0]);	// 得到三维向量在 x 轴上的投影
	Vec3 tempY = GetProjectionAOntoB(vertex, AuxiliaryVector[1]);	// 得到三维向量在 y 轴上的投影
	result.x = GetVec3Length(tempX);								// 得到 tempX 的模长,模长就是结果的 x 值的绝对值
	result.y = GetVec3Length(tempY);								// 得到 tempY 的模长,模长就是结果的 y 值的绝对值
	if (tempX * AuxiliaryVector[0] < 0)result.x *= -1;				// 如果 tempX 向量与 x 轴正方向的向量夹角大于 90 度,也就是向量点乘为负数,那么结果的 x 值为负数
	if (tempY * AuxiliaryVector[1] < 0)result.y *= -1;				// 如果 tempY 向量与 y 轴正方向的向量夹角大于 90 度,也就是向量点乘为负数,那么结果的 y 值为负数
	if (isParallel)return result;
	Vec3 Vec_Z = getVerticalAxis(AuxiliaryVector) * SIDE * FocalLength;
	Vec3 target = vertex - Vec_Z;
	return result * (SIDE * FocalLength / GetVec3Length(GetProjectionAOntoB(target, Vec_Z)));
}

// 画一个正方体
void drawCube(Vec3 Vertex[8], Vec3 AuxiliaryVector[2], Vec2 pericenter, bool isParallel)
{
	Vec2 Temp[8];
	for (int i = 0; i < 8; i++)
	{
		Vec2 temp = Transform3DTo2D(Vertex[i], AuxiliaryVector, isParallel);
		Temp[i] = temp;
		setfillcolor(VertexColor[i]);
		solidcircle(temp.x + pericenter.x, temp.y + pericenter.y, DISPLAY);
	}
	line(Temp[0].x + pericenter.x, Temp[0].y + pericenter.y, Temp[3].x + pericenter.x, Temp[3].y + pericenter.y);
	line(Temp[0].x + pericenter.x, Temp[0].y + pericenter.y, Temp[1].x + pericenter.x, Temp[1].y + pericenter.y);
	line(Temp[0].x + pericenter.x, Temp[0].y + pericenter.y, Temp[4].x + pericenter.x, Temp[4].y + pericenter.y);
	line(Temp[1].x + pericenter.x, Temp[1].y + pericenter.y, Temp[2].x + pericenter.x, Temp[2].y + pericenter.y);
	line(Temp[1].x + pericenter.x, Temp[1].y + pericenter.y, Temp[5].x + pericenter.x, Temp[5].y + pericenter.y);
	line(Temp[2].x + pericenter.x, Temp[2].y + pericenter.y, Temp[3].x + pericenter.x, Temp[3].y + pericenter.y);
	line(Temp[2].x + pericenter.x, Temp[2].y + pericenter.y, Temp[6].x + pericenter.x, Temp[6].y + pericenter.y);
	line(Temp[3].x + pericenter.x, Temp[3].y + pericenter.y, Temp[7].x + pericenter.x, Temp[7].y + pericenter.y);
	line(Temp[4].x + pericenter.x, Temp[4].y + pericenter.y, Temp[5].x + pericenter.x, Temp[5].y + pericenter.y);
	line(Temp[4].x + pericenter.x, Temp[4].y + pericenter.y, Temp[7].x + pericenter.x, Temp[7].y + pericenter.y);
	line(Temp[5].x + pericenter.x, Temp[5].y + pericenter.y, Temp[6].x + pericenter.x, Temp[6].y + pericenter.y);
	line(Temp[6].x + pericenter.x, Temp[6].y + pericenter.y, Temp[7].x + pericenter.x, Temp[7].y + pericenter.y);
	char arr[128];
	WCHAR ano[128];
	settextstyle(0, 0, _T("Consolas"));
	settextcolor(WHITE);
	sprintf_s(arr, "x:(%f, %f, %f)", AuxiliaryVector[0].x, AuxiliaryVector[0].y, AuxiliaryVector[0].z);
	MultiByteToWideChar(CP_ACP, 0, arr, -1, ano, 128);
	outtextxy(10, HEIGHT / 6 * 5, ano);
	sprintf_s(arr, "y:(%f, %f, %f)", AuxiliaryVector[1].x, AuxiliaryVector[1].y, AuxiliaryVector[1].z);
	MultiByteToWideChar(CP_ACP, 0, arr, -1, ano, 128);
	outtextxy(10, HEIGHT / 9 * 8, ano);
}

// 得到两个点之间的距离(二维)
double getTwoPointDistance(Vec2 a, Vec2 b)
{
	return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

// 得到 8 个顶点中距离 point 这个二维点最近的点,p 是判断两点间距离是否符合要求的
int getNearestIndex(Vec3 Vertex[8], Vec3 AuxiliaryVector[2], Vec2 point, bool isParallel, bool (*p)(double) = nullptr)
{
	int result = 0;
	double nearestDistance = getTwoPointDistance(Transform3DTo2D(Vertex[0], AuxiliaryVector, isParallel), point);
	for (int i = 1; i < 8; i++)
	{
		double temp = getTwoPointDistance(Transform3DTo2D(Vertex[i], AuxiliaryVector, isParallel), point);
		if (temp < nearestDistance)
		{
			result = i;
			nearestDistance = temp;
		}
	}
	if (p != nullptr && !p(nearestDistance))return -1;
	return result;
}

void Line(Vec2 begin, Vec2 end)
{
	line(begin.x, begin.y, end.x, end.y);
}

// grading 是粒度,一条线分为几份,0.1 是 10 份
void progressiveLine(Vec3 begincol, Vec3 endcol, Vec2 started, Vec2 finaled, double grading = 0.1)
{
	Vec2 AddLine = (finaled - started) * grading;
	Vec3 AddCol = (endcol - begincol) * grading;
	Vec3 nowcol = begincol;
	for (int i = 0; i < 1 / grading; i++)
	{
		nowcol = nowcol + AddCol;
		setlinecolor(RGB(nowcol.x, nowcol.y, nowcol.z));
		Line(started + AddLine * i, started + AddLine * (i + 1));
	}
}

// 画坐标轴,pericenter 是中心点,size 是一个单位长度的长度
void drawCoordinateAxis(Vec2 pericenter, double size)
{
	setlinestyle(PS_SOLID, 1);
	setlinecolor(WHITE);
	settextcolor(WHITE);
	settextstyle(20, 0, _T("Consolas"));
	double index_X = size * sqrt(3) / sqrt(5);
	double index_Y = size * sqrt(2) / sqrt(5);
	progressiveLine({ 0, 0, 0 }, { 255, 255, 255 }, pericenter + Vec2({ -index_X, -index_Y }),
		pericenter + Vec2({ index_X, index_Y }));
	line(pericenter.x + index_X - ARROWS, pericenter.y + index_Y, pericenter.x + index_X, pericenter.y + index_Y);
	line(pericenter.x + index_X, pericenter.y + index_Y - ARROWS, pericenter.x + index_X, pericenter.y + index_Y);
	outtextxy(pericenter.x + index_X, pericenter.y + index_Y, L"y");

	progressiveLine({ 0, 0, 0 }, { 255, 255, 255 }, pericenter + Vec2({ index_X, -index_Y }),
		pericenter + Vec2({ -index_X, +index_Y }));
	line(pericenter.x - index_X + ARROWS, pericenter.y + index_Y, pericenter.x - index_X, pericenter.y + index_Y);
	line(pericenter.x - index_X, pericenter.y + index_Y - ARROWS, pericenter.x - index_X, pericenter.y + index_Y);
	outtextxy(pericenter.x - index_X, pericenter.y + index_Y, L"x");

	progressiveLine({ 0, 0, 0 }, { 255, 255, 255 }, pericenter + Vec2({ 0, index_X }),
		pericenter + Vec2({ 0, -index_X }));
	line(pericenter.x + ARROWS, pericenter.y - index_X + ARROWS, pericenter.x, pericenter.y - index_X);
	line(pericenter.x - ARROWS, pericenter.y - index_X + ARROWS, pericenter.x, pericenter.y - index_X);
	outtextxy(pericenter.x, pericenter.y - index_X - 20, L"z");
}

// 画出辅助向量
void drawAuxiliaryVector(Vec3 AuxiliaryVector[2], Vec2 pericenter, double size, bool isParallel)
{
	settextstyle(20, 0, _T("Consolas"));
	Vec3 Auxiliary[2] = { {-1, 1, 0}, {-1, -1, 1} };
	Auxiliary[0] = Auxiliary[0] / GetVec3Length(Auxiliary[0]);
	Auxiliary[1] = Auxiliary[1] / GetVec3Length(Auxiliary[1]);
	Vec2 temp[2];
	temp[0] = Transform3DTo2D(AuxiliaryVector[0] * size, Auxiliary, isParallel);	// x 轴
	temp[1] = Transform3DTo2D(AuxiliaryVector[1] * size, Auxiliary, isParallel);	// y 轴
	double Cos_XX = GetCosineOfTheAngle(getVerticalAxis(Auxiliary), AuxiliaryVector[0]);
	double Cos_YY = GetCosineOfTheAngle(getVerticalAxis(Auxiliary), AuxiliaryVector[1]);
	if (Cos_XX < 0.0 && Cos_YY < 0.0)
	{
		setlinestyle(PS_SOLID, 2);
		progressiveLine({ 127, 0, 0 }, { 127 + 127 * Cos_XX, 0, 0 }, pericenter,
			pericenter + Vec2({ temp[0].x, -temp[0].y }));
		progressiveLine({ 127, 0, 0 }, { 127 + 127 * Cos_YY, 0, 0 }, pericenter,
			pericenter + Vec2({ temp[1].x, -temp[1].y }));
		drawCoordinateAxis(pericenter, size);
	}
	else if (Cos_XX >= 0.0 && Cos_YY < 0.0)
	{
		setlinestyle(PS_SOLID, 2);
		progressiveLine({ 127, 0, 0 }, { 127 + 127 * Cos_YY, 0, 0 }, pericenter,
			pericenter + Vec2({ temp[1].x, -temp[1].y }));
		drawCoordinateAxis(pericenter, size);
		setlinestyle(PS_SOLID, 2);
		progressiveLine({ 127, 0, 0 }, { 127 + 127 * Cos_XX, 0, 0 }, pericenter,
			pericenter + Vec2({ temp[0].x, -temp[0].y }));
	}
	else if (Cos_XX < 0.0 && Cos_YY >= 0.0)
	{
		setlinestyle(PS_SOLID, 2);
		progressiveLine({ 127, 0, 0 }, { 127 + 127 * Cos_XX, 0, 0 }, pericenter,
			pericenter + Vec2({ temp[0].x, -temp[0].y }));
		drawCoordinateAxis(pericenter, size);
		setlinestyle(PS_SOLID, 2);
		progressiveLine({ 127, 0, 0 }, { 127 + 127 * Cos_YY, 0, 0 }, pericenter,
			pericenter + Vec2({ temp[1].x, -temp[1].y }));
	}
	else if (Cos_XX >= 0.0 && Cos_YY >= 0.0)
	{
		drawCoordinateAxis(pericenter, size);
		setlinestyle(PS_SOLID, 2);
		progressiveLine({ 127, 0, 0 }, { 127 + 127 * Cos_XX, 0, 0 }, pericenter,
			pericenter + Vec2({ temp[0].x, -temp[0].y }));
		progressiveLine({ 127, 0, 0 }, { 127 + 127 * Cos_YY, 0, 0 }, pericenter,
			pericenter + Vec2({ temp[1].x, -temp[1].y }));
	}
	settextcolor(RED);
	outtextxy(pericenter.x + temp[0].x, pericenter.y - temp[0].y, L"X");
	outtextxy(pericenter.x + temp[1].x, pericenter.y - temp[1].y, L"Y");
	setlinestyle(PS_SOLID, 1);
	setlinecolor(WHITE);
}

// x 轴固定在 xoy 平面上,旋转 x 轴和 z 轴就能看到这个三维物体的所有角度!!!
int main()
{
	bool isExit = false;
	initgraph(WIDTH, HEIGHT);
	BeginBatchDraw();
	Vec3 AuxiliaryVector[2] = { { sqrt(2) / 2.0, sqrt(2) / 2.0, 0 },
		{ -sqrt(3) / 3.0, sqrt(3) / 3, sqrt(3) / 3.0 } };	// 辅助向量,分别是 x 轴,y 轴的单位向量

	bool isParallel = false;

	TCW_GUI::Button* button_param[2];

	TCW_GUI::ButtonManager manager;
	TCW_GUI::Button* button_temp = manager.AddButton(TCW_GUI::Button(TCW_GUI::Rect(TCW_GUI::Vec2(WIDTH * 5 / 6.0, HEIGHT / 6.0),
		TCW_GUI::Vec2(WIDTH / 7.0, HEIGHT / 7.0)), L"透视投影",
		[&](void* param)
		{
			TCW_GUI::Button** button = (TCW_GUI::Button**)param;
			if (isParallel)
			{
				button[0]->buttontext = L"透视投影";
				button[1]->RefreshButton();
				isParallel = false;
			}
			else
			{
				button[0]->buttontext = L"平行投影";
				button[1]->ForbidButton();
				isParallel = true;
			}
			return 0;
		}, nullptr));
	button_temp->releaseParam = button_param;
	button_param[0] = button_temp;

	button_param[1] = manager.AddButton(TCW_GUI::Button(TCW_GUI::Rect(TCW_GUI::Vec2(WIDTH * 5 / 6.0, HEIGHT / 3.0),
		TCW_GUI::Vec2(WIDTH / 7.0, HEIGHT / 7.0)), L"透视距离",
		[&](void* param)
		{
			WCHAR arr[128];
			char ano[128];
			InputBox(arr, 128, L"请输入透视距离(推荐 1~10, 可小数)");
			WideCharToMultiByte(CP_UTF8, 0, arr, -1, ano, 128, NULL, FALSE);
			sscanf(ano, "%lf", &FocalLength);
			return 0;
		}, nullptr));

	manager.AddButton(TCW_GUI::Button(TCW_GUI::Rect(TCW_GUI::Vec2(WIDTH * 5 / 6.0, HEIGHT / 2.0),
		TCW_GUI::Vec2(WIDTH / 7.0, HEIGHT / 7.0)), L"结束程序",
		[](void* param)
		{
			bool* isExit = (bool*)param;
			*isExit = true;
			return 0;
		}, &isExit));

	Vec3 Vertex[8];										// 8 个顶点的坐标
	// 初始化 8 个顶点,这 8 个顶点是固定的,可以改变为任意坐标值,我们只是从不同的角度看这 8 个顶点
	Vertex[0] = { -GAMEPAD, -GAMEPAD, -GAMEPAD };
	Vertex[1] = { GAMEPAD, -GAMEPAD, -GAMEPAD };
	Vertex[2] = { GAMEPAD, GAMEPAD, -GAMEPAD };
	Vertex[3] = { -GAMEPAD, GAMEPAD, -GAMEPAD };
	Vertex[4] = { -GAMEPAD, -GAMEPAD, GAMEPAD };
	Vertex[5] = { GAMEPAD, -GAMEPAD, GAMEPAD };
	Vertex[6] = { GAMEPAD, GAMEPAD, GAMEPAD };
	Vertex[7] = { -GAMEPAD, GAMEPAD, GAMEPAD };
	ExMessage msg;							// 鼠标信息
	bool ispress = false;					// 是否按下
	bool isLpress = false;					// 是否按下左键
	bool isRpress = false;					// 是否按下右键
	double originalX = 0, originalY = 0;		// 原来的坐标
	int vertexIndex = 0;					// 右键点击时要操作的顶点的坐标
	while (!isExit)
	{
		while (peekmessage(&msg, EM_MOUSE))
		{
			if (!ispress && (msg.lbutton || msg.rbutton))
			{
				ispress = true;
				if (msg.lbutton)isLpress = true;										// 左键按下
				else if (msg.rbutton)													// 右键按下
				{
					isRpress = true;
					vertexIndex = getNearestIndex(Vertex, AuxiliaryVector, 				// 得到距离按下的位置最近的正方体顶点的下标
						{ (double)msg.x - WIDTH / 2, (double)msg.y - HEIGHT / 2 }, isParallel,
						[](double num) {if (num < DISPLAY)return true; return false; });	// 这个 lambda 表达式是为了让点到的地方距离最近的正方体顶点距离小于展示出来正方体顶点的尺寸才能生效
				}
				originalX = msg.x;
				originalY = msg.y;
			}
			else if (isLpress && msg.lbutton)
			{
				double DelFi = (msg.y - originalY) / 6 / GAMEPAD * PI;
				double DelTh = (msg.x - originalX) / GAMEPAD / 6 * PI;
				Vec3 tempVectorX = AuxiliaryVector[0];
				Vec3 tempVectorY = AuxiliaryVector[1];
				Vec3 tempVectorZ = getVerticalAxis(AuxiliaryVector);

				AuxiliaryVector[0] = tempVectorX * cos(DelTh) + tempVectorZ * sin(DelTh);	// 改变 x 轴向量
				tempVectorZ = tempVectorZ * cos(DelTh) - tempVectorX * sin(DelTh);
				AuxiliaryVector[1] = tempVectorY * cos(DelFi) + tempVectorZ * sin(DelFi);	// 改变 y 轴向量
				originalX = msg.x;
				originalY = msg.y;
			}
			else if (isRpress && msg.rbutton && vertexIndex != -1)
			{
				double lengthX = msg.x - originalX;			// 在投影面横坐标上移动的距离
				double lengthY = msg.y - originalY;			// 在投影面纵坐标上移动的距离
				// 对于选中的顶点,它变为它自身的向量加上投影面上的向量
				Vertex[vertexIndex] =
					Vertex[vertexIndex] +
					AuxiliaryVector[0] * lengthX / GetVec3Length(AuxiliaryVector[0]) +
					AuxiliaryVector[1] * lengthY / GetVec3Length(AuxiliaryVector[1]);
				originalX = msg.x;
				originalY = msg.y;
			}
			else if (ispress && !msg.lbutton)
			{
				ispress = false;
				isLpress = false;
				isRpress = false;
			}
			else if (msg.wheel)
			{
				double DelTh = msg.wheel / 120.0 * PI / 60.0;	// 滚动 120 度旋转 3 度
				Vec3 tempVectorX = AuxiliaryVector[0];
				Vec3 tempVectorY = AuxiliaryVector[1];
				Vec3 tempVectorZ = getVerticalAxis(AuxiliaryVector);
				AuxiliaryVector[0] = tempVectorX * cos(DelTh) + tempVectorY * sin(DelTh);	// 改变 x 轴向量
				AuxiliaryVector[1] = tempVectorY * cos(DelTh) - tempVectorX * sin(DelTh);	// 改变 y 轴向量
			}
		}
		// 用鼠标不能进行精密控制,在这里用 wasd 实现键盘控制
		if (_kbhit())
		{
			// 按一下移动 3 度
			double DelFi = 0, DelTh = 0;
			Vec3 tempVectorX = AuxiliaryVector[0];
			Vec3 tempVectorY = AuxiliaryVector[1];
			Vec3 tempVectorZ = getVerticalAxis(AuxiliaryVector);
			switch (_getch())
			{
			case 'w':DelFi -= PI / 60.0;	break;
			case 'a':DelTh += PI / 60.0;	break;
			case 's':DelFi += PI / 60.0;	break;
			case 'd':DelTh -= PI / 60.0;	break;
			default:
				break;
			}
			AuxiliaryVector[0] = tempVectorX * cos(DelTh) + tempVectorZ * sin(DelTh);	// 改变 x 轴向量
			tempVectorZ = tempVectorZ * cos(DelTh) - tempVectorZ * sin(DelTh);
			AuxiliaryVector[1] = tempVectorY * cos(DelFi) + tempVectorZ * sin(DelFi);	// 改变 y 轴向量
		}
		cleardevice();
		drawCube(Vertex, AuxiliaryVector, { WIDTH / 2.0, HEIGHT / 2.0 }, isParallel);
		drawAuxiliaryVector(AuxiliaryVector, { WIDTH / 6 * 5, HEIGHT / 6 * 5 }, min(WIDTH, HEIGHT) / 9,
			isParallel);
		manager.ReceiveMessage(&msg);
		manager.DrawButton();
		FlushBatchDraw();
	}
	closegraph();
	return 0;
}

c语言基础学习的个人空间-c语言基础学习个人主页-哔哩哔哩视频哔哩哔哩c语言基础学习的个人空间,提供c语言基础学习分享的视频、音频、文章、动态、收藏等内容,关注c语言基础学习账号,第一时间了解UP注动态。每天分享一个编程技术C/C++游戏源码素材及各种安装包:724050348 私信不常看!https://space.bilibili.com/2061978075?spm_id_from=333.788.0.0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/141849.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【寒假第一天】LeetCode刷题

&#x1f308;一.选择题&#x1f47f;1.1.堆是一种有用的数据结构。下列那个关键码序列是一个堆&#xff08; &#xff09;。 A. 94,31,53,23,16,72 B. 94,53,31,72,16,23 C. 16,53,23,94,31,72 D. 16,31,23,94,53,72D堆排序有两种排序方法&#xff1a;大堆排序-----根结点要大…

【Kotlin】Kotlin 函数总结 ( 具名函数 | 匿名函数 | Lambda 表达式 | 闭包 | 内联函数 | 函数引用 )

文章目录一、函数头声明二、函数参数1、默认参数值2、具名参数三、Unit 函数四、TODO 函数抛出异常返回 Nothing 类型五、反引号函数名六、匿名函数七、匿名函数的函数类型八、匿名函数的隐式返回九、匿名函数参数十、匿名函数 it 关键字十一、匿名函数变量类型推断十二、匿名函…

JS中BOM 浏览器对象 提供的定时器

window对象提供了2个定时器方法&#xff1a; settTimeout()和setInterval() 1.setTimeout()定时器 语法&#xff1a; window.setTimeout(调用函数,[延迟的毫秒数]);延迟时间可以省略&#xff0c;省略则为0 用于设置一个定时器&#xff0c;该定时器再定时器到期后执行调用函数 …

【nodejs】npm与包

1、什么是包 Node.js中的第三方模块又叫包 2、包的来源 由第三方个人或团队开发出来的&#xff0c;免费供所有人使用 3、为什么需要包 由于Node.js的内置模块仅提供了一些底层的API&#xff0c;导致在基于内置模块进行项目开发时&#xff0c;效率很低。 包是基于内置模块封装出…

杨校老师课堂之IntellJ IDEA的使用技巧

下载地址&#xff1a; https://www.jetbrains.com.cn/idea/download/#sectionwindows 一、常规操作 1、忽略大小写&#xff0c;进行提示 2、启用Idea时&#xff0c;默认不再打开上次项目 3、设置主题 4、设置默认的字体 5、修改类头的文档注释信息 6、设置项目文件编码 7、统一…

electron与jquery起冲突,使用jquery报错解决方法

问题原因&#xff1a;Electron 为了整合 Node.js&#xff0c;会在 DOM 加入 module、exports、require 等模块和函数&#xff0c;和jQuery、RequireJS、Meteor、AngularJS 等发生冲突。 暴力解决方法&#xff1a;去除node功能加持&#xff0c;在加载browserWindow或者browserVi…

C++:闭包:闭包Closure理解

一&#xff1a;什么是闭包 闭包有很多定义&#xff0c;一种说法是&#xff1a;闭包是带有上下文的函数&#xff0c;说白了&#xff0c;就是有状态的函数&#xff0c;这其实就是一个类&#xff0c;换个名字而已。 一个函数&#xff0c;带上一个状态&#xff0c;就变成了闭包&…

共享模型之管程(四)

1.wait/notify 1.1.为什么需要wait? 小故事: ①.假设多个用户(线程)都需要进入房间使用算盘(CPU)进行计算工作,但是为了保证计算过程中的安全,老王设计了一把锁(Synchronized),每次只允许一个用户(线程)拿到钥匙进入房间(成为Owner线程); ②.小南(线程)费了九牛二虎之力,抢…

【Docker】搭建Zookeeper集群

【Docker】搭建Zookeeper集群 下载镜像 docker pull zookeeper:3.5.8wy:study wy$ docker pull zookeeper:3.5.8 3.5.8: Pulling from library/zookeeperDigest: sha256:12af523731cbe390f5332d6c1e254f1d56c734a786910d5582653445a5cee299 Status: Downloaded newer image f…

Allegro174版本新功能介绍之动态铜皮对单独层面参数设置

Allegro174版本新功能介绍之动态铜皮对单独层面参数设置 Allegro升级到了174版本的时候,可以支持动态铜皮对单独的层面进行参数设置,如下图 具体操作如下 在低版本166以及172的时候,只有Global Dynamic Shape Parameter设置,如下图,只有全局的铜皮参数设置升级到了174时候…

WMS智能仓储管理系统源码 SpringMVC物流仓库管理系统源码

淘源码&#xff1a;国内知名的源码免费下载平台 需要源码学习可私信我。 系统介绍&#xff1a; 基于SpringMVCHibernatMinidao&#xff08;类Mybatis&#xff09;Easyui&#xff08;UI库&#xff09; Jquery Boostrap Ehcache Redis Ztree等基础架构开发的物流仓库管理系…

人脸识别:我是如何工作的?

任何自动人脸识别过程都必须考虑导致其复杂性的几个因素&#xff0c;因为人脸是一个动态实体&#xff0c;在多个因素的影响下不断变化&#xff0c;例如光照、姿势、年龄……这三个参数中的任何一个的变化都会导致同一个人的两幅图像之间的误差值大于不同个体的两幅图像之间的误…

分享136个PHP源码,总有一款适合您

PHP源码 分享136个PHP源码&#xff0c;总有一款适合您 136个PHP源码下载链接&#xff1a;https://pan.baidu.com/s/1A5sR357dh_SlS7pu33lW1Q?pwdkzgn 提取码&#xff1a;kzgn import os# 查找指定文件夹下所有相同名称的文件 def search_file(dirPath, fileName):dirs os…

红中私教-文件上传漏洞DVWA靶场实战(浅析)

前言 &#x1f340;作者简介&#xff1a;被吉师散养、喜欢前端、学过后端、练过CTF、玩过DOS、不喜欢java的不知名学生。 &#x1f341;个人主页&#xff1a;红中 首先呢&#xff0c;针对于文件上传漏洞 这个漏洞为什么存在&#xff1f;目的是什么&#xff1f;我们为什幺要攻击…

开发环境和测试环境共用Eureka

问题描述 在开发过程中会遇到一种情况&#xff0c;那就是只需要修改一个服务&#xff0c;但是这个服务依赖了其他的3个服务&#xff0c;导致开发人员在本地也要启动其他的3个服务&#xff0c;还要启动一个Eureka注册中心。问题显而易见&#xff0c;在依赖过多的情况下&#xf…

一种多维数据库的数据事务专利解读

什么是事务&#xff1f; 事务是数据库系统中的核心机制。我们要理解下事务概念&#xff1a;什么是事务呢&#xff1f;事务是并发控制的单位&#xff0c;是用户定义的一个操作序列。有四个特性(ACID)&#xff1a; 原子性(Atomicity)&#xff1a; 事务是数据库的逻辑工作单位&…

使用 VSCode 开发的必备插件,你都安装了吗?

0️⃣前言 VSCode是由微软研发的一款免费、开源的跨平台代码编辑器&#xff0c;目前是前端开发使用最多的一款软件开发工具。 因为每个开发者所接触项目、所有技术不同, 用到的插件不同, 但总有几个插件基本是必备的, 以下就给出一些插件推荐&#xff0c;希望能给大家一些参考。…

腾讯会议发布录屏工具“会记”,让云端视频协作随用随录、随享随看

随着云端协同成为新常态&#xff0c;企业和组织沟通的形式也在不断丰富。1月5日&#xff0c;腾讯会议发布云端录屏工具“会记”&#xff0c; 用户在腾讯会议中就能自由使用人像、屏幕、声音、窗口等多种组合方式进行录制&#xff0c;录制完成后视频将自动上传至云端&#xff0c…

Centos7.9安装WebLogic详细步骤

目录 一、weblogic下载 二、准备环境 三、创建用户和组 四、安装jdk 安装 五、安装WebLogic 1、使用root用户创建目录/opt/weblogic并授权 2.创建 oraInst.loc 文件 3、创建wls.rsp 响应文件 4、安装weblogic 5、静默创建域 六、启动weblogic 一、weblogic下载 直…

程序员面试中一面、二面、三面有什么区别?

很多公司面试都分一面、二面、三面甚至更多&#xff0c;大家可能会好奇&#xff0c;为什么要面这么多面&#xff0c;每一面又有啥区别呢&#xff1f; 首先我来回答下为什么要这么多面&#xff0c;最核心的是最后3点&#xff1a; 如果光是一个人面&#xff0c;担心会看走眼&…