利用C語言繪制一個正方體
程序截圖
操作方法
鼠標(biāo)拖動。左鍵拖動及滾輪能看到不同角度下正方體的形狀,右鍵拖動能將最近的正方體頂點(diǎn)挪到這個投影面的相應(yīng)位置。
按鍵控制。wasd 控制投影面旋轉(zhuǎn),ws 關(guān)于 x 軸旋轉(zhuǎn),ad 關(guān)于 y 軸旋轉(zhuǎn)。
個人思路
首先投影面的確立需要兩個量,一個 x 軸方向的單位向量,一個 y 軸方向的單位向量,求原點(diǎn)與三維空間中的點(diǎn)的連線到這兩個單位向量的投影就能得到三維空間中的點(diǎn)在二維投影面中的坐標(biāo)。記 x 軸方向單位向量為 X,y 軸方向單位向量為 Y,X 與 Y 互相垂直,模都為 1。
正方體的八個頂點(diǎn)位置隨意,X,Y 兩個單位向量只需是互相垂直的非零向量即可。
鼠標(biāo)橫向拖動時,X 關(guān)于 Y 旋轉(zhuǎn),這是怎么做到的呢。要做到這一點(diǎn),就需要一個新的向量,也就是投影面的法向量,投影面的法向量可以根據(jù)兩向量叉乘得到。叉乘的推法用的是線性代數(shù)的方法,但是不好理解,我用我的方法推出來,希望能方便理解。
設(shè)投影面法向量為 Z(x2, y2, z2),X(x0, y0, z0) 和 Y(x1, y1, z1) 與 Z 的點(diǎn)乘為 0,這就能列出兩個式子:
① x0x2+y0y2+z0z2 = 0
② x1x2+y1y2+z1z2 = 0
將①式轉(zhuǎn)化為以 x2 和 y2 表示的 z2 得
③ z2 = - (x0x2 + y0y2) / z0
將②式和③式結(jié)合轉(zhuǎn)化為以 x2 表示的 y2 得
④ y2 = (x0z1 - x1z0) / (y1z0 - y0z1) * x2
④式代回③式得
⑤ z2 = (x1y0 - x0y1) / (y1z0 - y0z1) * x2 (推這一步時負(fù)號別忘了看)
也就是 Z = [x2, (x0z1 - x1z0) / (y1z0 - y0z1) * x2, (x1y0 - x0y1) / (y1z0 - y0z1) * x2]
仔細(xì)觀察 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)] 就是投影面的法向量。到這一步還沒有結(jié)束,因?yàn)榇怪庇谝粋€面的法向量有兩個,一個符合右手坐標(biāo)系,一個符合左手坐標(biāo)系,將 X(1, 0, 0),Y(0, 1, 0) 代入得 Z(0, 0, -1),所以這個 Z 是符合左手坐標(biāo)系的投影面法向量,要轉(zhuǎn)換成右手坐標(biāo)系只需乘個 -1 就行,也就是 Z 最終為 (y0z1 - y1z0, x1z0 - x0z1, x0y1 - x1y0)。
由于 X,Y 都是單位向量,這個 Z 還是投影面的單位法向量。
Z 求出來了,X 關(guān)于 Y 旋轉(zhuǎn)可以看做 X 在 XOZ 平面上旋轉(zhuǎn),問題轉(zhuǎn)化成了求平面中某個向量轉(zhuǎn)過θ度后的向量,如下圖,將 X 看做下圖中的紅色向量,Z 看做下圖中的綠色向量,虛線為向量旋轉(zhuǎn)后θ度后的向量,可以發(fā)現(xiàn) cos(θ)X - sin(θ)Z,就能求出 X 順時針轉(zhuǎn)動θ度后的向量,而 cos(θ)Z + sin(θ)X 就能求出 Z 順時針轉(zhuǎn)動θ度后的向量。
其它的旋轉(zhuǎn)方式皆可以此類推。
代碼實(shí)現(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; } // 恢復(fù)按鈕 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("微軟雅黑")); // 設(shè)置字體為寬高 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) // 手柄,控制面旋轉(zhuǎn)幅度的量 #define DISPLAY 3 // 展示出來頂點(diǎn)的尺寸 #define ARROWS 3 // 箭頭尺寸 #define PIECE 360 double FocalLength = 6; // 觀察點(diǎn)到投影面的距離 // 8 個頂點(diǎn)的顏色,用于分辨 8 個不同的點(diǎn) 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 }; } // 三維向量,也可以表示一個坐標(biāo) 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 }; } // 得到兩向量點(diǎn)乘的值 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 度,結(jié)果要變?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; } // 將三維的點(diǎn)的值轉(zhuǎn)換為在對應(yīng) xoy 面上的投影的坐標(biāo) 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 的模長,模長就是結(jié)果的 x 值的絕對值 result.y = GetVec3Length(tempY); // 得到 tempY 的模長,模長就是結(jié)果的 y 值的絕對值 if (tempX * AuxiliaryVector[0] < 0)result.x *= -1; // 如果 tempX 向量與 x 軸正方向的向量夾角大于 90 度,也就是向量點(diǎn)乘為負(fù)數(shù),那么結(jié)果的 x 值為負(fù)數(shù) if (tempY * AuxiliaryVector[1] < 0)result.y *= -1; // 如果 tempY 向量與 y 軸正方向的向量夾角大于 90 度,也就是向量點(diǎn)乘為負(fù)數(shù),那么結(jié)果的 y 值為負(fù)數(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); } // 得到兩個點(diǎn)之間的距離(二維) 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 個頂點(diǎn)中距離 point 這個二維點(diǎn)最近的點(diǎn),p 是判斷兩點(diǎn)間距離是否符合要求的 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)); } } // 畫坐標(biāo)軸,pericenter 是中心點(diǎn),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 平面上,旋轉(zhuǎn) 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"結(jié)束程序", [](void* param) { bool* isExit = (bool*)param; *isExit = true; return 0; }, &isExit)); Vec3 Vertex[8]; // 8 個頂點(diǎn)的坐標(biāo) // 初始化 8 個頂點(diǎn),這 8 個頂點(diǎn)是固定的,可以改變?yōu)槿我庾鴺?biāo)值,我們只是從不同的角度看這 8 個頂點(diǎn) 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; // 鼠標(biāo)信息 bool ispress = false; // 是否按下 bool isLpress = false; // 是否按下左鍵 bool isRpress = false; // 是否按下右鍵 double originalX = 0, originalY = 0; // 原來的坐標(biāo) int vertexIndex = 0; // 右鍵點(diǎn)擊時要操作的頂點(diǎn)的坐標(biāo) 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, // 得到距離按下的位置最近的正方體頂點(diǎn)的下標(biāo) { (double)msg.x - WIDTH / 2, (double)msg.y - HEIGHT / 2 }, isParallel, [](double num) {if (num < DISPLAY)return true; return false; }); // 這個 lambda 表達(dá)式是為了讓點(diǎn)到的地方距離最近的正方體頂點(diǎn)距離小于展示出來正方體頂點(diǎn)的尺寸才能生效 } 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; // 在投影面橫坐標(biāo)上移動的距離 double lengthY = msg.y - originalY; // 在投影面縱坐標(biāo)上移動的距離 // 對于選中的頂點(diǎn),它變?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 度旋轉(zhuǎn) 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 軸向量 } } // 用鼠標(biāo)不能進(jìn)行精密控制,在這里用 wasd 實(shí)現(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; }
到此這篇關(guān)于利用C語言繪制一個正方體的文章就介紹到這了,更多相關(guān)C語言繪制正方體內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言實(shí)現(xiàn)3*3數(shù)組對角線之和示例
今天小編就為大家分享一篇C語言實(shí)現(xiàn)3*3數(shù)組對角線之和示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12C/C++經(jīng)典實(shí)例之模擬計算器示例代碼
最近在看到的一個需求,本以為比較簡單,但花了不少時間,所以下面這篇文章主要給大家介紹了關(guān)于C/C++經(jīng)典實(shí)例之模擬計算器的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-10-10數(shù)據(jù)結(jié)構(gòu)之?dāng)?shù)組Array實(shí)例詳解
這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)之?dāng)?shù)組Array實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05