利用C語言繪制一個正方體
程序截圖

操作方法
鼠標拖動。左鍵拖動及滾輪能看到不同角度下正方體的形狀,右鍵拖動能將最近的正方體頂點挪到這個投影面的相應位置。
按鍵控制。wasd 控制投影面旋轉,ws 關于 x 軸旋轉,ad 關于 y 軸旋轉。
個人思路
首先投影面的確立需要兩個量,一個 x 軸方向的單位向量,一個 y 軸方向的單位向量,求原點與三維空間中的點的連線到這兩個單位向量的投影就能得到三維空間中的點在二維投影面中的坐標。記 x 軸方向單位向量為 X,y 軸方向單位向量為 Y,X 與 Y 互相垂直,模都為 1。
正方體的八個頂點位置隨意,X,Y 兩個單位向量只需是互相垂直的非零向量即可。
鼠標橫向拖動時,X 關于 Y 旋轉,這是怎么做到的呢。要做到這一點,就需要一個新的向量,也就是投影面的法向量,投影面的法向量可以根據(jù)兩向量叉乘得到。叉乘的推法用的是線性代數(shù)的方法,但是不好理解,我用我的方法推出來,希望能方便理解。
設投影面法向量為 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 看做下圖中的綠色向量,虛線為向量旋轉后θ度后的向量,可以發(fā)現(xiàn) cos(θ)X - sin(θ)Z,就能求出 X 順時針轉動θ度后的向量,而 cos(θ)Z + sin(θ)X 就能求出 Z 順時針轉動θ度后的向量。

其它的旋轉方式皆可以此類推。
代碼實現(xiàn)
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:
// 程序:一個正方體
// 編譯環(huán)境: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 度,結果要變?yōu)橄喾聪蛄?
if (length > 0)return result;
return result * (-1.0);
}
// 根據(jù)投影面 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 度,也就是向量點乘為負數(shù),那么結果的 x 值為負數(shù)
if (tempY * AuxiliaryVector[1] < 0)result.y *= -1; // 如果 tempY 向量與 y 軸正方向的向量夾角大于 90 度,也就是向量點乘為負數(shù),那么結果的 y 值為負數(shù)
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, 可小數(shù))");
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 個頂點是固定的,可以改變?yōu)槿我庾鴺酥?,我們只是從不同的角度看這 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; // 在投影面縱坐標上移動的距離
// 對于選中的頂點,它變?yōu)樗陨淼南蛄考由贤队懊嫔系南蛄?
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 實現(xiàn)鍵盤控制
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語言繪制正方體內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
數(shù)據(jù)結構之數(shù)組Array實例詳解
這篇文章主要介紹了數(shù)據(jù)結構之數(shù)組Array實例詳解的相關資料,需要的朋友可以參考下2017-05-05

