Skip to content

Instantly share code, notes, and snippets.

@CompaqDisc
Created August 2, 2019 18:33
Show Gist options
  • Save CompaqDisc/b382d716e94602a78d32b2148ed0c470 to your computer and use it in GitHub Desktop.
Save CompaqDisc/b382d716e94602a78d32b2148ed0c470 to your computer and use it in GitHub Desktop.
--- p3.cc 2019-08-02 01:00:02.824687700 -0600
+++ SimpleDemo3DPGE.cc 2019-08-02 12:21:52.749790300 -0600
@@ -1,95 +1,57 @@
/*
-OneLoneCoder.com - 3D Graphics Part #3 - Cameras & Clipping
-"Tredimensjonal Grafikk" - @Javidx9
-
-License
-~~~~~~~
-One Lone Coder Console Game Engine Copyright (C) 2018 Javidx9
-This program comes with ABSOLUTELY NO WARRANTY.
-This is free software, and you are welcome to redistribute it
-under certain conditions; See license for details.
-Original works located at:
-https://www.github.com/onelonecoder
-https://www.onelonecoder.com
-https://www.youtube.com/javidx9
-GNU GPLv3
-https://github.com/OneLoneCoder/videos/blob/master/LICENSE
-
-From Javidx9 :)
-~~~~~~~~~~~~~~~
-Hello! Ultimately I don't care what you use this for. It's intended to be
-educational, and perhaps to the oddly minded - a little bit of fun.
-Please hack this, change it and use it in any way you see fit. You acknowledge
-that I am not responsible for anything bad that happens as a result of
-your actions. However this code is protected by GNU GPLv3, see the license in the
-github repo. This means you must attribute me if you use it. You can view this
-license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE
-Cheers!
-
-Background
-~~~~~~~~~~
-3D Graphics is an interesting, visually pleasing suite of algorithms. This is the
-first video in a series that will demonstrate the fundamentals required to
-build your own software based 3D graphics systems.
-
-Video
-~~~~~
-https://youtu.be/ih20l3pJoeU
-https://youtu.be/XgMWc6LumG4
-https://youtu.be/HXSuNxpCzdM
-
-Author
-~~~~~~
-Twitter: @javidx9
-Blog: http://www.onelonecoder.com
-Discord: https://discord.gg/WhwHUMV
-
-
-Last Updated: 14/08/2018
+ PGE Output
+ Created by Bradan J. Wolbeck (CompaqDisc),
+ based on the tutorial series by YouTube user javidx9.
*/
-
-#include "olcConsoleGameEngine.h"
+#define OLC_PGE_APPLICATION
+#include "olcPixelGameEngine.h"
+#include <stdlib.h>
#include <fstream>
+#include <iostream>
#include <strstream>
+#include <cstring>
#include <algorithm>
-using namespace std;
+#define WINDOW_WIDTH 1920
+#define WINDOW_HEIGHT 1080
struct vec3d
{
float x = 0;
float y = 0;
float z = 0;
- float w = 1; // Need a 4th term to perform sensible matrix vector multiplication
+ float w = 1;
};
struct triangle
{
vec3d p[3];
- wchar_t sym;
- short col;
+ olc::Pixel color;
};
struct mesh
{
- vector<triangle> tris;
+ std::vector<triangle> tris;
- bool LoadFromObjectFile(string sFilename)
+ bool LoadFromObjectFile(std::string sFilename)
{
- ifstream f(sFilename);
- if (!f.is_open())
+ std::cout << "Loading file: " << sFilename << std::endl;
+ std::ifstream f(sFilename);
+ if (!f.is_open()) {
+ std::cerr << "Failed to load object file!" << std::endl;
return false;
+ }
- // Local cache of verts
- vector<vec3d> verts;
+ // Local cache of verts.
+ std::vector<vec3d> verts;
- while (!f.eof())
+ while(!f.eof())
{
char line[128];
f.getline(line, 128);
- strstream s;
+ std::strstream s;
s << line;
char junk;
@@ -108,6 +70,10 @@
tris.push_back({ verts[f[0] - 1], verts[f[1] - 1], verts[f[2] - 1] });
}
}
+
+ f.close();
+ printf("Done loading %ld faces!\n", tris.size());
+
return true;
}
};
@@ -117,24 +83,40 @@
float m[4][4] = { 0 };
};
-class olcEngine3D : public olcConsoleGameEngine
+class SimpleDemo3D : public olc::PixelGameEngine
{
public:
- olcEngine3D()
+ SimpleDemo3D()
{
- m_sAppName = L"3D Demo";
+ sAppName = "SimpleDemo3D";
+ }
+
+ void setDebugRenderMode(bool bWireframe, bool bClippingColors) {
+ bDebugEnableWireframe = bWireframe;
+ bDebugEnableClippingColors = bClippingColors;
}
+ void setObjectFile(const char* sFilename) {
+ sObjFile = sFilename;
+ }
private:
mesh meshCube;
- mat4x4 matProj; // Matrix that converts from view space to screen space
- vec3d vCamera; // Location of camera in world space
- vec3d vLookDir; // Direction vector along the direction camera points
- float fYaw; // FPS Camera rotation in XZ plane
- float fTheta; // Spins World transform
+ mat4x4 matProjection;
- vec3d Matrix_MultiplyVector(mat4x4 &m, vec3d &i)
+ bool bDebugEnableWireframe = false;
+ bool bDebugEnableClippingColors = false;
+ bool bWishesToExit = false;
+ const char* sObjFile;
+
+ vec3d vCamera = { 0.0f, 0.0f, 0.0f };
+ vec3d vLookDir = { 0.0f, 0.0f, 0.0f };
+
+ float fYaw = 0.0f;
+
+ float fTheta = 0.0f;
+
+ vec3d MatrixMultiplyVector(mat4x4 &m, vec3d &i)
{
vec3d v;
v.x = i.x * m.m[0][0] + i.y * m.m[1][0] + i.z * m.m[2][0] + i.w * m.m[3][0];
@@ -144,8 +126,7 @@
return v;
}
- mat4x4 Matrix_MakeIdentity()
- {
+ mat4x4 MatrixMakeIdentity() {
mat4x4 matrix;
matrix.m[0][0] = 1.0f;
matrix.m[1][1] = 1.0f;
@@ -154,8 +135,7 @@
return matrix;
}
- mat4x4 Matrix_MakeRotationX(float fAngleRad)
- {
+ mat4x4 MatrixMakeRotationX(float fAngleRad) {
mat4x4 matrix;
matrix.m[0][0] = 1.0f;
matrix.m[1][1] = cosf(fAngleRad);
@@ -166,8 +146,7 @@
return matrix;
}
- mat4x4 Matrix_MakeRotationY(float fAngleRad)
- {
+ mat4x4 MatrixMakeRotationY(float fAngleRad) {
mat4x4 matrix;
matrix.m[0][0] = cosf(fAngleRad);
matrix.m[0][2] = sinf(fAngleRad);
@@ -178,8 +157,7 @@
return matrix;
}
- mat4x4 Matrix_MakeRotationZ(float fAngleRad)
- {
+ mat4x4 MatrixMakeRotationZ(float fAngleRad) {
mat4x4 matrix;
matrix.m[0][0] = cosf(fAngleRad);
matrix.m[0][1] = sinf(fAngleRad);
@@ -190,8 +168,7 @@
return matrix;
}
- mat4x4 Matrix_MakeTranslation(float x, float y, float z)
- {
+ mat4x4 MatrixMakeTranslation(float x, float y, float z) {
mat4x4 matrix;
matrix.m[0][0] = 1.0f;
matrix.m[1][1] = 1.0f;
@@ -203,8 +180,7 @@
return matrix;
}
- mat4x4 Matrix_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar)
- {
+ mat4x4 MatrixMakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar) {
float fFovRad = 1.0f / tanf(fFovDegrees * 0.5f / 180.0f * 3.14159f);
mat4x4 matrix;
matrix.m[0][0] = fAspectRatio * fFovRad;
@@ -216,8 +192,7 @@
return matrix;
}
- mat4x4 Matrix_MultiplyMatrix(mat4x4 &m1, mat4x4 &m2)
- {
+ mat4x4 MatrixMultiplyMatrix(mat4x4 &m1, mat4x4 &m2) {
mat4x4 matrix;
for (int c = 0; c < 4; c++)
for (int r = 0; r < 4; r++)
@@ -225,36 +200,33 @@
return matrix;
}
- mat4x4 Matrix_PointAt(vec3d &pos, vec3d &target, vec3d &up)
- {
- // Calculate new forward direction
- vec3d newForward = Vector_Sub(target, pos);
- newForward = Vector_Normalise(newForward);
+ mat4x4 MatrixPointAt(vec3d &pos, vec3d &target, vec3d &up) {
+ // Calculate new forward direction.
+ vec3d newForward = VectorSubtract(target, pos);
+ newForward = VectorNormalize(newForward);
- // Calculate new Up direction
- vec3d a = Vector_Mul(newForward, Vector_DotProduct(up, newForward));
- vec3d newUp = Vector_Sub(up, a);
- newUp = Vector_Normalise(newUp);
+ // Calculate new up direction.
+ vec3d a = VectorMultiply(newForward, VectorDotProduct(up, newForward));
+ vec3d newUp = VectorSubtract(up, a);
+ newUp = VectorNormalize(newUp);
- // New Right direction is easy, its just cross product
- vec3d newRight = Vector_CrossProduct(newUp, newForward);
+ // New 'right' direction is easy, just a cross product.
+ vec3d newRight = VectorCrossProduct(newUp, newForward);
- // Construct Dimensioning and Translation Matrix
+ // Construct Matrix
mat4x4 matrix;
matrix.m[0][0] = newRight.x; matrix.m[0][1] = newRight.y; matrix.m[0][2] = newRight.z; matrix.m[0][3] = 0.0f;
- matrix.m[1][0] = newUp.x; matrix.m[1][1] = newUp.y; matrix.m[1][2] = newUp.z; matrix.m[1][3] = 0.0f;
+ matrix.m[1][0] = newUp.x; matrix.m[1][1] = newUp.y; matrix.m[1][2] = newUp.z; matrix.m[1][3] = 0.0f;
matrix.m[2][0] = newForward.x; matrix.m[2][1] = newForward.y; matrix.m[2][2] = newForward.z; matrix.m[2][3] = 0.0f;
- matrix.m[3][0] = pos.x; matrix.m[3][1] = pos.y; matrix.m[3][2] = pos.z; matrix.m[3][3] = 1.0f;
+ matrix.m[3][0] = pos.x; matrix.m[3][1] = pos.y; matrix.m[3][2] = pos.z; matrix.m[3][3] = 1.0f;
return matrix;
-
}
- mat4x4 Matrix_QuickInverse(mat4x4 &m) // Only for Rotation/Translation Matrices
- {
+ mat4x4 MatrixQuickInverse(mat4x4 &m) { // Only for Rotation/Translation
mat4x4 matrix;
- matrix.m[0][0] = m.m[0][0]; matrix.m[0][1] = m.m[1][0]; matrix.m[0][2] = m.m[2][0]; matrix.m[0][3] = 0.0f;
- matrix.m[1][0] = m.m[0][1]; matrix.m[1][1] = m.m[1][1]; matrix.m[1][2] = m.m[2][1]; matrix.m[1][3] = 0.0f;
- matrix.m[2][0] = m.m[0][2]; matrix.m[2][1] = m.m[1][2]; matrix.m[2][2] = m.m[2][2]; matrix.m[2][3] = 0.0f;
+ matrix.m[0][0] = m.m[0][0]; matrix.m[0][1] = m.m[1][0]; matrix.m[0][2] = m.m[2][0]; matrix.m[0][3] = 0.0f;
+ matrix.m[1][0] = m.m[0][1]; matrix.m[1][1] = m.m[1][1]; matrix.m[1][2] = m.m[2][1]; matrix.m[1][3] = 0.0f;
+ matrix.m[2][0] = m.m[0][2]; matrix.m[2][1] = m.m[1][2]; matrix.m[2][2] = m.m[2][2]; matrix.m[2][3] = 0.0f;
matrix.m[3][0] = -(m.m[3][0] * matrix.m[0][0] + m.m[3][1] * matrix.m[1][0] + m.m[3][2] * matrix.m[2][0]);
matrix.m[3][1] = -(m.m[3][0] * matrix.m[0][1] + m.m[3][1] * matrix.m[1][1] + m.m[3][2] * matrix.m[2][1]);
matrix.m[3][2] = -(m.m[3][0] * matrix.m[0][2] + m.m[3][1] * matrix.m[1][2] + m.m[3][2] * matrix.m[2][2]);
@@ -262,44 +234,36 @@
return matrix;
}
- vec3d Vector_Add(vec3d &v1, vec3d &v2)
- {
+ vec3d VectorAdd(vec3d &v1, vec3d &v2) {
return { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z };
}
- vec3d Vector_Sub(vec3d &v1, vec3d &v2)
- {
+ vec3d VectorSubtract(vec3d &v1, vec3d &v2) {
return { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z };
}
- vec3d Vector_Mul(vec3d &v1, float k)
- {
- return { v1.x * k, v1.y * k, v1.z * k };
+ vec3d VectorMultiply(vec3d &v1, float k) {
+ return { v1.x * k , v1.y * k, v1.z * k };
}
- vec3d Vector_Div(vec3d &v1, float k)
- {
+ vec3d VectorDivide(vec3d &v1, float k) {
return { v1.x / k, v1.y / k, v1.z / k };
}
- float Vector_DotProduct(vec3d &v1, vec3d &v2)
- {
- return v1.x*v2.x + v1.y*v2.y + v1.z * v2.z;
+ float VectorDotProduct(vec3d &v1, vec3d &v2) {
+ return { v1.x * v2.x + v1.y * v2.y + v1.z * v2.z };
}
- float Vector_Length(vec3d &v)
- {
- return sqrtf(Vector_DotProduct(v, v));
+ float VectorLength(vec3d &v) {
+ return sqrtf(VectorDotProduct(v, v));
}
- vec3d Vector_Normalise(vec3d &v)
- {
- float l = Vector_Length(v);
+ vec3d VectorNormalize(vec3d &v) {
+ float l = VectorLength(v);
return { v.x / l, v.y / l, v.z / l };
}
- vec3d Vector_CrossProduct(vec3d &v1, vec3d &v2)
- {
+ vec3d VectorCrossProduct(vec3d &v1, vec3d &v2) {
vec3d v;
v.x = v1.y * v2.z - v1.z * v2.y;
v.y = v1.z * v2.x - v1.x * v2.z;
@@ -307,28 +271,25 @@
return v;
}
- vec3d Vector_IntersectPlane(vec3d &plane_p, vec3d &plane_n, vec3d &lineStart, vec3d &lineEnd)
- {
- plane_n = Vector_Normalise(plane_n);
- float plane_d = -Vector_DotProduct(plane_n, plane_p);
- float ad = Vector_DotProduct(lineStart, plane_n);
- float bd = Vector_DotProduct(lineEnd, plane_n);
+ vec3d VectorIntersectPlane(vec3d &plane_p, vec3d &plane_n, vec3d &lineStart, vec3d &lineEnd) {
+ plane_n = VectorNormalize(plane_n);
+ float plane_d = -VectorDotProduct(plane_n, plane_p);
+ float ad = VectorDotProduct(lineStart, plane_n);
+ float bd = VectorDotProduct(lineEnd, plane_n);
float t = (-plane_d - ad) / (bd - ad);
- vec3d lineStartToEnd = Vector_Sub(lineEnd, lineStart);
- vec3d lineToIntersect = Vector_Mul(lineStartToEnd, t);
- return Vector_Add(lineStart, lineToIntersect);
+ vec3d lineStartToEnd = VectorSubtract(lineEnd, lineStart);
+ vec3d lineToIntersect = VectorMultiply(lineStartToEnd, t);
+ return VectorAdd(lineStart, lineToIntersect);
}
- int Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle &in_tri, triangle &out_tri1, triangle &out_tri2)
- {
- // Make sure plane normal is indeed normal
- plane_n = Vector_Normalise(plane_n);
+ int TriangleClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle &in_tri, triangle &out_tri1, triangle &out_tri2) {
+ // Ensure plane normal is indeed normal.
+ plane_n = VectorNormalize(plane_n);
- // Return signed shortest distance from point to plane, plane normal must be normalised
- auto dist = [&](vec3d &p)
- {
- vec3d n = Vector_Normalise(p);
- return (plane_n.x * p.x + plane_n.y * p.y + plane_n.z * p.z - Vector_DotProduct(plane_n, plane_p));
+ // Return signed shortest distance from point to plane, plane normal must be normalized.
+ auto dist = [&](vec3d &p) {
+ vec3d n = VectorNormalize(p);
+ return (plane_n.x * p.x + plane_n.y * p.y + plane_n.z * p.z - VectorDotProduct(plane_n, plane_p));
};
// Create two temporary storage arrays to classify points either side of plane
@@ -336,7 +297,7 @@
vec3d* inside_points[3]; int nInsidePointCount = 0;
vec3d* outside_points[3]; int nOutsidePointCount = 0;
- // Get signed distance of each point in triangle to plane
+ // Get signed distance of each point in triangle to plane.
float d0 = dist(in_tri.p[0]);
float d1 = dist(in_tri.p[1]);
float d2 = dist(in_tri.p[2]);
@@ -348,7 +309,7 @@
if (d2 >= 0) { inside_points[nInsidePointCount++] = &in_tri.p[2]; }
else { outside_points[nOutsidePointCount++] = &in_tri.p[2]; }
- // Now classify triangle points, and break the input triangle into
+ // Now classify triangle points, and break the input triangle into
// smaller output triangles if required. There are four possible
// outcomes...
@@ -369,240 +330,191 @@
return 1; // Just the one returned original triangle is valid
}
- if (nInsidePointCount == 1 && nOutsidePointCount == 2)
- {
+ if (nInsidePointCount == 1 && nOutsidePointCount == 2) {
// Triangle should be clipped. As two points lie outside
- // the plane, the triangle simply becomes a smaller triangle
+ // the plane, the triangle simply becomes a smaller triangle.
- // Copy appearance info to new triangle
- out_tri1.col = in_tri.col;
- out_tri1.sym = in_tri.sym;
+ // Copy appearance info to new triangle.
+ out_tri1.color = in_tri.color;
+ if (bDebugEnableClippingColors)
+ out_tri1.color.g = 0;
// The inside point is valid, so keep that...
out_tri1.p[0] = *inside_points[0];
- // but the two new points are at the locations where the
+ // but the two new points are at the locations where the
// original sides of the triangle (lines) intersect with the plane
- out_tri1.p[1] = Vector_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0]);
- out_tri1.p[2] = Vector_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[1]);
+ out_tri1.p[1] = VectorIntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0]);
+ out_tri1.p[2] = VectorIntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[1]);
return 1; // Return the newly formed single triangle
}
- if (nInsidePointCount == 2 && nOutsidePointCount == 1)
- {
+ if (nInsidePointCount == 2 && nOutsidePointCount == 1) {
// Triangle should be clipped. As two points lie inside the plane,
// the clipped triangle becomes a "quad". Fortunately, we can
// represent a quad with two new triangles
// Copy appearance info to new triangles
- out_tri1.col = in_tri.col;
- out_tri1.sym = in_tri.sym;
-
- out_tri2.col = in_tri.col;
- out_tri2.sym = in_tri.sym;
+ out_tri1.color = in_tri.color;
+ if (bDebugEnableClippingColors)
+ out_tri1.color.r = 0;
+ out_tri2.color = in_tri.color;
+ if (bDebugEnableClippingColors)
+ out_tri2.color.b = 0;
// The first triangle consists of the two inside points and a new
// point determined by the location where one side of the triangle
// intersects with the plane
out_tri1.p[0] = *inside_points[0];
out_tri1.p[1] = *inside_points[1];
- out_tri1.p[2] = Vector_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0]);
+ out_tri1.p[2] = VectorIntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0]);
// The second triangle is composed of one of he inside points, a
- // new point determined by the intersection of the other side of the
+ // new point determined by the intersection of the other side of the
// triangle and the plane, and the newly created point above
out_tri2.p[0] = *inside_points[1];
out_tri2.p[1] = out_tri1.p[2];
- out_tri2.p[2] = Vector_IntersectPlane(plane_p, plane_n, *inside_points[1], *outside_points[0]);
+ out_tri2.p[2] = VectorIntersectPlane(plane_p, plane_n, *inside_points[1], *outside_points[0]);
return 2; // Return two newly formed triangles which form a quad
}
- }
-
-
- // Taken From Command Line Webcam Video
- CHAR_INFO GetColour(float lum)
- {
- short bg_col, fg_col;
- wchar_t sym;
- int pixel_bw = (int)(13.0f*lum);
- switch (pixel_bw)
- {
- case 0: bg_col = BG_BLACK; fg_col = FG_BLACK; sym = PIXEL_SOLID; break;
-
- case 1: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_QUARTER; break;
- case 2: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_HALF; break;
- case 3: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_THREEQUARTERS; break;
- case 4: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_SOLID; break;
-
- case 5: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_QUARTER; break;
- case 6: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_HALF; break;
- case 7: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_THREEQUARTERS; break;
- case 8: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_SOLID; break;
-
- case 9: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_QUARTER; break;
- case 10: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_HALF; break;
- case 11: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_THREEQUARTERS; break;
- case 12: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_SOLID; break;
- default:
- bg_col = BG_BLACK; fg_col = FG_BLACK; sym = PIXEL_SOLID;
- }
-
- CHAR_INFO c;
- c.Attributes = bg_col | fg_col;
- c.Char.UnicodeChar = sym;
- return c;
+ //return -1;
}
public:
- bool OnUserCreate() override
- {
- // Load object file
- meshCube.LoadFromObjectFile("mountains.obj");
+ bool OnUserCreate() override {
+ meshCube.LoadFromObjectFile(sObjFile);
+
+ matProjection = MatrixMakeProjection(
+ 90.0f,
+ (float) ScreenHeight() / (float) ScreenWidth(),
+ 0.1f,
+ 1000.0f
+ );
- // Projection Matrix
- matProj = Matrix_MakeProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.1f, 1000.0f);
return true;
}
- bool OnUserUpdate(float fElapsedTime) override
- {
- if (GetKey(VK_UP).bHeld)
- vCamera.y += 8.0f * fElapsedTime; // Travel Upwards
-
- if (GetKey(VK_DOWN).bHeld)
- vCamera.y -= 8.0f * fElapsedTime; // Travel Downwards
-
-
- // Dont use these two in FPS mode, it is confusing :P
- if (GetKey(VK_LEFT).bHeld)
- vCamera.x -= 8.0f * fElapsedTime; // Travel Along X-Axis
+ bool OnUserUpdate(float fElapsedTime) override {
+ // Take input.
+ if (GetKey(olc::Key::UP).bHeld)
+ vCamera.y += 8.0f * fElapsedTime;
- if (GetKey(VK_RIGHT).bHeld)
- vCamera.x += 8.0f * fElapsedTime; // Travel Along X-Axis
- ///////
+ if (GetKey(olc::Key::DOWN).bHeld)
+ vCamera.y -= 8.0f * fElapsedTime;
+ if (GetKey(olc::Key::LEFT).bHeld)
+ vCamera.x -= 8.0f * fElapsedTime;
- vec3d vForward = Vector_Mul(vLookDir, 8.0f * fElapsedTime);
+ if (GetKey(olc::Key::RIGHT).bHeld)
+ vCamera.x += 8.0f * fElapsedTime;
- // Standard FPS Control scheme, but turn instead of strafe
- if (GetKey(L'W').bHeld)
- vCamera = Vector_Add(vCamera, vForward);
+ vec3d vForward = VectorMultiply(vLookDir, 8.0f * fElapsedTime);
+ if (GetKey(olc::Key::W).bHeld)
+ vCamera = VectorAdd(vCamera, vForward);
- if (GetKey(L'S').bHeld)
- vCamera = Vector_Sub(vCamera, vForward);
+ if (GetKey(olc::Key::S).bHeld)
+ vCamera = VectorSubtract(vCamera, vForward);
- if (GetKey(L'A').bHeld)
+ if (GetKey(olc::Key::A).bHeld)
fYaw -= 2.0f * fElapsedTime;
- if (GetKey(L'D').bHeld)
+ if (GetKey(olc::Key::D).bHeld)
fYaw += 2.0f * fElapsedTime;
-
-
-
- // Set up "World Tranmsform" though not updating theta
- // makes this a bit redundant
+ // Set up ratation matrices
mat4x4 matRotZ, matRotX;
- //fTheta += 1.0f * fElapsedTime; // Uncomment to spin me right round baby right round
- matRotZ = Matrix_MakeRotationZ(fTheta * 0.5f);
- matRotX = Matrix_MakeRotationX(fTheta);
+ //fTheta += 1.0f * fElapsedTime;
+
+ matRotZ = MatrixMakeRotationZ(fTheta * 0.5f);
+ matRotX = MatrixMakeRotationX(fTheta);
mat4x4 matTrans;
- matTrans = Matrix_MakeTranslation(0.0f, 0.0f, 5.0f);
+ matTrans = MatrixMakeTranslation(0.0f, 0.0f, 5.0f);
mat4x4 matWorld;
- matWorld = Matrix_MakeIdentity(); // Form World Matrix
- matWorld = Matrix_MultiplyMatrix(matRotZ, matRotX); // Transform by rotation
- matWorld = Matrix_MultiplyMatrix(matWorld, matTrans); // Transform by translation
+ matWorld = MatrixMakeIdentity();
+ matWorld = MatrixMultiplyMatrix(matRotZ, matRotX);
+ matWorld = MatrixMultiplyMatrix(matWorld, matTrans);
- // Create "Point At" Matrix for camera
- vec3d vUp = { 0,1,0 };
- vec3d vTarget = { 0,0,1 };
- mat4x4 matCameraRot = Matrix_MakeRotationY(fYaw);
- vLookDir = Matrix_MultiplyVector(matCameraRot, vTarget);
- vTarget = Vector_Add(vCamera, vLookDir);
- mat4x4 matCamera = Matrix_PointAt(vCamera, vTarget, vUp);
+ vec3d vUp = { 0, 1, 0 };
+ vec3d vTarget = { 0, 0, 1 };
+ mat4x4 matCameraRot = MatrixMakeRotationY(fYaw);
+ vLookDir = MatrixMultiplyVector(matCameraRot, vTarget);
+ vTarget = VectorAdd(vCamera, vLookDir);
+
+ mat4x4 matCamera = MatrixPointAt(vCamera, vTarget, vUp);
// Make view matrix from camera
- mat4x4 matView = Matrix_QuickInverse(matCamera);
+ mat4x4 matView = MatrixQuickInverse(matCamera);
- // Store triagles for rastering later
- vector<triangle> vecTrianglesToRaster;
+ std::vector<triangle> vecTrianglesToRaster;
// Draw Triangles
- for (auto tri : meshCube.tris)
- {
+ for (auto tri : meshCube.tris) {
triangle triProjected, triTransformed, triViewed;
- // World Matrix Transform
- triTransformed.p[0] = Matrix_MultiplyVector(matWorld, tri.p[0]);
- triTransformed.p[1] = Matrix_MultiplyVector(matWorld, tri.p[1]);
- triTransformed.p[2] = Matrix_MultiplyVector(matWorld, tri.p[2]);
+ triTransformed.p[0] = MatrixMultiplyVector(matWorld, tri.p[0]);
+ triTransformed.p[1] = MatrixMultiplyVector(matWorld, tri.p[1]);
+ triTransformed.p[2] = MatrixMultiplyVector(matWorld, tri.p[2]);
- // Calculate triangle Normal
+ // Calculate triangle normal
vec3d normal, line1, line2;
// Get lines either side of triangle
- line1 = Vector_Sub(triTransformed.p[1], triTransformed.p[0]);
- line2 = Vector_Sub(triTransformed.p[2], triTransformed.p[0]);
+ line1 = VectorSubtract(triTransformed.p[1], triTransformed.p[0]);
+ line2 = VectorSubtract(triTransformed.p[2], triTransformed.p[0]);
- // Take cross product of lines to get normal to triangle surface
- normal = Vector_CrossProduct(line1, line2);
+ // Take cross product of lines to get normal to triangle surface.
+ normal = VectorCrossProduct(line1, line2);
- // You normally need to normalise a normal!
- normal = Vector_Normalise(normal);
-
- // Get Ray from triangle to camera
- vec3d vCameraRay = Vector_Sub(triTransformed.p[0], vCamera);
+ // You normally need to normalize a normal!
+ normal = VectorNormalize(normal);
- // If ray is aligned with normal, then triangle is visible
- if (Vector_DotProduct(normal, vCameraRay) < 0.0f)
- {
+ // Get ray from triangle to camera
+ vec3d vCameraRay = VectorSubtract(triTransformed.p[0], vCamera);
+
+ /*
+ Test if the triangle should be seen by the 'camera'
+ */
+ if (VectorDotProduct(normal, vCameraRay) < 0.0f) {
// Illumination
- vec3d light_direction = { 0.0f, 1.0f, -1.0f };
- light_direction = Vector_Normalise(light_direction);
+ vec3d lightDirection = { 0.0f, 1.0f, -1.0f };
+ lightDirection = VectorNormalize(lightDirection);
- // How "aligned" are light direction and triangle surface normal?
- float dp = max(0.1f, Vector_DotProduct(light_direction, normal));
+ float dp = std::max(0.1f, VectorDotProduct(lightDirection, normal));
- // Choose console colours as required (much easier with RGB)
- CHAR_INFO c = GetColour(dp);
- triTransformed.col = c.Attributes;
- triTransformed.sym = c.Char.UnicodeChar;
+ unsigned char grayscaleValue = (unsigned char) (dp * 255);
+ triTransformed.color.a = 255;
+ triTransformed.color.r = grayscaleValue;
+ triTransformed.color.g = grayscaleValue;
+ triTransformed.color.b = grayscaleValue;
// Convert World Space --> View Space
- triViewed.p[0] = Matrix_MultiplyVector(matView, triTransformed.p[0]);
- triViewed.p[1] = Matrix_MultiplyVector(matView, triTransformed.p[1]);
- triViewed.p[2] = Matrix_MultiplyVector(matView, triTransformed.p[2]);
- triViewed.sym = triTransformed.sym;
- triViewed.col = triTransformed.col;
+ triViewed.p[0] = MatrixMultiplyVector(matView, triTransformed.p[0]);
+ triViewed.p[1] = MatrixMultiplyVector(matView, triTransformed.p[1]);
+ triViewed.p[2] = MatrixMultiplyVector(matView, triTransformed.p[2]);
+ triViewed.color = triTransformed.color;
// Clip Viewed Triangle against near plane, this could form two additional
- // additional triangles.
+ // triangles.
int nClippedTriangles = 0;
triangle clipped[2];
- nClippedTriangles = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.1f }, { 0.0f, 0.0f, 1.0f }, triViewed, clipped[0], clipped[1]);
+ nClippedTriangles = TriangleClipAgainstPlane({ 0.0f, 0.0f, 0.1f }, { 0.0f, 0.0f, 1.0f }, triViewed, clipped[0], clipped[1]);
- // We may end up with multiple triangles form the clip, so project as
- // required
- for (int n = 0; n < nClippedTriangles; n++)
- {
- // Project triangles from 3D --> 2D
- triProjected.p[0] = Matrix_MultiplyVector(matProj, clipped[n].p[0]);
- triProjected.p[1] = Matrix_MultiplyVector(matProj, clipped[n].p[1]);
- triProjected.p[2] = Matrix_MultiplyVector(matProj, clipped[n].p[2]);
- triProjected.col = clipped[n].col;
- triProjected.sym = clipped[n].sym;
+ for (int n = 0; n < nClippedTriangles; n++) {
+ // Project from 3D -> 2D
+ triProjected.p[0] = MatrixMultiplyVector(matProjection, clipped[n].p[0]);
+ triProjected.p[1] = MatrixMultiplyVector(matProjection, clipped[n].p[1]);
+ triProjected.p[2] = MatrixMultiplyVector(matProjection, clipped[n].p[2]);
+ triProjected.color = clipped[n].color;
- // Scale into view, we moved the normalising into cartesian space
- // out of the matrix.vector function from the previous videos, so
- // do this manually
- triProjected.p[0] = Vector_Div(triProjected.p[0], triProjected.p[0].w);
- triProjected.p[1] = Vector_Div(triProjected.p[1], triProjected.p[1].w);
- triProjected.p[2] = Vector_Div(triProjected.p[2], triProjected.p[2].w);
+ // Scale Into View.
+ triProjected.p[0] = VectorDivide(triProjected.p[0], triProjected.p[0].w);
+ triProjected.p[1] = VectorDivide(triProjected.p[1], triProjected.p[1].w);
+ triProjected.p[2] = VectorDivide(triProjected.p[2], triProjected.p[2].w);
// X/Y are inverted so put them back
triProjected.p[0].x *= -1.0f;
@@ -612,102 +524,150 @@
triProjected.p[1].y *= -1.0f;
triProjected.p[2].y *= -1.0f;
- // Offset verts into visible normalised space
- vec3d vOffsetView = { 1,1,0 };
- triProjected.p[0] = Vector_Add(triProjected.p[0], vOffsetView);
- triProjected.p[1] = Vector_Add(triProjected.p[1], vOffsetView);
- triProjected.p[2] = Vector_Add(triProjected.p[2], vOffsetView);
- triProjected.p[0].x *= 0.5f * (float)ScreenWidth();
- triProjected.p[0].y *= 0.5f * (float)ScreenHeight();
- triProjected.p[1].x *= 0.5f * (float)ScreenWidth();
- triProjected.p[1].y *= 0.5f * (float)ScreenHeight();
- triProjected.p[2].x *= 0.5f * (float)ScreenWidth();
- triProjected.p[2].y *= 0.5f * (float)ScreenHeight();
+ // Offset into normalized space.
+ vec3d vOffsetView = { 1, 1, 0 };
+ triProjected.p[0] = VectorAdd(triProjected.p[0], vOffsetView);
+ triProjected.p[1] = VectorAdd(triProjected.p[1], vOffsetView);
+ triProjected.p[2] = VectorAdd(triProjected.p[2], vOffsetView);
+
+ triProjected.p[0].x *= 0.5f * (float) ScreenWidth();
+ triProjected.p[0].y *= 0.5f * (float) ScreenHeight();
+ triProjected.p[1].x *= 0.5f * (float) ScreenWidth();
+ triProjected.p[1].y *= 0.5f * (float) ScreenHeight();
+ triProjected.p[2].x *= 0.5f * (float) ScreenWidth();
+ triProjected.p[2].y *= 0.5f * (float) ScreenHeight();
// Store triangle for sorting
vecTrianglesToRaster.push_back(triProjected);
- }
+ }
}
}
// Sort triangles from back to front
- sort(vecTrianglesToRaster.begin(), vecTrianglesToRaster.end(), [](triangle &t1, triangle &t2)
+ std::sort(vecTrianglesToRaster.begin(), vecTrianglesToRaster.end(), [](triangle &t1, triangle &t2)
{
float z1 = (t1.p[0].z + t1.p[1].z + t1.p[2].z) / 3.0f;
float z2 = (t2.p[0].z + t2.p[1].z + t2.p[2].z) / 3.0f;
+
return z1 > z2;
});
// Clear Screen
- Fill(0, 0, ScreenWidth(), ScreenHeight(), PIXEL_SOLID, FG_BLACK);
+ Clear(olc::BLACK);
- // Loop through all transformed, viewed, projected, and sorted triangles
for (auto &triToRaster : vecTrianglesToRaster)
{
- // Clip triangles against all four screen edges, this could yield
- // a bunch of triangles, so create a queue that we traverse to
- // ensure we only test new triangles generated against planes
+ // Clip triangles against all four screen edges.
triangle clipped[2];
- list<triangle> listTriangles;
-
- // Add initial triangle
+ std::list<triangle> listTriangles;
listTriangles.push_back(triToRaster);
int nNewTriangles = 1;
- for (int p = 0; p < 4; p++)
- {
+ for (int p = 0; p < 4; p++) {
int nTrisToAdd = 0;
- while (nNewTriangles > 0)
- {
+ while (nNewTriangles > 0) {
// Take triangle from front of queue
triangle test = listTriangles.front();
listTriangles.pop_front();
nNewTriangles--;
- // Clip it against a plane. We only need to test each
+ // Clip it against a plane. We only need to test each
// subsequent plane, against subsequent new triangles
// as all triangles after a plane clip are guaranteed
// to lie on the inside of the plane. I like how this
// comment is almost completely and utterly justified
- switch (p)
- {
- case 0: nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, test, clipped[0], clipped[1]); break;
- case 1: nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, (float)ScreenHeight() - 1, 0.0f }, { 0.0f, -1.0f, 0.0f }, test, clipped[0], clipped[1]); break;
- case 2: nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, test, clipped[0], clipped[1]); break;
- case 3: nTrisToAdd = Triangle_ClipAgainstPlane({ (float)ScreenWidth() - 1, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, test, clipped[0], clipped[1]); break;
+ switch (p) {
+ case 0: nTrisToAdd = TriangleClipAgainstPlane({ 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, test, clipped[0], clipped[1]); break;
+ case 1: nTrisToAdd = TriangleClipAgainstPlane({ 0.0f, (float) ScreenHeight() - 1, 0.0f }, { 0.0f, -1.0f, 0.0f }, test, clipped[0], clipped[1]); break;
+ case 2: nTrisToAdd = TriangleClipAgainstPlane({ 0.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, test, clipped[0], clipped[1]); break;
+ case 3: nTrisToAdd = TriangleClipAgainstPlane({ (float) ScreenWidth() - 1, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, test, clipped[0], clipped[1]); break;
}
- // Clipping may yield a variable number of triangles, so
- // add these new ones to the back of the queue for subsequent
- // clipping against next planes
+ // Clipping may yield a variable number of triangles.
for (int w = 0; w < nTrisToAdd; w++)
listTriangles.push_back(clipped[w]);
}
- nNewTriangles = listTriangles.size();
}
+ for (auto &t : listTriangles) {
+ // Rasterize triangle
+ FillTriangle(
+ t.p[0].x, t.p[0].y,
+ t.p[1].x, t.p[1].y,
+ t.p[2].x, t.p[2].y,
+ triToRaster.color
+ );
- // Draw the transformed, viewed, clipped, projected, sorted, clipped triangles
- for (auto &t : listTriangles)
- {
- FillTriangle(t.p[0].x, t.p[0].y, t.p[1].x, t.p[1].y, t.p[2].x, t.p[2].y, t.sym, t.col);
- //DrawTriangle(t.p[0].x, t.p[0].y, t.p[1].x, t.p[1].y, t.p[2].x, t.p[2].y, PIXEL_SOLID, FG_BLACK);
+ if (bDebugEnableWireframe) {
+ DrawTriangle(
+ t.p[0].x, t.p[0].y,
+ t.p[1].x, t.p[1].y,
+ t.p[2].x, t.p[2].y,
+ olc::GREEN
+ );
+ }
}
}
-
return true;
}
-
};
+int main(int argc, char** argv)
+{
+ bool bDebugEnableWireframe = false;
+ bool bDebugEnableClippingColors = false;
+ bool bEnableFullscreen = false;
+ const char* sObjFile = "teapot.obj";
+ uint32_t nWidth = 1920;
+ uint32_t nHeight = 1080;
+ uint32_t nScale = 1;
+ for (int i = 0; i < argc; i++) {
+ if (strcmp(argv[i], "--debugEnableWireframe") == 0) {
+ bDebugEnableWireframe = true;
+ printf("Enabled debug wireframe.\n");
+ }
+ if (strcmp(argv[i], "--debugEnableClippingColors") == 0) {
+ bDebugEnableClippingColors = true;
+ printf("Enabled debug clipping colors.\n");
+ }
-int main()
-{
- olcEngine3D demo;
- if (demo.ConstructConsole(256, 240, 4, 4))
- demo.Start();
- return 0;
+ if (strcmp(argv[i], "--fullscreen") == 0) {
+ bEnableFullscreen = true;
+ printf("Enabled fullscreen.\n");
+ }
+
+ if (strcmp(argv[i], "--objFile") == 0) {
+ sObjFile = argv[i+1];
+ printf("Set object file to [%s].\n", sObjFile);
+ }
+
+ if (strcmp(argv[i], "--size") == 0) {
+ nWidth = (uint32_t) std::stoi(argv[i+1]);
+ nHeight = (uint32_t) std::stoi(argv[i+2]);
+ printf("Set custom resolution [%dx%d]\n", nWidth, nHeight);
+ }
+
+ if (strcmp(argv[i], "--scale") == 0) {
+ nScale = (uint32_t) std::stoi(argv[i+1]);
+ printf("Set custom scale [%d]\n", nScale);
+ }
+ }
+
+ SimpleDemo3D demo;
+ demo.setDebugRenderMode(bDebugEnableWireframe, bDebugEnableClippingColors);
+ demo.setObjectFile(sObjFile);
+ if (
+ demo.Construct(
+ nWidth / nScale,
+ nHeight / nScale,
+ nScale,
+ nScale,
+ bEnableFullscreen
+ )
+ ) { demo.Start(); }
+
+ return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment