简体   繁体   中英

3d projection Axis inversion problem (Java/Processing)

Brief introduction: Unfortunately I always had problems with math and was never good at it. I'm currently trying to combine math with my knowledge and my passion for programming. In linear algebra, we now have topics of matrix multiplication and 3D projection, which sounds more interesting to me as an enthusiastic programmer than the whole thing with abstract number theories. So I set myself the goal of programming a small 3D game in Processing (Java). My first success was to rotate a Cube in 3 dimensions and had no problems with that.


What I want : After that however I wanted to have a camera perspective for a three-dimensional space . I took the 3D projection matrix from here: Mathematical Formula

The issue : I noticed that when I rotate the camera, the projection (the box in this case) flips horizontally and vertically as seen in the video here . What is happening (timestamp 0:14) and how can I fix it?

This is my code:

import java.util.Arrays;


PVector[] points;

PVector cam, cam_angle, point;

float angle = 0.0;

void setup() {
  //fullScreen();  
  
  size(1000,400);
  noCursor();

  cam = point = new PVector(0, 0, -1000);
  cam_angle = new PVector(0,0,0);

  points = new PVector[] {
    new PVector(-100, -100, -100),
    new PVector(-100, -100, 100),
    new PVector(-100, 100, -100),
    new PVector(-100, 100, 100),
    new PVector( 100, -100, -100),
    new PVector( 100, -100, 100),
    new PVector( 100, 100, -100),
    new PVector( 100, 100, 100),
  };
  
}


PVector Rotate2d(PVector p, float a) {
  // a = angle

  float[][] m2 = {
    {cos(a), -sin(a)},
    {sin(a), cos(a)}
  };

  float[][] rotated = matmul(m2, new float[][] {
    { p.x },
    { p.y }
    });

  return new PVector(rotated[0][0], rotated[1][0]);
}


PVector Rotate3d(PVector p, float[][] m2) {

  float[][] rotated = matmul(m2, new float[][] {
    { p.x },
    { p.y },
    { p.z }
    });

  return new PVector(rotated[0][0], rotated[1][0], rotated[2][0]);
}



PVector Rotate3d_x(PVector p, float a) {
  return Rotate3d(p,
    new float[][] {
    {1, 0, 0},
    {0, cos(a), -sin(a)},
    {0, sin(a), cos(a)}
    });
};

PVector Rotate3d_y(PVector p, float a) {
  return Rotate3d(p,
    new float[][] {
    {cos(a), 0, sin(a)},
    {0, 1, 0},
    {-sin(a), 0, cos(a)}
    });
}

PVector Rotate3d_z(PVector p, float a) {
  return Rotate3d(p,
    new float[][] {
    {cos(a), -sin(a), 0},
    {sin(a), cos(a), 0},
    {0, 0, 1}
    });
}

PVector Rotate3d(PVector p, PVector a) {
  return Rotate3d_z( Rotate3d_y(Rotate3d_x(p, a.x), a.y), a.z );
}



PVector applyPerspective(PVector p) {
   PVector c = cam;
   PVector co = cam_angle;
   PVector e =  new PVector(0, 0, 100);
  // c = camera position
  // co = camera orientation / camera rotation
  // e = displays surface pos relative to camera pinhole c

  // dx, dy, dz     https://en.wikipedia.org/wiki/3D_projection   :   Mathematical Formula
  float[][] dxyz = matmul(
    matmul(new float[][]{
    {1, 0, 0},
    {0, cos(co.x), sin(co.x)},
    {0, -sin(co.x), cos(co.x)}
    }, new float[][]{
      {cos(co.y), 0, -sin(co.y)},
      {0, 1, 0},
      {sin(co.y), 0, cos(co.y)}
    }),

    matmul(new float[][]{
      {cos(co.z), sin(co.z), 0},
      {-sin(co.z), cos(co.z), 0},
      {0, 0, 1}
    }, new float[][]{
      {p.x - c.x},
      {p.y - c.y},
      {p.z - c.z},
    }));

  PVector d = new PVector(dxyz[0][0], dxyz[1][0], dxyz[2][0]);

  return new PVector((e.z/d.z)*d.x+e.x, (e.z/d.z)*d.y+e.y);
}



// Matrixmultiplikation
float[][] matmul(float[][] m1, float[][] m2) {

  int cols_m1 = m1.length,
    rows_m1 = m1[0].length;

  int cols_m2 = m2.length,
    rows_m2 = m2[0].length;

  try {
    if (rows_m1 != cols_m2) throw new Exception("Rows of m1 must match Columns of m2!");
  }
  catch(Exception e) {
    println(e);
  }


  float[][] res = new float[cols_m2][rows_m2];


  for (int c=0; c < cols_m1; c++) {

    for (int r2=0; r2 < rows_m2; r2++) {

      float sum = 0;
      float[] buf = new float[rows_m1];

      // Multiply rows of m1 with columns of m2 and store in buf
      for (int r=0; r < rows_m1; r++) {
        buf[r] = m1[c][r]* m2[r][r2];
      }

      // Add up all entries into sum
      for (float entry : buf) {
        sum += entry;
      }

      res[c][r2] = sum;
    }
  }

  return res;
}


