/*
* 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]);
}