DanIsraelMalta / Numerics

a collection of efficient and precise numerical algorithms and operations with focus on linear algebra and computational geometry.
MIT License
2 stars 0 forks source link
geometry linear-algebra numerical-methods

Welcome to Numerics

"Numerics" is a modern c++ (requires c++23) collection of numerical algorithms, structures and operations with particular focus on linear algebra and computational geometry.

Features include:

Sample:

//
// GLSL swizzle syntax
//
vec2 a(1.0f, -1.0f);
vec2 b(3.2f, 2.0f);
vec2 c = a.yx + b.xy; // c = {2.2f, 3.0f}
a.xy += b.xx; // a = {4.2f, 2.2f}

vec3 d(0.0f, 1.0f, 2.0f);
vec3 e(2.0f, 1.0f, 3.0f);
vec3 f = GLSL::mix<0.5f>(d, e); // f = {1.0f, 1.0f, 2.5f}

//
// GLSL matrices
//

// build matrix from column vectors
// /4 7\
// \5 8/
mat2i b(ivec2(4, 5), ivec2(7, 8));

// multiply vector by matrix
//  (4 5) * /4 7\ = 4*4 + 5*5 = (41 68)
//          \5 8/   4*7 + 5*8
ivec2 xb = b.x() * b;
assert(GLSL::equal(xb, ivec2(41, 68)));

// multiply matrix by vector
// /4 7\ * /4\ = 4*4 + 7*5 = (51, 60)
// \5 8/   \5/   5*4 + 8*5
ivec2 bx = b * b.x();
assert(GLSL::equal(bx, ivec2(51, 60)));

// basic operations
assert(GLSL::determinant(b) == -3);
assert(GLSL::equal(GLSL::transpose(b), mat2i(4, 7, 5, 8)));

//
// basic spatial transformations
//

// define axis and angle
vec3 axis = GLSL::normalize(vec3(1.0f, 2.0f, 3.0f));
float angle{ std::numbers::pi_v<float> / 4.0f };

// create rotation matrix and quaternion from axis anf angle
const auto Rx = Transformation::rotation_matrix_from_axis_angle(vec3(1.0f, 0.0f, 0.0f), std::numbers::pi_v<float> / 2.0f);
vec4 quat = Transformation::create_quaternion_from_axis_angle(axis, angle);

// create rotation matrix from quaternion and extract rotation axis
mat3 mat = Transformation::create_rotation_matrix_from_quaternion(quat);
auto axis_from_mat = Transformation::get_axis_angle_from_rotation_matrix(mat);

//
// decompositions and linear equation system solvers
//

mat3 a(12.0, -51.0, 4.0,
       6.0,  167.0, -68.0,
       -4.0, 24.0,  -41.0);

// calculate eigenvalues
auto eigs = Decomposition::eigenvalues(a);
// eigs = {-34.196675001469160, 156.13668406196879, 16.059990939500377}

// perform QR decomposition
auto qr = Decomposition::QR_GivensRotation(a);
// qr.Q = (0.228375, -0.9790593, 0.076125,
//         0.618929, 0.084383,   -0.780901,
//         0.751513, 0.225454,   0.619999);
// qr.R = (52.545219,   0.0,       -0.0,
//         -165.895209, 70.906839,  0.0,
//          -27.328842, 31.566433, -23.015097);

// solve set of linear equation using LU decomposition
vec3 b(70.0, 12.0, 50.0);
auto solution = Solvers::SolveQR(a, b);
// solution = {3.71118, 1.74416, -3.75020}

//
// sign distance field calculation, ray-intersections and axis aligned bounding boxes
//

// define polygon and calculate its SDF in various locations
std::array<vec2, 5> polygon{ {vec2(2.0f, 1.0f), vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 5.0f), vec2(5.0f, 1.0f) }};
float distance = PointDistance::sdf_to_polygon<5>(polygon, vec2(2.0f, 0.0f)); // distance = 1
distance = PointDistance::sdf_to_polygon<5>(polygon, vec2(3.0f, 1.5f)); // distance = -0.5

// calculate ellipse SDF
distance = PointDistance::sdf_to_ellipse(vec2(5.0f, 0.0f), vec2(1.0f, 2.0f)); // distance = -4

// calculate triangle SDF
vec2 p0(1.0f);
vec2 p1(2.0f, 3.0f);
vec2 p2(0.0f, 3.0f);
distance = PointDistance::sdf_to_triangle(vec2(0.0f), p0, p1, p2); // distance = std::sqrt(2)

// ray-triangle intersections
vec3 p0(1.0f, 1.0f, 0.0f);
vec3 p1(2.0f, 3.0f, 0.0f);
vec3 p2(0.0f, 3.0f, 0.0f);
vec3 intersection = RayIntersections::triangle_intersect_cartesian(vec3(0.0f), GLSL::normalize(p0), p0, p1, p2); // intersection = vec3(-1.0f)
intersection = RayIntersections::triangle_intersect_barycentric(vec3(1.0f, 2.0f, 3.0f), vec3(0.0f, 0.0f, -1.0f), p0, p1, p2); // intersection = vec3(3.0f, 0.25f, 0.25f)))

// ray-ellipsoid intersections
intersections = RayIntersections::ellipsoid_intersection(vec3(0.0f, 10.0f, 0.0f), vec3(0.0f, -1.0f, 0.0f), vec3(1.0f, 2.0f, 3.0f)); // intersection = vec2(8.0f, 12.0f)

// cloud points axis aligned bounding box
std::list<vec2> points{ {vec2(0.0f), vec2(1.0f), vec2(2.0f), vec2(-5.0f), vec2(10.0f), vec2(-17.0f, -3.0f)} };
auto aabb = AxisLignedBoundingBox::point_cloud_aabb(points.begin(), points.end());

// ellipse bounding box
vec3 center{10.0f, 10.0f, 0.0f};
vec3 axis1{2.0f, 0.0f, 0.0f};
vec3 axis2{0.0f, 2.0f, 0.0f};
auto aabb = AxisLignedBoundingBox::ellipse_aabb(center, axis1, axis2);

//
// computational geometry
//

// define polygon
std::vector<vec2> polygon{ {vec2(3.0f, 1.0f), vec2(5.0f, 1.0f), vec2(5.0f, 4.0f), vec2(4.0f, 6.0f), vec2(7.0f, 7.0f ), vec2(10.0f, 7.0f), vec2(10.0f, 9.0f),
                            vec2(8.0f, 9.0f), vec2(6.0f, 10.0f), vec2(1.0f, 10.0f), vec2(1.0f, 8.0f), vec2(2.0f, 8.0f), vec2(2.0f, 6.0f), vec2(1.0f, 6.0f),
                            vec2(1.0f, 2.0f)} };

// calculate bounding areas
const auto convex = Algorithms2D::get_convex_hull(polygon.begin(), polygon.end()); // get polygon convex hull
const auto concave = Algorithms2D::get_concave_hull(polygon.begin(), polygon.end(), 0.3f); // get polygon concave hull
const auto antipodal = Algorithms2D::get_convex_diameter(convex); // get polygon convex hull diameter
const auto obb = Algorithms2D::get_convex_hull_minimum_area_bounding_rectangle(convex); // get polygon minimal area boundingbox
const auto circle = Algorithms2D::get_minimal_bounding_circle(convex); // get polygon minimal bounding circle

// create random points on the plane
std::vector<vec2> points;
for (std::size_t i{}; i < 50; ++i) {
    points.emplace_back(vec2(static_cast<float>(rand() % 100 - 50),
                             static_cast<float>(rand() % 100 - 50)));
}

// sort points in clock wise manner and calculate its principle axis
const vec2 centroid = Algorithms2D::Internals::get_centroid(points.cbegin(), points.cend());
Algorithms2D::sort_points_clock_wise(points.begin(), points.end(), centroid);
const vec2 direction{ Algorithms2D::get_principle_axis(points.cbegin(), points.cend()) };

// create points in space
std::vector<vec2> points;
for (std::size_t i{}; i < 10000; ++i) {
    points.emplace_back(vec2(static_cast<float>(rand()) / RAND_MAX * 100.0f,
                             static_cast<float>(rand()) / RAND_MAX * 100.0f));
}

// calculate nearest neighbors
SpacePartitioning::KDTree<vec2> kdtree;
kdtree.construct(points.begin(), points.end());
auto pointsInCube = kdtree.range_query(SpacePartitioning::RangeSearchType::Manhattan, vec2(50.0f), 5.0f); // nearest neighbours in cube
auto pointsInSphere = kdtree.range_query(SpacePartitioning::RangeSearchType::Radius, vec2(50.0f), 5.0f); // nearest neighbours in sphere
const auto nearest10 = kdtree.nearest_neighbors_query(vec2(50.0f), 19); // get 19 nearest neighbors ot point at (50, 50)
kdtree.clear();

// cluster/partition points using DBSCAN with a kd-tree
const auto clusterIds = Clustering::get_density_based_clusters(points.cbegin(), points.cend(), kdtree, 1.0f, 10);
kdtree.clear();

// cluster/partition points using DBSCAN with a lattice-bin grid
SpacePartitioning::Grid<vec2> grid;
clusterIds0= Clustering::get_density_based_clusters(points.cbegin(), points.cend(), grid, 1.0f, 10);
grid.clear();

// k-mean might be faster...
clusterIds0 = Clustering::k_means(points.cbegin(), points.cend(), 3, 10, 0.01f);

It is also possible to export two dimensional calculation in scalable vector graphic format for debug purposes. As an example, here is a graphic representation of the calculated top/bottom envelope of a sine signal and a different bounding shapes for a polygon:

   // find a 2D signal envelope
   {
       // define 2D signal
       std::vector<vec2> points;
       for (std::size_t i{}; i < 200; ++i) {
           float fi{ static_cast<float>(i) };
           points.emplace_back(vec2(fi, 10.0f + 180.0f * std::abs(std::sin(fi))));
       }

       // calculate signal envelope
       const auto envelope = Algorithms2D::get_points_envelope(points.begin(), points.end());

       // export signal and envelope to SVG
       svg<vec2> envelope_test_svg(200, 200);
       for (const vec2 p : points) {
           envelope_test_svg.add_circle(p, 2.0f, "none", "black", 0.5f);
       }
       envelope_test_svg.add_polyline(envelope.top.begin(), envelope.top.end(), "none", "red", 1.0f);
       envelope_test_svg.add_polyline(envelope.bottom.begin(), envelope.bottom.end(), "none", "green", 1.0f);
       envelope_test_svg.to_file("envelope_test_svg.svg");
   }

   // find convex hull and bounding shapes
   {
       // define polygon
       std::vector<vec2> polygon{ {vec2(3.0f, 1.0f), vec2(5.0f, 1.0f), vec2(5.0f, 4.0f), vec2(4.0f, 6.0f), vec2(7.0f, 7.0f), vec2(10.0f, 7.0f), vec2(10.0f, 9.0f),
                                    vec2(8.0f, 9.0f), vec2(6.0f, 10.0f), vec2(1.0f, 10.0f), vec2(1.0f, 8.0f), vec2(2.0f, 8.0f), vec2(2.0f, 6.0f), vec2(1.0f, 6.0f),
                                    vec2(1.0f, 2.0f)} };

       // calculate convex hull
       auto convex = Algorithms2D::get_convex_hull(polygon.begin(), polygon.end());

       // calculate minimal area bounding rectangle
       const auto obb = Algorithms2D::get_convex_hull_minimum_area_bounding_rectangle(convex);
       std::vector<vec2> obbs{ {obb.p0, obb.p1, obb.p2, obb.p3} };

       // close convex and obb for for drawing purposes
       convex.emplace_back(convex.front());
       obbs.emplace_back(obbs.front());

       // scale polygons for drawing purposes
       for (auto& p : polygon) {
           p = 50.0f * p + 50.0f;
       }
       for (auto& p : convex) {
           p = 50.0f * p + 50.0f;
       }
       for (auto& p : obbs) {
           p = 50.0f * p + 50.0f;
       }

       // calculate minimal bounding circle
       const auto circle = Algorithms2D::get_minimal_bounding_circle(convex);

       // export polygon and its bounding objects to SVG
       svg<vec2> polygon_test_svg(650, 650);
       polygon_test_svg.add_polygon(polygon.begin(), polygon.end(), "none", "black", 5.0f);
       polygon_test_svg.add_polyline(convex.begin(), convex.end(), "none", "green", 1.0f);
       for (const vec2 p : convex) {
           polygon_test_svg.add_circle(p, 10.0f, "green", "green", 1.0f);
       }
       polygon_test_svg.add_polyline(obbs.begin(), obbs.end(), "none", "red", 2.0f);
       polygon_test_svg.add_circle(circle.center, std::sqrt(circle.radius_squared), "none", "blue", 2.0f);
       polygon_test_svg.to_file("polygon_test_svg.svg");
   }

and here is the outcome (left image shows the signal as black circles, signal top envelope in green, signal bottom envelope in red; right image shows the polygon in black, convex hull in green, convex hull points in green circles, minimal bounding circle in blue and minimal area bounding rectangle in red):

envelope_test_svg image

Files in repository: