1#include "clip.hpp"
2
3#include <cmath>
4#include <vector>
5
6#include "math.hpp"
7
8using std::vector;
9
10// canonical view volume
11float plane_x_min(const vec4f &v) {
12 return v.w + v.x; // x >= -w
13}
14float plane_x_max(const vec4f &v) {
15 return v.w - v.x; // x <= w
16}
17float plane_y_min(const vec4f &v) {
18 return v.w + v.y; // y >= -w
19}
20float plane_y_max(const vec4f &v) {
21 return v.w - v.y; // y <= w
22}
23float plane_z_min(const vec4f &v) {
24 return v.w + v.z; // z >= -w
25}
26float plane_z_max(const vec4f &v) {
27 return v.w - v.z; // z <= w
28}
29
30// dead simple helpers to handle basic floating point precision on pushing
31inline bool almost_same(const vec4f &a, const vec4f &b, float eps = 1e-6f) {
32 return fabs(a.x - b.x) < eps &&
33 fabs(a.y - b.y) < eps &&
34 fabs(a.z - b.z) < eps &&
35 fabs(a.w - b.w) < eps;
36}
37
38inline void push_unique(vector<ClipVertex> &out, const ClipVertex &v) {
39 if (out.empty() || !almost_same(out.back().pos, v.pos)) {
40 out.push_back(v);
41 }
42}
43
44ClipVertex lerp_vert(const ClipVertex &a, const ClipVertex &b, float t) {
45 ClipVertex result;
46
47 result.pos = vec4f_lerp(a.pos, b.pos, t);
48
49 float a_w = a.pos.w;
50 float b_w = b.pos.w;
51 float result_w = result.pos.w;
52
53 result.color = vec3f_lerp(a.color, b.color, t);
54 result.uv = vec2f_lerp(a.uv, b.uv, t);
55 result.normal = vec3f_lerp(a.normal, b.normal, t);
56 return result;
57}
58
59vector<ClipVertex> clip_against_plane(
60 const vector<ClipVertex> &polygon_in, float (*plane_dist)(const vec4f &)
61) {
62 vector<ClipVertex> clipped_polygon;
63 if (polygon_in.empty()) {
64 return clipped_polygon;
65 }
66
67 // prev vertex and distance to compare against
68 ClipVertex prev_vert = polygon_in.back();
69 float prev_dist = plane_dist(prev_vert.pos);
70 bool prev_inside = (prev_dist >= 0.0f);
71
72 // loop over each vertex to compare
73 for (size_t i = 0; i < polygon_in.size(); ++i) {
74 const ClipVertex &curr_vert = polygon_in[i];
75 float curr_dist = plane_dist(curr_vert.pos);
76 bool curr_inside = (curr_dist >= 0.0f);
77
78 // case 1: both prev and curr are inside the plane
79 // so the curr vertex also gets added to the new clipped polygon
80 // notes/clipping/9
81 if (prev_inside && curr_inside) {
82 push_unique(clipped_polygon, curr_vert);
83 }
84
85 // case 2: either vertex ends up inside while the other is outside the plane
86 // time to create a new vert on the intersection and add it to the new polygon
87 else if (prev_inside != curr_inside) {
88 float dist_difference = prev_dist - curr_dist;
89 float edge_interpolation_factor = prev_dist / (dist_difference);
90 ClipVertex intersect_vert = lerp_vert(prev_vert, curr_vert, edge_interpolation_factor);
91 push_unique(clipped_polygon, intersect_vert);
92
93 // case 3 (more like case 2.5): If the curr vertex is also already inside
94 // safely add it to the clipped polygon
95 // notes/clipping/10
96 if (curr_inside) {
97 push_unique(clipped_polygon, curr_vert);
98 }
99 }
100
101 // update prev_vert for the next iter
102 prev_vert = curr_vert;
103 prev_dist = curr_dist;
104 prev_inside = curr_inside;
105 }
106
107 return clipped_polygon;
108}
109
110// Clip space/Sutherland–Hodgman/Homogeneous clipping
111// clipping any polygon to the canonical view volume: -w <= x,y,z <= w.
112vector<ClipVertex> clip_polygon_with_attrs(
113 vec4f *clip_positions,
114 vec2f *input_uv,
115 vec3f input_color,
116 vec3f input_normal
117) {
118 vector<ClipVertex> clipped_vertices;
119 clipped_vertices.reserve(3);
120
121 for (int i = 0; i < 3; ++i) {
122 ClipVertex cv;
123 cv.pos = clip_positions[i];
124 cv.uv = input_uv[i];
125 cv.color = input_color;
126 cv.normal = input_normal;
127 clipped_vertices.push_back(cv);
128 }
129
130 clipped_vertices = clip_against_plane(clipped_vertices, plane_x_min);
131 clipped_vertices = clip_against_plane(clipped_vertices, plane_x_max);
132
133 clipped_vertices = clip_against_plane(clipped_vertices, plane_y_min);
134 clipped_vertices = clip_against_plane(clipped_vertices, plane_y_max);
135
136 clipped_vertices = clip_against_plane(clipped_vertices, plane_z_min);
137 clipped_vertices = clip_against_plane(clipped_vertices, plane_z_max);
138
139 return clipped_vertices; // could be empty if triangle fully outside
140}