clip.cpp

C++ software renderer

src/clip.cpp

4.35 KB
#include "clip.h"

#include <math.h>

#include <vector>

#include "util_math.h"

using std::vector;

// canonical view volume
float plane_x_min(const vec4f &v) {
    return v.w + v.x;  // x >= -w
}
float plane_x_max(const vec4f &v) {
    return v.w - v.x;  // x <=  w
}
float plane_y_min(const vec4f &v) {
    return v.w + v.y;  // y >= -w
}
float plane_y_max(const vec4f &v) {
    return v.w - v.y;  // y <=  w
}
float plane_z_min(const vec4f &v) {
    return v.w + v.z;  // z >= -w
}
float plane_z_max(const vec4f &v) {
    return v.w - v.z;  // z <=  w
}

// dead simple helpers to handle basic floating point precision on pushing
inline bool almost_same(const vec4f &a, const vec4f &b, float eps = 1e-6f) {
    return fabs(a.x - b.x) < eps &&
           fabs(a.y - b.y) < eps &&
           fabs(a.z - b.z) < eps &&
           fabs(a.w - b.w) < eps;
}

inline void push_unique(vector<ClipVertex> &out, const ClipVertex &v) {
    if (out.empty() || !almost_same(out.back().pos, v.pos)) {
        out.push_back(v);
    }
}

ClipVertex lerp_vert(const ClipVertex &a, const ClipVertex &b, float t) {
    ClipVertex result;
    result.pos    = vec4f_lerp(a.pos, b.pos, t);
    result.color  = vec3f_lerp(a.color, b.color, t);
    result.uv     = vec2f_lerp(a.uv, b.uv, t);
    result.normal = vec3f_lerp(a.normal, b.normal, t);
    return result;
}

vector<ClipVertex> clip_against_plane(
    const vector<ClipVertex> &polygon_in, float (*plane_dist)(const vec4f &)
) {
    vector<ClipVertex> clipped_polygon;
    if (polygon_in.empty()) {
        return clipped_polygon;
    }

    // prev vertex and distance to compare against
    ClipVertex prev_vert   = polygon_in.back();
    float      prev_dist   = plane_dist(prev_vert.pos);
    bool       prev_inside = (prev_dist >= 0.0f);

    // loop over each vertex to compare
    for (usize i = 0; i < polygon_in.size(); ++i) {
        const ClipVertex &curr_vert   = polygon_in[i];
        float             curr_dist   = plane_dist(curr_vert.pos);
        bool              curr_inside = (curr_dist >= 0.0f);

        // case 1: both prev and curr are inside the plane
        // so the curr vertex also gets added to the new clipped polygon
        // notes/clipping/9
        if (prev_inside && curr_inside) {
            push_unique(clipped_polygon, curr_vert);
        }

        // case 2: either vertex ends up inside while the other is outside the plane
        // time to create a new vert on the intersection and add it to the new polygon
        else if (prev_inside != curr_inside) {
            float      dist_difference           = prev_dist - curr_dist;
            float      edge_interpolation_factor = prev_dist / (dist_difference);
            ClipVertex intersect_vert            = lerp_vert(prev_vert, curr_vert, edge_interpolation_factor);
            push_unique(clipped_polygon, intersect_vert);

            // case 3 (more like case 2.5): If the curr vertex is also already inside
            // safely add it to the clipped polygon
            // notes/clipping/10
            if (curr_inside) {
                push_unique(clipped_polygon, curr_vert);
            }
        }

        // update prev_vert for the next iter
        prev_vert   = curr_vert;
        prev_dist   = curr_dist;
        prev_inside = curr_inside;
    }

    return clipped_polygon;
}

// Clip space/Sutherland–Hodgman/Homogeneous clipping
// clipping any polygon to the canonical view volume: -w <= x,y,z <= w.
vector<ClipVertex> clip_polygon_with_attrs(
    vec4f *clip_positions,
    vec2f *input_uv,
    vec3f  input_color,
    vec3f  input_normal
) {
    vector<ClipVertex> clipped_vertices;
    clipped_vertices.reserve(3);

    for (int i = 0; i < 3; ++i) {
        ClipVertex cv;
        cv.pos    = clip_positions[i];
        cv.uv     = input_uv[i];
        cv.color  = input_color;
        cv.normal = input_normal;
        clipped_vertices.push_back(cv);
    }

    clipped_vertices = clip_against_plane(clipped_vertices, plane_x_min);
    clipped_vertices = clip_against_plane(clipped_vertices, plane_x_max);

    clipped_vertices = clip_against_plane(clipped_vertices, plane_y_min);
    clipped_vertices = clip_against_plane(clipped_vertices, plane_y_max);

    clipped_vertices = clip_against_plane(clipped_vertices, plane_z_min);
    clipped_vertices = clip_against_plane(clipped_vertices, plane_z_max);

    return clipped_vertices;  // could be empty if triangle fully outside
}