3D Cube: Problem when zooming into cube


#1

I am trying to learn how to create 3D shapes so that I can manipulate them in 3D space and draw them onto the 2D screen. So far I have a cube made from 8 points and 12 lines. Unfortunately, there seems to be a problem when I zoom into the cube - I get a graphics error as I start to go through it. When I zoom into the cube the lines seem to warp. Also, if I keep zooming in the cube reverses and goes away from me. Any help with this would be great at helping me start my journey into 3D gaming, thanks!

Here are the controls:
Up/Down: Moves Camera Up/Down
Left/Right: Moves Camera Left/Right
z/a: Zoom In/Out
s/x: Rotate on x-axis
d/c: Rotate on y-axis
f/v: Rotate on z-axis

Please note that the text isn’t used, but I have it there because I use it to display variables, etc. as needed.

#include <iostream>
#include <math.h>
using namespace std;

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>

// 3D Structs
struct Point3D
{
  double x;
  double y;
  double z;
};

struct Point2D
{
  double x;
  double y;
};

void init_SDL(SDL_Renderer *& renderer, SDL_Window *& window, int w, int h,
              string window_name);

void draw_text(SDL_Renderer *& renderer, TTF_Font *& font,
               std::string text_str, int x, int y);

void stop_SDL(SDL_Renderer *& renderer, SDL_Window *& window);

double limitAngle(double angle_arg);

void drawThree(SDL_Renderer *& renderer, Point2D (&p2d)[8], int s, int p1, int p2, int p3);

