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();
}