/* * InfiniPaint * Copyright (C) 2025-2026 Yousef Khadadeh * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include #include #include #include #include #include using namespace Eigen; template T color_mul_alpha(const T& c, float a) { return T{c[0], c[1], c[2], c[3] * a}; } template T circular_mod(T a, T b) { if(a >= 0) return a % b; else return b - (std::abs(a) % b); } template T circular_fmod(T a, T b) { if(a >= 0) return std::fmod(a, b); else return b - std::fmod(a, b); } template T round_to_multiple(T a, MultipleType multiple) { T toRet = round(a / multiple) * multiple; if((toRet == +0.0 || toRet == -0.0) && std::signbit(toRet)) toRet = 0.0; return toRet; } template T round_vec_to_multiple(T a, MultipleType multiple) { for(size_t i = 0; i < static_cast(a.size()); i++) a[i] = round_to_multiple(a[i], multiple); return a; } template Matrix perpendicular_vec2(const Matrix& vec) { return Matrix{-vec.y(), vec.x()}; } template T dist_point_line_segment(const Matrix& point, const Matrix& lineP1, const Matrix& lineP2) { T lenSqrd = (lineP1 - lineP2).squaredNorm(); if(lenSqrd == 0) return (point - lineP1).norm(); T t = std::max(0, std::min(1, (point - lineP1).dot(lineP2 - lineP1) / lenSqrd)); return (point - (lineP1 + t * (lineP2 - lineP1))).norm(); } template bool collision_circle_line_segment(const Matrix& circleCenter, T circleRadius, const Matrix& lineP1, const Matrix& lineP2) { return dist_point_line_segment(circleCenter, lineP1, lineP2) <= circleRadius; } // https://stackoverflow.com/a/53896859 template bool is_collision_ray_line_segment(const Vector& rayOrigin, const Vector& rayDirection, const Vector& point1, const Vector& point2) { Vector v1 = rayOrigin - point1; Vector v2 = point2 - point1; Vector v3 = Vector{-rayDirection.y(), rayDirection.x()}; Vector v2three{v2.x(), v2.y(), 0.0}; Vector v1three{v1.x(), v1.y(), 0.0}; T dot = v2.dot(v3); if (std::abs(dot) < 0.000001) return false; T t1 = v2three.cross(v1three) / dot; T t2 = v1.dot(v3) / dot; return (t1 >= 0.0 && (t2 >= 0.0 && t2 <= 1.0)); } // https://www.geeksforgeeks.org/program-for-point-of-intersection-of-two-lines/ template Vector line_line_intersection(const Vector& a, const Vector& b, const Vector& c, const Vector& d) { T a1 = b.y() - a.y(); T b1 = a.x() - b.x(); T c1 = a1*(a.x()) + b1*(a.y()); T a2 = d.y() - c.y(); T b2 = c.x() - d.x(); T c2 = a2*(c.x())+ b2*(c.y()); T determinant = a1*b2 - a2*b1; T x = (b2*c1 - b1*c2)/determinant; T y = (a1*c2 - a2*c1)/determinant; if(std::fabs(determinant) < 0.0000001f) return Vector{std::numeric_limits::max(), std::numeric_limits::max()}; return Vector{x, y}; } template Vector project_point_on_vec(const Vector& point, const Vector unnormalizedVec) { return ((point.dot(unnormalizedVec)) / unnormalizedVec.dot(unnormalizedVec)) * unnormalizedVec; } template Vector project_point_on_normalized_vec(const Vector& point, const Vector normalizedVec) { return point.dot(normalizedVec) * normalizedVec; } // https://stackoverflow.com/a/61342198 template Vector project_point_on_line(const Vector& p, const Vector& b, const Vector& a) { Vector ap = p - a; Vector ab = b - a; T t = ap.dot(ab) / ab.dot(ab); return a + t * ab; } // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection template Vector line_line_intersection_inaccurate(const Matrix& a, const Matrix& b, const Matrix& c, const Matrix& d) { T x1 = a.x(); T x2 = b.x(); T x3 = c.x(); T x4 = d.x(); T y1 = a.y(); T y2 = b.y(); T y3 = c.y(); T y4 = d.y(); T p1 = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)); T p2 = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)); return Vector{p1, p2}; } // https://www.reddit.com/r/algorithms/comments/9moad4/what_is_the_simplest_to_implement_line_segment/ // Taken from https://github.com/vlecomte/cp-geo template T cross_vals_vec(const Matrix& a, const Matrix& b) { return a.x() * b.y() - a.y() * b.x(); } template T orient_vals_vec(const Matrix& a, const Matrix& b, const Matrix& c) { return cross_vals_vec((b - a).eval(), (c - a).eval()); } template bool is_collision_line_segment_line_segment(const Matrix& a, const Matrix& b, const Matrix& c, const Matrix& d) { T oa = orient_vals_vec(c, d, a); T ob = orient_vals_vec(c, d, b); T oc = orient_vals_vec(a, b, c); T od = orient_vals_vec(a, b, d); return (oa * ob < T(0) && oc * od < T(0)); } template std::optional> collision_line_segment_line_segment_pos(const Matrix& a, const Matrix& b, const Matrix& c, const Matrix& d) { T oa = orient_vals_vec(c, d, a); T ob = orient_vals_vec(c, d, b); T oc = orient_vals_vec(a, b, c); T od = orient_vals_vec(a, b, d); if(oa * ob < T(0) && oc * od < T(0)) return std::optional>((a * ob - b * oa) / (ob - oa)); return std::nullopt; } template Matrix collision_line_segment_line_segment_guaranteed(const Matrix& a, const Matrix& b, const Matrix& c, const Matrix& d) { T oa = orient_vals_vec(c, d, a); T ob = orient_vals_vec(c, d, b); T oc = orient_vals_vec(a, b, c); T od = orient_vals_vec(a, b, d); return Matrix((a * ob - b * oa) / (ob - oa)); } template bool is_collision_aabb_line_segment(const AlignedBox& aabb, const Matrix& lineP1, const Matrix& lineP2) { return aabb.contains(lineP1) || aabb.contains(lineP2) || is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox::TopLeft), aabb.corner(AlignedBox::TopRight)) || is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox::TopRight), aabb.corner(AlignedBox::BottomRight)) || is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox::BottomLeft), aabb.corner(AlignedBox::BottomRight)) || is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox::TopLeft), aabb.corner(AlignedBox::BottomLeft)); } template std::vector> collision_aabb_small_line_segment_large(const AlignedBox& aabb, const Matrix& lineP1, const Matrix& lineP2) { std::vector> toRet; auto optCollision = collision_line_segment_line_segment_pos(lineP1, lineP2, aabb.corner(AlignedBox::TopLeft), aabb.corner(AlignedBox::TopRight)); if(optCollision.has_value()) toRet.emplace_back(optCollision.value()); optCollision = collision_line_segment_line_segment_pos(lineP1, lineP2, aabb.corner(AlignedBox::TopRight), aabb.corner(AlignedBox::BottomRight)); if(optCollision.has_value()) toRet.emplace_back(optCollision.value()); optCollision = collision_line_segment_line_segment_pos(lineP1, lineP2, aabb.corner(AlignedBox::BottomLeft), aabb.corner(AlignedBox::BottomRight)); if(optCollision.has_value()) toRet.emplace_back(optCollision.value()); optCollision = collision_line_segment_line_segment_pos(lineP1, lineP2, aabb.corner(AlignedBox::TopLeft), aabb.corner(AlignedBox::BottomLeft)); if(optCollision.has_value()) toRet.emplace_back(optCollision.value()); return toRet; } template T vec_distance(const Vector& v1, const Vector& v2) { return (v1 - v2).norm(); } template T vec_distance_sqrd(const Vector& v1, const Vector& v2) { return (v1 - v2).squaredNorm(); } template T vec_length(const Vector& v) { return v.norm(); } template T cwise_vec_min(const T& v1, const T& v2) { T toRet; for(unsigned i = 0; i < v1.size(); i++) toRet[i] = std::min(v1[i], v2[i]); return toRet; } template T cwise_vec_max(const T& v1, const T& v2) { T toRet; for(unsigned i = 0; i < v1.size(); i++) toRet[i] = std::max(v1[i], v2[i]); return toRet; } template T cwise_vec_clamp(const T& v1, const T& v2, const T& v3) { T toRet; for(unsigned i = 0; i < v1.size(); i++) toRet[i] = std::clamp(v1[i], v2[i], v3[i]); return toRet; } template T cwise_vec_modf(const T& v1, T& v2) { T toRet; for(unsigned i = 0; i < v1.size(); i++) toRet[i] = std::modf(v1[i], &v2[i]); return toRet; } template std::vector> arc_vec(const Vector& arcCenter, const Vector& arcDirStart, const Vector& arcDirEnd, const Vector& arcDirCenter, T arcRadius, unsigned numOfDivisions) { T a1 = std::atan2(arcDirStart.y(), arcDirStart.x()); T a2 = std::atan2(arcDirEnd.y(), arcDirEnd.x()); T a3 = std::atan2(arcDirCenter.y(), arcDirCenter.x()); T minAngle = std::min(a1, a2); T maxAngle = std::max(a1, a2); bool swapHappen = (a3 > minAngle && a3 > maxAngle) || (a3 < minAngle && a3 < maxAngle); if(swapHappen) { minAngle += 2.0 * std::numbers::pi; std::swap(minAngle, maxAngle); } bool flipped = minAngle == a2; std::vector> toRet; T tStep = 1.0 / static_cast(numOfDivisions); if(flipped) { for(unsigned i = numOfDivisions - 1; i > 0; i--) { T newAngle = std::lerp(minAngle, maxAngle, tStep * i); toRet.emplace_back(arcCenter + Vector{std::cos(newAngle), std::sin(newAngle)} * arcRadius); } } else { for(unsigned i = 1; i < numOfDivisions; i++) { T newAngle = std::lerp(minAngle, maxAngle, tStep * i); toRet.emplace_back(arcCenter + Vector{std::cos(newAngle), std::sin(newAngle)} * arcRadius); } } return toRet; } template std::vector> gen_circle_points(const Vector& center, T radius, unsigned numOfPoints) { std::vector> toRet; for(unsigned i = 0; i < numOfPoints; i++) { T angle = (static_cast(i) / static_cast(numOfPoints)) * std::numbers::pi * 2; toRet.emplace_back(std::cos(angle) * radius + center.x(), std::sin(angle) * radius + center.y()); } return toRet; } // https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline template T catmull_rom_get_t(T t, T alpha, const Vector& p0, const Vector& p1) { Vector d = p1 - p0; T a = d.dot(d); T b = std::pow(a, alpha * 0.5); return (b + t); } template Vector catmull_rom(const Vector& p0, const Vector& p1, const Vector& p2, const Vector& p3, T t, T alpha = 0.5) { T t0 = 0.0; T t1 = catmull_rom_get_t(t0, alpha, p0, p1); T t2 = catmull_rom_get_t(t1, alpha, p1, p2); T t3 = catmull_rom_get_t(t2, alpha, p2, p3); t = std::lerp(t1, t2, t); Vector A1 = ( t1-t )/( t1-t0 )*p0 + ( t-t0 )/( t1-t0 )*p1; Vector A2 = ( t2-t )/( t2-t1 )*p1 + ( t-t1 )/( t2-t1 )*p2; Vector A3 = ( t3-t )/( t3-t2 )*p2 + ( t-t2 )/( t3-t2 )*p3; Vector B1 = ( t2-t )/( t2-t0 )*A1 + ( t-t0 )/( t2-t0 )*A2; Vector B2 = ( t3-t )/( t3-t1 )*A2 + ( t-t1 )/( t3-t1 )*A3; Vector C = ( t2-t )/( t2-t1 )*B1 + ( t-t1 )/( t2-t1 )*B2; return C; } template T transform_scalar(const Transform& transform, T scalar) { return (transform.linear() * Vector{scalar, scalar}).x(); } template std::array, 4> triangle_from_rect_points(const Vector& min, const Vector& max) { return {min, {min.x(), max.y()}, max, {max.x(), min.y()}}; } template std::string vec_pretty(const T& a) { if(a.size() == 0) return "[]"; std::stringstream ss; ss << "["; for(size_t i = 0; i < static_cast(a.size()); i++) { if(i == static_cast(a.size() - 1)) ss << a[i] << "]"; else ss << a[i] << ", "; } return ss.str(); } template Vector ensure_points_have_distance(const Vector& a, const Vector& b, T d) { Vector toRet = b; if(std::abs(a.x() - b.x()) < d) toRet.x() = a.x() + d; if(std::abs(a.y() - b.y()) < d) toRet.y() = a.y() + d; return toRet; } // https://www.gabrielgambetta.com/computer-graphics-from-scratch/11-clipping.html template void clip_triangle_against_axis(std::vector, 3>>& triangleList, const std::array, 3>& t, const std::array, 2>& axisLineSegment, const std::function&)>& isInClippingAreaFunc) { std::array inClippingArea = { isInClippingAreaFunc(t[0]), isInClippingAreaFunc(t[1]), isInClippingAreaFunc(t[2]) }; auto oneVertexInClippingArea = [&](const Vector& a, const Vector& b, const Vector& c) { Vector bPrime = collision_line_segment_line_segment_guaranteed(a, b, axisLineSegment[0], axisLineSegment[1]); Vector cPrime = collision_line_segment_line_segment_guaranteed(a, c, axisLineSegment[0], axisLineSegment[1]); triangleList.emplace_back(std::array, 3>{a, bPrime, cPrime}); }; auto twoVerticesInClippingArea = [&](const Vector& a, const Vector& b, const Vector& c) { Vector aPrime = collision_line_segment_line_segment_guaranteed(a, c, axisLineSegment[0], axisLineSegment[1]); Vector bPrime = collision_line_segment_line_segment_guaranteed(b, c, axisLineSegment[0], axisLineSegment[1]); triangleList.emplace_back(std::array, 3>{a, b, aPrime}); triangleList.emplace_back(std::array, 3>{aPrime, b, bPrime}); }; if(inClippingArea[0] && inClippingArea[1] && inClippingArea[2]) triangleList.emplace_back(t); else if(!inClippingArea[0] && !inClippingArea[1] && !inClippingArea[2]) return; else if(inClippingArea[0] && !inClippingArea[1] && !inClippingArea[2]) oneVertexInClippingArea(t[0], t[1], t[2]); else if(!inClippingArea[0] && inClippingArea[1] && !inClippingArea[2]) oneVertexInClippingArea(t[1], t[0], t[2]); else if(!inClippingArea[0] && !inClippingArea[1] && inClippingArea[2]) oneVertexInClippingArea(t[2], t[0], t[1]); else if(inClippingArea[0] && inClippingArea[1] && !inClippingArea[2]) twoVerticesInClippingArea(t[0], t[1], t[2]); else if(inClippingArea[0] && !inClippingArea[1] && inClippingArea[2]) twoVerticesInClippingArea(t[0], t[2], t[1]); else if(!inClippingArea[0] && inClippingArea[1] && inClippingArea[2]) twoVerticesInClippingArea(t[1], t[2], t[0]); }