diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index ee967a9..889d665 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -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", diff --git a/CMakeLists.txt b/CMakeLists.txt index dd87b53..98b2062 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) diff --git a/Engine/input/input.cpp b/Engine/input/input.cpp new file mode 100644 index 0000000..20188bc --- /dev/null +++ b/Engine/input/input.cpp @@ -0,0 +1,157 @@ +#include "input.h" +#include + +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(glfwGetWindowUserPointer(w)); + manager->scroll_delta.x += static_cast(xoffset); + manager->scroll_delta.y += static_cast(yoffset); + }); + double x, y; + glfwGetCursorPos(window, &x, &y); + last_mouse_position = Vec2(static_cast(x), static_cast(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(x), static_cast(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); + } +} \ No newline at end of file diff --git a/Engine/input/input.h b/Engine/input/input.h new file mode 100644 index 0000000..673573d --- /dev/null +++ b/Engine/input/input.h @@ -0,0 +1,97 @@ +#include +#include +#include +#include + +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 previous_key_states; + std::array 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(); +}; \ No newline at end of file diff --git a/Engine/loader/mesh.cpp b/Engine/loader/mesh.cpp deleted file mode 100644 index e686f22..0000000 --- a/Engine/loader/mesh.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "mesh.h" -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -std::map load_mtl_materials(const std::string& mtl_path) -{ - std::map 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(r * 255), - static_cast(g * 255), - static_cast(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 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 positions; - std::vector texcoords; - std::map materials; - std::map texture_map; - std::vector 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 tokens; - std::string token; - while (ss >> token) tokens.push_back(token); - - std::vector 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; -} \ No newline at end of file diff --git a/Engine/loader/mesh.h b/Engine/loader/mesh.h deleted file mode 100644 index 3d7008a..0000000 --- a/Engine/loader/mesh.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include -#include -#include "renderer.h" // includes Vec3, Vec2, Triangle, Color - -struct Material -{ - Color color; - std::string texture_path; -}; - -std::map load_mtl_materials(const std::string& mtl_path); -std::vector load_obj(const std::string& obj_path, Renderer* renderer); \ No newline at end of file diff --git a/Engine/loader/obj_loader.cpp b/Engine/loader/obj_loader.cpp new file mode 100644 index 0000000..48ebc37 --- /dev/null +++ b/Engine/loader/obj_loader.cpp @@ -0,0 +1,134 @@ +#include "obj_loader.h" +#include +#include +#include +#include +#include + +std::vector ObjLoader::load_obj(const std::string& obj_filepath, + Renderer& renderer) +{ + std::vector triangles; + std::vector vertices; + std::vector uvs; + std::vector normals; + std::map 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> 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; +} \ No newline at end of file diff --git a/Engine/loader/obj_loader.h b/Engine/loader/obj_loader.h new file mode 100644 index 0000000..c520f90 --- /dev/null +++ b/Engine/loader/obj_loader.h @@ -0,0 +1,15 @@ +#ifndef OBJ_LOADER_H +#define OBJ_LOADER_H + +#include "renderer.h" +#include +#include + +class ObjLoader +{ +public: + static std::vector load_obj(const std::string& obj_filepath, + Renderer& renderer); +}; + +#endif \ No newline at end of file diff --git a/Engine/math/math.h b/Engine/math/math.h new file mode 100644 index 0000000..267194f --- /dev/null +++ b/Engine/math/math.h @@ -0,0 +1,3 @@ +#include +#include +#include \ No newline at end of file diff --git a/Engine/math/matrix4.h b/Engine/math/matrix4.h new file mode 100644 index 0000000..a8d245e --- /dev/null +++ b/Engine/math/matrix4.h @@ -0,0 +1,147 @@ +#pragma once + +#include "vec3.h" +#include + +#ifndef FIXED_POINT_PRECISION +#define FIXED_POINT_PRECISION 4096 +#endif + +inline int fixed_sin(int angle) +{ + return std::sin(angle / static_cast(FIXED_POINT_PRECISION)) * FIXED_POINT_PRECISION; +} + +inline int fixed_cos(int angle) +{ + return std::cos(angle / static_cast(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(FIXED_POINT_PRECISION); + int cos_val = static_cast(std::cos(rad) * FIXED_POINT_PRECISION); + int sin_val = static_cast(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(FIXED_POINT_PRECISION); + int cos_val = static_cast(std::cos(rad) * FIXED_POINT_PRECISION); + int sin_val = static_cast(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(FIXED_POINT_PRECISION); + int cos_val = static_cast(std::cos(rad) * FIXED_POINT_PRECISION); + int sin_val = static_cast(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((1.0f / (aspect * tanHalfFov)) * FIXED_POINT_PRECISION); + result.m[5] = static_cast((1.0f / tanHalfFov) * FIXED_POINT_PRECISION); + result.m[10] = static_cast(((far + near) / (near - far)) * FIXED_POINT_PRECISION); + result.m[11] = static_cast(((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((2.0f / (right - left)) * FIXED_POINT_PRECISION); + result.m[5] = static_cast((2.0f / (top - bottom)) * FIXED_POINT_PRECISION); + result.m[10] = static_cast((-2.0f / (far - near)) * FIXED_POINT_PRECISION); + result.m[12] = static_cast((-(right + left) / (right - left)) * FIXED_POINT_PRECISION); + result.m[13] = static_cast((-(top + bottom) / (top - bottom)) * FIXED_POINT_PRECISION); + result.m[14] = static_cast((-(far + near) / (far - near)) * FIXED_POINT_PRECISION); + result.m[15] = FIXED_POINT_PRECISION; + return result; + } +}; + +} \ No newline at end of file diff --git a/Engine/math/vec2.h b/Engine/math/vec2.h new file mode 100644 index 0000000..c21635e --- /dev/null +++ b/Engine/math/vec2.h @@ -0,0 +1,96 @@ +#pragma once + +#include +#include + +#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(std::sqrt(static_cast(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(std::sqrt(static_cast(distance_squared(other)))); + } + + inline static Vec2 zero() { return Vec2(0, 0); } +}; + +} \ No newline at end of file diff --git a/Engine/math/vec3.h b/Engine/math/vec3.h new file mode 100644 index 0000000..de6db2a --- /dev/null +++ b/Engine/math/vec3.h @@ -0,0 +1,198 @@ +#pragma once + +#include +#include +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(x * x + y * y + z * z)); + } + + inline Vec3 normalized() const + { + float len = length(); + return len == 0.0f ? *this : Vec3(static_cast(x / len), static_cast(y / len), static_cast(z / len)); + } + + inline float distance(const Vec3 &other) const + { + return std::sqrt(static_cast((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(std::sqrt(static_cast(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(std::sqrt(static_cast(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); + } +}; + +} \ No newline at end of file diff --git a/Engine/ps1engine.h b/Engine/ps1engine.h index 58e2f1b..56888a4 100644 --- a/Engine/ps1engine.h +++ b/Engine/ps1engine.h @@ -1,2 +1,5 @@ #include -#include \ No newline at end of file +#include +#include +#include +#include \ No newline at end of file diff --git a/Engine/renderer/renderer.cpp b/Engine/renderer/renderer.cpp index 3719ff1..d345ced 100644 --- a/Engine/renderer/renderer.cpp +++ b/Engine/renderer/renderer.cpp @@ -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() -{ - for (int i = 0; i < 16; i++) - m[i] = (i % 5 == 0) ? FIXED_POINT_PRECISION : 0; -} +Texture::Texture(GLuint id_, int width_, int height_) : id(id_), width(width_), height(height_) {} -Vec3 matrix_multiply(const Matrix4 &mat, const Vec3 &v) +Texture::~Texture() { - 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) + if (id != 0) { - 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; - } + glDeleteTextures(1, &id); } - - return result; } Color convert_color(const Color &color) @@ -115,9 +86,9 @@ void Renderer::render(const std::vector &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 &triangles) screen_tri.v0.y += (rand() % 3) - 1; } - screen_tri.uv0 = tri.uv0; - screen_tri.uv1 = tri.uv1; - screen_tri.uv2 = tri.uv2; + 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> 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& 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 &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(tri.v0.x), static_cast(tri.v0.y), static_cast(tri.v0.z) / 32767.0f); - if (tex != 0) + if (tex_id != 0) { glTexCoord2f(tri.uv1.x, tri.uv1.y); } glVertex3f(static_cast(tri.v1.x), static_cast(tri.v1.y), static_cast(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 &triangles) glVertex3f(static_cast(tri.v1.x), static_cast(tri.v1.y), static_cast(tri.v1.z) / 32767.0f); glVertex3f(static_cast(tri.v2.x), static_cast(tri.v2.y), static_cast(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); } \ No newline at end of file diff --git a/Engine/renderer/renderer.h b/Engine/renderer/renderer.h index bb25284..c66a616 100644 --- a/Engine/renderer/renderer.h +++ b/Engine/renderer/renderer.h @@ -6,18 +6,9 @@ #include #include #include +#include -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 &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; } }; diff --git a/Engine/time/time_manager.cpp b/Engine/time/time_manager.cpp new file mode 100644 index 0000000..d347e2d --- /dev/null +++ b/Engine/time/time_manager.cpp @@ -0,0 +1,32 @@ +#include "time_manager.h" +#include + +Time::Time() + : lastFrameTime(glfwGetTime()) + , currentFrameTime(lastFrameTime) + , deltaTime(0) + , elapsedTime(0.0f) { +} + +void Time::Update() { + currentFrameTime = glfwGetTime(); + float deltaTimeSeconds = static_cast(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(seconds * FIXED_POINT_SCALE); +} \ No newline at end of file diff --git a/Engine/time/time_manager.h b/Engine/time/time_manager.h new file mode 100644 index 0000000..0729993 --- /dev/null +++ b/Engine/time/time_manager.h @@ -0,0 +1,32 @@ +#ifndef TIME_H +#define TIME_H + +#include + +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 \ No newline at end of file diff --git a/examples/cube.cpp b/examples/cube.cpp index 97405a4..99833ea 100644 --- a/examples/cube.cpp +++ b/examples/cube.cpp @@ -145,66 +145,6 @@ std::vector create_cube(int size = 4096) return triangles; } -Matrix4 create_rotation_y(float angle) -{ - Matrix4 mat; - int cos_val = static_cast(std::cos(angle) * 4096); - int sin_val = static_cast(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(std::cos(angle) * 4096); - int sin_val = static_cast(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(std::cos(angle) * 4096); - int sin_val = static_cast(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 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(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(rotation * FIXED_POINT_PRECISION); + Matrix4 rot_x = Matrix4::rotateX(static_cast(rotation * 0.7f * FIXED_POINT_PRECISION)); + Matrix4 rot_y = Matrix4::rotateY(fixed_rotation); + Matrix4 rot_z = Matrix4::rotateZ(static_cast(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;