#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
}