void draw() {
  cam_angle = new PVector(0.01*(mouseY-width/2), 0.01*(mouseX-height/2), 0);
  
  background(255);
  translate(width/2, height/2);
  strokeWeight(1);
  fill(0);

  PVector[] points_projected = new PVector[points.length];

  for (int i=0; i < points.length; i++) {
    points_projected[i] =   applyPerspective(points[i]);
  }

  
  for (int i=0; i < points_projected.length; i++) {    
    for (int a=0; a < points_projected.length; a++) {
      // Alle Punkte verbinden
      line(points_projected[i].x, points_projected[i].y, points_projected[a].x, points_projected[a].y);
    }
  }  
}


void keyPressed() {
  if (key == 'w') {
    cam.add(Rotate3d(new PVector(0, 0, -20), cam_angle));
  }
  
  if (key == 'a') {
    cam.add(Rotate3d(new PVector(20, 0, 0), cam_angle));

  }
  
  if (key == 's') {
    cam.add(Rotate3d(new PVector(0, 0, 20), cam_angle));

  }
  
  if (key == 'd') {
   cam.add(Rotate3d(new PVector(-20, 0, 0), cam_angle));

  }  
}

I also tried changing the values for

// e = displays surface position relative to camera pinhole c
PVector e = new PVector(0, 0, 1000);

around, which didn't help either.

The perspective projection formulas, x / z and y / z are valid only when z > 0 . If z < 0 , it gets flipped: -x / abs(z) and -y / abs(z) . So, in case any portions of the model can go to the backside of camera by rotation, you need to clip only valid parts ( z > 0 ) prior to applying perspective projection.

For example, minimal near-plane clipping for the wireframe model can be:

void draw()
{

     :

    //PVector[] points_projected = new PVector[points.length];
    PVector[] points_view = new PVector[points.length];

    for (int i = 0; i < points.length; i++)
    {
        //points_projected[i] = applyPerspective(points[i]);
        points_view[i] = applyViewTransform(points[i]);
    }

    float nearPlane = 1.0F;

    for (int i = 0; i < points_view.length; i++)
    {
        for (int a = 0; a < points_view.length; a++)
        {
            // Alle Punkte verbinden
            if (i == a) continue;
            PVector p0 = points_view[i];
            PVector p1 = points_view[a];
            switch (((p0.z >= nearPlane) ? 1 : 0) | ((p1.z >= nearPlane) ? 2 : 0))
            {
                case 0: // Both ends are in back of camera.
                    continue;
                case 3: // Both ends are in front of camera.
                    break;
                case 1: // p0.z >= nearPlane && p1.z < nearPlane
                    p1 = PVector.lerp(p0, p1, (p0.z - nearPlane) / (p0.z - p1.z));
                    break;
                case 2: // p1.z >= nearPlane && p0.z < nearPlane
                    p0 = PVector.lerp(p1, p0, (p1.z - nearPlane) / (p1.z - p0.z));
                    break;
            }

            //line(points_projected[i].x, points_projected[i].y, points_projected[a].x, points_projected[a].y);

            PVector[] points_projected = {applyPerspectiveTransform(p0), applyPerspectiveTransform(p1)};
            line(points_projected[0].x, points_projected[0].y, points_projected[1].x, points_projected[1].y);

        }
    }
}

Where new functions, applyViewTransform() and applyPerspectiveTransform() were made by splitting applyPerspective() function as follows;

  • applyViewTransform() : Transform from world-space to camera-space.
  • applyPerspectiveTransform() : Perspective transform from 3d-space to 2d-screen-space.

.

PVector applyPerspective(PVector p)
{
    PVector d = applyViewTransform(p);
    return applyPerspectiveTransform(d);
}

PVector applyViewTransform(PVector p)
{
   
    PVector c = cam;
    PVector co = cam_angle;
    //PVector e = new PVector(0, 0, 100);
    // c = camera position
    // co = camera orientation / camera rotation

    // dx, dy, dz     https://en.wikipedia.org/wiki/3D_projection   :   Mathematical Formula
    float[][] dxyz = matmul(
            matmul(new float[][]{
                    {1, 0, 0},
                    {0, cos(co.x), sin(co.x)},
                    {0, -sin(co.x), cos(co.x)}
            }, new float[][]{
                    {cos(co.y), 0, -sin(co.y)},
                    {0, 1, 0},
                    {sin(co.y), 0, cos(co.y)}
            }),

            matmul(new float[][]{
                    {cos(co.z), sin(co.z), 0},
                    {-sin(co.z), cos(co.z), 0},
                    {0, 0, 1}
            }, new float[][]{
                    {p.x - c.x},
                    {p.y - c.y},
                    {p.z - c.z},
            }));

    PVector d = new PVector(dxyz[0][0], dxyz[1][0], dxyz[2][0]);
    return d;
}

PVector applyPerspectiveTransform(PVector d)
{
    PVector e = new PVector(0, 0, 100);
    // e = displays surface pos relative to camera pinhole c
    return new PVector((e.z / d.z) * d.x + e.x, (e.z / d.z) * d.y + e.y);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM