如何从头开始构建具有人类可读角度的旋转矩阵? [英] How do I compose a rotation matrix with human readable angles from scratch?

查看:21
本文介绍了如何从头开始构建具有人类可读角度的旋转矩阵?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一直阻碍我进行 3D 编程的一件事是无法理解数学的工作原理.我可以在使用方法和函数的编程流程中很好地进行数学运算,那么对我来说这一切都清晰且合乎逻辑,但在数学符号中,我无法从中得出正面或反面.

我一直在阅读网站,观看研究所的视频,试图解释这一点,但他们都使用数学符号,我只是迷失在其中,我的思想不会将其转化为可以理解的东西.我可能在那里有缺陷.

另外,仅仅使用某人的代码不是我的兴趣,我想了解它背后的机制,逻辑.我很乐意使用别人的代码,但我真的很想了解它是如何工作的.

问题

你能用简单的术语向我解释没有数学符号,只是编程符号/函数/伪代码,如何实现沿所有 3 个轴的矩阵变换?

理想情况下,我想要的是编写方法/对象的材料/理解,我可以在其中定义类似于 glRotate 的 3 个轴的角度来旋转我拥有的四边形/三角形集合.(我正在尝试编写立方体形状的 3D 旋转程序,而无需访问 OpenGL 函数来为我完成此操作,因为每次显示列表中的某些内容发生变化时,这都是在一次绘制调用中完成的.)

我做了什么?

