#include "renderer.hpp" #include #include #include #include #include "math/vector.hpp" #include "o3d/tri.hpp" #include "o3d/tri_deriv.hpp" #include "o3d/polygon.hpp" #include "fb/chfb.hpp" #include "fb/pixfb.hpp" using namespace engine; using engine::math::Vector2, engine::math::Vector3, engine::math::Vector4, engine::o3d::Triangle, engine::o3d::TriangleDerived, engine::o3d::VertexData, engine::o3d::DerivedVertex, engine::fb::CharacterFrameBuffer, engine::fb::PixelFrameBuffer; template Renderer::Renderer(FrameBuffer&& fb) : fb { std::forward(fb) } { depth_buf.resize(this->fb.width() * this->fb.height()); } template void Renderer::resize(unsigned int w, unsigned int h) { fb.resize(w, h); depth_buf.resize(fb.width() * fb.height()); } template void Renderer::clear() { std::fill(depth_buf.begin(), depth_buf.end(), 1.f); fb.clear(); } enum class TriangleSide { top, bottom }; template void Renderer::draw_triangle(const Triangle& triangle) { const auto& polygon = engine::o3d::polygon::div_by_w(engine::o3d::polygon::from_triangle_derived(triangle.to_derived()).clip_z(0.f, 1.f)); if (engine::o3d::polygon::signed_area_xy(polygon) >= 0) return; const auto& [final_triangles_count, final_triangles] = polygon.clip_xy(-1.f, -1.f, 1.f, 1.f) .map_xy({ -1.f, -1.f }, { 1.f, 1.f }, { -.5f, -.5f }, { static_cast(fb.width()) - .5f, static_cast(fb.height()) - .5f }) .to_triangles(); for (std::size_t i = 0; i < final_triangles_count; i++) _draw_cropped_triangle(triangle, final_triangles[i]); } // TODO: the renaming of w to z or the inverse is very confusing. The reason why it happens is // because we use Vector3 to store x, y and w, which therefore gets renamed to w. We should find // another way of doing this template void Renderer::_draw_cropped_triangle(const Triangle& root, const TriangleDerived& triangle) { std::array*, 3> sorted_vs = { &triangle.derived_vertex1, &triangle.derived_vertex2, &triangle.derived_vertex3 }; { const auto swap_if_gt = [&](auto x, auto y) { if (sorted_vs[x]->vertex.y > sorted_vs[y]->vertex.y) { auto temp = sorted_vs[x]; sorted_vs[x] = sorted_vs[y]; sorted_vs[y] = temp; } }; swap_if_gt(0, 1); swap_if_gt(1, 2); swap_if_gt(0, 1); } auto middle_vl = *sorted_vs[1]; const float fac = (sorted_vs[1]->vertex.y - sorted_vs[0]->vertex.y) / (sorted_vs[2]->vertex.y - sorted_vs[0]->vertex.y); const float middle_vr_vertex_w = 1.f / (1.f / sorted_vs[0]->vertex.z + fac * (1.f / sorted_vs[2]->vertex.z - 1.f / sorted_vs[0]->vertex.z)); const float fac_b0 = middle_vr_vertex_w * (1.f - fac) / sorted_vs[0]->vertex.z; DerivedVertex middle_vr { { sorted_vs[0]->vertex.x + fac * (sorted_vs[2]->vertex.x - sorted_vs[0]->vertex.x), sorted_vs[1]->vertex.y, middle_vr_vertex_w }, fac_b0 * sorted_vs[0]->b0 + (1.f - fac_b0) * sorted_vs[2]->b0, fac_b0 * sorted_vs[0]->b1 + (1.f - fac_b0) * sorted_vs[2]->b1 }; if (middle_vr.vertex.x < middle_vl.vertex.x) { auto temp = middle_vr; middle_vr = middle_vl; middle_vl = temp; } const auto _draw_cropped_triangle_side = [&]() { const int vertex_end_index = ([&]() { if constexpr (side == TriangleSide::top) return 0; else return 2; })(); const DerivedVertex& vertex_end = *sorted_vs[vertex_end_index]; if (vertex_end.vertex.y == sorted_vs[1]->vertex.y) return; int top_y; int bottom_y; if constexpr (side == TriangleSide::top) { top_y = static_cast(std::floor(vertex_end.vertex.y + 1.f)); bottom_y = static_cast(std::ceil(sorted_vs[1]->vertex.y - 1.f)); } else { bottom_y = static_cast(std::floor(vertex_end.vertex.y)); top_y = static_cast(std::ceil(sorted_vs[1]->vertex.y)); } for (int y = top_y; y <= bottom_y; y++) { float s = (static_cast(y) - vertex_end.vertex.y) / (sorted_vs[1]->vertex.y - vertex_end.vertex.y); float projected_relative_b0 = 1.f - s; float xl = vertex_end.vertex.x + s * (middle_vl.vertex.x - vertex_end.vertex.x); float xr = vertex_end.vertex.x + s * (middle_vr.vertex.x - vertex_end.vertex.x); int left_x = static_cast(std::ceil(xl)); int right_x = static_cast(std::ceil(xr - 1.f)); for (int x = left_x; x <= right_x; x++) { float t = (static_cast(x) - xl) / (xr - xl); float projected_relative_b1 = s * (1.f - t); float point_w = 1.f / ( projected_relative_b0 / vertex_end.vertex.z + projected_relative_b1 / middle_vl.vertex.z + (1.f - projected_relative_b0 - projected_relative_b1) / middle_vr.vertex.z ); float relative_b0 = point_w * projected_relative_b0 / vertex_end.vertex.z; float relative_b1 = point_w * projected_relative_b1 / middle_vl.vertex.z; float b0 = relative_b0 * vertex_end.b0 + relative_b1 * middle_vl.b0 + (1.f - relative_b0 - relative_b1) * middle_vr.b0; float b1 = relative_b0 * vertex_end.b1 + relative_b1 * middle_vl.b1 + (1.f - relative_b0 - relative_b1) * middle_vr.b1; auto loc = Vector3::bilerp(root.vertex1.vertex.xyz(), root.vertex2.vertex.xyz(), root.vertex3.vertex.xyz(), b0, b1); loc.z /= point_w; if (loc.z >= depth_buf[x + y * fb.width()]) continue; depth_buf[x + y * fb.width()] = loc.z; fb.draw_point(x, y, loc, Vector3 ::bilerp(root.vertex1.normal, root.vertex2.normal, root.vertex3.normal, b0, b1), Vector2 ::bilerp(root.vertex1.uv, root.vertex2.uv, root.vertex3.uv, b0, b1), VertexData::bilerp(root.vertex1.data, root.vertex2.data, root.vertex3.data, b0, b1)); } } }; _draw_cropped_triangle_side.template operator()(); _draw_cropped_triangle_side.template operator()(); } template class Renderer; template class Renderer; 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
#include "obj_parser.hpp"

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cstddef>
#include <array>
#include "math/vector.hpp"
#include "o3d/mesh.hpp"
#include "o3d/vertex_data.hpp"

