├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── ascii-graphics.cpp └── src ├── CMakeLists.txt ├── Camera.cpp ├── Camera.hpp ├── Lights.cpp ├── Lights.hpp ├── Mesh.cpp ├── Mesh.hpp ├── Screen.cpp ├── Screen.hpp ├── agm.cpp └── agm.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | build/ 35 | models/ 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.23.1) 2 | 3 | project(Ascii-Graphics) 4 | 5 | add_executable(${PROJECT_NAME} ascii-graphics.cpp) 6 | 7 | add_subdirectory(src) 8 | 9 | target_link_libraries(${PROJECT_NAME} agm) 10 | target_link_libraries(${PROJECT_NAME} Camera) 11 | target_link_libraries(${PROJECT_NAME} Lights) 12 | target_link_libraries(${PROJECT_NAME} Mesh) 13 | target_link_libraries(${PROJECT_NAME} Screen) 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 addr0x414b 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |
6 | Logo 7 | 8 |

ASCII Graphics

9 | 10 |

11 | C++ 3D graphics library that runs in a Linux terminal 12 |

13 |
14 | 15 | 16 | ## About The Project 17 | 18 | 19 | 20 | Ascii-graphics is a basic 3D graphics library designed to run in a linux terminal. 21 | 22 | 23 | ## Features 24 | 25 | Below shows some key features of the ascii-graphics library. 26 | 27 | ### Custom OBJ Model Loading 28 | 29 | 30 | 31 | ### Smooth Lighting 32 | 33 | 34 | 35 | ### Z Buffering 36 | 37 | 38 | 39 | ### Other Features 40 | * Outline meshes 41 | * Wireframe mode 42 | * Fill entire mesh with single character 43 | * Basic lighting 44 | 45 | ## Installation 46 | 47 | 1. Clone the repo 48 | ``` 49 | git clone https://github.com/addr0x414b/ascii-graphics.git 50 | ``` 51 | 2. In the cloned folder, create a build directory and cd to it 52 | ``` 53 | mkdir build && cd build 54 | ``` 55 | 3. Run cmake 56 | ``` 57 | cmake ../ 58 | ``` 59 | 4. Run make 60 | ``` 61 | make 62 | ``` 63 | 5. Run the program 64 | ``` 65 | ./Ascii-Graphics 66 | ``` 67 | 68 | 69 | ## Usage 70 | 71 | Below is the default code the repo comes with: 72 | 73 | ```c++ 74 | //ascii-graphics.cpp 75 | 76 | int gScreenWidth = 200; // Define the screens width and height 77 | int gScreenHeight = 50; 78 | float gAspect = (float)gScreenWidth / (float)gScreenHeight; 79 | 80 | int main() { 81 | 82 | Camera camera(0.0f, 0.0f, 0.0f, gAspect); // Define the scenes camera 83 | LightD light; // Define the scenes light 84 | Screen screen(gScreenWidth, gScreenHeight, camera, light); // Define the screen of the scene 85 | 86 | Cube cube; // Library comes with one default mesh 87 | cube.translate(0.0f, 0.0f, -4.f); // Must translate the mesh away from the camera (-z is into the screen) 88 | 89 | float deg = 0.1f; 90 | while (1) { // Screen loop 91 | screen.start(); // Begin calculating the delta time per frame 92 | 93 | cube.rotate(0.0f, deg, deg); // Rotate cube 94 | deg += 50 * screen.deltaTime; // Increase the degrees of rotation 95 | 96 | screen.shadeMesh(cube); // Print the cube into the buffer 97 | 98 | screen.print(); // Print the buffer 99 | screen.clear(); // Clear the screen 100 | } 101 | 102 | return 0; 103 | } 104 | ``` 105 | 106 | ### Create Scene 107 | 108 | 1. Define size of the screen 109 | ```c++ 110 | int gScreenWidth = 200; // Width and height should be no larger than terminal 111 | int gScreenHeight = 50; // Can increase size with decreased terminal font size 112 | float gAspect = (float)gScreenWidth / (float)gScreenHeight; 113 | ``` 114 | 2. Define camera, light, and screen 115 | ```c++ 116 | Camera camera(0.0f, 0.0f, 0.0f, gAspect); // Constructor uses default projection values 117 | LightD light; // Define the scenes light 118 | Screen screen(gScreenWidth, gScreenHeight, camera, light); // Define the screen 119 | ``` 120 | 121 | ### To Load a Custom Mesh 122 | 123 | 1. Mesh MUST be in OBJ format 124 | 2. Mesh MUST be triangulated, with normals 125 | 3. OBJ file should be in the same directory as the ascii-graphics.cpp file 126 | 4. File path MUST have "../" in front (assuming file is in the same directory as ascii-graphics.cpp) 127 | 128 | * Flat Shaded Mesh: 129 | ```c++ 130 | Mesh meshName("../path_to_flat_mesh.obj"); // Load a FLAT SHADED mesh 131 | meshName.translate(0.0f, 0.0f, -4.0f); // Translate the mesh away from the camera 132 | ``` 133 | 134 | * Smooth Shaded Mesh: 135 | ```c++ 136 | Mesh meshName(1, "../path_to_smooth_mesh.obj") // Load a SMOOTH SHADED mesh 137 | meshName.translate(0.0f, 0.0f, -4.0f); // Translate the mesh away from the camera 138 | ``` 139 | 140 | ### Display the Graphics 141 | 142 | ```c++ 143 | float deg = 0.1f; 144 | while (1) { // Screen loop 145 | screen.start(); // Begin calculating the delta time per frame 146 | 147 | cube.rotate(0.0f, deg, deg); // Rotate cube 148 | deg += 50 * screen.deltaTime; // Increase the degrees of rotation 149 | 150 | screen.shadeMesh(cube); // Print the cube into the buffer 151 | 152 | screen.print(); // Print the buffer 153 | screen.clear(); // Clear the screen 154 | } 155 | 156 | ``` 157 | 158 | ### Different Display Methods 159 | 160 | * Display a smooth shaded mesh 161 | ```c++ 162 | screen.shadeMeshSmooth(meshName); 163 | ``` 164 | 165 | * Display a flat shaded mesh 166 | ```c++ 167 | screen.shadeMesh(meshName); 168 | ``` 169 | 170 | * Fill entire mesh with a single character, no shading 171 | ```c++ 172 | screen.fillMesh(meshName, '*'); // Pick character here 173 | ``` 174 | 175 | * Show mesh triangles 176 | ```c++ 177 | screen.drawMesh(meshName, '*'); // Pick character here 178 | ``` 179 | 180 | * Display wireframe of mesh 181 | ```c++ 182 | screen.drawMeshWire(meshName, '*'); // Pick character here 183 | ``` 184 | 185 | * Can combine different methods 186 | ```c++ 187 | // Results in an outlined cube 188 | screen.drawMesh(cube, '#'); // Draw the triangles of the cube 189 | screen.fillMesh(cube, '*'); // Fill in the cube 190 | ``` 191 | 192 | 193 | ## License 194 | 195 | Distributed under the MIT License. See `LICENSE` for more information. 196 | 197 | 198 | ## Contact 199 | 200 | YouTube: https://www.youtube.com/watch?v=X4QSm_p7Cy4 201 | 202 | 203 | ## Acknowledgments 204 | 205 | Thanks to the following for allowing use of their 3D models: 206 | 207 | * [Man model by printable_models](https://free3d.com/3d-model/male-base-mesh-6682.html) 208 | * [Rocket model by Paul Chen uploaded by nixor](https://free3d.com/3d-model/rocket-ship-v1--579030.html) 209 | * [Cat model by snippysnappets](https://free3d.com/3d-model/low-poly-cat-46138.html) 210 | 211 | 212 | -------------------------------------------------------------------------------- /ascii-graphics.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "src/Screen.hpp" 4 | #include "src/agm.hpp" 5 | #include "src/Camera.hpp" 6 | #include "src/Lights.hpp" 7 | #include "src/Mesh.hpp" 8 | 9 | int gScreenWidth = 200; // Define the screens width and height 10 | int gScreenHeight = 50; 11 | float gAspect = (float)gScreenWidth / (float)gScreenHeight; 12 | 13 | int main() { 14 | 15 | Camera camera(0.0f, 0.0f, 0.0f, gAspect); // Define the scenes camera 16 | LightD light; // Define the scenes light 17 | Screen screen(gScreenWidth, gScreenHeight, camera, light); // Define the 18 | // screen of the 19 | // scene 20 | 21 | Cube cube; // Library comes with one default mesh 22 | cube.translate(0.0f, 0.0f, -4.f); // Must translate the mesh away from the 23 | // camera (-z is into the screen) 24 | 25 | float deg = 0.1f; 26 | while (1) { // Screen loop 27 | screen.start(); // Begin calculating the delta time per frame 28 | 29 | cube.rotate(0.0f, deg, deg); // Rotate our cube 30 | deg += 50 * screen.deltaTime; // Increase the degrees of rotation 31 | 32 | screen.shadeMesh(cube); 33 | 34 | screen.print(); // Print the entire screen 35 | screen.clear(); // Clear the screen 36 | } 37 | 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(agm SHARED agm.cpp) 2 | add_library(Camera SHARED Camera.cpp) 3 | add_library(Lights SHARED Lights.cpp) 4 | add_library(Mesh SHARED Mesh.cpp) 5 | add_library(Screen SHARED Screen.cpp) 6 | -------------------------------------------------------------------------------- /src/Camera.cpp: -------------------------------------------------------------------------------- 1 | #include "Camera.hpp" 2 | 3 | /* Default constructor 4 | * @params x,y,z: position of our camera 5 | * @param aspect: the screen aspect ratio 6 | * @param fov: the field of view 7 | * @param zNear: the z near clipping 8 | * @param zFar: the z far clipping */ 9 | Camera::Camera(float x, float y, float z, float aspect, 10 | float fov, float zNear, float zFar) { 11 | pos.x = x; // Set camera position 12 | pos.y = y; 13 | pos.z = z; 14 | 15 | /* Create the projection matrix based on the inputted values */ 16 | projMat = perspective(aspect, fov, zNear, zFar); 17 | 18 | /* Assign the camera values */ 19 | a = aspect; 20 | f = fov; 21 | zN = zNear; 22 | zF = zFar; 23 | 24 | } 25 | 26 | /* Default constructor - uses default values for projection 27 | * @params x,y,z: position of our camera 28 | * @param aspect: the screen aspect ratio 29 | * Default value: fov = 30.0f 30 | * Default value: zNear = 0.1f 31 | * Default value: zFar = 1000.f */ 32 | Camera::Camera(float x, float y, float z, float aspect) { 33 | pos.x = x; 34 | pos.y = y; 35 | pos.z = z; 36 | 37 | projMat = perspective(aspect, 30.f, 0.1f, 1000.f); 38 | 39 | a = aspect; 40 | f = 30.f; 41 | zN = 0.1f; 42 | zF = 1000.f; 43 | } 44 | 45 | /* Default constructor - create camera w/o any projection 46 | * Default values: x,y,z = 0 */ 47 | Camera::Camera() { 48 | pos.x = 0.0f; 49 | pos.y = 0.0f; 50 | pos.z = 0.0f; 51 | } 52 | -------------------------------------------------------------------------------- /src/Camera.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "agm.hpp" 4 | 5 | // Camera class 6 | class Camera { 7 | public: 8 | Vert pos; // Camera position 9 | 10 | Mat4 projMat; 11 | 12 | /* Store the cameras aspect ratio, field of view, the zNear clipping plane, 13 | * and the zFar clipping plane */ 14 | float a; 15 | float f; 16 | float zN; 17 | float zF; 18 | 19 | /* Default constructor 20 | * @params x,y,z: position of our camera 21 | * @param aspect: the screen aspect ratio 22 | * @param fov: the field of view 23 | * @param zNear: the z near clipping 24 | * @param zFar: the z far clipping */ 25 | Camera(float x, float y, float z, float aspect, 26 | float fov, float zNear, float zFar); 27 | 28 | /* Default constructor - uses default values for projection 29 | * @params x,y,z: position of our camera 30 | * @param aspect: the screen aspect ratio 31 | * Default value: fov = 30.0f 32 | * Default value: zNear = 0.1f 33 | * Default value: zFar = 1000.f */ 34 | Camera(float x, float y, float z, float aspect); 35 | 36 | /* Default constructor - create camera w/o any projection 37 | * Default values: x,y,z = 0 */ 38 | Camera(); 39 | 40 | }; 41 | -------------------------------------------------------------------------------- /src/Lights.cpp: -------------------------------------------------------------------------------- 1 | #include "Lights.hpp" 2 | 3 | 4 | /* Default Constructor - Create directional light 5 | * @param x,y,z: light location 6 | * @param xd,yd,zd: light directional vector */ 7 | LightD::LightD(float x, float y, float z, float xd, float yd, float zd) { 8 | Vert p(x, y, z); 9 | position = p; 10 | Vert d(xd, yd, zd); 11 | direction = d; 12 | } 13 | 14 | /* Default Constructor - Create directional light 15 | * Default value: pos = 0,0,0 16 | * Default value: dir = 0,0,-1 */ 17 | LightD::LightD() { 18 | Vert p(0.0f, 0.0f, 0.0f); 19 | position = p; 20 | Vert d(0.0f, 0.0f, -1.0f); 21 | direction = d; 22 | } 23 | -------------------------------------------------------------------------------- /src/Lights.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "agm.hpp" 4 | #include "Mesh.hpp" 5 | 6 | // Simple directional light 7 | class LightD { 8 | public: 9 | Vert position; // Light position 10 | Vert direction; // Light direction vector 11 | 12 | /* Default Constructor - Create directional light 13 | * @param x,y,z: light location 14 | * @param xd,yd,zd: light directional vector */ 15 | LightD(float x, float y, float z, float xd, float yd, float zd); 16 | 17 | /* Default Constructor - Create directional light 18 | * Default value: pos = 0,0,0 19 | * Default value: dir = 0,0,-1 */ 20 | LightD(); 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /src/Mesh.cpp: -------------------------------------------------------------------------------- 1 | #include "Mesh.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /* Default constructor - create a vertex with NO normal 9 | * @param xx: x position 10 | * @param yy: y position 11 | * @param zz: z position */ 12 | Vert::Vert(float xx, float yy, float zz) { 13 | x = xx; 14 | y = yy; 15 | z = zz; 16 | xn = 0.f; 17 | yn = 0.f; 18 | zn = 0.f; 19 | } 20 | 21 | /* Default constructor - create a vertex WITH a normal 22 | * @param xx: x position 23 | * @param yy: y position 24 | * @param zz: z position 25 | * @param nx: x normal location 26 | * @param ny: y normal location 27 | * @param nz: z normal location */ 28 | Vert::Vert(float xx, float yy, float zz, float nx, float ny, float nz) { 29 | x = xx; 30 | y = yy; 31 | z = zz; 32 | xn = nx; 33 | yn = ny; 34 | zn = nz; 35 | } 36 | 37 | /* Default constructor - create a vertex 38 | * Default values: x,y,z, xn,yn,zn = 0 */ 39 | Vert::Vert() { 40 | x = 0.0f; 41 | y = 0.0f; 42 | z = 0.0f; 43 | xn = 0.f; 44 | yn = 0.f; 45 | zn = 0.f; 46 | } 47 | 48 | /* Default constructor - create a triangle 49 | * @params p1,p2,p3: vertices of the triangle 50 | * @params x,y,z: the face normal of the triangle */ 51 | Trig::Trig(Vert p1, Vert p2, Vert p3, float x, float y, float z) { 52 | verts[0] = p1; 53 | verts[1] = p2; 54 | verts[2] = p3; 55 | 56 | Vert p1n(p1.xn, p1.yn, p1.zn); 57 | Vert p2n(p2.xn, p2.yn, p2.zn); 58 | Vert p3n(p3.xn, p3.yn, p3.zn); 59 | 60 | norms[0] = p1n; 61 | norms[1] = p2n; 62 | norms[2] = p3n; 63 | 64 | Vert n(x, y, z); 65 | fNormal = n; 66 | } 67 | 68 | /* Default constructor - load an obj file mesh thats smoothed 69 | * @param s: any random number, doesn't matter - doesn't do anything 70 | * @param objFile: path to file */ 71 | Mesh::Mesh(int s, std::string objFile) { 72 | 73 | std::fstream file; 74 | std::string index; 75 | std::string x; 76 | std::string y; 77 | std::string z; 78 | 79 | std::string nx, ny, nz; 80 | 81 | std::vector vertices; 82 | std::vector normals; 83 | 84 | file.open(objFile); 85 | while(file >> index) { 86 | if (index == "v") { 87 | file >> x; 88 | file >> y; 89 | file >> z; 90 | vertices.push_back(x + " " + y + " " + z); 91 | } else if (index == "vn") { 92 | file >> x; 93 | file >> y; 94 | file >> z; 95 | normals.push_back(x + " " + y + " " + z); 96 | } else if (index == "f") { 97 | file >> x; 98 | file >> y; 99 | file >> z; 100 | std::replace(x.begin(), x.end(), '/', ' '); 101 | std::replace(y.begin(), y.end(), '/', ' '); 102 | std::replace(z.begin(), z.end(), '/', ' '); 103 | 104 | std::istringstream a(x); 105 | std::istringstream b(y); 106 | std::istringstream c(z); 107 | std::string p1, n1, n2, n3; 108 | std::string p2; 109 | std::string p3; 110 | a >> p1 >> n1; 111 | b >> p2 >> n2; 112 | c >> p3 >> n3; 113 | 114 | std::istringstream v(vertices[std::stoi(p1)-1]); 115 | v >> x; 116 | v >> y; 117 | v >> z; 118 | std::istringstream norm1(normals[std::stoi(n1)-1]); 119 | norm1 >> nx; 120 | norm1 >> ny; 121 | norm1 >> nz; 122 | Vert v1(std::stof(x), std::stof(y), std::stof(z), 123 | std::stof(nx), std::stof(ny), std::stof(nz)); 124 | 125 | std::istringstream vv(vertices[std::stoi(p2)-1]); 126 | vv >> x; 127 | vv >> y; 128 | vv >> z; 129 | std::istringstream norm2(normals[std::stoi(n2)-1]); 130 | norm2 >> nx; 131 | norm2 >> ny; 132 | norm2 >> nz; 133 | Vert v2(std::stof(x), std::stof(y), std::stof(z), 134 | std::stof(nx), std::stof(ny), std::stof(nz)); 135 | 136 | std::istringstream vvv(vertices[std::stoi(p3)-1]); 137 | vvv >> x; 138 | vvv >> y; 139 | vvv >> z; 140 | std::istringstream norm3(normals[std::stoi(n3)-1]); 141 | norm3 >> nx; 142 | norm3 >> ny; 143 | norm3 >> nz; 144 | Vert v3(std::stof(x), std::stof(y), std::stof(z), 145 | std::stof(nx), std::stof(ny), std::stof(nz)); 146 | 147 | Vert dir1(v2.x-v1.x, v2.y-v1.y, v2.z-v1.z); 148 | Vert dir2(v3.x-v1.x, v3.y-v1.y, v3.z-v1.z); 149 | Vert cross((dir1.y*dir2.z)-(dir1.z*dir2.y), 150 | (dir1.z*dir2.x)-(dir1.x*dir2.z), (dir1.x*dir2.y)-(dir1.y*dir2.x)); 151 | 152 | float l = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z); 153 | cross.x /= l; 154 | cross.y /= l; 155 | cross.z /= l; 156 | 157 | Trig t(v1, v2, v3, cross.x, cross.y, cross.z); 158 | 159 | trigs.push_back(t); 160 | } 161 | } 162 | } 163 | 164 | /* Default constructor - load an obj file mesh (MUST BE FLAT SHADED) 165 | * @param objFile: path to file */ 166 | Mesh::Mesh(std::string objFile) { 167 | 168 | std::fstream file; 169 | std::string index; 170 | std::string x; 171 | std::string y; 172 | std::string z; 173 | 174 | std::vector vertices; 175 | std::vector normals; 176 | 177 | file.open(objFile); 178 | while(file >> index) { 179 | if (index == "v") { 180 | file >> x; 181 | file >> y; 182 | file >> z; 183 | vertices.push_back(x + " " + y + " " + z); 184 | } else if (index == "vn") { 185 | file >> x; 186 | file >> y; 187 | file >> z; 188 | normals.push_back(x + " " + y + " " + z); 189 | } else if (index == "f") { 190 | file >> x; 191 | file >> y; 192 | file >> z; 193 | std::replace(x.begin(), x.end(), '/', ' '); 194 | std::replace(y.begin(), y.end(), '/', ' '); 195 | std::replace(z.begin(), z.end(), '/', ' '); 196 | 197 | std::istringstream a(x); 198 | std::istringstream b(y); 199 | std::istringstream c(z); 200 | std::string p1, n; 201 | std::string p2; 202 | std::string p3; 203 | a >> p1 >> n; 204 | b >> p2; 205 | c >> p3; 206 | 207 | std::istringstream v(vertices[std::stoi(p1)-1]); 208 | v >> x; 209 | v >> y; 210 | v >> z; 211 | Vert v1(std::stof(x), std::stof(y), std::stof(z)); 212 | 213 | std::istringstream vv(vertices[std::stoi(p2)-1]); 214 | vv >> x; 215 | vv >> y; 216 | vv >> z; 217 | Vert v2(std::stof(x), std::stof(y), std::stof(z)); 218 | 219 | std::istringstream vvv(vertices[std::stoi(p3)-1]); 220 | vvv >> x; 221 | vvv >> y; 222 | vvv >> z; 223 | Vert v3(std::stof(x), std::stof(y), std::stof(z)); 224 | 225 | std::istringstream norm(normals[std::stoi(n)-1]); 226 | norm >> x; 227 | norm >> y; 228 | norm >> z; 229 | Trig t(v1, v2, v3, std::stof(x), std::stof(y), std::stof(z)); 230 | 231 | trigs.push_back(t); 232 | } 233 | } 234 | } 235 | 236 | /* Default constructor - Set default translation/rotation amounts */ 237 | Mesh::Mesh() { 238 | transAmt.x = 0.0f; 239 | transAmt.y = 0.0f; 240 | transAmt.z = 0.0f; 241 | 242 | rotAmt.x = 0.0f; 243 | rotAmt.y = 0.0f; 244 | rotAmt.z = 0.0f; 245 | } 246 | 247 | /* Default constructor - creates a simple unit cube. Inherits from mesh */ 248 | Cube::Cube() { 249 | // Front Face 250 | Vert p1(-1.0f, 1.0f, 1.0f); 251 | Vert p2(-1.0f, -1.0f, 1.0f); 252 | Vert p3(1.0f, -1.0f, 1.0f); 253 | Trig t1(p1, p2, p3, 0.0f, 0.0f, 1.0f); 254 | Vert p4(1.0f, -1.0f, 1.0f); 255 | Vert p5(1.0f, 1.0f, 1.0f); 256 | Vert p6(-1.0f, 1.0f, 1.0f); 257 | Trig t2(p4, p5, p6, 0.0f, 0.0f, 1.0f); 258 | // Top Face 259 | Vert p7(-1.0f, 1.0f, 1.0f); 260 | Vert p8(1.0f, 1.0f, 1.0f); 261 | Vert p9(-1.0f, 1.0f, -1.0f); 262 | Trig t3(p7, p8, p9, 0.0f, 1.0f, 0.0f); 263 | Vert p10(1.0f, 1.0f, 1.0f); 264 | Vert p11(1.0f, 1.0f, -1.0f); 265 | Vert p12(-1.0f, 1.0f, -1.0f); 266 | Trig t4(p10, p11, p12, 0.0f, 1.0f, 0.0f); 267 | // Right Face 268 | Vert p13(1.0f, 1.0f, 1.0f); 269 | Vert p14(1.0f, -1.0f, 1.0f); 270 | Vert p15(1.0f, -1.0f, -1.0f); 271 | Trig t5(p13, p14, p15, 1.0f, 0.0f, 0.0f); 272 | Vert p16(1.0f, 1.0f, 1.0f); 273 | Vert p17(1.0f, 1.0f, -1.0f); 274 | Vert p18(1.0f, -1.0f, -1.0f); 275 | Trig t6(p16, p17, p18, 1.0f, 0.0f, 0.0f); 276 | // Left Face 277 | Vert p19(-1.0f, 1.0f, 1.0f); 278 | Vert p20(-1.0f, -1.0f, 1.0f); 279 | Vert p21(-1.0f, -1.0f, -1.0f); 280 | Trig t7(p19, p20, p21, -1.0f, 0.0f, 0.0f); 281 | Vert p22(-1.0f, 1.0f, 1.0f); 282 | Vert p23(-1.0f, 1.0f, -1.0f); 283 | Vert p24(-1.0f, -1.0f, -1.0f); 284 | Trig t8(p22, p23, p24, -1.0f, 0.0f, 0.0f); 285 | // Back Face 286 | Vert p25(-1.0f, 1.0f, -1.0f); 287 | Vert p26(-1.0f, -1.0f, -1.0f); 288 | Vert p27(1.0f, 1.0f, -1.0f); 289 | Trig t9(p25, p26, p27, 0.0f, 0.0f, -1.0f); 290 | Vert p28(1.0f, 1.0f, -1.0f); 291 | Vert p29(1.0f, -1.0f, -1.0f); 292 | Vert p30(-1.0f, -1.0f, -1.0f); 293 | Trig t10(p28, p29, p30, 0.0f, 0.0f, -1.0f); 294 | // Bottom Face 295 | Vert p31(-1.0f, -1.0f, 1.0f); 296 | Vert p32(-1.0f, -1.0f, -1.0f); 297 | Vert p33(1.0f, -1.0f, 1.0f); 298 | Trig t11(p31, p32, p33, 0.0f, -1.0f, 0.0f); 299 | Vert p34(1.0f, -1.0f, 1.0f); 300 | Vert p35(1.0f, -1.0f, -1.0f); 301 | Vert p36(-1.0f, -1.0f, -1.0f); 302 | Trig t12(p34, p35, p36, 0.0f, -1.0f, 0.0f); 303 | 304 | trigs = {t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12}; 305 | 306 | transAmt.x = 0.0f; 307 | transAmt.y = 0.0f; 308 | transAmt.z = 0.0f; 309 | 310 | rotAmt.x = 0.0f; 311 | rotAmt.y = 0.0f; 312 | rotAmt.z = 0.0f; 313 | } 314 | -------------------------------------------------------------------------------- /src/Mesh.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /* Vertices used in a triangle 7 | * Contains the vertex position and normal (if a smooth mesh) */ 8 | class Vert { 9 | public: 10 | float x, y, z; // Points to the vertex 11 | float xn, yn, zn; // Vertex normal 12 | 13 | /* Default constructor - create a vertex with NO normal 14 | * @param xx: x position 15 | * @param yy: y position 16 | * @param zz: z position */ 17 | Vert(float xx, float yy, float zz); 18 | 19 | /* Default constructor - create a vertex WITH a normal 20 | * @param xx: x position 21 | * @param yy: y position 22 | * @param zz: z position 23 | * @param nx: x normal location 24 | * @param ny: y normal location 25 | * @param nz: z normal location */ 26 | Vert(float xx, float yy, float zz, float nx, float ny, float nz); 27 | 28 | /* Default constructor - create a vertex 29 | * Default values: x,y,z, xn,yn,zn = 0 */ 30 | Vert(); 31 | }; 32 | 33 | // Triangle, consists of vertices along with their normals (if smooth mesh) 34 | class Trig { 35 | public: 36 | Vert verts[3]; // Vertices of the triangle 37 | Vert norms[3]; // The vertex normals 38 | Vert fNormal; // Face normal vector 39 | 40 | /* Default constructor - create a triangle 41 | * @params p1,p2,p3: vertices of the triangle 42 | * @params x,y,z: the face normal of the triangle */ 43 | Trig(Vert p1, Vert p2, Vert p3, float x, float y, float z); 44 | }; 45 | 46 | // Mesh, consists of triangles 47 | class Mesh { 48 | public: 49 | std::vector trigs; // All of the triangles in the mesh 50 | 51 | /* When the mesh is rotated or translated, set the amount. Then use it when 52 | * the mesh is untranslated or unrotated */ 53 | Vert transAmt; 54 | Vert rotAmt; 55 | 56 | /* Default constructor - Set default translation/rotation amounts */ 57 | Mesh(); 58 | 59 | /* Default constructor - load an obj file mesh (MUST BE FLAT SHADED) 60 | * @param objFile: path to file */ 61 | Mesh(std::string objFile); 62 | 63 | /* Default constructor - load an obj file mesh that's smoothed 64 | * @param s: any random number, doesn't matter - doesn't do anything 65 | * @param objFile: path to file */ 66 | Mesh(int s, std::string objFile); 67 | 68 | /* Translate the mesh 69 | * @params x,y,z: amount to translate in the x,y,z axis */ 70 | void translate(float x, float y, float z); 71 | 72 | /* Translate the mesh statically, as in set the points and that's it 73 | * @params x,y,z: amount to translate in the x,y,z axis */ 74 | void staticTranslate(float x, float y, float z); 75 | 76 | /* Scale the mesh 77 | * @param amt: the amount to scale in x,y,z */ 78 | void scale(float amt); 79 | 80 | /* Undo translation */ 81 | void unTranslate(); 82 | 83 | /* Undo rotation on the Z axis */ 84 | void unRotateZ(); 85 | 86 | /* Undo rotation on the Y axis */ 87 | void unRotateY(); 88 | 89 | /* Undo rotation on the X axis */ 90 | void unRotateX(); 91 | 92 | /* Rotate the mesh 93 | * @params x,y,z: degrees in each axis */ 94 | void rotate(float x, float y, float z); 95 | }; 96 | 97 | class Cube : public Mesh { 98 | public: 99 | /* Default constructor - creates a simple unit cube. Inherits from mesh */ 100 | Cube(); 101 | }; 102 | -------------------------------------------------------------------------------- /src/Screen.cpp: -------------------------------------------------------------------------------- 1 | #include "Screen.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | /* Default constructor 7 | * @param w: screen width 8 | * @param h: screen height 9 | * @param c: screens camera 10 | * @param l: scenes light */ 11 | Screen::Screen(int w, int h, Camera& c, LightD l) { 12 | camera = c; 13 | width = w; 14 | height = h; 15 | fillChar = ' '; 16 | light = l; 17 | shadeValues = ".:+*=#%@"; 18 | // Allocate memory to our screen buffer. Fill with empty chars 19 | buffer.resize(height, std::vector(width, fillChar)); 20 | zBuffer.resize(height, std::vector(width, 100000.f)); 21 | lastTime = std::chrono::steady_clock::now(); 22 | currTime = std::chrono::steady_clock::now(); 23 | renderMode = 1; // Use z buffering by default 24 | } 25 | 26 | // Calculate deltaTime 27 | void Screen::start() { 28 | lastTime = currTime; 29 | currTime = std::chrono::steady_clock::now(); 30 | auto dT = std::chrono::duration_cast 31 | (currTime - lastTime); 32 | deltaTime = dT.count() / 1000.f; 33 | } 34 | 35 | // Print the contents of the screen buffer 36 | void Screen::print() { 37 | for (int y = 0; y < height; y++) { 38 | for (int x = 0; x < width; x++) { 39 | std::cout << buffer[y][x]; 40 | } 41 | std::cout << "\n"; 42 | } 43 | // Sleep values slow the program down so it is less jittery 44 | //usleep(8500); 45 | //usleep(9000); 46 | usleep(12000); 47 | //usleep(15000); 48 | } 49 | 50 | // Clear the contents of the screen buffer and z buffer 51 | void Screen::clear() { 52 | buffer.clear(); 53 | buffer.resize(height, std::vector(width, fillChar)); 54 | zBuffer.clear(); 55 | zBuffer.resize(height, std::vector(width, 100000.f)); 56 | } 57 | 58 | /* Draw to the buffer at a specific point. Does bounds checking 59 | * @param x: x position 60 | * @param y: y position 61 | * @param c: character to draw to the buffer */ 62 | void Screen::drawToBuffer(float x, float y, char c) { 63 | if (x < width && y < height && x >= 0 && y >= 0) { 64 | x = round(x); 65 | y = round(y); 66 | if (renderMode == 0) { // Z buffer disabled, simply draw 67 | buffer[y][x] = c; 68 | } else if (renderMode == 1) { 69 | float z = calcZ(x, y, zCross, zVert); // Calculate the z value 70 | if(checkZB(x, y, z)) { // Check if z is < than current z buffer value 71 | zBuffer[y][x] = z; // Replace with new z value 72 | if (c == 'S') { // If smooth shading 73 | // All that's happening is calculating the shade of the pixel 74 | float w1, w2, w3; 75 | calcBary(tP1, tP2, tP3, x, y, w1, w2, w3); 76 | 77 | /* Use barycentric coords to calculate the weights between the normals 78 | * of the triangle */ 79 | float xx = ((w1 * p1N.x) + (w2 * p2N.x) + (w3 * p3N.x)); 80 | float yy = ((w1 * p1N.y) + (w2 * p2N.y) + (w3 * p3N.y)); 81 | float zz = ((w1 * p1N.z) + (w2 * p2N.z) + (w3 * p3N.z)); 82 | float l = sqrtf(xx*xx + yy*yy + zz*zz); 83 | xx /= l; 84 | yy /= l; 85 | zz /= l; 86 | Vert pNormal(xx, yy, zz); // The normal vector of the pixel 87 | 88 | float shade = round((abs(dot(pNormal, light.direction)) * 8)) - 1; 89 | if (shade <= 0) { 90 | shade = 0; 91 | } else if (shade >= 7) { 92 | shade = 7; 93 | } else if (std::isnan(shade)) { 94 | shade = 0; 95 | } 96 | c = shadeValues[shade]; 97 | } 98 | buffer[y][x] = c; 99 | } 100 | } 101 | } 102 | } 103 | 104 | /* Check the Z Buffer at a specific location 105 | * @params x,y: the x and y coordinate in z buffer 106 | * @param z: z we want to check with 107 | * @return bool: if the z we check with is < than z in z buffer */ 108 | bool Screen::checkZB(float x, float y, float z) { 109 | /* 110 | if (round(abs(zBuffer[y][x] - z)) == 0) { 111 | return false; 112 | } else { 113 | return z < zBuffer[y][x]; 114 | }*/ 115 | return z < zBuffer[y][x]; 116 | } 117 | 118 | /* Draw a line to the buffer using individual coordinates 119 | * @param x1: x position of point 1 120 | * @param y1: y position of point 1 121 | * @param x2: x position of point 2 122 | * @param y2: y position of point 2 123 | * @param c: character to draw to the buffer */ 124 | void Screen::drawLine(float x1, float y1, float x2, float y2, char c) { 125 | x1 = round(x1); 126 | y1 = round(y1); 127 | x2 = round(x2); 128 | y2 = round(y2); 129 | if (x1 == x2) { // If the x's are the same, either vertical line or point 130 | if (y1 == y2) { 131 | drawToBuffer(x1, y1, c); 132 | } else { 133 | if (y1 > y2) { 134 | std::swap(y1, y2); // Make sure y1 is smaller than y2 for the loop 135 | } 136 | for (int y = y1; y <= y2; y++) { 137 | drawToBuffer(x1, y, c); 138 | } 139 | } 140 | } else { 141 | float slope = (float)(y2 - y1) / (float)(x2 - x1); 142 | float intercept = y1 - (slope * x1); 143 | if (x1 > x2) { 144 | std::swap(x1, x2); // Make sure x1 is smaller than x2 so we loop properly 145 | } 146 | for (int x = x1; x <= x2; x++) { 147 | int y = round((slope * x) + intercept); 148 | drawToBuffer(x, y, c); 149 | } 150 | if (y1 > y2) { 151 | std::swap(y1, y2); 152 | } 153 | for (int y = y1; y <= y2; y++) { 154 | int x = round((y-intercept) / slope); 155 | drawToBuffer(x, y, c); 156 | } 157 | } 158 | } 159 | 160 | /* Draw a line to the buffer using verts 161 | * @param a: first vertex 162 | * @param b: second vertex 163 | * @param c: character to draw to the buffer */ 164 | void Screen::drawLine(Vert a, Vert b, char c) { 165 | drawLine(a.x, a.y, b.x, b.y, c); 166 | } 167 | 168 | /* Draw a triangle to the buffer 169 | * @param t: triangle to draw 170 | * @param c: character to draw to the buffer */ 171 | void Screen::drawTrig(Trig t, char c) { 172 | drawLine(t.verts[0], t.verts[1], c); 173 | drawLine(t.verts[1], t.verts[2], c); 174 | drawLine(t.verts[2], t.verts[0], c); 175 | } 176 | 177 | /* Draw all of the triangles in a mesh to the buffer 178 | * @param m: mesh 179 | * @param c: draw character */ 180 | void Screen::drawMesh(Mesh m, char c) { 181 | for (auto &trig : m.trigs) { 182 | // Check if the triangle is visible to the camera via its normal 183 | if (dot(trig.fNormal, direc(trig.verts[0], camera.pos)) < 0.0f) { 184 | 185 | project(trig, camera.projMat); 186 | 187 | centerFlipY(trig); 188 | 189 | // Get zCross and zVert, which is used for z buffer calculations 190 | Vert v1 = direc(trig.verts[1], trig.verts[0]); 191 | Vert v2 = direc(trig.verts[2], trig.verts[0]); 192 | zCross = cross(v1, v2); 193 | zVert = trig.verts[0]; 194 | 195 | drawTrig(trig, c); 196 | } 197 | } 198 | } 199 | 200 | /* Draw all of the triangles in wireframe. DOESN'T consider normals 201 | * @param m: mesh 202 | * @param c: draw character */ 203 | void Screen::drawMeshWire(Mesh m, char c) { 204 | for (auto &trig : m.trigs) { 205 | project(trig, camera.projMat); 206 | 207 | centerFlipY(trig); 208 | 209 | // Get zCross and zVert, which is used for z buffer calculations 210 | Vert v1 = direc(trig.verts[1], trig.verts[0]); 211 | Vert v2 = direc(trig.verts[2], trig.verts[0]); 212 | zCross = cross(v1, v2); 213 | zVert = trig.verts[0]; 214 | 215 | drawTrig(trig, c); 216 | } 217 | } 218 | 219 | /* Fill in a mesh via a character 220 | * @param m: mesh 221 | * @param c: draw character */ 222 | void Screen::fillMesh(Mesh m, char c) { 223 | for (auto &trig : m.trigs) { 224 | // Check if triangle is visible to camera via its normal 225 | if (dot(trig.fNormal, direc(trig.verts[0], camera.pos)) < 0.0f) { 226 | project(trig, camera.projMat); 227 | 228 | centerFlipY(trig); 229 | 230 | // Get zCross and zVert, which is used for z buffer calculations 231 | Vert v1 = direc(trig.verts[1], trig.verts[0]); 232 | Vert v2 = direc(trig.verts[2], trig.verts[0]); 233 | zCross = cross(v1, v2); 234 | zVert = trig.verts[0]; 235 | 236 | // Triangles must be sorted basted on y value. This allows to determine 237 | // is it's a flat bottom, flat top, or to split it via the middle point 238 | std::sort(trig.verts, trig.verts + 3, 239 | [](Vert const& a, Vert const& b) -> bool { 240 | return a.y < b.y; 241 | }); 242 | 243 | if (trig.verts[1].y == trig.verts[2].y) { 244 | fillFb(trig, c); 245 | } else if (trig.verts[0].y == trig.verts[1].y) { 246 | fillFt(trig, c); 247 | } else { 248 | // Not flat bottom or top, thus split the triangle in half via mid point 249 | float m1 = (trig.verts[0].y - trig.verts[2].y) / 250 | (trig.verts[0].x - trig.verts[2].x); 251 | 252 | float b1 = trig.verts[0].y - (m1 * trig.verts[0].x); 253 | Vert n((trig.verts[1].y-b1)/m1, trig.verts[1].y, 0.0f); 254 | Trig fb(trig.verts[0], n, trig.verts[1], 0.0f, 0.0f, 0.0f); 255 | Trig ft(n, trig.verts[1], trig.verts[2], 0.0f, 0.0f, 0.0f); 256 | fillFt(ft, c); 257 | fillFb(fb, c); 258 | } 259 | drawTrig(trig, c); 260 | } 261 | } 262 | } 263 | 264 | /* Fill a flat bottom triangle 265 | * @param t: triangle 266 | * @param c: draw character */ 267 | void Screen::fillFb(Trig t, char c) { 268 | 269 | float m1 = (t.verts[0].y - t.verts[1].y) / (t.verts[0].x - t.verts[1].x); 270 | float b1 = t.verts[0].y - (m1 * t.verts[0].x); 271 | 272 | float m2 = (t.verts[0].y - t.verts[2].y) / (t.verts[0].x - t.verts[2].x); 273 | float b2 = t.verts[0].y - (m2 * t.verts[0].x); 274 | 275 | drawLine(t.verts[0].x, t.verts[0].y, t.verts[0].x, t.verts[0].y, c); 276 | 277 | float x1; 278 | float x2; 279 | 280 | for (int y = t.verts[0].y+1; y <= t.verts[2].y; y++) { 281 | if (std::isfinite(m1)) { // If divide by 0 (vertical line) 282 | x1 = (y - b1) / m1; 283 | } else { 284 | x1 = t.verts[0].x; 285 | } 286 | 287 | if (std::isfinite(m2)) { 288 | x2 = (y - b2) / m2; 289 | } else { 290 | x2 = t.verts[0].x; 291 | } 292 | drawLine(x1, y, x2, y, c); 293 | } 294 | } 295 | 296 | /* Fill a flat top triangle 297 | * @param t: triangle 298 | * @param c: draw character */ 299 | void Screen::fillFt(Trig t, char c) { 300 | float m1 = (t.verts[2].y - t.verts[0].y) / (t.verts[2].x - t.verts[0].x); 301 | float b1 = t.verts[2].y - (m1 * t.verts[2].x); 302 | 303 | float m2 = (t.verts[2].y - t.verts[1].y) / (t.verts[2].x - t.verts[1].x); 304 | float b2 = t.verts[2].y - (m2 * t.verts[2].x); 305 | 306 | drawLine(t.verts[2].x, t.verts[2].y, t.verts[2].x, t.verts[2].y, c); 307 | 308 | float x1; 309 | float x2; 310 | for (int y = t.verts[2].y; y >= t.verts[0].y; y--) { 311 | if (std::isfinite(m1)) { // If divide by 0 (vertical line) 312 | x1 = (y - b1) / m1; 313 | } else { 314 | x1 = t.verts[2].x; 315 | } 316 | 317 | if (std::isfinite(m2)) { 318 | x2 = (y - b2) / m2; 319 | } else { 320 | x2 = t.verts[2].x; 321 | } 322 | drawLine(x1, y, x2, y, c); 323 | } 324 | } 325 | 326 | /* Shade in the mesh based on the light direction. FLAT SHADED! 327 | * @param m: mesh */ 328 | void Screen::shadeMesh(Mesh m) { 329 | for (auto &trig : m.trigs) { 330 | // Check if the triangle is visible via its normal vector 331 | if (dot(trig.fNormal, direc(trig.verts[0], camera.pos)) < 0.0f) { 332 | 333 | project(trig, camera.projMat); 334 | 335 | centerFlipY(trig); 336 | 337 | // zCross and zVert used for z buffer calculations 338 | Vert v1 = direc(trig.verts[1], trig.verts[0]); 339 | Vert v2 = direc(trig.verts[2], trig.verts[0]); 340 | zCross = cross(v1, v2); 341 | zVert = trig.verts[0]; 342 | 343 | // Calculate the shade character based on the light direction 344 | float shade = round((abs(dot(trig.fNormal, light.direction)) * 8)) - 1; 345 | if (shade <= 0) { 346 | shade = 0; 347 | } else if(shade >= 7) { 348 | shade = 7; 349 | } else if (std::isnan(shade)) { 350 | shade = 0; 351 | } 352 | char c = shadeValues[shade]; 353 | 354 | // Triangles must be sorted basted on y value. This allows to determine 355 | // is it's a flat bottom, flat top, or to split it via the middle point 356 | std::sort(trig.verts, trig.verts + 3, 357 | [](Vert const& a, Vert const& b) -> bool { 358 | return a.y < b.y; 359 | }); 360 | 361 | if (trig.verts[1].y == trig.verts[2].y) { 362 | fillFb(trig, c); 363 | } else if (trig.verts[0].y == trig.verts[1].y) { 364 | fillFt(trig, c); 365 | } else { 366 | float m1 = (trig.verts[0].y - trig.verts[2].y) / 367 | (trig.verts[0].x - trig.verts[2].x); 368 | 369 | float b1 = trig.verts[0].y - (m1 * trig.verts[0].x); 370 | Vert n((trig.verts[1].y-b1)/m1, trig.verts[1].y, 0.0f); 371 | n.z = calcZ(n.x, n.y, zCross, zVert); 372 | Trig fb(trig.verts[0], n, trig.verts[1], 0.0f, 0.0f, 0.0f); 373 | Trig ft(n, trig.verts[1], trig.verts[2], 0.0f, 0.0f, 0.0f); 374 | fillFt(ft, c); 375 | fillFb(fb, c); 376 | } 377 | drawTrig(trig, c); 378 | } 379 | } 380 | } 381 | 382 | /* Shade in the mesh smooth based on the light direction 383 | * @param m: SMOOTHED mesh */ 384 | void Screen::shadeMeshSmooth(Mesh m) { 385 | for (auto &trig : m.trigs) { 386 | // Check if the triangle is visible via its normal vector 387 | if (dot(trig.fNormal, direc(trig.verts[0], camera.pos)) < 0.0f) { 388 | 389 | project(trig, camera.projMat); 390 | 391 | centerFlipY(trig); 392 | 393 | // zCross and zVert used for z buffer calculations 394 | Vert v1 = direc(trig.verts[1], trig.verts[0]); 395 | Vert v2 = direc(trig.verts[2], trig.verts[0]); 396 | zCross = cross(v1, v2); 397 | zVert = trig.verts[0]; 398 | 399 | // Store the triangle vertice normals and positions, this is used to 400 | // interpolate for each pixel and get the normals for each pixel 401 | p1N = trig.norms[0]; 402 | p2N = trig.norms[1]; 403 | p3N = trig.norms[2]; 404 | tP1 = trig.verts[0]; 405 | tP2 = trig.verts[1]; 406 | tP3 = trig.verts[2]; 407 | 408 | // Triangles must be sorted basted on y value. This allows to determine 409 | // is it's a flat bottom, flat top, or to split it via the middle point 410 | std::sort(trig.verts, trig.verts + 3, 411 | [](Vert const& a, Vert const& b) -> bool { 412 | return a.y < b.y; 413 | }); 414 | 415 | // The draw character is set as 'S' to tell the draw functions it will 416 | // be a smooth shaded object 417 | if (trig.verts[1].y == trig.verts[2].y) { 418 | fillFb(trig, 'S'); 419 | } else if (trig.verts[0].y == trig.verts[1].y) { 420 | fillFt(trig, 'S'); 421 | } else { 422 | float m1 = (trig.verts[0].y - trig.verts[2].y) / 423 | (trig.verts[0].x - trig.verts[2].x); 424 | 425 | float b1 = trig.verts[0].y - (m1 * trig.verts[0].x); 426 | Vert n((trig.verts[1].y-b1)/m1, trig.verts[1].y, 0.0f); 427 | n.z = calcZ(n.x, n.y, zCross, zVert); 428 | Trig fb(trig.verts[0], n, trig.verts[1], 0.0f, 0.0f, 0.0f); 429 | Trig ft(n, trig.verts[1], trig.verts[2], 0.0f, 0.0f, 0.0f); 430 | fillFt(ft, 'S'); 431 | fillFb(fb, 'S'); 432 | } 433 | drawTrig(trig, 'S'); 434 | } 435 | } 436 | } 437 | 438 | /* Center the triangles to the middle of the screen, flip the triangles so 439 | * +y is up, and scale the x based on aspect ratio 440 | * @param t: triangle */ 441 | void Screen::centerFlipY(Trig& t) { 442 | 443 | // Flip triangle verts 444 | t.verts[0].y *= -1.f; 445 | t.verts[1].y *= -1.f; 446 | t.verts[2].y *= -1.f; 447 | 448 | // Scale by aspect ratio 449 | t.verts[0].x *= camera.a*2.5; 450 | t.verts[1].x *= camera.a*2.5; 451 | t.verts[2].x *= camera.a*2.5f; 452 | 453 | // Move the very center of screen 454 | t.verts[0].x += (float)width/2; 455 | t.verts[0].y += (float)height/2.f; 456 | t.verts[1].x += (float)width/2; 457 | t.verts[1].y += (float)height/2.f; 458 | t.verts[2].x += (float)width/2; 459 | t.verts[2].y += (float)height/2.f; 460 | } 461 | -------------------------------------------------------------------------------- /src/Screen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* Screen library for simulating screen in terminal */ 4 | 5 | #include 6 | #include 7 | #include "agm.hpp" 8 | #include "Camera.hpp" 9 | #include "Lights.hpp" 10 | #include "Mesh.hpp" 11 | #include 12 | 13 | // Screen simulation that allows printing ascii characters to the terminal 14 | class Screen { 15 | public: 16 | // 2D vector that stores the 'pixels' to be printed 17 | std::vector> buffer; 18 | // 2D vector that stores the z values of each pixel 19 | std::vector> zBuffer; 20 | 21 | int renderMode; // 0 to disable zBuffer, 1 to enable zBuffer; 22 | Vert zCross; // Our cross product for zBuffer calculation 23 | Vert zVert; // Our vert for interpolating Z for pixel 24 | 25 | Vert p1N; // Current triangle vertex normals. Used for smooth shading 26 | Vert p2N; // Only used when we call the shadeMeshSmooth function 27 | Vert p3N; 28 | 29 | Vert tP1; // Our current triangle points. Used for calculating bary coords 30 | Vert tP2; 31 | Vert tP3; 32 | 33 | // Screen width and height 34 | int width, height; 35 | 36 | float deltaTime; // Delta time of the program 37 | 38 | std::string shadeValues; // Store the different ascii characters for shading 39 | 40 | // What the screen buffer is filled with. By default it's an empty space, so 41 | // that the screen is empty. Can change for debug/style purposes 42 | char fillChar; 43 | 44 | // The camera that the screen will use 45 | Camera camera; 46 | 47 | // The light in the scene 48 | LightD light; 49 | 50 | /* Default constructor 51 | * @param w: screen width 52 | * @param h: screen height 53 | * @param c: screens camera 54 | * @param l: scenes light */ 55 | Screen(int w, int h, Camera& c, LightD l); 56 | 57 | // Print the contents of the screen buffer 58 | void print(); 59 | 60 | // Clear the contents of the screen buffer and z buffer 61 | void clear(); 62 | 63 | // Calculate deltaTime 64 | void start(); 65 | 66 | // Used to calculate the deltaTime 67 | std::chrono::steady_clock::time_point lastTime; 68 | std::chrono::steady_clock::time_point currTime; 69 | 70 | /* Draw a line to the buffer using individual coordinates 71 | * @param x1: x position of point 1 72 | * @param y1: y position of point 1 73 | * @param x2: x position of point 2 74 | * @param y2: y position of point 2 75 | * @param c: character to draw to the buffer */ 76 | void drawLine(float x1, float y1, float x2, float y2, char c); 77 | 78 | /* Draw a line to the buffer using verts 79 | * @param a: first vertex 80 | * @param b: second vertex 81 | * @param c: character to draw to the buffer */ 82 | void drawLine(Vert a, Vert b, char c); 83 | 84 | /* Draw a triangle to the buffer 85 | * @param t: triangle to draw 86 | * @param c: character to draw to the buffer */ 87 | void drawTrig(Trig t, char c); 88 | 89 | /* Draw all of the triangles in a mesh to the buffer 90 | * @param m: mesh 91 | * @param c: draw character */ 92 | void drawMesh(Mesh m, char c); 93 | 94 | /* Draw all of the triangles in wireframe. DOESN'T consider normals 95 | * @param m: mesh 96 | * @param c: draw character */ 97 | void drawMeshWire(Mesh m, char c); 98 | 99 | /* Fill in a mesh via a character 100 | * @param m: mesh 101 | * @param c: draw character */ 102 | void fillMesh(Mesh m, char c); 103 | 104 | /* Shade in the mesh based on the light direction. FLAT SHADED! 105 | * @param m: mesh */ 106 | void shadeMesh(Mesh m); 107 | 108 | /* Shade in the mesh smooth based on the light direction 109 | * @param m: SMOOTHED mesh */ 110 | void shadeMeshSmooth(Mesh m); 111 | 112 | /* Fill a flat bottom triangle 113 | * @param t: triangle 114 | * @param c: draw character */ 115 | void fillFb(Trig t, char c); 116 | 117 | /* Fill a flat top triangle 118 | * @param t: triangle 119 | * @param c: draw character */ 120 | void fillFt(Trig t, char c); 121 | 122 | private: 123 | /* Draw to the buffer at a specific point. Does bounds checking 124 | * @param x: x position 125 | * @param y: y position 126 | * @param c: character to draw to the buffer */ 127 | void drawToBuffer(float x, float y, char c); 128 | 129 | /* Center the triangles to the middle of the screen, flip the triangles so 130 | * +y is up, and scale the x based on aspect ratio 131 | * @param t: triangle */ 132 | void centerFlipY(Trig& t); 133 | 134 | /* Check the Z Buffer at a specific location 135 | * @params x,y: the x and y coordinate in z buffer 136 | * @param z: z we want to check with 137 | * @return bool: if the z we check with is < than z in z buffer */ 138 | bool checkZB(float x, float y, float z); 139 | 140 | }; 141 | -------------------------------------------------------------------------------- /src/agm.cpp: -------------------------------------------------------------------------------- 1 | #include "agm.hpp" 2 | #include 3 | 4 | /* Default constructor - Create 4x4 matrix with 0.0f */ 5 | Mat4::Mat4() { 6 | m.resize(4, std::vector(4, 0.0f)); 7 | } 8 | 9 | /* Creates a Mat4 with the perspective projection matrix values applied 10 | * @param aspect: the screen aspect ratio 11 | * @param fov: the field of view 12 | * @param zNear: the near clipping plane 13 | * @param zFar: the far clipping plane 14 | * @return Mat4: matrix with projection values */ 15 | Mat4 perspective(float aspect, float fov, float zNear, float zFar) { 16 | Mat4 m; 17 | float fovRad = (fov/2.f) * (3.141592f / 180.f); // Convert to radians 18 | 19 | m.m[0][0] = (1.f / (tan(fovRad))) / aspect; 20 | m.m[1][1] = 1.f / tanf(fovRad); 21 | m.m[2][2] = ((-2.f * zNear) / (zFar - zNear)) - 1.f; 22 | m.m[3][2] = (-zNear * zFar) / (zFar - zNear); 23 | m.m[2][3] = -1.0f; 24 | 25 | return m; 26 | } 27 | 28 | /* Creates a Mat4 with the rotation matrix along the X axis 29 | * @param degrees: the number of degrees to rotate 30 | * @return Mat4: rotation matrix */ 31 | Mat4 rotX(float degrees) { 32 | Mat4 m; 33 | float radians = (degrees) * (3.141592f / 180.f); 34 | 35 | m.m[0][0] = 1.0f; 36 | m.m[1][1] = cosf(radians); 37 | m.m[1][2] = -sinf(radians); 38 | m.m[2][1] = sinf(radians); 39 | m.m[2][2] = cosf(radians); 40 | m.m[3][3] = 1.0f; 41 | 42 | return m; 43 | } 44 | 45 | /* Creates a Mat4 with the rotation matrix along the Y axis 46 | * @param degrees: the number of degrees to rotate 47 | * @return Mat4: rotation matrix */ 48 | Mat4 rotY(float degrees) { 49 | Mat4 m; 50 | float radians = (degrees) * (3.141592f / 180.f); 51 | 52 | m.m[0][0] = cosf(radians); 53 | m.m[0][2] = sinf(radians); 54 | m.m[1][1] = 1.0f; 55 | m.m[2][0] = -sinf(radians); 56 | m.m[2][2] = cosf(radians); 57 | m.m[3][3] = 1.0f; 58 | 59 | return m; 60 | } 61 | 62 | /* Creates a Mat4 with the rotation matrix along the Z axis 63 | * @param degrees: the number of degrees to rotate 64 | * @return Mat4: rotation matrix */ 65 | Mat4 rotZ(float degrees) { 66 | Mat4 m; 67 | float radians = (degrees) * (3.141592f / 180.f); 68 | 69 | m.m[0][0] = cosf(radians); 70 | m.m[0][1] = -sinf(radians); 71 | m.m[1][0] = sinf(radians); 72 | m.m[1][1] = cosf(radians); 73 | m.m[2][2] = 1.f; 74 | m.m[3][3] = 1.f; 75 | 76 | return m; 77 | } 78 | 79 | /* Rotate the mesh 80 | * @params x,y,z: degrees in each axis */ 81 | void Mesh::rotate(float x, float y, float z) { 82 | Mat4 xMat = ::rotX(x); 83 | Mat4 yMat = ::rotY(y); 84 | Mat4 zMat = ::rotZ(z); 85 | 86 | /* Since the mesh should rotate locally, it must untranslate back to the 87 | * origin, unrotate the mesh, then apply the new rotations, and then move the 88 | * mesh back to its original location */ 89 | unTranslate(); 90 | unRotateZ(); 91 | unRotateX(); 92 | unRotateY(); 93 | for (auto &trig : trigs) { 94 | trig.verts[0] = mult4(trig.verts[0], yMat); 95 | trig.verts[1] = mult4(trig.verts[1], yMat); 96 | trig.verts[2] = mult4(trig.verts[2], yMat); 97 | trig.norms[0] = mult4(trig.norms[0], yMat); 98 | trig.norms[1] = mult4(trig.norms[1], yMat); 99 | trig.norms[2] = mult4(trig.norms[2], yMat); 100 | trig.fNormal = mult4(trig.fNormal, yMat); 101 | 102 | trig.verts[0] = mult4(trig.verts[0], xMat); 103 | trig.verts[1] = mult4(trig.verts[1], xMat); 104 | trig.verts[2] = mult4(trig.verts[2], xMat); 105 | trig.norms[0] = mult4(trig.norms[0], xMat); 106 | trig.norms[1] = mult4(trig.norms[1], xMat); 107 | trig.norms[2] = mult4(trig.norms[2], xMat); 108 | trig.fNormal = mult4(trig.fNormal, xMat); 109 | 110 | trig.verts[0] = mult4(trig.verts[0], zMat); 111 | trig.verts[1] = mult4(trig.verts[1], zMat); 112 | trig.verts[2] = mult4(trig.verts[2], zMat); 113 | trig.norms[0] = mult4(trig.norms[0], zMat); 114 | trig.norms[1] = mult4(trig.norms[1], zMat); 115 | trig.norms[2] = mult4(trig.norms[2], zMat); 116 | trig.fNormal = mult4(trig.fNormal, zMat); 117 | } 118 | staticTranslate(transAmt.x, transAmt.y, transAmt.z); // Bring the mesh back 119 | 120 | /* These amounts are used to unrotate the mesh. Set them here and then 121 | * use them next time the unrotate functions are called */ 122 | rotAmt.x = x; 123 | rotAmt.y = y; 124 | rotAmt.z = z; 125 | 126 | } 127 | 128 | /* Translate the mesh 129 | * @params x,y,z: amount to translate in the x,y,z axis */ 130 | void Mesh::translate(float x, float y, float z) { 131 | /* Set the translated amounts. These are used when the mesh is untranslated 132 | * for its local rotations */ 133 | transAmt.x += x; 134 | transAmt.y += y; 135 | transAmt.z += z; 136 | 137 | for (auto &trig : trigs) { 138 | trig.verts[0].x += x; 139 | trig.verts[0].y += y; 140 | trig.verts[0].z += z; 141 | 142 | trig.verts[1].x += x; 143 | trig.verts[1].y += y; 144 | trig.verts[1].z += z; 145 | 146 | trig.verts[2].x += x; 147 | trig.verts[2].y += y; 148 | trig.verts[2].z += z; 149 | } 150 | } 151 | 152 | /* Translate the mesh statically, as in set the points and that's it 153 | * @params x,y,z: amount to translate in the x,y,z axis */ 154 | void Mesh::staticTranslate(float x, float y, float z) { 155 | for (auto &trig : trigs) { 156 | trig.verts[0].x += x; 157 | trig.verts[0].y += y; 158 | trig.verts[0].z += z; 159 | 160 | trig.verts[1].x += x; 161 | trig.verts[1].y += y; 162 | trig.verts[1].z += z; 163 | 164 | trig.verts[2].x += x; 165 | trig.verts[2].y += y; 166 | trig.verts[2].z += z; 167 | } 168 | } 169 | 170 | /* Scale the mesh 171 | * @param amt: the amount to scale in x,y,z */ 172 | void Mesh::scale(float amt) { 173 | unTranslate(); 174 | for (auto &trig : trigs) { 175 | trig.verts[0].x *= amt; 176 | trig.verts[0].y *= amt; 177 | trig.verts[0].z *= amt; 178 | 179 | trig.verts[1].x *= amt; 180 | trig.verts[1].y *= amt; 181 | trig.verts[1].z *= amt; 182 | 183 | trig.verts[2].x *= amt; 184 | trig.verts[2].y *= amt; 185 | trig.verts[2].z *= amt; 186 | } 187 | staticTranslate(transAmt.x, transAmt.y, transAmt.z); 188 | } 189 | 190 | /* Undo rotation on the X axis */ 191 | void Mesh::unRotateX() { 192 | 193 | Mat4 m = ::rotX(-rotAmt.x); 194 | 195 | for (auto &trig : trigs) { 196 | trig.verts[0] = mult4(trig.verts[0], m); 197 | trig.verts[1] = mult4(trig.verts[1], m); 198 | trig.verts[2] = mult4(trig.verts[2], m); 199 | trig.norms[0] = mult4(trig.norms[0], m); 200 | trig.norms[1] = mult4(trig.norms[1], m); 201 | trig.norms[2] = mult4(trig.norms[2], m); 202 | trig.fNormal = mult4(trig.fNormal, m); 203 | } 204 | rotAmt.x = 0.0f; 205 | } 206 | 207 | /* Undo rotation on the Y axis */ 208 | void Mesh::unRotateY() { 209 | Mat4 m = ::rotY(-rotAmt.y); 210 | for (auto &trig : trigs) { 211 | trig.verts[0] = mult4(trig.verts[0], m); 212 | trig.verts[1] = mult4(trig.verts[1], m); 213 | trig.verts[2] = mult4(trig.verts[2], m); 214 | trig.norms[0] = mult4(trig.norms[0], m); 215 | trig.norms[1] = mult4(trig.norms[1], m); 216 | trig.norms[2] = mult4(trig.norms[2], m); 217 | trig.fNormal = mult4(trig.fNormal, m); 218 | } 219 | rotAmt.y = 0.0f; 220 | 221 | } 222 | 223 | /* Undo rotation on the Z axis */ 224 | void Mesh::unRotateZ() { 225 | 226 | Mat4 m = ::rotZ(-rotAmt.z); 227 | for (auto &trig : trigs) { 228 | trig.verts[0] = mult4(trig.verts[0], m); 229 | trig.verts[1] = mult4(trig.verts[1], m); 230 | trig.verts[2] = mult4(trig.verts[2], m); 231 | trig.norms[0] = mult4(trig.norms[0], m); 232 | trig.norms[1] = mult4(trig.norms[1], m); 233 | trig.norms[2] = mult4(trig.norms[2], m); 234 | trig.fNormal = mult4(trig.fNormal, m); 235 | } 236 | rotAmt.z = 0.0f; 237 | } 238 | 239 | /* Undo translation */ 240 | void Mesh::unTranslate() { 241 | 242 | for (auto &trig : trigs) { 243 | trig.verts[0].x -= transAmt.x; 244 | trig.verts[0].y -= transAmt.y; 245 | trig.verts[0].z -= transAmt.z; 246 | 247 | trig.verts[1].x -= transAmt.x; 248 | trig.verts[1].y -= transAmt.y; 249 | trig.verts[1].z -= transAmt.z; 250 | 251 | trig.verts[2].x -= transAmt.x; 252 | trig.verts[2].y -= transAmt.y; 253 | trig.verts[2].z -= transAmt.z; 254 | 255 | } 256 | } 257 | 258 | /* Multiply a vertex by a 4x4 matrix - performs division by w 259 | * @param v: the vertex 260 | * @param m: the 4x4 matrix 261 | * @return Vert: product of multiplcation */ 262 | Vert mult4(Vert v, Mat4& m) { 263 | 264 | Vert a; 265 | 266 | // Temporary variables to multiply with 267 | float tx = v.x; 268 | float ty = v.y; 269 | float tz = v.z; 270 | 271 | a.x = (tx * m.m[0][0]) + (ty * m.m[0][1]) + (tz * m.m[0][2]) + m.m[0][3]; 272 | a.y = tx * m.m[1][0] + ty * m.m[1][1] + tz * m.m[1][2] + m.m[1][3]; 273 | a.z = tx * m.m[2][0] + ty * m.m[2][1] + tz * m.m[2][2] + m.m[2][3]; 274 | float w = tx * m.m[3][0] + ty * m.m[3][1] + tz * m.m[3][2] + m.m[3][3]; 275 | 276 | if (w != 0.0f) { 277 | a.x /= w; 278 | a.y /= w; 279 | a.z /= w; 280 | } 281 | 282 | a.xn = v.xn; 283 | a.yn = v.yn; 284 | a.zn = v.zn; 285 | 286 | return a; 287 | } 288 | 289 | /* Calculate the dot product between two vertices 290 | * @params a,b: vertices 291 | * @return float: result */ 292 | float dot(Vert a, Vert b) { 293 | return ((a.x*b.x) + (a.y*b.y) + (a.z*b.z)); 294 | } 295 | 296 | /* Calculate the direction vector a-b 297 | * @params a,b: vertices 298 | * @return Vert: direction vector result */ 299 | Vert direc(Vert a, Vert b) { 300 | Vert r(a.x-b.x, a.y-b.y, a.z-b.z); 301 | return r; 302 | } 303 | 304 | /* Calculate the cross product of two vectors 305 | * @params a,b: vertices 306 | * @return Vert: cross product result */ 307 | Vert cross(Vert a, Vert b) { 308 | Vert c((a.y*b.z)-(a.z*b.y), (a.z*b.x)-(a.x*b.z), (a.x*b.y)-(a.y*b.x)); 309 | return c; 310 | } 311 | 312 | /* Project a triangle with a perspective matrix 313 | * @param t: the triangle 314 | * @param m: the perspective matrix */ 315 | void project(Trig& t, Mat4 m) { 316 | t.verts[0] = mult4(t.verts[0], m); 317 | t.verts[1] = mult4(t.verts[1], m); 318 | t.verts[2] = mult4(t.verts[2], m); 319 | } 320 | 321 | /* Calculate the Z value from x,y using cross and vert of equation of plane 322 | * @params x,y: the input x and y values 323 | * @param c: cross product vertex from equation of plane 324 | * @param v: point from our original triangle from equation of plane 325 | * @return float: the z value */ 326 | float calcZ(float x, float y, Vert c, Vert v) { 327 | float k = dot(c, v); 328 | float z = ((c.x*x + c.y*y - k) / (-c.z)); 329 | return z; 330 | } 331 | 332 | /* Calculate the bary coordinates given 3 points of a triangle and x and y 333 | * inside of the triangle 334 | * @params p1,p2,p3: vertices of the triangle 335 | * @params x,y: current point within the triangle 336 | * @params w1,w2,w3: store the bary outputs */ 337 | void calcBary(Vert p1, Vert p2, Vert p3, int x, int y, float& w1, float& w2, 338 | float& w3) { 339 | w1 = (((p2.y - p3.y) * (x - p3.x)) + (p3.x - p2.x) * (y - p3.y)) / 340 | (((p2.y - p3.y) * (p1.x - p3.x)) + ((p3.x - p2.x) * (p1.y - p3.y))); 341 | w2 = (((p3.y - p1.y) * (x - p3.x)) + ((p1.x - p3.x) * (y - p3.y))) / 342 | (((p2.y - p3.y) * (p1.x - p3.x)) + ((p3.x - p2.x) * (p1.y - p3.y))); 343 | w3 = 1 - w1 - w2; 344 | } 345 | -------------------------------------------------------------------------------- /src/agm.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Mesh.hpp" 5 | 6 | /* Ascii Graphics Mathematics library */ 7 | 8 | /* 4x4 matrix of float values. Uses std vector */ 9 | class Mat4 { 10 | public: 11 | std::vector> m; 12 | 13 | /* Default constructor - Create 4x4 matrix with 0.0f */ 14 | Mat4(); 15 | }; 16 | 17 | 18 | /* Creates a Mat4 with the perspective projection matrix values applied 19 | * @param aspect: the screen aspect ratio 20 | * @param fov: the field of view 21 | * @param zNear: the near clipping plane 22 | * @param zFar: the far clipping plane 23 | * @return Mat4: matrix with projection values */ 24 | Mat4 perspective(float aspect, float fov, float zNear, float zFar); 25 | 26 | /* Creates a Mat4 with the rotation matrix along the Z axis 27 | * @param degrees: the number of degrees to rotate 28 | * @return Mat4: rotation matrix */ 29 | Mat4 rotZ(float degrees); 30 | 31 | /* Creates a Mat4 with the rotation matrix along the Y axis 32 | * @param degrees: the number of degrees to rotate 33 | * @return Mat4: rotation matrix */ 34 | Mat4 rotY(float degrees); 35 | 36 | /* Creates a Mat4 with the rotation matrix along the X axis 37 | * @param degrees: the number of degrees to rotate 38 | * @return Mat4: rotation matrix */ 39 | Mat4 rotX(float degrees); 40 | 41 | /* Multiply a vertex by a 4x4 matrix - performs division by w 42 | * @param v: the vertex 43 | * @param m: the 4x4 matrix 44 | * @return Vert: product of multiplcation */ 45 | Vert mult4(Vert v, Mat4& m); 46 | 47 | /* Calculate the dot product between two vertices 48 | * @params a,b: vertices 49 | * @return float: result */ 50 | float dot(Vert a, Vert b); 51 | 52 | /* Calculate the direction vector a-b 53 | * @params a,b: vertices 54 | * @return Vert: direction vector result */ 55 | Vert direc(Vert a, Vert b); 56 | 57 | /* Calculate the cross product of two vectors 58 | * @params a,b: vertices 59 | * @return Vert: cross product result */ 60 | Vert cross(Vert a, Vert b); 61 | 62 | /* Calculate the Z value from x,y using cross and vert of equation of plane 63 | * @params x,y: the input x and y values 64 | * @param c: cross product vertex from equation of plane 65 | * @param v: point from our original triangle from equation of plane 66 | * @return float: the z value */ 67 | float calcZ(float x, float y, Vert c, Vert v); 68 | 69 | /* Project a triangle with a perspective matrix 70 | * @param t: the triangle 71 | * @param m: the perspective matrix */ 72 | void project(Trig& t, Mat4 m); 73 | 74 | /* Calculate the bary coordinates given 3 points of a triangle and x and y 75 | * inside of the triangle 76 | * @params p1,p2,p3: vertices of the triangle 77 | * @params x,y: current point within the triangle 78 | * @params w1,w2,w3: store the bary outputs */ 79 | void calcBary(Vert p1, Vert p2, Vert p3, int x, int y, float& w1, float& w2, 80 | float& w3); 81 | 82 | --------------------------------------------------------------------------------