summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGL/Clipper.cpp
blob: 5fc7cb298c117eac68f775d8f260fc4cf338be79 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/*
 * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/Format.h>
#include <AK/ScopeGuard.h>
#include <LibGL/Clipper.h>

bool GL::Clipper::point_within_clip_plane(const FloatVector4& vertex, ClipPlane plane)
{
    switch (plane) {
    case ClipPlane::LEFT:
        return vertex.x() > -vertex.w();
    case ClipPlane::RIGHT:
        return vertex.x() < vertex.w();
    case ClipPlane::TOP:
        return vertex.y() < vertex.w();
    case ClipPlane::BOTTOM:
        return vertex.y() > -vertex.w();
    case ClipPlane::NEAR:
        return vertex.z() > -vertex.w();
    case ClipPlane::FAR:
        return vertex.z() < vertex.w();
    }

    return false;
}

// TODO: This needs to interpolate color/UV data as well!
FloatVector4 GL::Clipper::clip_intersection_point(const FloatVector4& vec, const FloatVector4& prev_vec, ClipPlane plane_index)
{

    //
    // This is an implementation of the Cyrus-Beck algorithm to clip lines against a plane
    // using the triangle's line segment in parametric form.
    // In this case, we find that n . [P(t) - plane] == 0 if the point lies on
    // the boundary, which in this case is the clip plane. We then substitute
    // P(t)= P1 + (P2-P1)*t (where P2 is a point inside the clipping boundary, and P1 is,
    // in this case, the point that lies outside the boundary due to our implementation of Sutherland-Hogdman)
    // into P(t) to arrive at the equation:
    //
    //  n 路 [P1 + ((P2 - P1) * t) - plane] = 0.
    //  Substitude seg_length = P2 - P1 (length of line segment) and dist = P1 - plane (distance from P1 to plane)
    //
    //  By rearranging, we now end up with
    //
    //  n路[P1 + (seg_length * t) - plane] = 0
    //  n路(dist) + n路(seg_length * t) = 0
    //  n路(seg_length*t) = -n路(dist)
    //  Therefore
    //  t = (-n路(dist))/(n路seg_length)
    //
    // NOTE: n is the normal vector to the plane we are clipping against.
    //
    // Proof of algorithm found here
    // http://studymaterial.unipune.ac.in:8080/jspui/bitstream/123456789/4843/1/Cyrus_Beck_Algo.pdf
    // And here (slightly more intuitive with a better diagram)
    // https://www.csee.umbc.edu/~rheingan/435/pages/res/gen-5.Clipping-single-page-0.html

    FloatVector4 seg_length = vec - prev_vec;                // P2 - P1
    FloatVector4 dist = prev_vec - clip_planes[plane_index]; // Distance from "out of bounds" vector to plane

    float plane_normal_line_segment_dot_product = clip_plane_normals[plane_index].dot(seg_length);
    float neg_plane_normal_dist_dot_procut = -clip_plane_normals[plane_index].dot(dist);
    float t = (plane_normal_line_segment_dot_product / neg_plane_normal_dist_dot_procut);

    // P(t) = P1 + (t * (P2 - P1))
    FloatVector4 lerped_vec = prev_vec + (seg_length * t);

    return lerped_vec;
}

// FIXME: Getting too close to zNear causes VERY serious artifacting. Should we cull the whole triangle??
void GL::Clipper::clip_triangle_against_frustum(Vector<FloatVector4>& input_verts)
{
    Vector<FloatVector4, 6> clipped_polygon;
    Vector<FloatVector4, 6> input = input_verts;
    Vector<FloatVector4, 6>* current = &clipped_polygon;
    Vector<FloatVector4, 6>* output_list = &input;
    ScopeGuard guard { [&] { input_verts = *output_list; } };

    for (u8 plane = 0; plane < NUMBER_OF_CLIPPING_PLANES; plane++) {
        swap(current, output_list);
        clipped_polygon.clear();

        if (current->size() == 0) {
            return;
        }

        // Save me, C++23
        for (size_t i = 0; i < current->size(); i++) {
            const auto& curr_vec = current->at(i);
            const auto& prev_vec = current->at((i - 1) % current->size());

            if (point_within_clip_plane(curr_vec, static_cast<ClipPlane>(plane))) {
                if (!point_within_clip_plane(prev_vec, static_cast<ClipPlane>(plane))) {
                    FloatVector4 intersect = clip_intersection_point(prev_vec, curr_vec, static_cast<ClipPlane>(plane));
                    clipped_polygon.append(intersect);
                }

                clipped_polygon.append(curr_vec);
            } else if (point_within_clip_plane(prev_vec, static_cast<ClipPlane>(plane))) {
                FloatVector4 intersect = clip_intersection_point(prev_vec, curr_vec, static_cast<ClipPlane>(plane));
                clipped_polygon.append(intersect);
            }
        }
    }
}