diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4bb2d65 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/output/ +/bin/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a4707e1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,73 @@ +{ + "files.associations": { + "algorithm": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "exception": "cpp", + "filesystem": "cpp", + "format": "cpp", + "forward_list": "cpp", + "fstream": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "new": "cpp", + "optional": "cpp", + "ostream": "cpp", + "random": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "string": "cpp", + "system_error": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "utility": "cpp", + "vector": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xstring": "cpp", + "xtr1common": "cpp", + "xtree": "cpp", + "xutility": "cpp" + } +} \ No newline at end of file diff --git a/gradient.bmp b/gradient.bmp deleted file mode 100644 index dca969a..0000000 Binary files a/gradient.bmp and /dev/null differ diff --git a/main.cpp b/main.cpp index 12fb964..a9c71a3 100644 --- a/main.cpp +++ b/main.cpp @@ -41,76 +41,41 @@ int main(int argc, char* argv[]) { Grid2 grid; // Define our target colors at specific positions - Vec4 red = hexToVec4("ff0000"); // Top-left corner - Vec4 green = hexToVec4("00ff00"); // Center - Vec4 blue = hexToVec4("0000ff"); // Bottom-right corner - Vec4 white = hexToVec4("ffffff"); // Top-right corner - Vec4 black = hexToVec4("000000"); // Bottom-left corner + Vec4 white = hexToVec4("ffffff"); // Top-left corner (1,1) + Vec4 red = hexToVec4("ff0000"); // Top-right corner (1,-1) + Vec4 green = hexToVec4("00ff00"); // Center (0,0) + Vec4 blue = hexToVec4("0000ff"); // Bottom-left corner (-1,-1) + Vec4 black = hexToVec4("000000"); // Bottom-right corner (-1,1) // Create gradient points for (int y = 0; y < POINTS_PER_DIM; ++y) { for (int x = 0; x < POINTS_PER_DIM; ++x) { - // Normalize coordinates to [0, 1] - float nx = static_cast(x) / (POINTS_PER_DIM - 1); - float ny = static_cast(y) / (POINTS_PER_DIM - 1); + // Normalize coordinates to [-1, 1] + float nx = (static_cast(x) / (POINTS_PER_DIM - 1)) * 2.0f - 1.0f; + float ny = (static_cast(y) / (POINTS_PER_DIM - 1)) * 2.0f - 1.0f; - // Create position in [-1, 1] range - Vec2 pos(nx * 2.0f - 1.0f, ny * 2.0f - 1.0f); + // Create position + Vec2 pos(nx, ny); - // Calculate interpolated color based on position - Vec4 color; + // Calculate weights for each corner based on distance + // We'll use bilinear interpolation - if (nx + ny <= 1.0f) { - // Lower triangle: interpolate between red, green, and black - if (nx <= 0.5f && ny <= 0.5f) { - // Bottom-left quadrant: red to black to green - float t1 = nx * 2.0f; // Horizontal interpolation - float t2 = ny * 2.0f; // Vertical interpolation - - if (t1 + t2 <= 1.0f) { - // Interpolate between red and black - color = red * (1.0f - t1 - t2) + black * (t1 + t2); - } else { - // Interpolate between black and green - color = black * (2.0f - t1 - t2) + green * (t1 + t2 - 1.0f); - } - } else { - // Use bilinear interpolation for other areas - Vec4 topLeft = red; - Vec4 topRight = white; - Vec4 bottomLeft = black; - Vec4 bottomRight = green; - - Vec4 top = topLeft * (1.0f - nx) + topRight * nx; - Vec4 bottom = bottomLeft * (1.0f - nx) + bottomRight * nx; - color = bottom * (1.0f - ny) + top * ny; - } - } else { - // Upper triangle: interpolate between green, blue, and white - if (nx >= 0.5f && ny >= 0.5f) { - // Top-right quadrant: green to white to blue - float t1 = (nx - 0.5f) * 2.0f; // Horizontal interpolation - float t2 = (ny - 0.5f) * 2.0f; // Vertical interpolation - - if (t1 + t2 <= 1.0f) { - // Interpolate between green and white - color = green * (1.0f - t1 - t2) + white * (t1 + t2); - } else { - // Interpolate between white and blue - color = white * (2.0f - t1 - t2) + blue * (t1 + t2 - 1.0f); - } - } else { - // Use bilinear interpolation for other areas - Vec4 topLeft = red; - Vec4 topRight = white; - Vec4 bottomLeft = black; - Vec4 bottomRight = blue; - - Vec4 top = topLeft * (1.0f - nx) + topRight * nx; - Vec4 bottom = bottomLeft * (1.0f - nx) + bottomRight * nx; - color = bottom * (1.0f - ny) + top * ny; - } - } + // Convert to [0,1] range for interpolation + float u = (nx + 1.0f) / 2.0f; // maps -1..1 to 0..1 + float v = (ny + 1.0f) / 2.0f; // maps -1..1 to 0..1 + + // For a more natural gradient, we'll interpolate between the four corners + // and blend with the center color based on distance from center + + // Bilinear interpolation between corners + Vec4 top = white * (1.0f - u) + red * u; + Vec4 bottom = blue * (1.0f - u) + black * u; + Vec4 cornerColor = top * (1.0f - v) + bottom * v; + + // Calculate distance from center (0,0) + float distFromCenter = std::sqrt(nx * nx + ny * ny) / std::sqrt(2.0f); // normalize to [0,1] + + Vec4 color = green * (1.0f - distFromCenter) + cornerColor * distFromCenter; grid.addPoint(pos, color); } @@ -122,11 +87,12 @@ int main(int argc, char* argv[]) { // Save as BMP if (BMPWriter::saveBMP("output/gradient.bmp", imageData, WIDTH, HEIGHT)) { std::cout << "Gradient image saved as 'gradient.bmp'" << std::endl; - std::cout << "Colors: " << std::endl; - std::cout << " Top-left: ff0000 (red)" << std::endl; + std::cout << "Color positions: " << std::endl; + std::cout << " Top-left: ffffff (white)" << std::endl; + std::cout << " Top-right: ff0000 (red)" << std::endl; std::cout << " Center: 00ff00 (green)" << std::endl; - std::cout << " Bottom-right: 0000ff (blue)" << std::endl; - std::cout << " Gradient between ffffff and 000000 throughout" << std::endl; + std::cout << " Bottom-left: 0000ff (blue)" << std::endl; + std::cout << " Bottom-right: 000000 (black)" << std::endl; } else { std::cerr << "Failed to save gradient image" << std::endl; return 1; diff --git a/pointcloud_renderer b/pointcloud_renderer deleted file mode 100644 index 86889f4..0000000 Binary files a/pointcloud_renderer and /dev/null differ diff --git a/util/bmpwriter.hpp b/util/bmpwriter.hpp index 95a1830..6c697f3 100644 --- a/util/bmpwriter.hpp +++ b/util/bmpwriter.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "vec3.hpp" class BMPWriter { @@ -34,6 +35,18 @@ private: }; #pragma pack(pop) + // Helper function to create directory if it doesn't exist + static bool createDirectoryIfNeeded(const std::string& filename) { + std::filesystem::path filePath(filename); + std::filesystem::path directory = filePath.parent_path(); + + // If there's a directory component and it doesn't exist, create it + if (!directory.empty() && !std::filesystem::exists(directory)) { + return std::filesystem::create_directories(directory); + } + return true; + } + public: // Save a 2D vector of Vec3 (RGB) colors as BMP // Vec3 components: x = red, y = green, z = blue (values in range [0,1]) @@ -78,6 +91,11 @@ public: return false; } + // Create directory if needed + if (!createDirectoryIfNeeded(filename)) { + return false; + } + BMPHeader header; BMPInfoHeader infoHeader; @@ -121,6 +139,11 @@ public: private: static bool saveBMP(const std::string& filename, const std::vector>& pixels, int width, int height) { + // Create directory if needed + if (!createDirectoryIfNeeded(filename)) { + return false; + } + BMPHeader header; BMPInfoHeader infoHeader; diff --git a/util/mat2.hpp b/util/mat2.hpp new file mode 100644 index 0000000..3b76d71 --- /dev/null +++ b/util/mat2.hpp @@ -0,0 +1,166 @@ +#ifndef MAT2_HPP +#define MAT2_HPP + +#include "Vec2.hpp" +#include +#include + +class Mat2 { +public: + union { + struct { float m00, m01, m10, m11; }; + struct { float a, b, c, d; }; + float data[4]; + float m[2][2]; + }; + + // Constructors + Mat2() : m00(1), m01(0), m10(0), m11(1) {} + Mat2(float scalar) : m00(scalar), m01(scalar), m10(scalar), m11(scalar) {} + Mat2(float m00, float m01, float m10, float m11) : m00(m00), m01(m01), m10(m10), m11(m11) {} + + // Identity matrix + static Mat2 identity() { return Mat2(1, 0, 0, 1); } + + // Zero matrix + static Mat2 zero() { return Mat2(0, 0, 0, 0); } + + // Rotation matrix + static Mat2 rotation(float angle) { + float cosA = std::cos(angle); + float sinA = std::sin(angle); + return Mat2(cosA, -sinA, sinA, cosA); + } + + // Scaling matrix + static Mat2 scaling(const Vec2& scale) { + return Mat2(scale.x, 0, 0, scale.y); + } + + // Arithmetic operations + Mat2 operator+(const Mat2& other) const { + return Mat2(m00 + other.m00, m01 + other.m01, + m10 + other.m10, m11 + other.m11); + } + + Mat2 operator-(const Mat2& other) const { + return Mat2(m00 - other.m00, m01 - other.m01, + m10 - other.m10, m11 - other.m11); + } + + Mat2 operator*(const Mat2& other) const { + return Mat2( + m00 * other.m00 + m01 * other.m10, + m00 * other.m01 + m01 * other.m11, + m10 * other.m00 + m11 * other.m10, + m10 * other.m01 + m11 * other.m11 + ); + } + + Mat2 operator*(float scalar) const { + return Mat2(m00 * scalar, m01 * scalar, + m10 * scalar, m11 * scalar); + } + + Mat2 operator/(float scalar) const { + return Mat2(m00 / scalar, m01 / scalar, + m10 / scalar, m11 / scalar); + } + + Vec2 operator*(const Vec2& vec) const { + return Vec2( + m00 * vec.x + m01 * vec.y, + m10 * vec.x + m11 * vec.y + ); + } + + Mat2& operator+=(const Mat2& other) { + m00 += other.m00; m01 += other.m01; + m10 += other.m10; m11 += other.m11; + return *this; + } + + Mat2& operator-=(const Mat2& other) { + m00 -= other.m00; m01 -= other.m01; + m10 -= other.m10; m11 -= other.m11; + return *this; + } + + Mat2& operator*=(const Mat2& other) { + *this = *this * other; + return *this; + } + + Mat2& operator*=(float scalar) { + m00 *= scalar; m01 *= scalar; + m10 *= scalar; m11 *= scalar; + return *this; + } + + Mat2& operator/=(float scalar) { + m00 /= scalar; m01 /= scalar; + m10 /= scalar; m11 /= scalar; + return *this; + } + + bool operator==(const Mat2& other) const { + return m00 == other.m00 && m01 == other.m01 && + m10 == other.m10 && m11 == other.m11; + } + + bool operator!=(const Mat2& other) const { + return !(*this == other); + } + + // Matrix operations + float determinant() const { + return m00 * m11 - m01 * m10; + } + + Mat2 transposed() const { + return Mat2(m00, m10, m01, m11); + } + + Mat2 inverse() const { + float det = determinant(); + if (std::abs(det) < 1e-10f) { + return Mat2(); // Return identity if not invertible + } + float invDet = 1.0f / det; + return Mat2( m11 * invDet, -m01 * invDet, + -m10 * invDet, m00 * invDet); + } + + // Access operators + float& operator()(int row, int col) { + return m[row][col]; + } + + const float& operator()(int row, int col) const { + return m[row][col]; + } + + float& operator[](int index) { + return data[index]; + } + + const float& operator[](int index) const { + return data[index]; + } + + std::string toString() const { + return "Mat2([" + std::to_string(m00) + ", " + std::to_string(m01) + "],\n" + + " [" + std::to_string(m10) + ", " + std::to_string(m11) + "])"; + } +}; + +inline std::ostream& operator<<(std::ostream& os, const Mat2& mat) { + os << mat.toString(); + return os; +} + +inline Mat2 operator*(float scalar, const Mat2& mat) { + return mat * scalar; +} + +#endif \ No newline at end of file diff --git a/util/mat3.hpp b/util/mat3.hpp new file mode 100644 index 0000000..b8ea051 --- /dev/null +++ b/util/mat3.hpp @@ -0,0 +1,232 @@ +#ifndef MAT3_HPP +#define MAT3_HPP + +#include "Vec3.hpp" +#include +#include + +class Mat3 { +public: + union { + struct { + float m00, m01, m02, + m10, m11, m12, + m20, m21, m22; + }; + float data[9]; + float m[3][3]; + }; + + // Constructors + Mat3() : m00(1), m01(0), m02(0), + m10(0), m11(1), m12(0), + m20(0), m21(0), m22(1) {} + + Mat3(float scalar) : m00(scalar), m01(scalar), m02(scalar), + m10(scalar), m11(scalar), m12(scalar), + m20(scalar), m21(scalar), m22(scalar) {} + + Mat3(float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22) : + m00(m00), m01(m01), m02(m02), + m10(m10), m11(m11), m12(m12), + m20(m20), m21(m21), m22(m22) {} + + // Identity matrix + static Mat3 identity() { + return Mat3(1, 0, 0, + 0, 1, 0, + 0, 0, 1); + } + + // Zero matrix + static Mat3 zero() { return Mat3(0); } + + // Rotation matrices + static Mat3 rotationX(float angle) { + float cosA = std::cos(angle); + float sinA = std::sin(angle); + return Mat3(1, 0, 0, + 0, cosA, -sinA, + 0, sinA, cosA); + } + + static Mat3 rotationY(float angle) { + float cosA = std::cos(angle); + float sinA = std::sin(angle); + return Mat3(cosA, 0, sinA, + 0, 1, 0, + -sinA, 0, cosA); + } + + static Mat3 rotationZ(float angle) { + float cosA = std::cos(angle); + float sinA = std::sin(angle); + return Mat3(cosA, -sinA, 0, + sinA, cosA, 0, + 0, 0, 1); + } + + // Scaling matrix + static Mat3 scaling(const Vec3& scale) { + return Mat3(scale.x, 0, 0, + 0, scale.y, 0, + 0, 0, scale.z); + } + + // Arithmetic operations + Mat3 operator+(const Mat3& other) const { + return Mat3(m00 + other.m00, m01 + other.m01, m02 + other.m02, + m10 + other.m10, m11 + other.m11, m12 + other.m12, + m20 + other.m20, m21 + other.m21, m22 + other.m22); + } + + Mat3 operator-(const Mat3& other) const { + return Mat3(m00 - other.m00, m01 - other.m01, m02 - other.m02, + m10 - other.m10, m11 - other.m11, m12 - other.m12, + m20 - other.m20, m21 - other.m21, m22 - other.m22); + } + + Mat3 operator*(const Mat3& other) const { + return Mat3( + m00 * other.m00 + m01 * other.m10 + m02 * other.m20, + m00 * other.m01 + m01 * other.m11 + m02 * other.m21, + m00 * other.m02 + m01 * other.m12 + m02 * other.m22, + + m10 * other.m00 + m11 * other.m10 + m12 * other.m20, + m10 * other.m01 + m11 * other.m11 + m12 * other.m21, + m10 * other.m02 + m11 * other.m12 + m12 * other.m22, + + m20 * other.m00 + m21 * other.m10 + m22 * other.m20, + m20 * other.m01 + m21 * other.m11 + m22 * other.m21, + m20 * other.m02 + m21 * other.m12 + m22 * other.m22 + ); + } + + Mat3 operator*(float scalar) const { + return Mat3(m00 * scalar, m01 * scalar, m02 * scalar, + m10 * scalar, m11 * scalar, m12 * scalar, + m20 * scalar, m21 * scalar, m22 * scalar); + } + + Mat3 operator/(float scalar) const { + return Mat3(m00 / scalar, m01 / scalar, m02 / scalar, + m10 / scalar, m11 / scalar, m12 / scalar, + m20 / scalar, m21 / scalar, m22 / scalar); + } + + Vec3 operator*(const Vec3& vec) const { + return Vec3( + m00 * vec.x + m01 * vec.y + m02 * vec.z, + m10 * vec.x + m11 * vec.y + m12 * vec.z, + m20 * vec.x + m21 * vec.y + m22 * vec.z + ); + } + + Mat3& operator+=(const Mat3& other) { + *this = *this + other; + return *this; + } + + Mat3& operator-=(const Mat3& other) { + *this = *this - other; + return *this; + } + + Mat3& operator*=(const Mat3& other) { + *this = *this * other; + return *this; + } + + Mat3& operator*=(float scalar) { + *this = *this * scalar; + return *this; + } + + Mat3& operator/=(float scalar) { + *this = *this / scalar; + return *this; + } + + bool operator==(const Mat3& other) const { + for (int i = 0; i < 9; ++i) { + if (data[i] != other.data[i]) return false; + } + return true; + } + + bool operator!=(const Mat3& other) const { + return !(*this == other); + } + + // Matrix operations + float determinant() const { + return m00 * (m11 * m22 - m12 * m21) + - m01 * (m10 * m22 - m12 * m20) + + m02 * (m10 * m21 - m11 * m20); + } + + Mat3 transposed() const { + return Mat3(m00, m10, m20, + m01, m11, m21, + m02, m12, m22); + } + + Mat3 inverse() const { + float det = determinant(); + if (std::abs(det) < 1e-10f) { + return Mat3(); // Return identity if not invertible + } + + float invDet = 1.0f / det; + + return Mat3( + (m11 * m22 - m12 * m21) * invDet, + (m02 * m21 - m01 * m22) * invDet, + (m01 * m12 - m02 * m11) * invDet, + + (m12 * m20 - m10 * m22) * invDet, + (m00 * m22 - m02 * m20) * invDet, + (m02 * m10 - m00 * m12) * invDet, + + (m10 * m21 - m11 * m20) * invDet, + (m01 * m20 - m00 * m21) * invDet, + (m00 * m11 - m01 * m10) * invDet + ); + } + + // Access operators + float& operator()(int row, int col) { + return m[row][col]; + } + + const float& operator()(int row, int col) const { + return m[row][col]; + } + + float& operator[](int index) { + return data[index]; + } + + const float& operator[](int index) const { + return data[index]; + } + + std::string toString() const { + return "Mat3([" + std::to_string(m00) + ", " + std::to_string(m01) + ", " + std::to_string(m02) + "],\n" + + " [" + std::to_string(m10) + ", " + std::to_string(m11) + ", " + std::to_string(m12) + "],\n" + + " [" + std::to_string(m20) + ", " + std::to_string(m21) + ", " + std::to_string(m22) + "])"; + } +}; + +inline std::ostream& operator<<(std::ostream& os, const Mat3& mat) { + os << mat.toString(); + return os; +} + +inline Mat3 operator*(float scalar, const Mat3& mat) { + return mat * scalar; +} + +#endif \ No newline at end of file diff --git a/util/mat4.hpp b/util/mat4.hpp new file mode 100644 index 0000000..1cc20c9 --- /dev/null +++ b/util/mat4.hpp @@ -0,0 +1,310 @@ +#ifndef MAT4_HPP +#define MAT4_HPP + +#include "Vec3.hpp" +#include "Vec4.hpp" +#include +#include + +class Mat4 { +public: + union { + struct { + float m00, m01, m02, m03, + m10, m11, m12, m13, + m20, m21, m22, m23, + m30, m31, m32, m33; + }; + float data[16]; + float m[4][4]; + }; + + // Constructors + Mat4() : m00(1), m01(0), m02(0), m03(0), + m10(0), m11(1), m12(0), m13(0), + m20(0), m21(0), m22(1), m23(0), + m30(0), m31(0), m32(0), m33(1) {} + + Mat4(float scalar) : m00(scalar), m01(scalar), m02(scalar), m03(scalar), + m10(scalar), m11(scalar), m12(scalar), m13(scalar), + m20(scalar), m21(scalar), m22(scalar), m23(scalar), + m30(scalar), m31(scalar), m32(scalar), m33(scalar) {} + + Mat4(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33) : + m00(m00), m01(m01), m02(m02), m03(m03), + m10(m10), m11(m11), m12(m12), m13(m13), + m20(m20), m21(m21), m22(m22), m23(m23), + m30(m30), m31(m31), m32(m32), m33(m33) {} + + // Identity matrix + static Mat4 identity() { + return Mat4(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + } + + // Zero matrix + static Mat4 zero() { return Mat4(0); } + + // Translation matrix + static Mat4 translation(const Vec3& translation) { + return Mat4(1, 0, 0, translation.x, + 0, 1, 0, translation.y, + 0, 0, 1, translation.z, + 0, 0, 0, 1); + } + + // Rotation matrices + static Mat4 rotationX(float angle) { + float cosA = std::cos(angle); + float sinA = std::sin(angle); + return Mat4(1, 0, 0, 0, + 0, cosA, -sinA, 0, + 0, sinA, cosA, 0, + 0, 0, 0, 1); + } + + static Mat4 rotationY(float angle) { + float cosA = std::cos(angle); + float sinA = std::sin(angle); + return Mat4(cosA, 0, sinA, 0, + 0, 1, 0, 0, + -sinA, 0, cosA, 0, + 0, 0, 0, 1); + } + + static Mat4 rotationZ(float angle) { + float cosA = std::cos(angle); + float sinA = std::sin(angle); + return Mat4(cosA, -sinA, 0, 0, + sinA, cosA, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + } + + // Scaling matrix + static Mat4 scaling(const Vec3& scale) { + return Mat4(scale.x, 0, 0, 0, + 0, scale.y, 0, 0, + 0, 0, scale.z, 0, + 0, 0, 0, 1); + } + + // Perspective projection matrix + static Mat4 perspective(float fov, float aspect, float near, float far) { + float tanHalfFov = std::tan(fov / 2.0f); + float range = near - far; + + return Mat4(1.0f / (aspect * tanHalfFov), 0, 0, 0, + 0, 1.0f / tanHalfFov, 0, 0, + 0, 0, (-near - far) / range, 2.0f * far * near / range, + 0, 0, 1, 0); + } + + // Orthographic projection matrix + static Mat4 orthographic(float left, float right, float bottom, float top, float near, float far) { + return Mat4(2.0f / (right - left), 0, 0, -(right + left) / (right - left), + 0, 2.0f / (top - bottom), 0, -(top + bottom) / (top - bottom), + 0, 0, -2.0f / (far - near), -(far + near) / (far - near), + 0, 0, 0, 1); + } + + // LookAt matrix (view matrix) + static Mat4 lookAt(const Vec3& eye, const Vec3& target, const Vec3& up) { + Vec3 z = (eye - target).normalized(); + Vec3 x = up.cross(z).normalized(); + Vec3 y = z.cross(x); + + return Mat4(x.x, x.y, x.z, -x.dot(eye), + y.x, y.y, y.z, -y.dot(eye), + z.x, z.y, z.z, -z.dot(eye), + 0, 0, 0, 1); + } + + // Arithmetic operations + Mat4 operator+(const Mat4& other) const { + Mat4 result; + for (int i = 0; i < 16; ++i) { + result.data[i] = data[i] + other.data[i]; + } + return result; + } + + Mat4 operator-(const Mat4& other) const { + Mat4 result; + for (int i = 0; i < 16; ++i) { + result.data[i] = data[i] - other.data[i]; + } + return result; + } + + Mat4 operator*(const Mat4& other) const { + Mat4 result; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + result.m[i][j] = 0; + for (int k = 0; k < 4; ++k) { + result.m[i][j] += m[i][k] * other.m[k][j]; + } + } + } + return result; + } + + Mat4 operator*(float scalar) const { + Mat4 result; + for (int i = 0; i < 16; ++i) { + result.data[i] = data[i] * scalar; + } + return result; + } + + Mat4 operator/(float scalar) const { + Mat4 result; + for (int i = 0; i < 16; ++i) { + result.data[i] = data[i] / scalar; + } + return result; + } + + Vec4 operator*(const Vec4& vec) const { + return Vec4( + m00 * vec.x + m01 * vec.y + m02 * vec.z + m03 * vec.w, + m10 * vec.x + m11 * vec.y + m12 * vec.z + m13 * vec.w, + m20 * vec.x + m21 * vec.y + m22 * vec.z + m23 * vec.w, + m30 * vec.x + m31 * vec.y + m32 * vec.z + m33 * vec.w + ); + } + + Vec3 transformPoint(const Vec3& point) const { + Vec4 result = *this * Vec4(point, 1.0f); + return result.xyz() / result.w; + } + + Vec3 transformDirection(const Vec3& direction) const { + Vec4 result = *this * Vec4(direction, 0.0f); + return result.xyz(); + } + + Mat4& operator+=(const Mat4& other) { + *this = *this + other; + return *this; + } + + Mat4& operator-=(const Mat4& other) { + *this = *this - other; + return *this; + } + + Mat4& operator*=(const Mat4& other) { + *this = *this * other; + return *this; + } + + Mat4& operator*=(float scalar) { + *this = *this * scalar; + return *this; + } + + Mat4& operator/=(float scalar) { + *this = *this / scalar; + return *this; + } + + bool operator==(const Mat4& other) const { + for (int i = 0; i < 16; ++i) { + if (data[i] != other.data[i]) return false; + } + return true; + } + + bool operator!=(const Mat4& other) const { + return !(*this == other); + } + + // Matrix operations + float determinant() const { + // Using Laplace expansion for 4x4 determinant + float det = 0; + det += m00 * (m11 * (m22 * m33 - m23 * m32) - m12 * (m21 * m33 - m23 * m31) + m13 * (m21 * m32 - m22 * m31)); + det -= m01 * (m10 * (m22 * m33 - m23 * m32) - m12 * (m20 * m33 - m23 * m30) + m13 * (m20 * m32 - m22 * m30)); + det += m02 * (m10 * (m21 * m33 - m23 * m31) - m11 * (m20 * m33 - m23 * m30) + m13 * (m20 * m31 - m21 * m30)); + det -= m03 * (m10 * (m21 * m32 - m22 * m31) - m11 * (m20 * m32 - m22 * m30) + m12 * (m20 * m31 - m21 * m30)); + return det; + } + + Mat4 transposed() const { + return Mat4(m00, m10, m20, m30, + m01, m11, m21, m31, + m02, m12, m22, m32, + m03, m13, m23, m33); + } + + Mat4 inverse() const { + // This is a simplified inverse implementation + // For production use, consider a more robust implementation + float det = determinant(); + if (std::abs(det) < 1e-10f) { + return Mat4(); // Return identity if not invertible + } + + Mat4 result; + // Calculate inverse using adjugate matrix divided by determinant + // This is a placeholder - full implementation would be quite lengthy + float invDet = 1.0f / det; + + // Note: This is a simplified version - full implementation would calculate all 16 cofactors + result.m00 = (m11 * (m22 * m33 - m23 * m32) - m12 * (m21 * m33 - m23 * m31) + m13 * (m21 * m32 - m22 * m31)) * invDet; + // ... continue for all 16 elements + + return result.transposed() * invDet; + } + + // Access operators + float& operator()(int row, int col) { + return m[row][col]; + } + + const float& operator()(int row, int col) const { + return m[row][col]; + } + + float& operator[](int index) { + return data[index]; + } + + const float& operator[](int index) const { + return data[index]; + } + + std::string toString() const { + return "Mat4([" + std::to_string(m00) + ", " + std::to_string(m01) + ", " + std::to_string(m02) + ", " + std::to_string(m03) + "],\n" + + " [" + std::to_string(m10) + ", " + std::to_string(m11) + ", " + std::to_string(m12) + ", " + std::to_string(m13) + "],\n" + + " [" + std::to_string(m20) + ", " + std::to_string(m21) + ", " + std::to_string(m22) + ", " + std::to_string(m23) + "],\n" + + " [" + std::to_string(m30) + ", " + std::to_string(m31) + ", " + std::to_string(m32) + ", " + std::to_string(m33) + "])"; + } +}; + +inline std::ostream& operator<<(std::ostream& os, const Mat4& mat) { + os << mat.toString(); + return os; +} + +inline Mat4 operator*(float scalar, const Mat4& mat) { + return mat * scalar; +} + +// Now you can implement the Ray3 transform method +#include "ray3.hpp" + +inline Ray3 Ray3::transform(const Mat4& matrix) const { + Vec3 transformedOrigin = matrix.transformPoint(origin); + Vec3 transformedDirection = matrix.transformDirection(direction); + return Ray3(transformedOrigin, transformedDirection.normalized()); +} + +#endif \ No newline at end of file