Img2Num C++ (Internal Developer Docs) dev
API Documentation
Loading...
Searching...
No Matches
bezier.cpp
1#include "internal/bezier.h"
2
3#include <cmath>
4#include <limits>
5#include <vector>
6
7// --- Vector Math Helpers ---
8inline float dot(Point a, Point b) {
9 return a.x * b.x + a.y * b.y;
10}
11inline float len(Point a, Point b) {
12 Point c = a - b;
13 return std::sqrt(c.x * c.x + c.y * c.y);
14}
15
16// --- Evaluate Quadratic Bezier at t ---
17Point evalBezier(const QuadBezier &b, float t) {
18 // B(t) = (1-t)^2 * P0 + 2*t*(1-t) * P1 + t^2 * P2
19 float invT = 1.0 - t;
20 float c0 = invT * invT;
21 float c1 = 2.0f * t * invT;
22 float c2 = t * t;
23
24 return c0 * b.p0 + c1 * b.p1 + c2 * b.p2;
25}
26
27// --- Chord Length Parameterization ---
28// Assigns a 't' value (0.0 to 1.0) to each point based on distance
29std::vector<float> chordLengthParameterize(const std::vector<Point> &points) {
30 std::vector<float> u;
31 u.reserve(points.size());
32 u.push_back(0.0f);
33
34 for (int i = 1; i < points.size(); ++i) {
35 float dist = len(points[i], points[i - 1]);
36 u.push_back(u.back() + dist);
37 }
38
39 float totalLen = u.back();
40 if (totalLen == 0) return u; // Should not happen for valid ranges
41
42 for (float &val : u) {
43 val /= totalLen;
44 }
45 return u;
46}
47
48// --- Least Squares Fit for Control Point Q1 ---
49// We know Q0 (Start) and Q2 (End). We need to find Q1 that minimizes error.
50// Based on equation: P(t) = (1-t)^2 Q0 + 2t(1-t) Q1 + t^2 Q2
51// Rearranged: Q1 * [2t(1-t)] = P(t) - (1-t)^2 Q0 - t^2 Q2
52Point generateQuadBezier(const std::vector<Point> &points, const std::vector<float> &u) {
53 Point Q0 = points.front();
54 Point Q2 = points.back();
55
56 float numX = 0.0, numY = 0.0;
57 float den = 0.0;
58
59 for (int i = 0; i < u.size(); ++i) {
60 float t = u[i];
61 float invT = 1.0f - t;
62
63 // A = 2t(1-t)
64 float A = 2.0f * t * invT;
65
66 // V = P_actual - (Contribution of Q0 and Q2)
67 // V = P[i] - (1-t)^2 * Q0 - t^2 * Q2
68 float B0 = invT * invT;
69 float B2 = t * t;
70 Point V = points[i] - (Q0 * B0 + Q2 * B2);
71
72 // Least Squares Sums
73 numX += A * V.x;
74 numY += A * V.y;
75 den += A * A;
76 }
77
78 if (den < 1e-9) {
79 // Fallback for straight lines (den is 0 if all t are 0 or 1)
80 return Q0 + (Q2 - Q0) * 0.5;
81 }
82
83 return {numX / den, numY / den};
84}
85
86// --- Recursive Fit Function ---
87void fitRecursive(const std::vector<Point> &points, float errorLimit,
88 std::vector<QuadBezier> &outCurves) {
89 // Base Case: Not enough points, just connect them
90 if (points.size() <= 2) {
91 // Just a line segment
92 Point mid = points.front() + (points.back() - points.front()) * 0.5;
93 outCurves.push_back({points.front(), mid, points.back()});
94 return;
95 }
96
97 // 1. Parameterize Points
98 std::vector<float> u = chordLengthParameterize(points);
99
100 // 2. Find Optimal Control Point (Q1)
101 Point Q1 = generateQuadBezier(points, u);
102 QuadBezier curve = {points.front(), Q1, points.back()};
103
104 // 3. Calculate Maximum Error
105 float maxDistSq = 0.0f;
106 int splitPoint = 0;
107
108 // Check distance of every intermediate point to the curve
109 // Note: Technically we should find the nearest point on curve,
110 // but evaluating at parameter 't' is a standard approximation for speed.
111 for (int i = 0; i < u.size(); ++i) {
112 Point P = points[i];
113 Point CurveP = evalBezier(curve, u[i]);
114 float d2 = Point::distSq(P, CurveP);
115
116 if (d2 > maxDistSq) {
117 maxDistSq = d2;
118 splitPoint = i;
119 }
120 }
121
122 // 4. Check Error Threshold
123 if (maxDistSq < (errorLimit * errorLimit)) {
124 outCurves.push_back(curve); // Fit is good!
125 } else {
126 // Fit is bad, split at the point of maximum error
127 // Important: Prevent infinite recursion if split doesn't advance
128 if (splitPoint == 0 || splitPoint == points.size() - 1) {
129 // Fallback: simply bisect indices if geometric split fails
130 splitPoint = points.size() / 2;
131 }
132
133 std::vector<Point> p1(points.begin(), points.begin() + splitPoint + 1);
134 std::vector<Point> p2(points.begin() + splitPoint, points.end());
135
136 fitRecursive(p1, errorLimit, outCurves);
137 fitRecursive(p2, errorLimit, outCurves);
138 }
139}
140
141// --- Main Wrapper ---
142void fit_curve_reduction(const std::vector<std::vector<Point>> &chains,
143 std::vector<std::vector<QuadBezier>> &results, float tolerance) {
144 // if (chain.empty()) return result;
145 // results.resize(chains.size());
146 for (int i = 0; i < chains.size(); ++i) {
147 // Start recursion on the whole chain
148 std::vector<QuadBezier> result;
149 fitRecursive(chains[i], tolerance, result);
150 results.push_back(result);
151 }
152}
Definition Point.h:5