此次的內容是接着yogurt上一篇《二維空間裏的簡單矩形變換》(http://www.cnblogs.com/to-sunshine/p/6496697.html)繼續來說圖形的變化問題。其實如今有不少現成的庫能夠用於畫圖,比較牛的就有opencv、opengl等,實在感興趣的人能夠去仔細研究一下。固然和這些現成庫比起來,yogurt用C語言碼的三維透視變化就弱爆啦,不過不要緊,主要是爲了弄懂其中的變換原理嘛~~html
好啦,囉嗦的話yogurt就很少說了,最近在W3school上面自學Java和CSS有點兒心累,不過仍是要給你們良心推薦這個學習網站,真心不錯哦!ide
// 3Dchange.cpp : 定義控制檯應用程序的入口點。 // #include "stdafx.h" #include"Graph.h" #include"math.h" #define PI 3.1415926 typedef double matrix[4][4]; typedef struct { double x; double y; double z; }point; //叉乘 void Cross_Product(double pa[3], double pb[3], double pc[3]) { pc[0] = pa[1] * pb[2] - pa[2] * pb[1]; pc[1] = pa[2] * pb[0] - pa[0] * pb[2]; pc[2] = pa[0] * pb[1] - pa[1] * pb[0]; return; } //點乘 double Dot_Product(double pa[3], double pb[3]) { double p = 0; for (int i = 0; i < 3; i++) { p += pa[i] * pb[i]; } if (p < 1e-6)//當結果過小時,取0 return 0; else return p; } //單位化 void unit(double p[3]) { double k = sqrt(pow(p[0], 2) + pow(p[1], 2) + pow(p[2], 2)); for (int i = 0; i < 3; i++) p[i] /= k; return; } //求用戶座標系到觀察座標系變換矩陣 void transform(point a, double t1[4][4]) { double pn[3] = { a.x - 0, a.y - 0, a.z - 0 }; unit(pn); double pf[3] = { 0, 0, 1 }; double pu[3], pv[3]; Cross_Product(pf, pn, pu); unit(pu); Cross_Product(pn, pu, pv); unit(pv); double p[3] = { a.x, a.y, a.z }; double m[3] = { 0, 0, 0 }; m[0] = -Dot_Product(p, pu); m[1] = -Dot_Product(p, pv); m[2] = -Dot_Product(p, pn); for (int i = 0; i < 3; i++) t1[i][0] = pu[i]; for (int i = 0; i < 3; i++) t1[i][1] = pv[i]; for (int i = 0; i < 3; i++) t1[i][2] = pn[i]; for (int i = 0; i <3; i++) t1[i][3] = 0; for (int i = 0; i < 3; i++) t1[3][i] = m[i]; for (int i = 0; i < 3; i++) t1[i][3] = 0; t1[3][3] = 1; //獲得用戶座標系到觀察座標系的轉換矩陣t1[4][4] return; } //求透視投影變換矩陣 void perspective_Tran(point a, double ty[4][4]) { //觀察窗口-------------------------------------------------------------- double wwidth = getWindowWidth(), hheight = getWindowHeight(); double d = wwidth / hheight;//橫縱比 double heightt = 10 * tan(PI / 6);//半個窗口高 double height = 2 * heightt; double width = height*d;//窗口高和窗口寬 //規範化變換矩陣-------------------------------------------------------- //不須要進行投影中心和錯切的變換 double k = (double)10 / 1000; double a211 = (2 / width)*k; double a222 = (2 / height)*k; double a233 = 1 / 1000.0; double t2[4][4] = { a211 / 2.0, 0, 0, 0, 0, a222 / 2.0, 0, 0, 0, 0, a233, 0, 0, 0, 0, 1 }; //比例變換 double f = (double)10 / 1000; double a333 = 1 / (1 - f); double a343 = -f / (1 - f); double t3[4][4] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, a333, 1, 0, 0, a343, 0 }; //變爲平行投影的規範化觀察空間 for (int i = 0; i < 4; i++){ for (int j = 0; j < 4; j++) { ty[i][j] = 0; for (int w = 0; w < 4; w++) ty[i][j] += t2[i][w] * t3[w][j]; } }//獲得透視投影變換矩陣ty[4][4] int tmp = 0; return; } //獲得平移等變換矩陣 void command(double(*p)[4], char order) { switch (order) { case'y': printf("X、Y、Z平移量(形如:1,1,1):"); scanf("%lf,%lf,%lf", p[3], p[3] + 1, p[3] + 2); break; case'f': printf("X、Y、Z變化比例(形如:1,1,1):"); scanf("%lf,%lf,%lf", p[0], p[1] + 1, p[2] + 2); break; case'a': printf("繞X軸旋轉角度(如:30°輸入30):"); double angle_a; scanf("%lf", &angle_a); angle_a /= PI; *(p[1] + 1) = (double)cos(angle_a); *(p[1] + 2) = (double)sin(angle_a); *(p[2] + 1) = -(double)sin(angle_a); *(p[2] + 2) = (double)cos(angle_a); break; case'b': printf("繞Y軸旋轉角度:"); double angle_b; scanf("%lf", &angle_b); angle_b /= PI; *(p[0] + 0) = (double)cos(angle_b); *(p[0] + 2) = -(double)sin(angle_b); *(p[2] + 0) = (double)sin(angle_b); *(p[2] + 2) = (double)cos(angle_b); break; case'c': printf("繞Z軸旋轉角度:"); double angle_c; scanf("%lf", &angle_c); angle_c /= PI; *(p[0] + 0) = (double)cos(angle_c); *(p[0] + 1) = (double)sin(angle_c); *(p[1] + 0) = -(double)sin(angle_c); *(p[1] + 1) = (double)cos(angle_c); break; } getchar(); return; } //矩陣乘法 void change(double cuboid[8][4], double t[4][4], double new_cuboid[8][4]) { for (int i = 0; i < 8; i++) for (int j = 0; j < 4; j++) { new_cuboid[i][j] = 0; for (int w = 0; w < 4; w++) new_cuboid[i][j] += cuboid[i][w] * t[w][j]; } return; } //三維轉二維,便於在二維空間畫圖 void Tran3DTo2D(double(*rectangle)[4], double(*cuboid)[4]) { for (int i = 0; i < 8; i++) { rectangle[i][0] = cuboid[i][0] / cuboid[i][3]; rectangle[i][1] = cuboid[i][1] / cuboid[i][3]; rectangle[i][2] = cuboid[i][2] / cuboid[i][3]; rectangle[i][3] = cuboid[i][3] / cuboid[i][3]; } //三維轉二維(x,y,z,w)-->(x/w,y/w,z/w,w/w) return; } //畫圖 void draw(double(*tu)[4]) { double new_tu[8][2]; double w = getWindowWidth();//屏幕寬 for (int i = 0; i < 8; i++) { new_tu[i][0] = (*tu[i] + 1)*w / 2; new_tu[i][1] = (*(tu[i] + 1) + 1)*w / 2; } setOrig(0, 0); moveTo(new_tu[0][0], new_tu[0][1]); lineTo(new_tu[1][0], new_tu[1][1]); lineTo(new_tu[2][0], new_tu[2][1]); lineTo(new_tu[3][0], new_tu[3][1]); lineTo(new_tu[0][0], new_tu[0][1]); lineTo(new_tu[4][0], new_tu[4][1]); lineTo(new_tu[5][0], new_tu[5][1]); lineTo(new_tu[6][0], new_tu[6][1]); lineTo(new_tu[7][0], new_tu[7][1]); lineTo(new_tu[4][0], new_tu[4][1]); for (int i = 1; i < 4; i++) { moveTo(new_tu[i][0], new_tu[i][1]); lineTo(new_tu[i + 4][0], new_tu[i + 4][1]); } return; } int _tmain(int argc, _TCHAR* argv[]) { double cuboid[8][4] = { 0, 0, 0, 1, 300, 0, 0, 1, 300, 200,0, 1, 0, 200, 0, 1, 0, 0, 100, 1, 300,0, 100, 1, 300, 200, 100, 1, 0, 200, 100, 1 }; point a;//用戶座標系中的座標 char viewpoint; printf("是否開始/繼續改變視點(y,n):"); scanf("%c", &viewpoint); getchar(); while (viewpoint == 'y') { printf("請輸入觀察參考點在用戶座標系中的座標(x,y,z):"); scanf("%lf,%lf,%lf", &a.x, &a.y, &a.z); getchar(); matrix t1 = { 0 };//用戶座標系-->觀察座標系的轉換矩陣 matrix ty = { 0 };//在觀察座標系中的透視投影變換矩陣 transform(a, t1); perspective_Tran(a, ty); double new1_cuboid[8][4], new2_cuboid[8][4]; change(cuboid, t1, new1_cuboid);//用戶座標系-->觀察座標系中的座標 change(new1_cuboid, ty, new2_cuboid);//在觀察座標系中作透視投影后的座標 double rectangle[8][4]; Tran3DTo2D(rectangle, new2_cuboid); draw(rectangle); //未通過平移等變換的長方體 double commandmand[4][4] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; char code, order;; printf("是否開始/繼續變換(y,n):"); scanf("%c", &code); getchar(); while (code == 'y') { printf("請輸入平移(y)、放縮比例(f)、旋轉x軸y軸z軸(a、b、c):"); scanf("%c", &order); getchar(); command(commandmand, order); //修改三維變換命令矩陣commandmand double new3_cuboid[8][4]; change(cuboid, commandmand, new1_cuboid); //用戶座標系中先按命令三維座標變換進行變換 change(new1_cuboid, t1, new2_cuboid); change(new2_cuboid, ty, new3_cuboid); Tran3DTo2D(rectangle, new3_cuboid); clearWindow(); draw(rectangle); printf("是否開始/繼續變換(y,n):"); scanf("%c", &code); } printf("是否開始/繼續改變視點(y,n):"); scanf("%c", &viewpoint); } return 0; }
程序的穩定性還有一些小問題,在輸入輸出的如getchar上面還有待調試。哎呀不要在乎細節,具體咱們可以完成透視變換就已經棒棒噠啦!也歡迎小夥伴一塊兒調試給出意見和建議哦!學習
讓咱們來看一下代碼的運行結果吧:網站
(1)原始長方體:spa
(2)縮小一倍:調試
(3)繼續改變視點後獲得的原始長方體:code
(4)作平移變換:orm
(5)作旋轉變換:htm
繞X旋轉:blog
繞Y軸旋轉:
繞Z軸旋轉: