代码:
#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;
}
操作方式:鼠标左键移动视角,鼠标滚轮前进或后退
运行结果: