Refactor renderer and input handling; add OBJ loader and math utilities

- Updated renderer.h to replace Vec3 and Vec2 structs with Math namespace equivalents.
- Introduced Texture struct to manage texture properties.
- Modified Triangle struct to use Texture instead of GLuint for texture handling.
- Removed deprecated matrix functions and replaced them with Math namespace methods.
- Implemented InputManager class for better input handling, including key and mouse state tracking.
- Added ObjLoader class to load OBJ files and associated textures, with MTL file parsing.
- Created math utilities for fixed-point arithmetic and vector/matrix operations.
- Added time management class for frame timing and delta time calculations.
This commit is contained in:
ExilProductions
2025-05-02 15:46:45 +02:00
parent 6d308d3279
commit 475a1d606a
18 changed files with 982 additions and 308 deletions

View File

@@ -5,6 +5,9 @@
"includePath": [
"${workspaceFolder}/Engine/renderer",
"${workspaceFolder}/Engine/loader",
"${workspaceFolder}/Engine/math",
"${workspaceFolder}/Engine/input",
"${workspaceFolder}/Engine/time",
"${workspaceFolder}/Engine",
"${workspaceFolder}/external/**",
"/usr/include",

View File

@@ -11,13 +11,18 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
include_directories(${CMAKE_SOURCE_DIR}/Engine)
include_directories(${CMAKE_SOURCE_DIR}/Engine/renderer)
include_directories(${CMAKE_SOURCE_DIR}/Engine/loader)
include_directories(${CMAKE_SOURCE_DIR}/Engine/input)
include_directories(${CMAKE_SOURCE_DIR}/Engine/math)
include_directories(${CMAKE_SOURCE_DIR}/Engine/time)
include_directories(${CMAKE_SOURCE_DIR}/examples)
include_directories(${CMAKE_SOURCE_DIR}/external/stb)
# Source files
set(SOURCES
${CMAKE_SOURCE_DIR}/Engine/renderer/renderer.cpp
${CMAKE_SOURCE_DIR}/Engine/loader/mesh.cpp
${CMAKE_SOURCE_DIR}/Engine/loader/obj_loader.cpp
${CMAKE_SOURCE_DIR}/Engine/input/input.cpp
${CMAKE_SOURCE_DIR}/Engine/time/time_manager.cpp
${CMAKE_SOURCE_DIR}/examples/cube.cpp
)

157
Engine/input/input.cpp Normal file
View File

@@ -0,0 +1,157 @@
#include "input.h"
#include <algorithm>
InputManager::InputManager(GLFWwindow* win) : window(win), scroll_delta(0.0f, 0.0f) {
std::fill(previous_key_states.begin(), previous_key_states.end(), false);
std::fill(previous_mouse_button_states.begin(), previous_mouse_button_states.end(), false);
glfwSetWindowUserPointer(window, this);
glfwSetScrollCallback(window, [](GLFWwindow* w, double xoffset, double yoffset) {
InputManager* manager = static_cast<InputManager*>(glfwGetWindowUserPointer(w));
manager->scroll_delta.x += static_cast<float>(xoffset);
manager->scroll_delta.y += static_cast<float>(yoffset);
});
double x, y;
glfwGetCursorPos(window, &x, &y);
last_mouse_position = Vec2(static_cast<float>(x), static_cast<float>(y));
}
int InputManager::get_key_code(InputKey key) {
switch (key) {
case InputKey::KEY_A: return GLFW_KEY_A;
case InputKey::KEY_B: return GLFW_KEY_B;
case InputKey::KEY_C: return GLFW_KEY_C;
case InputKey::KEY_D: return GLFW_KEY_D;
case InputKey::KEY_E: return GLFW_KEY_E;
case InputKey::KEY_F: return GLFW_KEY_F;
case InputKey::KEY_G: return GLFW_KEY_G;
case InputKey::KEY_H: return GLFW_KEY_H;
case InputKey::KEY_I: return GLFW_KEY_I;
case InputKey::KEY_J: return GLFW_KEY_J;
case InputKey::KEY_K: return GLFW_KEY_K;
case InputKey::KEY_L: return GLFW_KEY_L;
case InputKey::KEY_M: return GLFW_KEY_M;
case InputKey::KEY_N: return GLFW_KEY_N;
case InputKey::KEY_O: return GLFW_KEY_O;
case InputKey::KEY_P: return GLFW_KEY_P;
case InputKey::KEY_Q: return GLFW_KEY_Q;
case InputKey::KEY_R: return GLFW_KEY_R;
case InputKey::KEY_S: return GLFW_KEY_S;
case InputKey::KEY_T: return GLFW_KEY_T;
case InputKey::KEY_U: return GLFW_KEY_U;
case InputKey::KEY_V: return GLFW_KEY_V;
case InputKey::KEY_W: return GLFW_KEY_W;
case InputKey::KEY_X: return GLFW_KEY_X;
case InputKey::KEY_Y: return GLFW_KEY_Y;
case InputKey::KEY_Z: return GLFW_KEY_Z;
case InputKey::KEY_0: return GLFW_KEY_0;
case InputKey::KEY_1: return GLFW_KEY_1;
case InputKey::KEY_2: return GLFW_KEY_2;
case InputKey::KEY_3: return GLFW_KEY_3;
case InputKey::KEY_4: return GLFW_KEY_4;
case InputKey::KEY_5: return GLFW_KEY_5;
case InputKey::KEY_6: return GLFW_KEY_6;
case InputKey::KEY_7: return GLFW_KEY_7;
case InputKey::KEY_8: return GLFW_KEY_8;
case InputKey::KEY_9: return GLFW_KEY_9;
case InputKey::KEY_UP: return GLFW_KEY_UP;
case InputKey::KEY_DOWN: return GLFW_KEY_DOWN;
case InputKey::KEY_LEFT: return GLFW_KEY_LEFT;
case InputKey::KEY_RIGHT: return GLFW_KEY_RIGHT;
case InputKey::KEY_ENTER: return GLFW_KEY_ENTER;
case InputKey::KEY_BACKSPACE: return GLFW_KEY_BACKSPACE;
case InputKey::KEY_TAB: return GLFW_KEY_TAB;
case InputKey::KEY_SHIFT: return GLFW_KEY_LEFT_SHIFT;
case InputKey::KEY_CONTROL: return GLFW_KEY_LEFT_CONTROL;
case InputKey::KEY_ALT: return GLFW_KEY_LEFT_ALT;
case InputKey::KEY_CAPS_LOCK: return GLFW_KEY_CAPS_LOCK;
case InputKey::KEY_SPACE: return GLFW_KEY_SPACE;
case InputKey::KEY_ESCAPE: return GLFW_KEY_ESCAPE;
default: return -1;
}
}
int InputManager::get_mouse_button_code(InputKey key) {
switch (key) {
case InputKey::MOUSE_LEFT: return GLFW_MOUSE_BUTTON_LEFT;
case InputKey::MOUSE_RIGHT: return GLFW_MOUSE_BUTTON_RIGHT;
case InputKey::MOUSE_MIDDLE: return GLFW_MOUSE_BUTTON_MIDDLE;
case InputKey::MOUSE_BUTTON_4: return GLFW_MOUSE_BUTTON_4;
case InputKey::MOUSE_BUTTON_5: return GLFW_MOUSE_BUTTON_5;
case InputKey::MOUSE_BUTTON_6: return GLFW_MOUSE_BUTTON_6;
case InputKey::MOUSE_BUTTON_7: return GLFW_MOUSE_BUTTON_7;
case InputKey::MOUSE_BUTTON_8: return GLFW_MOUSE_BUTTON_8;
default: return -1;
}
}
bool InputManager::is_pressed(InputKey key) {
int key_code = get_key_code(key);
if (key_code != -1) {
return glfwGetKey(window, key_code) == GLFW_PRESS;
}
int button = get_mouse_button_code(key);
if (button != -1) {
return glfwGetMouseButton(window, button) == GLFW_PRESS;
}
return false;
}
bool InputManager::was_pressed(InputKey key) {
int key_code = get_key_code(key);
if (key_code != -1) {
bool current = glfwGetKey(window, key_code) == GLFW_PRESS;
bool previous = previous_key_states[key_code];
return current && !previous;
}
int button = get_mouse_button_code(key);
if (button != -1) {
bool current = glfwGetMouseButton(window, button) == GLFW_PRESS;
bool previous = previous_mouse_button_states[button];
return current && !previous;
}
return false;
}
bool InputManager::was_released(InputKey key) {
int key_code = get_key_code(key);
if (key_code != -1) {
bool current = glfwGetKey(window, key_code) == GLFW_PRESS;
bool previous = previous_key_states[key_code];
return !current && previous;
}
int button = get_mouse_button_code(key);
if (button != -1) {
bool current = glfwGetMouseButton(window, button) == GLFW_PRESS;
bool previous = previous_mouse_button_states[button];
return !current && previous;
}
return false;
}
Vec2 InputManager::get_mouse_position() {
double x, y;
glfwGetCursorPos(window, &x, &y);
return Vec2(static_cast<float>(x), static_cast<float>(y));
}
Vec2 InputManager::get_mouse_delta() {
Vec2 current = get_mouse_position();
Vec2 delta = current - last_mouse_position;
last_mouse_position = current;
return delta;
}
Vec2 InputManager::get_scroll_delta() {
Vec2 delta = scroll_delta;
scroll_delta = Vec2(0.0f, 0.0f);
return delta;
}
void InputManager::update() {
for (int key = 0; key < 400; ++key) {
previous_key_states[key] = (glfwGetKey(window, key) == GLFW_PRESS);
}
for (int button = 0; button < 8; ++button) {
previous_mouse_button_states[button] = (glfwGetMouseButton(window, button) == GLFW_PRESS);
}
}

97
Engine/input/input.h Normal file
View File

@@ -0,0 +1,97 @@
#include <renderer.h>
#include <math.h>
#include <array>
#include <GLFW/glfw3.h>
enum class InputEvent
{
KEY_PRESS,
KEY_RELEASE,
MOUSE_MOVE,
MOUSE_BUTTON_PRESS,
MOUSE_BUTTON_RELEASE,
};
enum class InputKey
{
KEY_A,
KEY_B,
KEY_C,
KEY_D,
KEY_E,
KEY_F,
KEY_G,
KEY_H,
KEY_I,
KEY_J,
KEY_K,
KEY_L,
KEY_M,
KEY_N,
KEY_O,
KEY_P,
KEY_Q,
KEY_R,
KEY_S,
KEY_T,
KEY_U,
KEY_V,
KEY_W,
KEY_X,
KEY_Y,
KEY_Z,
KEY_0,
KEY_1,
KEY_2,
KEY_3,
KEY_4,
KEY_5,
KEY_6,
KEY_7,
KEY_8,
KEY_9,
KEY_UP,
KEY_DOWN,
KEY_LEFT,
KEY_RIGHT,
KEY_ENTER,
KEY_BACKSPACE,
KEY_TAB,
KEY_SHIFT,
KEY_CONTROL,
KEY_ALT,
KEY_CAPS_LOCK,
KEY_SPACE,
KEY_ESCAPE,
MOUSE_LEFT,
MOUSE_RIGHT,
MOUSE_MIDDLE,
MOUSE_BUTTON_4,
MOUSE_BUTTON_5,
MOUSE_BUTTON_6,
MOUSE_BUTTON_7,
MOUSE_BUTTON_8,
};
class InputManager
{
private:
GLFWwindow* window;
Vec2 scroll_delta;
Vec2 last_mouse_position;
std::array<bool, 400> previous_key_states;
std::array<bool, 8> previous_mouse_button_states;
int get_key_code(InputKey key);
int get_mouse_button_code(InputKey key);
public:
InputManager(GLFWwindow* win);
bool is_pressed(InputKey key);
bool was_pressed(InputKey key);
bool was_released(InputKey key);
Vec2 get_mouse_position();
Vec2 get_mouse_delta();
Vec2 get_scroll_delta();
void update();
};

View File

@@ -1,145 +0,0 @@
#include "mesh.h"
#include <fstream>
#include <sstream>
#include <iostream>
#include <stdexcept>
#include <filesystem>
namespace fs = std::filesystem;
std::map<std::string, Material> load_mtl_materials(const std::string& mtl_path)
{
std::map<std::string, Material> materials;
std::ifstream file(mtl_path);
if (!file.is_open()) {
std::cerr << "Warning: could not open MTL file: " << mtl_path << std::endl;
return materials;
}
std::string line, current_mtl;
while (std::getline(file, line)) {
std::istringstream ss(line);
std::string keyword;
ss >> keyword;
if (keyword == "newmtl") {
ss >> current_mtl;
materials[current_mtl] = Material{Color(255, 255, 255), ""}; // Default white, no texture
} else if (keyword == "Kd") {
float r, g, b;
ss >> r >> g >> b;
if (materials.count(current_mtl)) {
materials[current_mtl].color = Color(
static_cast<uint8_t>(r * 255),
static_cast<uint8_t>(g * 255),
static_cast<uint8_t>(b * 255)
);
}
} else if (keyword == "map_Kd") {
std::string texture_path;
ss >> texture_path;
if (materials.count(current_mtl)) {
materials[current_mtl].texture_path = texture_path;
}
}
}
return materials;
}
std::vector<Triangle> load_obj(const std::string& obj_path, Renderer* renderer)
{
std::ifstream file(obj_path);
if (!file.is_open()) {
throw std::runtime_error("Failed to open OBJ file: " + obj_path);
}
std::vector<Vec3> positions;
std::vector<Vec2> texcoords;
std::map<std::string, Material> materials;
std::map<std::string, GLuint> texture_map;
std::vector<Triangle> triangles;
std::string current_material = "";
fs::path base_path = fs::path(obj_path).parent_path();
std::string line;
while (std::getline(file, line)) {
std::istringstream ss(line);
std::string keyword;
ss >> keyword;
if (keyword == "v") {
float x, y, z;
ss >> x >> y >> z;
positions.emplace_back((int)(x * 4096), (int)(y * 4096), (int)(z * 4096));
} else if (keyword == "vt") {
float u, v;
ss >> u >> v;
texcoords.emplace_back(u, 1.0f - v); // flip v
} else if (keyword == "f") {
std::vector<std::string> tokens;
std::string token;
while (ss >> token) tokens.push_back(token);
std::vector<int> v_idx, t_idx;
for (const auto& t : tokens) {
std::stringstream ts(t);
std::string part;
int vi = -1, ti = -1;
std::getline(ts, part, '/');
if (!part.empty()) vi = std::stoi(part) - 1;
if (std::getline(ts, part, '/') && !part.empty())
ti = std::stoi(part) - 1;
v_idx.push_back(vi);
t_idx.push_back(ti);
}
// Triangulate face
for (size_t i = 1; i + 1 < v_idx.size(); ++i) {
Triangle tri;
tri.v0 = positions[v_idx[0]];
tri.v1 = positions[v_idx[i]];
tri.v2 = positions[v_idx[i + 1]];
tri.uv0 = (t_idx[0] >= 0 && t_idx[0] < (int)texcoords.size()) ? texcoords[t_idx[0]] : Vec2(0, 0);
tri.uv1 = (t_idx[i] >= 0 && t_idx[i] < (int)texcoords.size()) ? texcoords[t_idx[i]] : Vec2(0, 0);
tri.uv2 = (t_idx[i + 1] >= 0 && t_idx[i + 1] < (int)texcoords.size()) ? texcoords[t_idx[i + 1]] : Vec2(0, 0);
if (materials.count(current_material)) {
tri.color = materials[current_material].color;
tri.texture = texture_map[current_material];
} else {
tri.color = Color(255, 255, 255); // Fallback white
tri.texture = 0; // No texture
}
triangles.push_back(tri);
}
} else if (keyword == "mtllib") {
std::string mtl_file;
ss >> mtl_file;
fs::path mtl_path = base_path / mtl_file;
materials = load_mtl_materials(mtl_path.string());
// Load textures for all materials
for (const auto& mat_pair : materials) {
const std::string& mat_name = mat_pair.first;
const Material& mat = mat_pair.second;
if (!mat.texture_path.empty()) {
fs::path texture_full_path = base_path / mat.texture_path;
texture_map[mat_name] = renderer->load_texture(texture_full_path.string());
} else {
texture_map[mat_name] = 0; // No texture
}
}
} else if (keyword == "usemtl") {
ss >> current_material;
}
}
return triangles;
}

View File

@@ -1,15 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <map>
#include "renderer.h" // includes Vec3, Vec2, Triangle, Color
struct Material
{
Color color;
std::string texture_path;
};
std::map<std::string, Material> load_mtl_materials(const std::string& mtl_path);
std::vector<Triangle> load_obj(const std::string& obj_path, Renderer* renderer);

View File

@@ -0,0 +1,134 @@
#include "obj_loader.h"
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <map>
#include <filesystem>
std::vector<Triangle> ObjLoader::load_obj(const std::string& obj_filepath,
Renderer& renderer)
{
std::vector<Triangle> triangles;
std::vector<Vec3> vertices;
std::vector<Vec2> uvs;
std::vector<Vec3> normals;
std::map<std::string, Texture> material_textures; // Maps material names to textures
std::string current_material;
std::string mtl_filepath;
// Helper to parse MTL file
auto parse_mtl = [&](const std::string& mtl_path) {
std::ifstream mtl_file(mtl_path);
if (!mtl_file.is_open()) {
// Silently skip if MTL file cannot be opened
return;
}
std::string line, current_mtl;
while (std::getline(mtl_file, line)) {
std::istringstream iss(line);
std::string token;
iss >> token;
if (token == "newmtl") {
iss >> current_mtl;
}
else if (token == "map_Kd") {
std::string texture_path;
std::getline(iss, texture_path);
texture_path.erase(0, texture_path.find_first_not_of(" \t"));
// Construct full texture path relative to MTL directory
std::filesystem::path mtl_dir = std::filesystem::path(mtl_path).parent_path();
std::filesystem::path tex_path = mtl_dir / texture_path;
try {
material_textures[current_mtl] = renderer.load_texture(tex_path.string());
} catch (const std::exception& e) {
// Fallback to default texture (id=0) if loading fails
material_textures[current_mtl] = Texture(0, 0, 0);
}
}
}
mtl_file.close();
};
// Open and parse OBJ file
std::ifstream obj_file(obj_filepath);
if (!obj_file.is_open()) {
throw std::runtime_error("Failed to open OBJ file: " + obj_filepath);
}
std::filesystem::path obj_dir = std::filesystem::path(obj_filepath).parent_path();
std::string line;
while (std::getline(obj_file, line)) {
std::istringstream iss(line);
std::string token;
iss >> token;
if (token == "v") {
float x, y, z;
iss >> x >> y >> z;
vertices.emplace_back(x, y, z);
}
else if (token == "vt") {
float u, v;
iss >> u >> v;
uvs.emplace_back(u, v);
}
else if (token == "vn") {
float x, y, z;
iss >> x >> y >> z;
normals.emplace_back(x, y, z);
}
else if (token == "f") {
std::vector<std::tuple<int, int, int>> face_vertices; // v/vt/vn indices
std::string vertex;
while (iss >> vertex) {
int v_idx = 0, vt_idx = 0, vn_idx = 0;
std::sscanf(vertex.c_str(), "%d/%d/%d", &v_idx, &vt_idx, &vn_idx);
face_vertices.emplace_back(v_idx - 1, vt_idx - 1, vn_idx - 1); // Convert to 0-based
}
// Triangulate if necessary (e.g., for quads)
for (size_t i = 1; i < face_vertices.size() - 1; ++i) {
Triangle tri;
auto [v0, vt0, vn0] = face_vertices[0];
auto [v1, vt1, vn1] = face_vertices[i];
auto [v2, vt2, vn2] = face_vertices[i + 1];
// Assign vertices
tri.v0 = vertices[v0];
tri.v1 = vertices[v1];
tri.v2 = vertices[v2];
// Assign UVs (use default if not specified)
tri.uv0 = vt0 >= 0 && vt0 < uvs.size() ? uvs[vt0] : Vec2(0, 0);
tri.uv1 = vt1 >= 0 && vt1 < uvs.size() ? uvs[vt1] : Vec2(0, 0);
tri.uv2 = vt2 >= 0 && vt2 < uvs.size() ? uvs[vt2] : Vec2(0, 0);
// Assign default color
tri.color = Color(255, 255, 255);
// Assign texture based on current material
tri.texture = material_textures.count(current_material) ?
material_textures[current_material] : Texture(0, 0, 0);
triangles.push_back(tri);
}
}
else if (token == "mtllib") {
std::string mtl_name;
iss >> mtl_name;
mtl_filepath = (obj_dir / mtl_name).string();
parse_mtl(mtl_filepath);
}
else if (token == "usemtl") {
iss >> current_material;
}
}
obj_file.close();
return triangles;
}

View File

@@ -0,0 +1,15 @@
#ifndef OBJ_LOADER_H
#define OBJ_LOADER_H
#include "renderer.h"
#include <string>
#include <vector>
class ObjLoader
{
public:
static std::vector<Triangle> load_obj(const std::string& obj_filepath,
Renderer& renderer);
};
#endif

3
Engine/math/math.h Normal file
View File

@@ -0,0 +1,3 @@
#include <vec3.h>
#include <vec2.h>
#include <matrix4.h>

147
Engine/math/matrix4.h Normal file
View File

@@ -0,0 +1,147 @@
#pragma once
#include "vec3.h"
#include <cmath>
#ifndef FIXED_POINT_PRECISION
#define FIXED_POINT_PRECISION 4096
#endif
inline int fixed_sin(int angle)
{
return std::sin(angle / static_cast<float>(FIXED_POINT_PRECISION)) * FIXED_POINT_PRECISION;
}
inline int fixed_cos(int angle)
{
return std::cos(angle / static_cast<float>(FIXED_POINT_PRECISION)) * FIXED_POINT_PRECISION;
}
namespace Math {
struct Matrix4
{
int m[16];
inline Matrix4()
{
for (int i = 0; i < 16; ++i)
m[i] = (i % 5 == 0) ? FIXED_POINT_PRECISION : 0;
}
inline static Matrix4 identity()
{
return Matrix4();
}
inline static Matrix4 multiply(const Matrix4 &a, const Matrix4 &b)
{
Matrix4 result;
for (int row = 0; row < 4; ++row)
{
for (int col = 0; col < 4; ++col)
{
int sum = 0;
for (int k = 0; k < 4; ++k)
{
sum += (a.m[row * 4 + k] * b.m[k * 4 + col]) / FIXED_POINT_PRECISION;
}
result.m[row * 4 + col] = sum;
}
}
return result;
}
inline static Vec3 multiply(const Matrix4 &mat, const Vec3 &v)
{
int x = (mat.m[0] * v.x + mat.m[1] * v.y + mat.m[2] * v.z + mat.m[3]) / FIXED_POINT_PRECISION;
int y = (mat.m[4] * v.x + mat.m[5] * v.y + mat.m[6] * v.z + mat.m[7]) / FIXED_POINT_PRECISION;
int z = (mat.m[8] * v.x + mat.m[9] * v.y + mat.m[10] * v.z + mat.m[11]) / FIXED_POINT_PRECISION;
return Vec3(x, y, z);
}
inline static Matrix4 translation(int tx, int ty, int tz)
{
Matrix4 result = identity();
result.m[12] = tx * FIXED_POINT_PRECISION;
result.m[13] = ty * FIXED_POINT_PRECISION;
result.m[14] = tz * FIXED_POINT_PRECISION;
return result;
}
inline static Matrix4 scale(int sx, int sy, int sz)
{
Matrix4 result;
result.m[0] = sx * FIXED_POINT_PRECISION;
result.m[5] = sy * FIXED_POINT_PRECISION;
result.m[10] = sz * FIXED_POINT_PRECISION;
return result;
}
inline static Matrix4 rotateX(int angle)
{
Matrix4 mat = identity();
float rad = angle / static_cast<float>(FIXED_POINT_PRECISION);
int cos_val = static_cast<int>(std::cos(rad) * FIXED_POINT_PRECISION);
int sin_val = static_cast<int>(std::sin(rad) * FIXED_POINT_PRECISION);
mat.m[5] = cos_val;
mat.m[6] = -sin_val;
mat.m[9] = sin_val;
mat.m[10] = cos_val;
return mat;
}
inline static Matrix4 rotateY(int angle)
{
Matrix4 mat = identity();
float rad = angle / static_cast<float>(FIXED_POINT_PRECISION);
int cos_val = static_cast<int>(std::cos(rad) * FIXED_POINT_PRECISION);
int sin_val = static_cast<int>(std::sin(rad) * FIXED_POINT_PRECISION);
mat.m[0] = cos_val;
mat.m[2] = sin_val;
mat.m[8] = -sin_val;
mat.m[10] = cos_val;
return mat;
}
inline static Matrix4 rotateZ(int angle)
{
Matrix4 mat = identity();
float rad = angle / static_cast<float>(FIXED_POINT_PRECISION);
int cos_val = static_cast<int>(std::cos(rad) * FIXED_POINT_PRECISION);
int sin_val = static_cast<int>(std::sin(rad) * FIXED_POINT_PRECISION);
mat.m[0] = cos_val;
mat.m[1] = -sin_val;
mat.m[4] = sin_val;
mat.m[5] = cos_val;
return mat;
}
inline static Matrix4 perspective(float fov_rad, float aspect, float near, float far)
{
Matrix4 result = {};
float tanHalfFov = std::tan(fov_rad / 2.0f);
result.m[0] = static_cast<int>((1.0f / (aspect * tanHalfFov)) * FIXED_POINT_PRECISION);
result.m[5] = static_cast<int>((1.0f / tanHalfFov) * FIXED_POINT_PRECISION);
result.m[10] = static_cast<int>(((far + near) / (near - far)) * FIXED_POINT_PRECISION);
result.m[11] = static_cast<int>(((2.0f * far * near) / (near - far)) * FIXED_POINT_PRECISION);
result.m[14] = -FIXED_POINT_PRECISION;
result.m[15] = 0;
return result;
}
inline static Matrix4 orthographic(float left, float right, float bottom, float top, float near, float far)
{
Matrix4 result = {};
result.m[0] = static_cast<int>((2.0f / (right - left)) * FIXED_POINT_PRECISION);
result.m[5] = static_cast<int>((2.0f / (top - bottom)) * FIXED_POINT_PRECISION);
result.m[10] = static_cast<int>((-2.0f / (far - near)) * FIXED_POINT_PRECISION);
result.m[12] = static_cast<int>((-(right + left) / (right - left)) * FIXED_POINT_PRECISION);
result.m[13] = static_cast<int>((-(top + bottom) / (top - bottom)) * FIXED_POINT_PRECISION);
result.m[14] = static_cast<int>((-(far + near) / (far - near)) * FIXED_POINT_PRECISION);
result.m[15] = FIXED_POINT_PRECISION;
return result;
}
};
}

96
Engine/math/vec2.h Normal file
View File

@@ -0,0 +1,96 @@
#pragma once
#include <cmath>
#include <cstdint>
#ifndef FIXED_POINT_PRECISION
#define FIXED_POINT_PRECISION 4096
#endif
namespace Math {
struct Vec2
{
int x, y;
inline Vec2(int x_ = 0, int y_ = 0) : x(x_), y(y_) {}
inline Vec2 operator+(const Vec2 &other) const
{
return Vec2(x + other.x, y + other.y);
}
inline Vec2 operator-(const Vec2 &other) const
{
return Vec2(x - other.x, y - other.y);
}
inline Vec2 operator*(int scalar_fixed) const
{
return Vec2((x * scalar_fixed) / FIXED_POINT_PRECISION,
(y * scalar_fixed) / FIXED_POINT_PRECISION);
}
inline Vec2 operator/(int scalar_fixed) const
{
return Vec2((x * FIXED_POINT_PRECISION) / scalar_fixed,
(y * FIXED_POINT_PRECISION) / scalar_fixed);
}
inline Vec2 &operator+=(const Vec2 &other)
{
x += other.x;
y += other.y;
return *this;
}
inline Vec2 &operator-=(const Vec2 &other)
{
x -= other.x;
y -= other.y;
return *this;
}
inline static int dot(const Vec2 &a, const Vec2 &b)
{
return a.x * b.x + a.y * b.y;
}
inline static int cross(const Vec2 &a, const Vec2 &b)
{
return a.x * b.y - a.y * b.x;
}
inline int length_squared() const
{
return x * x + y * y;
}
inline int length() const
{
return static_cast<int>(std::sqrt(static_cast<float>(length_squared())));
}
inline Vec2 normalized() const
{
int len = length();
if (len == 0) return *this;
return Vec2((x * FIXED_POINT_PRECISION) / len,
(y * FIXED_POINT_PRECISION) / len);
}
inline int distance_squared(const Vec2 &other) const
{
int dx = x - other.x;
int dy = y - other.y;
return dx * dx + dy * dy;
}
inline int distance(const Vec2 &other) const
{
return static_cast<int>(std::sqrt(static_cast<float>(distance_squared(other))));
}
inline static Vec2 zero() { return Vec2(0, 0); }
};
}

198
Engine/math/vec3.h Normal file
View File

@@ -0,0 +1,198 @@
#pragma once
#include <cmath>
#include <cstdint>
using std::sqrt;
#ifndef FIXED_POINT_PRECISION
#define FIXED_POINT_PRECISION 4096
#endif
namespace Math {
struct Vec3
{
int x, y, z;
inline Vec3(int x_ = 0, int y_ = 0, int z_ = 0) : x(x_), y(y_), z(z_) {}
inline Vec3 operator+(const Vec3 &other) const
{
return Vec3(x + other.x, y + other.y, z + other.z);
}
inline Vec3 operator-(const Vec3 &other) const
{
return Vec3(x - other.x, y - other.y, z - other.z);
}
inline Vec3 operator*(int scalar) const
{
return Vec3(x * scalar, y * scalar, z * scalar);
}
inline Vec3 operator/(int scalar) const
{
return Vec3(x / scalar, y / scalar, z / scalar);
}
inline Vec3 &operator+=(const Vec3 &other)
{
x += other.x;
y += other.y;
z += other.z;
return *this;
}
inline Vec3 &operator-=(const Vec3 &other)
{
x -= other.x;
y -= other.y;
z -= other.z;
return *this;
}
inline static int dot(const Vec3 &a, const Vec3 &b)
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}
inline static Vec3 cross(const Vec3 &a, const Vec3 &b)
{
return Vec3(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x);
}
inline float length() const
{
return std::sqrt(static_cast<float>(x * x + y * y + z * z));
}
inline Vec3 normalized() const
{
float len = length();
return len == 0.0f ? *this : Vec3(static_cast<int>(x / len), static_cast<int>(y / len), static_cast<int>(z / len));
}
inline float distance(const Vec3 &other) const
{
return std::sqrt(static_cast<float>((x - other.x) * (x - other.x) + (y - other.y) * (y - other.y) + (z - other.z) * (z - other.z)));
}
inline float angle(const Vec3 &other) const
{
float dotProduct = dot(*this, other);
float lengths = length() * other.length();
return std::acos(dotProduct / lengths);
}
inline Vec3 reflect(const Vec3 &normal) const
{
int dotProduct = dot(*this, normal);
return *this - normal * (dotProduct * 2);
}
inline Vec3 refract(const Vec3 &normal, float ior) const
{
float cosi = -dot(*this, normal);
float eta = 1.0f / ior;
float k = 1.0f - eta * eta * (1.0f - cosi * cosi);struct Vec2
{
int x, y;
inline Vec2(int x_ = 0, int y_ = 0) : x(x_), y(y_) {}
inline Vec2 operator+(const Vec2 &other) const
{
return Vec2(x + other.x, y + other.y);
}
inline Vec2 operator-(const Vec2 &other) const
{
return Vec2(x - other.x, y - other.y);
}
inline Vec2 operator*(int scalar_fixed) const
{
return Vec2((x * scalar_fixed) / FIXED_POINT_PRECISION,
(y * scalar_fixed) / FIXED_POINT_PRECISION);
}
inline Vec2 operator/(int scalar_fixed) const
{
return Vec2((x * FIXED_POINT_PRECISION) / scalar_fixed,
(y * FIXED_POINT_PRECISION) / scalar_fixed);
}
inline Vec2 &operator+=(const Vec2 &other)
{
x += other.x;
y += other.y;
return *this;
}
inline Vec2 &operator-=(const Vec2 &other)
{
x -= other.x;
y -= other.y;
return *this;
}
inline static int dot(const Vec2 &a, const Vec2 &b)
{
return a.x * b.x + a.y * b.y;
}
inline static int cross(const Vec2 &a, const Vec2 &b)
{
return a.x * b.y - a.y * b.x;
}
inline int length_squared() const
{
return x * x + y * y;
}
inline int length() const
{
return static_cast<int>(std::sqrt(static_cast<float>(length_squared())));
}
inline Vec2 normalized() const
{
int len = length();
if (len == 0) return *this;
return Vec2((x * FIXED_POINT_PRECISION) / len,
(y * FIXED_POINT_PRECISION) / len);
}
inline int distance_squared(const Vec2 &other) const
{
int dx = x - other.x;
int dy = y - other.y;
return dx * dx + dy * dy;
}
inline int distance(const Vec2 &other) const
{
return static_cast<int>(std::sqrt(static_cast<float>(distance_squared(other))));
}
inline static Vec2 zero() { return Vec2(0, 0); }
};
if (k < 0.0f)
return Vec3(0, 0, 0);
return *this * eta + normal * (eta * cosi - std::sqrt(k));
}
inline Vec3 project(const Vec3 &other) const
{
float dotProduct = dot(*this, other);
float lenSquared = other.x * other.x + other.y * other.y + other.z * other.z;
return other * (dotProduct / lenSquared);
}
};
}

View File

@@ -1,2 +1,5 @@
#include <renderer.h>
#include <mesh.h>
#include <time_manager.h>
#include <math.h>
#include <input.h>

View File

@@ -8,45 +8,16 @@
#define FIXED_POINT_PRECISION 4096
#define FIXED_POINT_SHIFT 12
Vec3::Vec3(int x_, int y_, int z_) : x(x_), y(y_), z(z_) {}
Vec2::Vec2(float x_, float y_) : x(x_), y(y_) {}
Color::Color(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
Matrix4::Matrix4()
Texture::Texture(GLuint id_, int width_, int height_) : id(id_), width(width_), height(height_) {}
Texture::~Texture()
{
for (int i = 0; i < 16; i++)
m[i] = (i % 5 == 0) ? FIXED_POINT_PRECISION : 0;
if (id != 0)
{
glDeleteTextures(1, &id);
}
Vec3 matrix_multiply(const Matrix4 &mat, const Vec3 &v)
{
int x = (mat.m[0] * v.x + mat.m[1] * v.y + mat.m[2] * v.z + mat.m[3] * FIXED_POINT_PRECISION) / FIXED_POINT_PRECISION;
int y = (mat.m[4] * v.x + mat.m[5] * v.y + mat.m[6] * v.z + mat.m[7] * FIXED_POINT_PRECISION) / FIXED_POINT_PRECISION;
int z = (mat.m[8] * v.x + mat.m[9] * v.y + mat.m[10] * v.z + mat.m[11] * FIXED_POINT_PRECISION) / FIXED_POINT_PRECISION;
return Vec3(x, y, z);
}
Matrix4 matrix_multiply(const Matrix4 &a, const Matrix4 &b)
{
Matrix4 result;
for (int row = 0; row < 4; ++row)
{
for (int col = 0; col < 4; ++col)
{
result.m[row * 4 + col] =
(a.m[row * 4 + 0] * b.m[col + 0]) +
(a.m[row * 4 + 1] * b.m[col + 4]) +
(a.m[row * 4 + 2] * b.m[col + 8]) +
(a.m[row * 4 + 3] * b.m[col + 12]);
result.m[row * 4 + col] /= 4096;
}
}
return result;
}
Color convert_color(const Color &color)
@@ -115,9 +86,9 @@ void Renderer::render(const std::vector<Triangle> &triangles)
for (const auto &tri : triangles)
{
Triangle t;
t.v0 = matrix_multiply(view_matrix, tri.v0);
t.v1 = matrix_multiply(view_matrix, tri.v1);
t.v2 = matrix_multiply(view_matrix, tri.v2);
t.v0 = Matrix4::multiply(view_matrix, tri.v0);
t.v1 = Matrix4::multiply(view_matrix, tri.v1);
t.v2 = Matrix4::multiply(view_matrix, tri.v2);
t.uv0 = tri.uv0;
t.uv1 = tri.uv1;
t.uv2 = tri.uv2;
@@ -180,33 +151,47 @@ void Renderer::render(const std::vector<Triangle> &triangles)
screen_tri.v0.y += (rand() % 3) - 1;
}
float dx1 = screen_tri.v1.x - screen_tri.v0.x;
float dy1 = screen_tri.v1.y - screen_tri.v0.y;
float dx2 = screen_tri.v2.x - screen_tri.v0.x;
float dy2 = screen_tri.v2.y - screen_tri.v0.y;
float area = dx1 * dy2 - dx2 * dy1;
if (std::abs(area) > 1e-6)
{
screen_tri.uv0 = tri.uv0;
screen_tri.uv1 = tri.uv1;
screen_tri.uv2 = tri.uv2;
}
else
{
screen_tri.uv0 = tri.uv0;
screen_tri.uv1 = tri.uv1;
screen_tri.uv2 = tri.uv2;
}
screen_tri.color = convert_color(tri.color);
screen_tri.texture = tri.texture;
screen_triangles.push_back(screen_tri);
}
// Group triangles by texture
std::map<GLuint, std::vector<Triangle>> texture_groups;
for (const auto& tri : screen_triangles)
{
texture_groups[tri.texture].push_back(tri);
texture_groups[tri.texture.id].push_back(tri);
}
glDisable(GL_BLEND);
// Render each texture group
for (const auto& group : texture_groups)
{
GLuint tex = group.first;
GLuint tex_id = group.first;
const std::vector<Triangle>& tris = group.second;
if (tex != 0)
if (tex_id != 0)
{
glBindTexture(GL_TEXTURE_2D, tex);
glBindTexture(GL_TEXTURE_2D, tex_id);
glEnable(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -222,19 +207,19 @@ void Renderer::render(const std::vector<Triangle> &triangles)
Color c = tri.color;
glColor3ub(c.r, c.g, c.b);
if (tex != 0)
if (tex_id != 0)
{
glTexCoord2f(tri.uv0.x, tri.uv0.y);
}
glVertex3f(static_cast<float>(tri.v0.x), static_cast<float>(tri.v0.y), static_cast<float>(tri.v0.z) / 32767.0f);
if (tex != 0)
if (tex_id != 0)
{
glTexCoord2f(tri.uv1.x, tri.uv1.y);
}
glVertex3f(static_cast<float>(tri.v1.x), static_cast<float>(tri.v1.y), static_cast<float>(tri.v1.z) / 32767.0f);
if (tex != 0)
if (tex_id != 0)
{
glTexCoord2f(tri.uv2.x, tri.uv2.y);
}
@@ -251,7 +236,7 @@ void Renderer::render(const std::vector<Triangle> &triangles)
glVertex3f(static_cast<float>(tri.v1.x), static_cast<float>(tri.v1.y), static_cast<float>(tri.v1.z) / 32767.0f);
glVertex3f(static_cast<float>(tri.v2.x), static_cast<float>(tri.v2.y), static_cast<float>(tri.v2.z) / 32767.0f);
glEnd();
if (tex != 0) glEnable(GL_TEXTURE_2D);
if (tex_id != 0) glEnable(GL_TEXTURE_2D);
}
}
}
@@ -268,7 +253,7 @@ bool Renderer::should_close()
return glfwWindowShouldClose(window);
}
GLuint Renderer::load_texture(const std::string &filepath)
Texture Renderer::load_texture(const std::string &filepath)
{
int width, height, channels;
unsigned char *image = stbi_load(filepath.c_str(), &width, &height, &channels, 3);
@@ -319,9 +304,9 @@ GLuint Renderer::load_texture(const std::string &filepath)
}
}
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
GLuint texture_id;
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_2D, texture_id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, new_width, new_height, 0, GL_RGB, GL_UNSIGNED_BYTE, resized);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
@@ -333,5 +318,5 @@ GLuint Renderer::load_texture(const std::string &filepath)
stbi_image_free(image);
delete[] resized;
return texture;
return Texture(texture_id, new_width, new_height);
}

View File

@@ -6,18 +6,9 @@
#include <vector>
#include <string>
#include <cstdint>
#include <math.h>
struct Vec3
{
int x, y, z;
Vec3(int x_ = 0, int y_ = 0, int z_ = 0);
};
struct Vec2
{
float x, y;
Vec2(float x_ = 0.0f, float y_ = 0.0f);
};
using namespace Math;
struct Color
{
@@ -25,10 +16,13 @@ struct Color
Color(uint8_t r_ = 255, uint8_t g_ = 255, uint8_t b_ = 255);
};
struct Matrix4
struct Texture
{
int m[16];
Matrix4();
GLuint id;
int width;
int height;
Texture(GLuint id_ = 0, int width_ = 0, int height_ = 0);
~Texture();
};
struct Triangle
@@ -36,13 +30,8 @@ struct Triangle
Vec3 v0, v1, v2;
Vec2 uv0, uv1, uv2;
Color color;
GLuint texture;
Texture texture;
};
Vec3 matrix_multiply(const Matrix4 &mat, const Vec3 &v);
Matrix4 matrix_multiply(const Matrix4 &a, const Matrix4 &b);
Color convert_color(const Color &color);
class Renderer
@@ -64,7 +53,7 @@ public:
void render(const std::vector<Triangle> &triangles);
void end_frame();
bool should_close();
GLuint load_texture(const std::string &filepath);
Texture load_texture(const std::string &filepath);
GLFWwindow *get_window() { return window; }
};

View File

@@ -0,0 +1,32 @@
#include "time_manager.h"
#include <GLFW/glfw3.h>
Time::Time()
: lastFrameTime(glfwGetTime())
, currentFrameTime(lastFrameTime)
, deltaTime(0)
, elapsedTime(0.0f) {
}
void Time::Update() {
currentFrameTime = glfwGetTime();
float deltaTimeSeconds = static_cast<float>(currentFrameTime - lastFrameTime);
// Store delta time in fixed-point and update elapsed time
deltaTime = SecondsToFixed(deltaTimeSeconds);
elapsedTime += deltaTimeSeconds;
lastFrameTime = currentFrameTime;
}
int32_t Time::GetDeltaTime() const {
return deltaTime;
}
float Time::GetElapsedTime() const {
return elapsedTime;
}
int32_t Time::SecondsToFixed(float seconds) const {
return static_cast<int32_t>(seconds * FIXED_POINT_SCALE);
}

View File

@@ -0,0 +1,32 @@
#ifndef TIME_H
#define TIME_H
#include <cstdint>
class Time {
public:
Time();
// Update time and calculate delta time (call each frame)
void Update();
// Get delta time in seconds (fixed-point representation)
int32_t GetDeltaTime() const;
// Get total elapsed time in seconds
float GetElapsedTime() const;
private:
// Fixed-point scale for PS1-style precision
static constexpr int32_t FIXED_POINT_SCALE = 4096; // 12-bit fractional part
double lastFrameTime; // Last frame timestamp (in seconds, GLFW time)
double currentFrameTime; // Current frame timestamp
int32_t deltaTime; // Delta time in fixed-point
float elapsedTime; // Total elapsed time in seconds
// Convert float seconds to fixed-point
int32_t SecondsToFixed(float seconds) const;
};
#endif // TIME_H

View File

@@ -145,66 +145,6 @@ std::vector<Triangle> create_cube(int size = 4096)
return triangles;
}
Matrix4 create_rotation_y(float angle)
{
Matrix4 mat;
int cos_val = static_cast<int>(std::cos(angle) * 4096);
int sin_val = static_cast<int>(std::sin(angle) * 4096);
mat.m[0] = cos_val;
mat.m[2] = -sin_val;
mat.m[8] = sin_val;
mat.m[10] = cos_val;
return mat;
}
Matrix4 create_rotation_x(float angle)
{
Matrix4 mat;
int cos_val = static_cast<int>(std::cos(angle) * 4096);
int sin_val = static_cast<int>(std::sin(angle) * 4096);
mat.m[5] = cos_val;
mat.m[6] = -sin_val;
mat.m[9] = sin_val;
mat.m[10] = cos_val;
return mat;
}
Matrix4 create_rotation_z(float angle)
{
Matrix4 mat;
int cos_val = static_cast<int>(std::cos(angle) * 4096);
int sin_val = static_cast<int>(std::sin(angle) * 4096);
mat.m[0] = cos_val;
mat.m[1] = -sin_val;
mat.m[4] = sin_val;
mat.m[5] = cos_val;
return mat;
}
Matrix4 create_translation(int x, int y, int z)
{
Matrix4 mat;
mat.m[3] = x;
mat.m[7] = y;
mat.m[11] = z;
return mat;
}
Matrix4 create_view_matrix(float camera_distance)
{
Matrix4 view;
view.m[11] = -camera_distance * 4096;
return view;
}
GLuint create_checkerboard_texture(int width, int height)
{
unsigned char *data = new unsigned char[width * height * 3];
@@ -239,7 +179,6 @@ int main()
{
try
{
Renderer renderer(800, 600, "Cube");
std::vector<Triangle> cube = create_cube(4096 * 4);
@@ -247,26 +186,25 @@ int main()
GLuint texture = create_checkerboard_texture(128, 128);
float rotation = 0.0f;
float camera_distance = 0.7f;
auto last_time = std::chrono::high_resolution_clock::now();
while (!renderer.should_close())
{
auto current_time = std::chrono::high_resolution_clock::now();
float delta_time = std::chrono::duration<float>(current_time - last_time).count();
last_time = current_time;
rotation += delta_time * 2.0f;
Matrix4 view = create_view_matrix(camera_distance);
Matrix4 view = Matrix4::perspective(90, 800.0f / 600.0f, 0.1f, 1000.0f);
Matrix4 rot_x = create_rotation_x(rotation * 0.7f);
Matrix4 rot_y = create_rotation_y(rotation);
Matrix4 rot_z = create_rotation_z(rotation * 1.3f);
int fixed_rotation = static_cast<int>(rotation * FIXED_POINT_PRECISION);
Matrix4 rot_x = Matrix4::rotateX(static_cast<int>(rotation * 0.7f * FIXED_POINT_PRECISION));
Matrix4 rot_y = Matrix4::rotateY(fixed_rotation);
Matrix4 rot_z = Matrix4::rotateZ(static_cast<int>(rotation * 1.3f * FIXED_POINT_PRECISION));
Matrix4 rotation_matrix = matrix_multiply(rot_z, matrix_multiply(rot_y, rot_x));
Matrix4 rotation_matrix = Matrix4::multiply(rot_z, Matrix4::multiply(rot_y, rot_x));
renderer.set_view_matrix(view);
@@ -276,9 +214,9 @@ int main()
for (const auto &tri : cube)
{
Triangle t;
t.v0 = matrix_multiply(rotation_matrix, tri.v0);
t.v1 = matrix_multiply(rotation_matrix, tri.v1);
t.v2 = matrix_multiply(rotation_matrix, tri.v2);
t.v0 = Matrix4::multiply(rotation_matrix, tri.v0);
t.v1 = Matrix4::multiply(rotation_matrix, tri.v1);
t.v2 = Matrix4::multiply(rotation_matrix, tri.v2);
t.uv0 = tri.uv0;
t.uv1 = tri.uv1;
t.uv2 = tri.uv2;