我曾尝试制作一个 90 度变换函数来掌握数学的窍门,但在制作理论上应该是最简单的正确矩阵方面完全失败.你可以在

  • 影响

    • 旋转是通过旋转轴来完成的,因此您可以通过参数圆方程计算每个轴...
    • 缩放是通过将轴乘以比例因子来完成的
    • 倾斜只是使用非垂直轴
  • 旋转

    大多数情况下使用增量旋转.有两种类型

    • 局部旋转 M'=M*rotation_matrix 它围绕局部坐标轴旋转,就像你控制飞机、汽车或玩家一样......大多数引擎/游戏不要使用这些并用 euler 角 伪造它,而是一种廉价的解决方案(有许多怪癖和问题),因为大多数使用 OpenGL 的人甚至不知道这是可能的,而是使用 glRotate/glTranslate 调用...

    • 全局旋转 M'=Inverse(Inverse(M)*rotation_matrix) 它绕全局坐标系轴旋转.

    其中 rotation_matrix 是任何标准的旋转变换矩阵.

    如果你有不同的矩阵布局(转置),那么局部和全局的旋转会以相反的方式计算......

    您还可以从 3 角度计算您的 rotation_matrix,例如:

    rotation_matrix=rotation_around_x(ax)*rotation_around_y(ay)*rotation_around_z(az);

    参见

    [注释]

    如果您有更多问题,请评论我...

    [Edit2] 经常需要的基本 3D 矢量操作

    如果您不知道如何计算交叉/点积或绝对值等向量运算,请参阅:

    //叉积:W = U x VW.x=(U.y*V.z)-(U.z*V.y)W.y=(U.z*V.x)-(U.x*V.z)W.z=(U.x*V.y)-(U.y*V.x)//点积:a = (U.V)a=U.x*V.x+U.y*V.y+U.z*V.z//向量的绝对值 a = |U|a=sqrt((U.x*U.x)+(U.y*U.y)+(U.z*U.z))

    这里是我的 C++ 向量数学:

    static double vector_tmp[3];双除法(双 x,双 y){ 如果((y>=-1e-30)&&(y<=+1e-30))返回 0.0;返回 x/y;}double* vector_ld(double x,double y,double z) { double *p=vector_tmp;p[0]=x;p[1]=y;p[2]=z;返回 p;}double* vector_ld(double *p,double x,double y,double z) { p[0]=x;p[1]=y;p[2]=z;返回 p;}void vector_copy(double *c,double *a) { for(int i=0;i<3;i++) c[i]=a[i];}void vector_abs(double *c,double *a) { for(int i=0;i<3;i++) c[i]=fabs(a[i]);}void vector_one(double *c,double *a){double l=divide(1.0,sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])));c[0]=a[0]*l;c[1]=a[1]*l;c[2]=a[2]*l;}void vector_len(double *c,double *a,double l){l=divide(l,sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])));c[0]=a[0]*l;c[1]=a[1]*l;c[2]=a[2]*l;}void vector_neg(double *c,double *a) { for(int i=0;i<3;i++) c[i]=-a[i];}void vector_add(double *c,double *a,double *b) { for(int i=0;i<3;i++) c[i]=a[i]+b[i];}void vector_sub(double *c,double *a,double *b) { for(int i=0;i<3;i++) c[i]=a[i]-b[i];}void vector_mul(double *c,double *a,double *b)//交叉{双 q[3];q[0]=(a[1]*b[2])-(a[2]*b[1]);q[1]=(a[2]*b[0])-(a[0]*b[2]);q[2]=(a[0]*b[1])-(a[1]*b[0]);for(int i=0;i<3;i++) c[i]=q[i];}void vector_mul(double *c,double *a,double b) { for(int i=0;i<3;i++) c[i]=a[i]*b;}void vector_mul(double *c,double a,double *b) { for(int i=0;i<3;i++) c[i]=a*b[i];}double vector_mul( double *a,double *b) { double c=0;for(int i=0;i<3;i++) c+=a[i]*b[i];返回 c;}//点double vector_len(double *a) { return sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2]));}double vector_len2(double *a) { return (a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2]);}

    [Edit3] 通过键盘控制相机和物体的局部旋转

    因为最近有人问这个问题,这里有一些我的示例答案和演示:

    The one thing that has always hindered me from doing 3D programming is failing to understand how math works. I can go along with math fine in programming flow using methods and functions, then its all clear and logical to me, but in mathematical notation, I just can't make heads or tails from it.

    I have been reading websites, a watching videos of institutes trying to explain this, but they all use mathematical notation and I simply get lost in it, my mind won't translate it to something understandable. I might have a defect there.

    Also, Just using someone's code isn't my interest, I want to understand the mechanics behind it, the logic. I'd be happy to use someone else's code, but I really want to understand how it works.

    The question

    Can you explain to me in simple terms without mathematical notation, just programming notation/functions/psuedocode, how to implement a matrix transform along all 3 axes?

    Ideally what I want is the material/understanding to write a method/object where I can define the angles of 3 axes similar to glRotate to rotate the collection of quads/triangles I have. (I am trying to program a 3D rotation of a cube shapes without having access to OpenGL functions to do it for me because this is done in one draw call every time something changes in the display list.)

    What have I done?

    I have attempted at making a 90 degrees transform function to get the hang of the math but failed utterly in making a proper matrix which in theory should have been the simplest to do. You can see my failed attempt in all its glory on http://jsfiddle.net/bLfg0tj8/5/

    Vec3 = function(x,y,z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
    Matrix = function Matrix() {
        this.matrixPoints = new Array();    
        this.rotationPoint = new Vec3(0,0,0);
        this.rotationAngle = 90;
    }
    Matrix.prototype.addVector = function(vector) {
        this.matrixPoints.push(vector);
    }
    Matrix.prototype.setRotationPoint = function(vector) {
        this.rotationPoint = vector; 
    }
    Matrix.prototype.setRotationAngle = function(angle) {
        this.rotationAngle = angle;
    }
    Matrix.prototype.populate = function() {
        translateToOrigin =     [[1,0,0-this.rotationPoint.x],
                                      [0,1,0-this.rotationPoint.y],
                                      [0,0,0-this.rotationPoint.z]];
        rotationMatrix =         [[0,-1,0],
                                      [0,1,0],
                                      [0,0,1]];
        translateEnd =         [[1,0,this.rotationPoint.x],
                                      [0,1,this.rotationPoint.y],
                                      [0,0,this.rotationPoint.z]];
        currentColumn = 0;
        currentRow = 0;
        this.combomatrix = this.mergeMatrices(this.mergeMatrices(translateEnd,rotationMatrix),
                                              translateToOrigin);
    }
    Matrix.prototype.transform = function() {
        newmatrix = new Array();
        for(c = 0;c<this.matrixPoints.length;c++) {
            newmatrix.push(this.applyToVertex(this.matrixPoints[c]));
        }
        return newmatrix;
    }
    Matrix.prototype.applyToVertex = function(vertex) {
        ret = new Vec3(vertex.x,vertex.y,vertex.z);
        ret.x = ret.x + this.combomatrix[0][0] * vertex.x +
                this.combomatrix[0][1] * vertex.y +
                this.combomatrix[0][2] * vertex.z;
        
        ret.y = ret.y + this.combomatrix[1][0] * vertex.x +
                this.combomatrix[1][1] * vertex.y +
                this.combomatrix[1][2] * vertex.z;
        
        ret.z = ret.z + this.combomatrix[2][0] * vertex.x +
                this.combomatrix[2][1] * vertex.y +
                this.combomatrix[2][2] * vertex.z;
        return ret;
    }
    Matrix.prototype.mergeMatrices = function(lastStep, oneInFront) {
        step1 = [[0,0,0],[0,0,0],[0,0,0]];
        step1[0][0] =   lastStep[0][0] * oneInFront[0][0] + 
                        lastStep[0][1] * oneInFront[1][0] + 
                        lastStep[0][2] * oneInFront[2][0];
        
        step1[0][1] =   lastStep[0][0] * oneInFront[0][1] + 
                        lastStep[0][1] * oneInFront[1][1] + 
                        lastStep[0][2] * oneInFront[2][1];
        
        step1[0][2] =   lastStep[0][0] * oneInFront[0][2] + 
                        lastStep[0][1] * oneInFront[1][2] + 
                        lastStep[0][2] * oneInFront[2][2];
        //============================================================
        step1[1][0] =   lastStep[1][0] * oneInFront[0][0] + 
                        lastStep[1][1] * oneInFront[1][0] + 
                        lastStep[1][2] * oneInFront[2][0];
        
        step1[1][1] =   lastStep[1][0] * oneInFront[0][1] + 
                        lastStep[1][1] * oneInFront[1][1] + 
                        lastStep[1][2] * oneInFront[2][1];
        
        step1[1][2] =   lastStep[1][0] * oneInFront[0][2] + 
                        lastStep[1][1] * oneInFront[1][2] + 
                        lastStep[1][2] * oneInFront[2][2];
        //============================================================
        step1[2][0] =   lastStep[2][0] * oneInFront[0][0] + 
                        lastStep[2][1] * oneInFront[1][0] + 
                        lastStep[2][2] * oneInFront[2][0];
        
        step1[2][1] =   lastStep[2][0] * oneInFront[0][1] + 
                        lastStep[2][1] * oneInFront[1][1] + 
                        lastStep[2][2] * oneInFront[2][1];
        
        step1[2][2] =   lastStep[2][0] * oneInFront[0][2] + 
                        lastStep[2][1] * oneInFront[1][2] + 
                        lastStep[2][2] * oneInFront[2][2];
        return step1;
    }
    Matrix.prototype.getCurrentMatrix = function() {
        return this.matrixPoints;
    }
    myvectors = [new Vec3(50,50,0), new Vec3(20,80,0), new Vec3(80, 80, 0)];
    
    function drawVectors(vectors,color) {
        for(c=0;c<vectors.length;c++) {
            document.getElementById("whoa").innerHTML += '<div style="color:'+color+';position:absolute;left:'+vectors[c].x+'px; top:'+vectors[c].y+'px;z-index:'+vectors[c].z+';">('+c+').</div>';
        }
    }
    matrix = new Matrix();
    for(c=0;c<myvectors.length;c++) {
        matrix.addVector(myvectors[c]);
    }
    matrix.setRotationPoint(new Vec3(50,70,0));
    matrix.populate();
    somematrix = matrix.transform();
    drawVectors(matrix.getCurrentMatrix(),"lime"); // draw current matrix that was hand coded
    drawVectors([matrix.rotationPoint],'white'); // draw rotation point
    drawVectors(somematrix,"red"); // transformed matrix... somehow two points merge

    <div id="whoa" style="position:relative;top:50px;left:150px;background-color:green;color:red;width:400px;height:300px;">
        &nbsp;
    </div>

    The green text is the original triangle, the white point the center point, the red points the failed transformation(I think, because it isn't aligned around the center point). The tutorial I was in thought me how to combine matrices into a combined matrix, but I guess I screwed up somewhere.

    As I said, it's really really hard for me to understand mathematical notation and speak. And not helping is that most teachers skip parts of the explanation. Took me 2 hours alone to understand when multiplying matrices you need to add each step together instead of just keep on multiplying. Yay for explanations.

    A practical example what I work with/want to work with

    For example I have a cube, loaded from a wavefront obj file located in the world at

    x = 50
    y = 100
    z = 200
    

    The cube is drawn using quads and some uv mapping. No problems here. It renders beautifully with all the textures showing correctly.

    These are the location coordinates for each "face" of the cube which is drawn using a quad.

    // Front face
    -1.0, -1.0,  1.0,
     1.0, -1.0,  1.0,
     1.0,  1.0,  1.0,
    -1.0,  1.0,  1.0,
    
    // Back face
    -1.0, -1.0, -1.0,
    -1.0,  1.0, -1.0,
     1.0,  1.0, -1.0,
     1.0, -1.0, -1.0,
    
    // Top face
    -1.0,  1.0, -1.0,
    -1.0,  1.0,  1.0,
     1.0,  1.0,  1.0,
     1.0,  1.0, -1.0,
    
    // Bottom face
    -1.0, -1.0, -1.0,
     1.0, -1.0, -1.0,
     1.0, -1.0,  1.0,
    -1.0, -1.0,  1.0,
    
    // Right face
     1.0, -1.0, -1.0,
     1.0,  1.0, -1.0,
     1.0,  1.0,  1.0,
     1.0, -1.0,  1.0,
    
    // Left face
    -1.0, -1.0, -1.0,
    -1.0, -1.0,  1.0,
    -1.0,  1.0,  1.0,
    -1.0,  1.0, -1.0
    

    So this works all great. But what if I want this cube rotated 90 degrees along the x axis and 45 degrees around the z axis? I cannot use glRotate because at the moment I pass the data to the tesselator object I cannot do any matrix transforms to it via the opengl functions because it's just taking in the data, not actually rendering it per se.

    The way the data is stored is as following:

    WaveFrontObject()
       |
       |-> Groups(String groupname)
            |
            |-> Faces()
                  |
                  |-> Vertex(float x, float y, float z)[] 
                  |-> Float UVmap[] corresponding to each vertex
                  |-> drawFace() // Draws the face as a quad or triangle
    

    So each of the above coordinates I gave is stored as a face of the wavefront object in the group "cube".

    When the cube is added to the tesselator it is translated to the right coordinates in the world and it renders normal.

    It always renders the same however. If I would want it to render at an angle I would have to make a seperate wavefront object at this moment to be able to do that. In my opnion that is madness to do when it can be solved with some math.

    Needed in the answer

    1. Explanation step by step how to build a translation matrix and an attempt to explain the math to me.
    2. Explanation how to apply the translation matrix to the quads/triangles in the faces whist they keep oriented around the center of their location

      x = 50.5 y = 100.5 z = 200.5

    3. Some example/pseudo code to go along with the explanation.

    The used programming language used to explain isn't really relevant as long as its in the C family

    Please try to stay away from mathematical notation/speak. I don't know what alpha beta, thetha is, I do know what x axis, y axis and z axis is. I do know what angles are, but I do not know the names mathematicians find for it.

    If you wish to use math names, please explain to me what they are in the 3D world/code and how they are formed/calculated.

    I simply want to make a method/object along the lines of

    Matrix.transformVertices(vertices[], 90deg x, 45 deg y, 0 deg z);
    

    解决方案

    So the question really is Understanding 4x4 homogenous transform matrices

    well without the math behind the only thing that left is geometric representation/meaning which is far better for human abstraction/understanding.

    1. So what the 4x4 matrix is?

      It is representation of some Cartesian coordinate system and it is composed of:

      1. 3 basis vectors (one for each axis) red,green,blue

        So if the red,green,blue vectors are perpendicular to each other then the coordinate system is orthogonal. If they are also unit vectors then it is orthonormal (like for example unit matrix).

      2. origin point gray

      3. projection and homogenous side (unmarked bottom rest of the matrix)

        This part is there only for enabling rotation and translation at once, therefore point used must be homogenous that means in form (x,y,z,w=1) for points and (x,y,z,w=0) for direction vectors. If it was just (x,y,z) then the matrix would be 3x3 and that is not enough for translation. I will not use any projections they are uneasy to explain geometrically.

      This layout is from OpenGL notation there are also transposed representation out there (vectors are rows not columns)

      now how to transform any point to/from this coordinate system:

      g=M*l;
      l=Inverse(M)*g;
      

      where:

      • M is transform matrix
      • l is M local coordinate system point (LCS)
      • g is global coordinate system point (GCS)

      for the transposed version (DirectX) it is:

      l=M*g;
      g=Inverse(M)*l;
      

      That is because transposed orthogonal rotation matrix is also inverse of itself

    2. how to visualize it

      Yes you can draw the matrix numbers but they do not make sense at first look especially if the numbers are changing so draw the axises vectors as on image above. Where each axis is a line from origin to origin + line_size*axis_vector

    3. how to construct it

      Just compute axis vectors and origin and put them inside matrix. To ensure orthogonality exploit cross product (but be careful with order of multiplicants to use the right direction) Here example of getting 3 basis vectors from direction

    4. effects

      • rotation is done by rotating the axises so you can compute each axis by parametric circle equation ...
      • scaling is done by multiplying axises by scale factor
      • skewing is just using non perpendicular axises
    5. rotation

      For most cases the incremental rotation is used. There are two types

      • local rotation M'=M*rotation_matrix it rotates around local coordinate axises like you will control plane or car or player ... Most engines/games do not use these and fake it with euler angles instead which is a cheap solution (have many quirks and problems) because most people who using OpenGL do not even know this is possible and rather stack list of glRotate/glTranslate calls...

      • global rotation M'=Inverse(Inverse(M)*rotation_matrix) it rotates around global coordinate system axises.

      where rotation_matrix is any standard rotation transform matrix.

      If you have different matrix layout (transposed) then the rotations local and global are computed the other way around ...

      You can also compute your rotation_matrix from 3 angles like:

      rotation_matrix=rotation_around_x(ax)*rotation_around_y(ay)*rotation_around_z(az);
      

      see Wiki rotation matrices the 3D Rx,Ry,Rz from Basic rotations are what you need. As you can see they are just unit circle parametric equation really. The order of multiplication change how the angles converge to target position. This is called Euler angles and I do not use it (I integrate step changes instead which has no restrictions if done properly not to mention it is simpler).

      Anyway if you need you can convert transform matrix into euler angles relatively easily see:

    6. glRotate

      If you want glRotate which is rotation around arbitrary axis not by 3 angles then There is workaround:

      1. create transform matrix N for that axis
      2. then transform your matrix M to it
      3. rotate N by angle
      4. then transform M back from N to global coordinates

      Or you can use Rodrigues_rotation_formula instead

      To transform Matrix to/from Matrix in this case just transform axises as points and leave the origin as is but the origin of N must be (0,0,0)!!! or the vectors transformed must have w=0 instead.

    7. usage

      Transformations are cumulative that means:

      • p'=M1*M2*M3*M4*p; is the same as M=M1*M2*M3*M4; p'=M*p

      So if you have many points to transform then you precompute all transformations to single matrix and use just it. Do not need to multiply points by all subsequent matrices. OK now the concept:

      you should have 3 coordinate systems:

      • camera C
      • world (usually unit matrix)
      • object O (each object have its own matrix)

      so if you have cube with 8 vertexes p0,...,p7 then you have to perform transformation on each point from object local coordinates to camera local coordinates. Some gfx api do some of it so you apply only what you have to so you really need:

      • p(i)'=inverse(C)*unit*M*p(i);

      the transforms are cumulative and unit matrix does not change anything so:

      • Q=inverse(C)*M; p(i)'=Q*p(i);

      so before drawing compute Q for drawed object then take each point p(i) of the object and compute the transformed p(i)' and draw/use the transformed one ... The p(i)' is in local camera coordinate system (x,y of the screen) but there is no perspective there so before drawing you can also add any of the projection matrices and divide by z cordinate at the end ... The projection is also cumulative so it can be also inside Q

    [edit1] C++ example

    //$$---- Form CPP ----
    //---------------------------------------------------------------------------
    // apart from math.h include you can ignore this machine generated VCL related code
    #include <vcl.h>
    #pragma hdrstop
    #include "win_main.h"
    #include <math.h>
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TMain *Main; // pointer to main window ...
    //---------------------------------------------------------------------------
    // Here is the important stuff some math first
    //---------------------------------------------------------------------------
    const double deg=M_PI/180.0;
    double divide(double x,double y);
    void  matrix_mul       (double *c,double *a,double *b); // c[16] = a[16] * b[16]
    void  matrix_mul_vector(double *c,double *a,double *b); // c[ 4] = a[16] * b[ 4]
    void  matrix_subdet    (double *c,double *a);           // c[16] = all subdets of a[16]
    double matrix_subdet   (          double *a,int r,int s);//      = subdet(r,s) of a[16]
    double matrix_det      (          double *a);           //       = det of a[16]
    double matrix_det      (          double *a,double *b); //       = det of a[16] and subdets b[16]
    void  matrix_inv       (double *c,double *a);           // c[16] = a[16] ^ -1
    //---------------------------------------------------------------------------
    double divide(double x,double y)
            {
            if (!y) return 0.0;
            return x/y;
            }
    void  matrix_mul       (double *c,double *a,double *b)
            {
            double q[16];
            q[ 0]=(a[ 0]*b[ 0])+(a[ 1]*b[ 4])+(a[ 2]*b[ 8])+(a[ 3]*b[12]);
            q[ 1]=(a[ 0]*b[ 1])+(a[ 1]*b[ 5])+(a[ 2]*b[ 9])+(a[ 3]*b[13]);
            q[ 2]=(a[ 0]*b[ 2])+(a[ 1]*b[ 6])+(a[ 2]*b[10])+(a[ 3]*b[14]);
            q[ 3]=(a[ 0]*b[ 3])+(a[ 1]*b[ 7])+(a[ 2]*b[11])+(a[ 3]*b[15]);
            q[ 4]=(a[ 4]*b[ 0])+(a[ 5]*b[ 4])+(a[ 6]*b[ 8])+(a[ 7]*b[12]);
            q[ 5]=(a[ 4]*b[ 1])+(a[ 5]*b[ 5])+(a[ 6]*b[ 9])+(a[ 7]*b[13]);
            q[ 6]=(a[ 4]*b[ 2])+(a[ 5]*b[ 6])+(a[ 6]*b[10])+(a[ 7]*b[14]);
            q[ 7]=(a[ 4]*b[ 3])+(a[ 5]*b[ 7])+(a[ 6]*b[11])+(a[ 7]*b[15]);
            q[ 8]=(a[ 8]*b[ 0])+(a[ 9]*b[ 4])+(a[10]*b[ 8])+(a[11]*b[12]);
            q[ 9]=(a[ 8]*b[ 1])+(a[ 9]*b[ 5])+(a[10]*b[ 9])+(a[11]*b[13]);
            q[10]=(a[ 8]*b[ 2])+(a[ 9]*b[ 6])+(a[10]*b[10])+(a[11]*b[14]);
            q[11]=(a[ 8]*b[ 3])+(a[ 9]*b[ 7])+(a[10]*b[11])+(a[11]*b[15]);
            q[12]=(a[12]*b[ 0])+(a[13]*b[ 4])+(a[14]*b[ 8])+(a[15]*b[12]);
            q[13]=(a[12]*b[ 1])+(a[13]*b[ 5])+(a[14]*b[ 9])+(a[15]*b[13]);
            q[14]=(a[12]*b[ 2])+(a[13]*b[ 6])+(a[14]*b[10])+(a[15]*b[14]);
            q[15]=(a[12]*b[ 3])+(a[13]*b[ 7])+(a[14]*b[11])+(a[15]*b[15]);
            for(int i=0;i<16;i++) c[i]=q[i];
            }
    void  matrix_mul_vector(double *c,double *a,double *b)
            {
            double q[3];
            q[0]=(a[ 0]*b[0])+(a[ 1]*b[1])+(a[ 2]*b[2])+(a[ 3]);
            q[1]=(a[ 4]*b[0])+(a[ 5]*b[1])+(a[ 6]*b[2])+(a[ 7]);
            q[2]=(a[ 8]*b[0])+(a[ 9]*b[1])+(a[10]*b[2])+(a[11]);
            for(int i=0;i<3;i++) c[i]=q[i];
            }
    void  matrix_subdet    (double *c,double *a)
            {
            double   q[16];
            int     i,j;
            for (i=0;i<4;i++)
             for (j=0;j<4;j++)
              q[j+(i<<2)]=matrix_subdet(a,i,j);
            for (i=0;i<16;i++) c[i]=q[i];
            }
    double matrix_subdet    (         double *a,int r,int s)
            {
            double   c,q[9];
            int     i,j,k;
            k=0;                            // q = sub matrix
            for (j=0;j<4;j++)
             if (j!=s)
              for (i=0;i<4;i++)
               if (i!=r)
                    {
                    q[k]=a[i+(j<<2)];
                    k++;
                    }
            c=0;
            c+=q[0]*q[4]*q[8];
            c+=q[1]*q[5]*q[6];
            c+=q[2]*q[3]*q[7];
            c-=q[0]*q[5]*q[7];
            c-=q[1]*q[3]*q[8];
            c-=q[2]*q[4]*q[6];
            if (int((r+s)&1)) c=-c;       // add signum
            return c;
            }
    double matrix_det       (         double *a)
            {
            double c=0;
            c+=a[ 0]*matrix_subdet(a,0,0);
            c+=a[ 4]*matrix_subdet(a,0,1);
            c+=a[ 8]*matrix_subdet(a,0,2);
            c+=a[12]*matrix_subdet(a,0,3);
            return c;
            }
    double matrix_det       (         double *a,double *b)
            {
            double c=0;
            c+=a[ 0]*b[ 0];
            c+=a[ 4]*b[ 1];
            c+=a[ 8]*b[ 2];
            c+=a[12]*b[ 3];
            return c;
            }
    void  matrix_inv       (double *c,double *a)
            {
            double   d[16],D;
            matrix_subdet(d,a);
            D=matrix_det(a,d);
            if (D) D=1.0/D;
            for (int i=0;i<16;i++) c[i]=d[i]*D;
            }
    //---------------------------------------------------------------------------
    // now the object representation
    //---------------------------------------------------------------------------
    const int pnts=8;
    double pnt[pnts*3]=     // Vertexes for 100x100x100 cube centered at (0,0,0)
        {
        -100.0,-100.0,-100.0,
        -100.0,+100.0,-100.0,
        +100.0,+100.0,-100.0,
        +100.0,-100.0,-100.0,
        -100.0,-100.0,+100.0,
        -100.0,+100.0,+100.0,
        +100.0,+100.0,+100.0,
        +100.0,-100.0,+100.0,
        };
    const int facs=6;
    int fac[facs*4]=        // faces (index of point used) no winding rule
        {
        0,1,2,3,
        4,5,6,7,
        0,1,5,4,
        1,2,6,5,
        2,3,7,6,
        3,0,4,7,
        };
    double rep[16]=        // 4x4 transform matrix of object (unit from start) at (0,0,+100)
        {
        1.0,0.0,0.0,  0.0,
        0.0,1.0,0.0,  0.0,
        0.0,0.0,1.0,100.0,
        0.0,0.0,0.0,1.0,
        };
    double eye[16]=        // 4x4 transform matrix of camera at (0,0,-150)
        {
        1.0,0.0,0.0,   0.0,
        0.0,1.0,0.0,   0.0,
        0.0,0.0,1.0,-150.0,
        0.0,0.0,0.0,1.0,
        };
    //---------------------------------------------------------------------------
    // this is how to draw it
    //---------------------------------------------------------------------------
    void obj(double *pnt,int pnts,int *fac,int facs,double *rep,double *ieye)
        {
        // variables for drawing
        int i;
        double p0[3],p1[3],p2[3],p3[3],m[16],d;
        // gfx api variables (change to your stuff) Main is the main form of this application
        TCanvas *scr=Main->bmp->Canvas;
        double xs2=Main->ClientWidth/2,ys2=Main->ClientHeight/2;
        double v=xs2*tan(30.0*deg); // 60 degree viewing angle perspective projection
    
        matrix_mul(m,ieye,rep);             // cumulate all needed transforms
    
        for (i=0;i<facs*4;)                 // go through all faces
            {
            // convert all points of face
            matrix_mul_vector(p0,m,&pnt[fac[i]*3]); i++;
            matrix_mul_vector(p1,m,&pnt[fac[i]*3]); i++;
            matrix_mul_vector(p2,m,&pnt[fac[i]*3]); i++;
            matrix_mul_vector(p3,m,&pnt[fac[i]*3]); i++;
            // here goes perspective divide by z coordinate if needed
            d=divide(v,p0[2]); p0[0]*=d; p0[1]*=d;
            d=divide(v,p1[2]); p1[0]*=d; p1[1]*=d;
            d=divide(v,p2[2]); p2[0]*=d; p2[1]*=d;
            d=divide(v,p3[2]); p3[0]*=d; p3[1]*=d;
            // here is viewport transform (just translate (0,0) to middle of screen in this case
            p0[0]+=xs2; p0[1]+=ys2;
            p1[0]+=xs2; p1[1]+=ys2;
            p2[0]+=xs2; p2[1]+=ys2;
            p3[0]+=xs2; p3[1]+=ys2;
            // draw quad
            // I use VCL GDI TCanvas you use what you have ...
            // and wireframe only to keep this simple (no Z buffer,winding culling,...)
            scr->Pen->Color=clAqua;     // perimeter wireframe
            scr->MoveTo(p0[0],p0[1]);
            scr->LineTo(p1[0],p1[1]);
            scr->LineTo(p2[0],p2[1]);
            scr->LineTo(p3[0],p3[1]);
            scr->LineTo(p0[0],p0[1]);
    //      scr->Pen->Color=clBlue;     // face cross to visualy check if I correctly generate the fac[]
    //      scr->MoveTo(p0[0],p0[1]);
    //      scr->LineTo(p2[0],p2[1]);
    //      scr->MoveTo(p1[0],p1[1]);
    //      scr->LineTo(p3[0],p3[1]);
            }
        }
    //---------------------------------------------------------------------------
    //---------------------------------------------------------------------------
    void TMain::draw()
        {
        if (!_redraw) return;
        bmp->Canvas->Brush->Color=clBlack;
        bmp->Canvas->FillRect(TRect(0,0,xs,ys));
    
        // compute inverse of camera need to compute just once for all objects
        double ieye[16];
        matrix_inv(ieye,eye);
        // draw all objects
        obj(pnt,pnts,fac,facs,rep,ieye);
    
        Main->Canvas->Draw(0,0,bmp);
        _redraw=false;
        }
    //---------------------------------------------------------------------------
    __fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
        {
        // window constructor you can ignore this ... (just create a backbuffer bitmap here)
        bmp=new Graphics::TBitmap;
        bmp->HandleType=bmDIB;
        bmp->PixelFormat=pf32bit;
        pyx=NULL;
        }
    //---------------------------------------------------------------------------
    void __fastcall TMain::FormDestroy(TObject *Sender)
        {
        // window destructor release memory ... also ignoe this
        if (pyx) delete pyx;
        delete bmp;
        }
    //---------------------------------------------------------------------------
    void __fastcall TMain::FormResize(TObject *Sender)
        {
        // on resize event ... just resize/redraw backbuffer also can ignore this
        xs=ClientWidth;  xs2=xs>>1;
        ys=ClientHeight; ys2=ys>>1;
        bmp->Width=xs;
        bmp->Height=ys;
        if (pyx) delete pyx;
        pyx=new int*[ys];
        for (int y=0;y<ys;y++) pyx[y]=(int*) bmp->ScanLine[y];
        _redraw=true;
        }
    //---------------------------------------------------------------------------
    void __fastcall TMain::FormPaint(TObject *Sender)
        {
        // repaint event can ignore
        _redraw=true;
        }
    //---------------------------------------------------------------------------
    void __fastcall TMain::tim_redrawTimer(TObject *Sender)
        {
        // timer event to animate the cube ...
        _redraw=true;
    
        // rotate the object to see it in motion
        double ang,c,s;
    
        ang=5.0*deg; c=cos(ang); s=sin(ang);    // rotate baround z by 5 degrees per timer step
        double rz[16]= { c, s, 0, 0,
                        -s, c, 0, 0,
                         0, 0, 1, 0,
                         0, 0, 0, 1 };
    
        ang=1.0*deg; c=cos(ang); s=sin(ang);    // rotate baround x by 1 degrees per timer step
        double rx[16]= { 1, 0, 0, 0,
                         0, c, s, 0,
                         0,-s, c, 0,
                         0, 0, 0, 1 };
        matrix_mul(rep,rep,rz);
        matrix_mul(rep,rep,rx);
    
        draw();
        }
    //---------------------------------------------------------------------------
    

    here is how it looks like:

    And GIF animation with back face culling:

    [notes]

    If you have more questions then comment me ...

    [Edit2] basic 3D vector operations often needed

    If you do not know how to compute vector operations like cross/dot products or absolute value see:

    // cross product: W = U x V
    W.x=(U.y*V.z)-(U.z*V.y)
    W.y=(U.z*V.x)-(U.x*V.z)
    W.z=(U.x*V.y)-(U.y*V.x)
    // dot product: a = (U.V)
    a=U.x*V.x+U.y*V.y+U.z*V.z
    // abs of vector a = |U|
    a=sqrt((U.x*U.x)+(U.y*U.y)+(U.z*U.z))
    

    here my C++ vector math:

    static double vector_tmp[3];
    double divide(double x,double y) { if ((y>=-1e-30)&&(y<=+1e-30)) return 0.0; return x/y; }
    double* vector_ld(double x,double y,double z)          { double *p=vector_tmp; p[0]=x; p[1]=y; p[2]=z; return p;}
    double* vector_ld(double *p,double x,double y,double z) {                      p[0]=x; p[1]=y; p[2]=z; return p;}
    void  vector_copy(double *c,double *a)         { for(int i=0;i<3;i++) c[i]=a[i];       }
    void  vector_abs(double *c,double *a)          { for(int i=0;i<3;i++) c[i]=fabs(a[i]); }
    void  vector_one(double *c,double *a)
            {
            double l=divide(1.0,sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])));
            c[0]=a[0]*l;
            c[1]=a[1]*l;
            c[2]=a[2]*l;
            }
    void  vector_len(double *c,double *a,double l)
            {
            l=divide(l,sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])));
            c[0]=a[0]*l;
            c[1]=a[1]*l;
            c[2]=a[2]*l;
            }
    void  vector_neg(double *c,double *a)          { for(int i=0;i<3;i++) c[i]=-a[i];      }
    void  vector_add(double *c,double *a,double *b) { for(int i=0;i<3;i++) c[i]=a[i]+b[i]; }
    void  vector_sub(double *c,double *a,double *b) { for(int i=0;i<3;i++) c[i]=a[i]-b[i]; }
    void  vector_mul(double *c,double *a,double *b) // cross
            {
            double   q[3];
            q[0]=(a[1]*b[2])-(a[2]*b[1]);
            q[1]=(a[2]*b[0])-(a[0]*b[2]);
            q[2]=(a[0]*b[1])-(a[1]*b[0]);
            for(int i=0;i<3;i++) c[i]=q[i];
            }
    void  vector_mul(double *c,double *a,double  b) { for(int i=0;i<3;i++) c[i]=a[i]*b; }
    void  vector_mul(double *c,double  a,double *b) { for(int i=0;i<3;i++) c[i]=a*b[i]; }
    double vector_mul(         double *a,double *b) { double c=0; for(int i=0;i<3;i++) c+=a[i]*b[i]; return c; } // dot
    double vector_len(double *a) { return sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])); }
    double vector_len2(double *a) { return (a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2]); }
    

    [Edit3] local rotations for camera and object control via keyboard

    As this has been asked a lot lately here some example answers of mine with demos:

    这篇关于如何从头开始构建具有人类可读角度的旋转矩阵?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