int main(int argc, char* argv[])
{
  // Constants
  const int SCREEN_W = 500;
  const int SCREEN_H = 500;
  const string WINDOW_NAME = "My Window";

  // Program Variables.
  bool quit = false;

  // 3D Calculation Constants
  const double deg2rad = 3.14159265358979323846 / 180.0;
  const double cameraXangle = 40.0 * deg2rad;
  const double cameraYangle = 40.0 * deg2rad;
  const double halfCanvas = SCREEN_W / 2;
  const double sideLength = 50;
  const double sideLengthN = -sideLength;

  // Cube points in space
  Point3D cubeMid = {250, 250, 250};

  // Point Matrices
  Point3D p3d[8];
  p3d[0] = {sideLengthN, sideLengthN, sideLengthN};
  p3d[1] = {sideLength, sideLengthN, sideLengthN};
  p3d[2] = {sideLength, sideLengthN, sideLength};
  p3d[3] = {sideLengthN, sideLengthN, sideLength};
  p3d[4] = {sideLengthN, sideLength, sideLengthN};
  p3d[5] = {sideLength, sideLength, sideLengthN};
  p3d[6] = {sideLength, sideLength, sideLength};
  p3d[7] = {sideLengthN, sideLength, sideLength};

  // Initial camera location.
  Point3D camera = {250, 250, 0};

  // Initial rotation about each axis (degrees).
  Point3D cRot = {0, 0, 0};

  // Initial rotation speed for each axis (60 fps per unit).
  Point3D rSpeed = {0, 0, 0};

  // Create a game window and renderer for game graphics.
  SDL_Renderer *renderer;
  SDL_Window* window;
  SDL_Event e;

  // Initialize SDL and TTF.
  init_SDL(renderer, window, SCREEN_W, SCREEN_H, WINDOW_NAME);

  // Create game fonts.
  TTF_Font *font = TTF_OpenFont("arial_black.ttf", 18);

  // Main game loop.
  while (!quit)
  {
    // See if the window was closed.
    while (SDL_PollEvent(&e))
    {
      switch(e.type)
      {
        case SDL_QUIT:
          quit = true;
          break;
        case SDL_KEYDOWN:
          switch(e.key.keysym.sym)
          {
            case SDLK_DOWN:
              camera.y += 2;
              break;
            case SDLK_UP:
              camera.y -= 2;
              break;
            case SDLK_LEFT:
              camera.x -= 2;
              break;
            case SDLK_RIGHT:
              camera.x += 2;
              break;
            case SDLK_a:
              camera.z -= 2;
              break;
            case SDLK_z:
              camera.z += 2;
              break;

            case SDLK_s:
              cRot.x -= 2;
              break;
            case SDLK_x:
              cRot.x += 2;
              break;
            case SDLK_d:
              cRot.y -= 2;
              break;
            case SDLK_c:
              cRot.y += 2;
              break;
            case SDLK_f:
              cRot.z -= 2;
              break;
            case SDLK_v:
              cRot.z += 2;
              break;

            default:
              break;
          }
          break;
      }
    }

    // Rotate scene.
    cRot.x = limitAngle(cRot.x + rSpeed.x);
    cRot.y = limitAngle(cRot.y + rSpeed.y);
    cRot.z = limitAngle(cRot.z + rSpeed.z);

    // Calculate offset angle and convert to radians.
    Point3D rad = {cRot.x * deg2rad, cRot.y * deg2rad, cRot.z * deg2rad};

    // Calculate cos/sin for each angle (radians).
    Point3D cos_rad = {cos(rad.x), cos(rad.y), cos(rad.z)};
    Point3D sin_rad = {sin(rad.x), sin(rad.y), sin(rad.z)};

    // 3D Points in 2D.
    Point2D p2d[8];

    for (int i = 0; i < 8; i++)
    {
      Point3D loc;
      loc.x = cubeMid.x + cos_rad.y * ((p3d[i].x * cos_rad.z) -
                                       (p3d[i].y * sin_rad.z)) +
                                       (p3d[i].z * sin_rad.y);

      loc.y = cubeMid.y + p3d[i].x * (sin_rad.x * sin_rad.y * cos_rad.z +
                                      cos_rad.x * sin_rad.z) +
                          p3d[i].y * (-sin_rad.x * sin_rad.y * sin_rad.z +
                                      cos_rad.x * cos_rad.z) -
                          p3d[i].z * (sin_rad.x * cos_rad.y);

      loc.z = cubeMid.x + p3d[i].x * (-cos_rad.x * sin_rad.y * cos_rad.z +
                                      sin_rad.x * sin_rad.z) +
                          p3d[i].y * (cos_rad.x * sin_rad.y * sin_rad.z +
                                      sin_rad.x * cos_rad.z) +
                          p3d[i].z * (cos_rad.x * cos_rad.y);

      // X, Y point difference from X, Y camera.
      // Assume everything is in the front of the camera (positive zDiff).
      Point3D diff = {loc.x - camera.x, loc.y - camera.y, loc.z - camera.z};

      // Maximum x and y on view screen at point distance.
      Point2D maxim = {diff.z * tan(cameraXangle), diff.z * tan(cameraYangle)};

      // X, Y percentages of the maximum.
      Point2D percent = {diff.x / maxim.x, diff.y / maxim.y};

      // Create a 2D point with the converted 3D data.
      p2d[i].x = halfCanvas + (percent.x * halfCanvas);
      p2d[i].y = halfCanvas + (percent.y * halfCanvas);

      // Clear the background.
      SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
      SDL_RenderClear(renderer);

      // Set line color for wire map.
      SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
      drawThree(renderer, p2d, 1, 0, 2, 5);
      drawThree(renderer, p2d, 3, 0, 2, 7);
      drawThree(renderer, p2d, 4, 0, 5, 7);
      drawThree(renderer, p2d, 6, 2, 5, 7);
    }

    // Draw everything to screen.
    SDL_RenderPresent(renderer);
  }

  // Destroy Font
  TTF_CloseFont(font);
  font = NULL;

  // Close down SDL and TTF.
  stop_SDL(renderer, window);

  return 0;
}

void drawThree(SDL_Renderer *& renderer, Point2D (&p2d)[8], int s, int p1, int p2, int p3)
{
  SDL_RenderDrawLine(renderer, p2d[s].x, p2d[s].y,
                     p2d[p1].x, p2d[p1].y);
  SDL_RenderDrawLine(renderer, p2d[s].x, p2d[s].y,
                     p2d[p2].x, p2d[p2].y);
  SDL_RenderDrawLine(renderer, p2d[s].x, p2d[s].y,
                     p2d[p3].x, p2d[p3].y);
}

// Limit angle from 0 to 360 degrees.
double limitAngle(double angle_arg)
{
  if (angle_arg >= 360.0)
    return (angle_arg - 360.0);
  else if (angle_arg < 0.0)
    return (angle_arg + 360.0);
  else
    return angle_arg;
}

