zoukankan      html  css  js  c++  java
  • 三维空间里的简单的长方体透视变换

        这次的内容是接着yogurt上一篇《二维空间里的简单矩形变换》(http://www.cnblogs.com/to-sunshine/p/6496697.html)继续来讲图形的变化问题。其实现在有很多现成的库可以用于画图,比较牛的就有opencv、opengl等,实在感兴趣的人可以去仔细研究一下。当然和这些现成库比起来,yogurt用C语言码的三维透视变化就弱爆啦,不过没关系,主要是为了弄懂其中的变换原理嘛~~

        好啦,啰嗦的话yogurt就不多说了,最近在W3school上面自学Java和CSS有点儿心累,不过还是要给大家良心推荐这个学习网站,真心不错哦!

    // 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)原始长方体:

    (2)缩小一倍:

    (3)继续改变视点后得到的原始长方体:

    (4)做平移变换:

    (5)做旋转变换:

    绕X旋转:

    绕Y轴旋转:

    绕Z轴旋转:

  • 相关阅读:
    C# 轻松读取、改变文件的创建、修改、访问时间 z
    C#中将dll汇入exe z
    ASP.NET中引用dll“找不到指定模块"的完美解决办法 z
    C# 调用第三方DLL z
    [ACM_贪心] Radar Installation
    Beauty Contest
    [ACM_几何] Wall
    [ACM_几何] Pipe
    [ACM_几何] Fishnet
    [ACM_动态规划] 找零种类
  • 原文地址:https://www.cnblogs.com/to-sunshine/p/6501208.html
Copyright © 2011-2022 走看看