参考:https://www.bilibili.com/video/BV1QP4y1e7w5
20面体建模
背面剔除
算法设计
完整代码
// 31-背面剔除算法
// 参考 https://www.bilibili.com/video/BV1QP4y1e7w5
#define UNICODE
#include <Windows.h>
#include <Windowsx.h>
#include <math.h>
#define WINDOW_TEXT L"31-背面剔除算法"
#define ROUND(d) int(floor(d)+0.5) // 四舍五入
#define PI 3.1415926
struct Point2 // 二维点
{
double x;
double y;
double w;
Point2() :x(0), y(0), w(1) {}
Point2(double x, double y) :x(x), y(y), w(1) {}
friend Point2 operator + (Point2 pt0, Point2 pt1)
{
return Point2(pt0.x + pt1.x, pt0.y + pt1.y);
}
friend Point2 operator * (Point2 pt, double n)
{
return Point2(pt.x * n, pt.y * n);
}
friend Point2 operator * (double n, Point2 pt)
{
return Point2(pt.x * n, pt.y * n);
}
};
struct Point3 :Point2 // 三维点
{
double z;
Point3() :z(0) {}
Point3(double x, double y, double z) :Point2(x, y), z(z) {}
friend Point3 operator + (Point3 pt0, Point3 pt1)
{
return Point3(pt0.x + pt1.x, pt0.y + pt1.y, pt0.z + pt1.z);
}
friend Point3 operator * (double scalar, const Point3& pt)
{
return Point3(pt.x * scalar, pt.y * scalar, pt.z * scalar);
}
friend Point3 operator * (const Point3& pt, double scalar)
{
return Point3(pt.x * scalar, pt.y * scalar, pt.z * scalar);
}
friend Point3 operator / (const Point3& pt, double scalar)
{
return Point3(pt.x / scalar, pt.y / scalar, pt.z / scalar);
}
double DotProduct(const Point3& p0, const Point3& p1) // 向量的点积
{
return(p0.x * p1.x + p0.y * p1.y + p0.z * p1.z);
}
Point3 CrossProduct(const Point3& v0, const Point3& v1) // 向量的叉积
{
return Point3(v0.y * v1.z - v0.z * v1.y,
v0.z * v1.x - v0.x * v1.z,
v0.x * v1.y - v0.y * v1.x);
}
};
class Vector3 // 向量类
{
public:
double x;
double y;
double z;
public:
Vector3() :x(0), y(0), z(0) {};
virtual ~Vector3() {};
Vector3(double x, double y, double z) :x(x), y(y), z(z){}; // 绝对向量
Vector3(const Point3& P) { x = P.x; y = P.y; z = P.z; };
Vector3(const Point3& P0, const Point3& P1) // 相对向量
{
x = P1.x - P0.x;
y = P1.y - P0.y;
z = P1.z - P0.z;
}
double Magnitude() // 计算向量模
{
return sqrt(x * x + y * y + z * z);
}
Vector3 Normalize()// 规范化向量
{
Vector3 vector;
double magintude = sqrt(x * x + y * y + z * z);
if (fabs(magintude)<1e-4)
{
magintude = 1.0;
}
vector.x = x / magintude;
vector.y = y / magintude;
vector.z = z / magintude;
return vector;
}
friend Vector3 operator -(const Vector3& v) // 向量取反
{
return Vector3(-v.x,-v.y,-v.z);
}
friend Vector3 operator +(const Vector3& v0, const Vector3& v1)
{
return Vector3(v0.x + v1.x , v0.y + v1.y , v0.z + v1.z);
}
friend Vector3 operator -(const Vector3& v0, const Vector3& v1)
{
return Vector3(v0.x - v1.x, v0.y - v1.y, v0.z - v1.z);
}
friend Vector3 operator *(double scalar, const Vector3& v)
{
return Vector3(scalar * v.x, scalar * v.y, scalar * v.z);
}
friend Vector3 operator *(const Vector3& v,double scalar)
{
return Vector3(scalar * v.x, scalar * v.y, scalar * v.z);
}
friend Vector3 operator /(const Vector3& v,double scalar)
{
return Vector3(v.x/ scalar, v.y / scalar, v.z / scalar);
}
friend Vector3 operator +=(const Vector3& v0, const Vector3& v1)
{
return Vector3(v0.x + v1.x, v0.y + v1.y, v0.z + v1.z);
}
friend Vector3 operator -=(const Vector3& v0, const Vector3& v1)
{
return Vector3(v0.x - v1.x, v0.y - v1.y, v0.z - v1.z);
}
friend double DotProduct(const Vector3& v0, const Vector3& v1)
{
return(v0.x * v1.x + v0.y * v1.y + v0.z * v1.z);
}
friend Vector3 CrossProduct(const Vector3& v0, const Vector3& v1)
{
return Vector3(v0.y * v1.z - v0.z * v1.y,
v0.z * v1.x - v0.x * v1.z,
v0.x* v1.y - v0.y * v1.x);
}
};
class Transform3
{
public:
Point3* _P; // 顶点数组
int _ptNumber; // 顶点数量
double _M[4][4]; // 变换矩阵
public:
Transform3() :_P(NULL), _ptNumber(0)
{
memset(_M, 0, sizeof(_M));
};
~Transform3() {};
void MultiplyMatrix() //矩阵相乘
{
Point3* PTemp = new Point3[_ptNumber];
for (int i = 0; i < _ptNumber; i++)
{
PTemp[i] = _P[i];
}
for (int i = 0; i < _ptNumber; i++)
{
_P[i].x = _M[0][0] * PTemp[i].x + _M[0][1] * PTemp[i].y + _M[0][2] * PTemp[i].z + _M[3][0]; // * PTemp[i].w
_P[i].y = _M[1][0] * PTemp[i].x + _M[1][1] * PTemp[i].y + _M[1][2] * PTemp[i].z + _M[3][1]; // * PTemp[i].w
_P[i].z = _M[2][0] * PTemp[i].x + _M[2][1] * PTemp[i].y + _M[2][2] * PTemp[i].z + _M[3][2]; // * PTemp[i].w
_P[i].w = _M[3][0] * PTemp[i].x + _M[3][1] * PTemp[i].y + _M[3][2] * PTemp[i].z + _M[3][3]; // * PTemp[i].w
}
delete[]PTemp;
}
void SetMatrix(Point3* P, int ptNumber) //顶点数组初始化
{
_P = P;
_ptNumber = ptNumber;
}
void Identity()
{
_M[0][0] = 1.0, _M[0][1] = 0.0, _M[0][2] = 0.0, _M[0][3] = 0.0;
_M[1][0] = 0.0, _M[1][1] = 1.0, _M[1][2] = 0.0, _M[1][3] = 0.0;
_M[2][0] = 0.0, _M[2][1] = 0.0, _M[2][2] = 1.0, _M[2][3] = 0.0;
_M[3][0] = 0.0, _M[3][1] = 0.0, _M[3][2] = 0.0, _M[3][3] = 1.0;
}
void Translate(double tx, double ty, double tz)//平移变换
{
Identity();
_M[3][0] = tx;
_M[3][1] = ty;
_M[3][2] = tz;
MultiplyMatrix();
}
void Scale(double sx, double sy, double sz)//缩放变换
{
Identity();
_M[0][0] = sx;
_M[1][1] = sy;
_M[2][2] = sz;
MultiplyMatrix();
}
void RotateX(double beta)//绕X轴旋转变换
{
Identity();
beta = beta * PI / 180;
_M[1][1] = cos(beta), _M[1][2] = -sin(beta);
_M[2][1] = sin(beta), _M[2][2] = cos(beta);
MultiplyMatrix();
}
void RotateY(double beta)//绕Y轴旋转变换
{
Identity();
beta = beta * PI / 180;
_M[0][0] = cos(beta), _M[0][2] = sin(beta);
_M[2][0] = -sin(beta), _M[2][2] = cos(beta);
MultiplyMatrix();
}
};
struct T2
{
double u;
double v;
T2() :u(0), v(0) {}
T2(double u, double v) :u(u), v(v) {}
friend T2 operator+(const T2& t0, const T2& t1)
{
return T2(t0.u + t1.u, t0.v + t1.v);
}
friend T2 operator/(const T2& t, double n)
{
return T2(t.u / n, t.v / n);
}
};
class Projection // 透视投影
{
public:
Point3 Eye; // 视点
double R; // 视点球坐标
double Phi; //
double Theta; //
double D; //
double K[8]; // 透视常数
public:
Projection()
{
R = 1000;
D = 800;
Phi = 90;
Theta = 0; // 世界坐标z轴正上方 看向原点
InitialParameter();
}
~Projection() {}
void InitialParameter() // 初始化参数
{
K[0] = sin(PI * Theta / 180);
K[1] = sin(PI * Phi / 180);
K[2] = cos(PI * Theta / 180);
K[3] = cos(PI * Phi / 180);
K[4] = K[1] * K[2];
K[5] = K[0] * K[1];
K[6] = K[2] * K[3];
K[7] = K[0] * K[3];
Eye = Point3(R * K[5], R * K[5], R * K[4]); // 设置视点
}
void SetEye(double r, double phi, double theta) // 设置视点
{
R = r;
Phi = phi;
Theta = theta;
InitialParameter();
}
Point3 GetEye()
{
return Eye;
}
Point2 OrthographicProjection(Point3 worldPoint) // 正交投影
{
return Point2(worldPoint.x, worldPoint.y);
}
Point2 CavalierProjection(Point3 worldPoint) // 斜等侧投影
{
Point2 screenPoint; // 屏幕坐标
double cota = 1;
double beta = PI / 4;
screenPoint.x = worldPoint.x - worldPoint.z * cota * cos(beta);
screenPoint.y = worldPoint.y - worldPoint.z * cota * cos(beta);
return screenPoint;
}
Point2 CabinetProjection(Point3 worldPoint) // 斜二侧投影
{
Point2 screenPoint; // 屏幕坐标
double cota = 0.5;
double beta = PI / 4;
screenPoint.x = worldPoint.x - worldPoint.z * cota * cos(beta);
screenPoint.y = worldPoint.y - worldPoint.z * cota * cos(beta);
return screenPoint;
}
Point2 PerspectiveProjection2(Point3 worldPoint) // 二维透视投影
{
Point3 viewPoint; // 观察坐标系
viewPoint.x = K[2] * worldPoint.x - K[0] * worldPoint.z;
viewPoint.y = -K[7] * worldPoint.x + K[1] * worldPoint.y - K[6] * worldPoint.z;
viewPoint.z = -K[5] * worldPoint.x - K[3] * worldPoint.y - K[4] * worldPoint.z + R;
Point2 screenPoint; // 屏幕坐标
screenPoint.x = D * viewPoint.x / viewPoint.z;
screenPoint.y = D * viewPoint.y / viewPoint.z;
return screenPoint;
}
};
class Face
{
public:
int ptNumber; // 面的顶点数量
int ptIndex[3] ; // 面的顶点索引
public:
Face():ptNumber(0){};
~Face() {};
};
class CIcosahedro
{
public:
Point3 V[12]; // 顶点数组
Face F[20]; // 小面数组
Projection _projection; // 投影
public:
CIcosahedro()
{
ReadVertex();
ReadFace();
};
~CIcosahedro() {};
void ReadVertex()
{
const double phi = 0.618;// 黄金分割数
V[ 0].x = 0; V[ 0].y = 1; V[ 0].z = phi;
V[ 1].x = 0; V[ 1].y = 1; V[ 1].z = -phi;
V[ 2].x = 1; V[ 2].y = phi; V[ 2].z = 0;
V[ 3].x = 1; V[ 3].y = -phi; V[ 3].z = 0;
V[ 4].x = 0; V[ 4].y = -1; V[ 4].z = -phi;
V[ 5].x = 0; V[ 5].y = -1; V[ 5].z = phi;
V[ 6].x = phi; V[ 6].y = 0; V[ 6].z = 1;
V[ 7].x = -phi; V[ 7].y = 0; V[ 7].z = 1;
V[ 8].x = phi; V[ 8].y = 0; V[ 8].z = -1;
V[ 9].x = -phi; V[ 9].y = 0; V[ 9].z = -1;
V[10].x = -1; V[10].y = phi; V[10].z = 0;
V[11].x = -1; V[11].y = -phi; V[11].z = 0;
}
void ReadFace()
{
F[0].ptIndex[0] = 0; F[0].ptIndex[1] = 6; F[0].ptIndex[2] = 2;
F[1].ptIndex[0] = 2; F[1].ptIndex[1] = 6; F[1].ptIndex[2] = 3;
F[2].ptIndex[0] = 3; F[2].ptIndex[1] = 6; F[2].ptIndex[2] = 5;
F[3].ptIndex[0] = 5; F[3].ptIndex[1] = 6; F[3].ptIndex[2] = 7;
F[4].ptIndex[0] = 0; F[4].ptIndex[1] = 7; F[4].ptIndex[2] = 6;
F[5].ptIndex[0] = 2; F[5].ptIndex[1] = 3; F[5].ptIndex[2] = 8;
F[6].ptIndex[0] = 1; F[6].ptIndex[1] = 2; F[6].ptIndex[2] = 8;
F[7].ptIndex[0] = 0; F[7].ptIndex[1] = 2; F[7].ptIndex[2] = 1;
F[8].ptIndex[0] = 0; F[8].ptIndex[1] = 1; F[8].ptIndex[2] = 10;
F[9].ptIndex[0] = 1; F[9].ptIndex[1] = 9; F[9].ptIndex[2] = 10;
F[10].ptIndex[0] = 1; F[10].ptIndex[1] = 8; F[10].ptIndex[2] = 9;
F[11].ptIndex[0] = 3; F[11].ptIndex[1] = 4; F[11].ptIndex[2] = 8;
F[12].ptIndex[0] = 3; F[12].ptIndex[1] = 5; F[12].ptIndex[2] = 4;
F[13].ptIndex[0] = 4; F[13].ptIndex[1] = 5; F[13].ptIndex[2] = 11;
F[14].ptIndex[0] = 7; F[14].ptIndex[1] = 10; F[14].ptIndex[2] = 11;
F[15].ptIndex[0] = 0; F[15].ptIndex[1] = 10; F[15].ptIndex[2] = 7;
F[16].ptIndex[0] = 4; F[16].ptIndex[1] = 11; F[16].ptIndex[2] = 9;
F[17].ptIndex[0] = 4; F[17].ptIndex[1] = 9; F[17].ptIndex[2] = 8;
F[18].ptIndex[0] = 5; F[18].ptIndex[1] = 7; F[18].ptIndex[2] = 11;
F[19].ptIndex[0] = 9; F[19].ptIndex[1] = 11; F[19].ptIndex[2] = 10;
}
void Draw(HDC hdc)
{
Point2 point[3]; // 二维投影点
Point3 Eye = _projection.GetEye(); // 视点
for (int nFace = 0; nFace < 20; nFace++) // 面循环
{
Vector3 viewVector(V[F[nFace].ptIndex[0]], Eye); // 面的视向量
viewVector = viewVector.Normalize(); // 视向量规范化
Vector3 vector01(V[F[nFace].ptIndex[0]], V[F[nFace].ptIndex[1]]);
Vector3 vector02(V[F[nFace].ptIndex[0]], V[F[nFace].ptIndex[2]]);
Vector3 faceNormal = CrossProduct(vector01, vector02); // 面法向量
faceNormal = faceNormal.Normalize(); // 法向量规范
if (DotProduct(viewVector, faceNormal) >= 0) // !!!背面提出算法
{
for (int nPoint = 0; nPoint < 3; nPoint++)
{
point[nPoint] = _projection.PerspectiveProjection2(V[F[nFace].ptIndex[nPoint]]);
}
MoveToEx(hdc, point[0].x, point[0].y,NULL );
LineTo(hdc, point[0].x, point[0].y);
LineTo(hdc, point[1].x, point[1].y);
LineTo(hdc, point[2].x, point[2].y);
LineTo(hdc, point[0].x, point[0].y);
}
}
}
};
static bool m_Play = false;
static wchar_t* m_str = L"左键旋转";
CIcosahedro m_icosahedro; // 二十面体;
Transform3 m_transform;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
RECT rect;
static double alpha;
static double beta;
switch (uMsg)
{
case WM_TIMER:
alpha += 0.5;
beta += 0.5;
InvalidateRect(hwnd, NULL, FALSE);
return 0;
case WM_LBUTTONDOWN:
m_Play = !m_Play;
if (m_Play)
{
SetTimer(hwnd, 0, 0, NULL);
}
else
{
KillTimer(hwnd, 0);
}
return 0;
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
SetMapMode(hdc, MM_ANISOTROPIC);
SetWindowExtEx(hdc, rect.right, rect.bottom, NULL);
SetViewportExtEx(hdc, rect.right, -rect.bottom, NULL);
SetViewportOrgEx(hdc, rect.right / 2, rect.bottom / 2, NULL);
// 内存DC
HDC memDC = CreateCompatibleDC(hdc); // 创建兼容DC 画板
HBITMAP newBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom); // 创建画布
HGDIOBJ oldBitmap = SelectObject(memDC, newBitmap); // 将画布选入画板
FillRect(memDC, &rect, (HBRUSH)(COLOR_WINDOW + 1));
DrawText(memDC, m_str, wcslen(m_str), &rect, NULL);
SetMapMode(memDC, MM_ANISOTROPIC);
SetWindowExtEx(memDC, rect.right, rect.bottom, NULL);
SetViewportExtEx(memDC, rect.right, -rect.bottom, NULL);
SetViewportOrgEx(memDC, rect.right / 2, rect.bottom / 2, NULL);
// 绘制图形
{
double scale = 300;
m_icosahedro.ReadFace();
m_icosahedro.ReadVertex();
m_transform.SetMatrix(m_icosahedro.V, 12); // 设置顶点
m_transform.Scale(scale, scale, scale); // 放大正二十面体
if (m_Play)
{
m_transform.RotateX(alpha);
m_transform.RotateY(beta);
}
m_icosahedro.Draw(memDC);
}
// 内存dc复制到设备
BitBlt(hdc, ROUND(-rect.right / 2), ROUND(-rect.bottom / 2), rect.right, rect.bottom, memDC, ROUND(-rect.right / 2), ROUND(-rect.bottom / 2), SRCCOPY);
SelectObject(memDC, oldBitmap);
DeleteObject(newBitmap);
DeleteDC(memDC);
EndPaint(hwnd, &ps);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"CAG";
WNDCLASS wc = { };
wc.style = CS_HREDRAW | CS_VREDRAW; // 重新绘制整个工作区
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
WINDOW_TEXT, // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
ShowWindow(hwnd, nCmdShow);
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}