void init_SDL(SDL_Renderer *& renderer, SDL_Window *& window, int w, int h,
              string window_name)
{
  // Initialize SDL and TTF.
  SDL_Init(SDL_INIT_EVERYTHING);
  TTF_Init();

  // Create a window for your game.
  window = SDL_CreateWindow(window_name.c_str(), SDL_WINDOWPOS_UNDEFINED,
                            SDL_WINDOWPOS_UNDEFINED, w, h, 0);

  // Create a renderer to display your graphics.
  renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED |
                                SDL_RENDERER_PRESENTVSYNC);

}

void draw_text(SDL_Renderer *& renderer, TTF_Font *&font,
               std::string text_str, int x, int y)
{
  // Variables for text box width and height.
  int temp_w, temp_h;

  // Color for text.
  SDL_Color text_color = {0, 0, 0};

  // Text for output.
  const char * text = text_str.c_str();

  // Create a temporary surface and texture for the text.
  SDL_Surface *text_surface = TTF_RenderText_Blended(font, text, text_color);
  SDL_Texture *text_texture = SDL_CreateTextureFromSurface(renderer, text_surface);

  // Find the size of the text to get the width and height for the rectangle.
  TTF_SizeText(font, text, &temp_w, &temp_h);

  // Create the text rectangle.
  SDL_Rect text_rect = {x, y, temp_w, temp_h};

  // Draw the text to the renderer.
  SDL_RenderCopy(renderer, text_texture, NULL, &text_rect);

  // Destroy surface and texture.
  SDL_FreeSurface(text_surface);
  text_surface = NULL;
  SDL_DestroyTexture(text_texture);
  text_texture = NULL;
}

void stop_SDL(SDL_Renderer *& renderer, SDL_Window *& window)
{
  // Close down SDL and TTF.
  SDL_DestroyRenderer(renderer);
  renderer = NULL;
  SDL_DestroyWindow(window);
  window = NULL;

  // Quit TTF and SDL.
  TTF_Quit();
  SDL_Quit();
}

#3

You need to not render it when it is outside the camera view, this is called clipping, or culling. The image is reversed because it is behind the camera.

The method depends if you are moving the world or the camera. Generally, it is easier to move the world opposite the camera, and keep the camera at 0.0.

Good luck, I created a 3D game from scratch using c++, sdl 2.0 and OpenGL. It is called BIOCODE X, it’s free right now, I figured out how to port it to Opengl es, so it will be on Android someday. You can check it out here http://www.biocode-x.wikidot.com

If you have more questions about 3D, feel free to ask.


#4

Not rendering things off screen makes sense. I tried to use some if statements to avoid rendering things, but it didn’t seem to work. How exactly do you know if the image is not on screen, as not drawing things that were less than the camera’s Z didn’t seem to work fully (ie. some stuff still got drawn in reverse before vanishing)? Also, is there an easy way to check for x and y when they are off the screen?

Also, I like the idea of keeping the camera in one place, especially at 0, so I will copy my program and attempt to make a version where that is the case.

I took a brief look at your game and it looks interesting. I’ll have to look at it further when I have more time

Speaking of SDL 2.0 and OpenGL, is it better for me to go ahead and use OpenGL directly for 3D, or should I stick with using math and SDL 2.0?

Thanks!


#5

Generally if statements are the way to do it. You want to clip a little in front of the camera, something like 0.2. I don’t remember exactly to do software clipping. Basically, you start with pure SDL software 3D rendering. However, it is too slow to render 3D textures. I followed the “Lazy foo” sdl 2.0 tutorials. You cannot only use OpenGL, it must be used with SDL and some other language such as c.

Thanks for checking out my game, it was a lot of effort.


#6

I’ll experiment with the clipping and see what works. Is there an easier way to do hardware clipping?

Right, I figured I would have to use C, but I didn’t know if I would be using OpenGL alone or with SDL. I’ll have to check out those tutorials. I used Lazy Foo for my SDL installation, so maybe they can help me make some 3D stuff too.

Good luck with your future game development, and thanks!