namespace engine {

namespace {

std::vector<std::string> split(const std::string& s, char sep) {
    std::vector<std::string> res;
    std::string::size_type last_ind = 0;
    for (std::string::size_type ind = 0; ind < s.length(); ind++) {
        if (s[ind] == sep) {
            res.push_back(s.substr(last_ind, ind - last_ind));
            last_ind = ind + 1;
        }
    }
    res.push_back(s.substr(last_ind));
    return res;
}

float parse_float(const std::string& s) {
    std::string::size_type ind = 0;
    bool positive = true;
    while (ind < s.length() && (s[ind] == '+' || s[ind] == '-'))
        if (s[ind++] == '-')
            positive = !positive;

    // TODO: improve error checking
    if (ind == s.length())
        return 0.f;

    int n = 0;
    while (ind < s.length() && s[ind] >= '0' && s[ind] <= '9')
        n = n * 10 + static_cast<int>(s[ind++]) - static_cast<int>('0');

    if (ind == s.length())
        return (positive ? 1.f : -1.f) * static_cast<float>(n);

    // TODO: improve error checking
    if (s[ind] != '.')
        return 0.f;

    ind++;

    float decimal_part = 0.f;
    float decimal_fac = .1f;
    while (ind < s.length() && s[ind] >= '0' && s[ind] <= '9') {
        decimal_part += decimal_fac * static_cast<float>(static_cast<int>(s[ind++]) - static_cast<int>('0'));
        decimal_fac *= .1f;
    }

    // TODO: improve error checking
    if (ind != s.length())
        return 0.f;

    return (positive ? 1.f : -1.f) * (static_cast<float>(n) + decimal_part);
}

float parse_int(const std::string& s) {
    std::string::size_type ind = 0;
    bool positive = true;
    while (ind < s.length() && (s[ind] == '+' || s[ind] == '-'))
        if (s[ind++] == '-')
            positive = !positive;

    // TODO: improve error checking
    if (ind == s.length())
        return 0.f;

    int n = 0;
    while (ind < s.length() && s[ind] >= '0' && s[ind] <= '9')
        n = n * 10 + static_cast<int>(s[ind++]) - static_cast<int>('0');

    // TODO: improve error checking
    if (ind != s.length())
        return 0.f;

    return (positive ? 1.f : -1.f) * n;
}

}

o3d::Mesh parse_object(const std::string& obj_path) {
    o3d::Mesh mesh;
    std::ifstream obj_file(obj_path);
    if (!obj_file.is_open()) {
        std::cerr << "file `" << obj_path << "'not found" << std::endl; // TODO: improve
        std::exit(1);
    }
    std::string line;
    while (std::getline(obj_file, line)) {
        if (line.length() == 0 || line[0] == '#')
            continue;
        if (line.rfind("o ", 0) == 0) {
            // std::cout << "Object: " << line.substr(2) << std::endl;
        } else if (line.rfind("v ", 0) == 0) {
            auto s_coords = split(line.substr(2), ' ');
            math::Vector4 v{parse_float(s_coords[0]), parse_float(s_coords[1]), parse_float(s_coords[2]), 1.f};
            // std::cout << "Vertex x: " << v.x << " y: " << v.y << " z: " << v.z << std::endl;
            mesh.vertices.push_back(v);
        } else if (line.rfind("vn ", 0) == 0) {
            auto s_coords = split(line.substr(3), ' ');
            math::Vector3 vn{parse_float(s_coords[0]), parse_float(s_coords[1]), parse_float(s_coords[2])};
            // std::cout << "Vertex normal x: " << vn.x << " y: " << vn.y << " z: " << vn.z << std::endl;
            mesh.normals.push_back(vn);
        } else if (line.rfind("s ", 0) == 0) {
            // auto smooth = false;
            // auto s_smooth = line.substr(2);
            // if (s_smooth == "0" || s_smooth == "off") {
            //     smooth = false;
            // } else if (s_smooth == "1" || s_smooth == "on") {
            //     smooth = true;
            // }
            // std::cout << "Smooth: " << std::boolalpha << smooth << std::endl;
        } else if (line.rfind("f ", 0) == 0) {
            std::array<std::array<std::size_t, 2>, 3> indices;
            auto line_split = split(line.substr(2), ' ');
            for (int i = 0; i < 3; i++) {
                auto indices_s_group = split(line_split[i], '/');
                indices[i][0] = parse_int(indices_s_group[0]) - 1;
                indices[i][1] = parse_int(indices_s_group[2]) - 1;
            }
            // std::cout << "Face:"
            //     << " 1: vertex: " << indices[0][0] << " normal: " << indices[0][1]
            //     << " 2: vertex: " << indices[1][0] << " normal: " << indices[1][1]
            //     << " 3: vertex: " << indices[2][0] << " normal: " << indices[2][1]
            //     << std::endl;
            mesh.indices.push_back(indices);
        }
    }
    return mesh;
}

}