三維空間裏的簡單的長方體透視變換

    此次的內容是接着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;
}
View Code

    程序的穩定性還有一些小問題,在輸入輸出的如getchar上面還有待調試。哎呀不要在乎細節,具體咱們可以完成透視變換就已經棒棒噠啦!也歡迎小夥伴一塊兒調試給出意見和建議哦!學習

    讓咱們來看一下代碼的運行結果吧:網站

 

(1)原始長方體:spa

(2)縮小一倍:調試

(3)繼續改變視點後獲得的原始長方體:code

(4)作平移變換:orm

(5)作旋轉變換:htm

繞X旋轉:blog

繞Y軸旋轉:

繞Z軸旋轉:

相關文章
相關標籤/搜索