The official distribution of olcConsoleGameEngine, a tool used in javidx9's YouTube videos and projects
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
videos/OneLoneCoder_olcEngine3D_Pa...

382 lines
22 KiB

<EFBFBD><EFBFBD>/*
OneLoneCoder.com - 3D Graphics Part #2 - Normals, Culling, Lighting & Object Files
"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
Author
~~~~~~
Twitter: @javidx9
Blog: http://www.onelonecoder.com
Discord: https://discord.gg/WhwHUMV
Last Updated: 29/07/2018
*/
#include "olcConsoleGameEngine.h"
#include <fstream>
#include <strstream>
#include <algorithm>
using namespace std;
struct vec3d
{
float x, y, z;
};
struct triangle
{
vec3d p[3];
wchar_t sym;
short col;
};
struct mesh
{
vector<triangle> tris;
bool LoadFromObjectFile(string sFilename)
{
ifstream f(sFilename);
if (!f.is_open())
return false;
// Local cache of verts
vector<vec3d> verts;
while (!f.eof())
{
char line[128];
f.getline(line, 128);
strstream s;
s << line;
char junk;
if (line[0] == 'v')
{
vec3d v;
s >> junk >> v.x >> v.y >> v.z;
verts.push_back(v);
}
if (line[0] == 'f')
{
int f[3];
s >> junk >> f[0] >> f[1] >> f[2];
tris.push_back({ verts[f[0] - 1], verts[f[1] - 1], verts[f[2] - 1] });
}
}
return true;
}
};
struct mat4x4
{
float m[4][4] = { 0 };
};
class olcEngine3D : public olcConsoleGameEngine
{
public:
olcEngine3D()
{
m_sAppName = L"3D Demo";
}
private:
mesh meshCube;
mat4x4 matProj;
vec3d vCamera;
float fTheta;
void MultiplyMatrixVector(vec3d &i, vec3d &o, mat4x4 &m)
{
o.x = i.x * m.m[0][0] + i.y * m.m[1][0] + i.z * m.m[2][0] + m.m[3][0];
o.y = i.x * m.m[0][1] + i.y * m.m[1][1] + i.z * m.m[2][1] + m.m[3][1];
o.z = i.x * m.m[0][2] + i.y * m.m[1][2] + i.z * m.m[2][2] + m.m[3][2];
float w = i.x * m.m[0][3] + i.y * m.m[1][3] + i.z * m.m[2][3] + m.m[3][3];
if (w != 0.0f)
{
o.x /= w; o.y /= w; o.z /= w;
}
}
// 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;
}
public:
bool OnUserCreate() override
{
//meshCube.tris = {
//// SOUTH
//{ 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f },
//{ 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f },
//// EAST
//{ 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f },
//{ 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f },
//// NORTH
//{ 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f },
//{ 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f },
//// WEST
//{ 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f },
//{ 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f },
//// TOP
//{ 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f },
//{ 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f },
//// BOTTOM
//{ 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f },
//{ 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f },
//};
meshCube.LoadFromObjectFile("VideoShip.obj");
// Projection Matrix
float fNear = 0.1f;
float fFar = 1000.0f;
float fFov = 90.0f;
float fAspectRatio = (float)ScreenHeight() / (float)ScreenWidth();
float fFovRad = 1.0f / tanf(fFov * 0.5f / 180.0f * 3.14159f);
matProj.m[0][0] = fAspectRatio * fFovRad;
matProj.m[1][1] = fFovRad;
matProj.m[2][2] = fFar / (fFar - fNear);
matProj.m[3][2] = (-fFar * fNear) / (fFar - fNear);
matProj.m[2][3] = 1.0f;
matProj.m[3][3] = 0.0f;
return true;
}
bool OnUserUpdate(float fElapsedTime) override
{
// Clear Screen
Fill(0, 0, ScreenWidth(), ScreenHeight(), PIXEL_SOLID, FG_BLACK);
// Set up rotation matrices
mat4x4 matRotZ, matRotX;
fTheta += 1.0f * fElapsedTime;
// Rotation Z
matRotZ.m[0][0] = cosf(fTheta);
matRotZ.m[0][1] = sinf(fTheta);
matRotZ.m[1][0] = -sinf(fTheta);
matRotZ.m[1][1] = cosf(fTheta);
matRotZ.m[2][2] = 1;
matRotZ.m[3][3] = 1;
// Rotation X
matRotX.m[0][0] = 1;
matRotX.m[1][1] = cosf(fTheta * 0.5f);
matRotX.m[1][2] = sinf(fTheta * 0.5f);
matRotX.m[2][1] = -sinf(fTheta * 0.5f);
matRotX.m[2][2] = cosf(fTheta * 0.5f);
matRotX.m[3][3] = 1;
// Store triagles for rastering later
vector<triangle> vecTrianglesToRaster;
// Draw Triangles
for (auto tri : meshCube.tris)
{
triangle triProjected, triTranslated, triRotatedZ, triRotatedZX;
// Rotate in Z-Axis
MultiplyMatrixVector(tri.p[0], triRotatedZ.p[0], matRotZ);
MultiplyMatrixVector(tri.p[1], triRotatedZ.p[1], matRotZ);
MultiplyMatrixVector(tri.p[2], triRotatedZ.p[2], matRotZ);
// Rotate in X-Axis
MultiplyMatrixVector(triRotatedZ.p[0], triRotatedZX.p[0], matRotX);
MultiplyMatrixVector(triRotatedZ.p[1], triRotatedZX.p[1], matRotX);
MultiplyMatrixVector(triRotatedZ.p[2], triRotatedZX.p[2], matRotX);
// Offset into the screen
triTranslated = triRotatedZX;
triTranslated.p[0].z = triRotatedZX.p[0].z + 8.0f;
triTranslated.p[1].z = triRotatedZX.p[1].z + 8.0f;
triTranslated.p[2].z = triRotatedZX.p[2].z + 8.0f;
// Use Cross-Product to get surface normal
vec3d normal, line1, line2;
line1.x = triTranslated.p[1].x - triTranslated.p[0].x;
line1.y = triTranslated.p[1].y - triTranslated.p[0].y;
line1.z = triTranslated.p[1].z - triTranslated.p[0].z;
line2.x = triTranslated.p[2].x - triTranslated.p[0].x;
line2.y = triTranslated.p[2].y - triTranslated.p[0].y;
line2.z = triTranslated.p[2].z - triTranslated.p[0].z;
normal.x = line1.y * line2.z - line1.z * line2.y;
normal.y = line1.z * line2.x - line1.x * line2.z;
normal.z = line1.x * line2.y - line1.y * line2.x;
// It's normally normal to normalise the normal
float l = sqrtf(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);
normal.x /= l; normal.y /= l; normal.z /= l;
//if (normal.z < 0)
if(normal.x * (triTranslated.p[0].x - vCamera.x) +
normal.y * (triTranslated.p[0].y - vCamera.y) +
normal.z * (triTranslated.p[0].z - vCamera.z) < 0.0f)
{
// Illumination
vec3d light_direction = { 0.0f, 0.0f, -1.0f };
float l = sqrtf(light_direction.x*light_direction.x + light_direction.y*light_direction.y + light_direction.z*light_direction.z);
light_direction.x /= l; light_direction.y /= l; light_direction.z /= l;
// How similar is normal to light direction
float dp = normal.x * light_direction.x + normal.y * light_direction.y + normal.z * light_direction.z;
// Choose console colours as required (much easier with RGB)
CHAR_INFO c = GetColour(dp);
triTranslated.col = c.Attributes;
triTranslated.sym = c.Char.UnicodeChar;
// Project triangles from 3D --> 2D
MultiplyMatrixVector(triTranslated.p[0], triProjected.p[0], matProj);
MultiplyMatrixVector(triTranslated.p[1], triProjected.p[1], matProj);
MultiplyMatrixVector(triTranslated.p[2], triProjected.p[2], matProj);
triProjected.col = triTranslated.col;
triProjected.sym = triTranslated.sym;
// Scale into view
triProjected.p[0].x += 1.0f; triProjected.p[0].y += 1.0f;
triProjected.p[1].x += 1.0f; triProjected.p[1].y += 1.0f;
triProjected.p[2].x += 1.0f; triProjected.p[2].y += 1.0f;
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)
{
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;
});
for (auto &triProjected : vecTrianglesToRaster)
{
// Rasterize triangle
FillTriangle(triProjected.p[0].x, triProjected.p[0].y,
triProjected.p[1].x, triProjected.p[1].y,
triProjected.p[2].x, triProjected.p[2].y,
triProjected.sym, triProjected.col);
/*DrawTriangle(triProjected.p[0].x, triProjected.p[0].y,
triProjected.p[1].x, triProjected.p[1].y,
triProjected.p[2].x, triProjected.p[2].y,
PIXEL_SOLID, FG_BLACK);*/
}
return true;
}
};
int main()
{
olcEngine3D demo;
if (demo.ConstructConsole(256, 240, 4, 4))
demo.Start();
return 0;
}