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)
41 return u; // Should not happen for valid ranges
42
43 for (float& val : u) {
44 val /= totalLen;
45 }
46 return u;
47}
48
49// --- Least Squares Fit for Control Point Q1 ---
50// We know Q0 (Start) and Q2 (End). We need to find Q1 that minimizes error.
51// Based on equation: P(t) = (1-t)^2 Q0 + 2t(1-t) Q1 + t^2 Q2
52// Rearranged: Q1 * [2t(1-t)] = P(t) - (1-t)^2 Q0 - t^2 Q2
53Point generateQuadBezier(const std::vector<Point>& points, const std::vector<float>& u) {
54 Point Q0 = points.front();
55 Point Q2 = points.back();
56
57 float numX = 0.0, numY = 0.0;
58 float den = 0.0;
59
60 for (int i = 0; i < u.size(); ++i) {
61 float t = u[i];
62 float invT = 1.0f - t;
63
64 // A = 2t(1-t)
65 float A = 2.0f * t * invT;
66
67 // V = P_actual - (Contribution of Q0 and Q2)
68 // V = P[i] - (1-t)^2 * Q0 - t^2 * Q2
69 float B0 = invT * invT;
70 float B2 = t * t;
71 Point V = points[i] - (Q0 * B0 + Q2 * B2);
72
73 // Least Squares Sums
74 numX += A * V.x;
75 numY += A * V.y;
76 den += A * A;
77 }
78
79 if (den < 1e-9) {
80 // Fallback for straight lines (den is 0 if all t are 0 or 1)
81 return Q0 + (Q2 - Q0) * 0.5;
82 }
83
84 return {numX / den, numY / den};
85}
86
87// --- Recursive Fit Function ---
88void fitRecursive(
89 const std::vector<Point>& points, float errorLimit, std::vector<QuadBezier>& outCurves
90) {
91 // Base Case: Not enough points, just connect them
92 if (points.size() <= 2) {
93 // Just a line segment
94 Point mid = points.front() + (points.back() - points.front()) * 0.5;
95 outCurves.push_back({points.front(), mid, points.back()});
96 return;
97 }
98
99 // 1. Parameterize Points
100 std::vector<float> u = chordLengthParameterize(points);
101
102 // 2. Find Optimal Control Point (Q1)
103 Point Q1 = generateQuadBezier(points, u);
104 QuadBezier curve = {points.front(), Q1, points.back()};
105
106 // 3. Calculate Maximum Error
107 float maxDistSq = 0.0f;
108 int splitPoint = 0;
109
110 // Check distance of every intermediate point to the curve
111 // Note: Technically we should find the nearest point on curve,
112 // but evaluating at parameter 't' is a standard approximation for speed.
113 for (int i = 0; i < u.size(); ++i) {
114 Point P = points[i];
115 Point CurveP = evalBezier(curve, u[i]);
116 float d2 = Point::distSq(P, CurveP);
117
118 if (d2 > maxDistSq) {
119 maxDistSq = d2;
120 splitPoint = i;
121 }
122 }
123
124 // 4. Check Error Threshold
125 if (maxDistSq < (errorLimit * errorLimit)) {
126 outCurves.push_back(curve); // Fit is good!
127 } else {
128 // Fit is bad, split at the point of maximum error
129 // Important: Prevent infinite recursion if split doesn't advance
130 if (splitPoint == 0 || splitPoint == points.size() - 1) {
131 // Fallback: simply bisect indices if geometric split fails
132 splitPoint = points.size() / 2;
133 }
134
135 std::vector<Point> p1(points.begin(), points.begin() + splitPoint + 1);
136 std::vector<Point> p2(points.begin() + splitPoint, points.end());
137
138 fitRecursive(p1, errorLimit, outCurves);
139 fitRecursive(p2, errorLimit, outCurves);
140 }
141}
142
143// --- Main Wrapper ---
144void fit_curve_reduction(
145 const std::vector<std::vector<Point>>& chains, std::vector<std::vector<QuadBezier>>& results,
146 float tolerance
147) {
148 // if (chain.empty()) return result;
149 // results.resize(chains.size());
150 for (int i = 0; i < chains.size(); ++i) {
151 // Start recursion on the whole chain
152 std::vector<QuadBezier> result;
153 fitRecursive(chains[i], tolerance, result);
154 results.push_back(result);
155 }
156}
157
158// --- Junction-aware wrapper ---
159// Splits each chain at its fixed (junction) points and fits the pieces
160// separately. Because fitRecursive always keeps a segment's first and last point
161// exactly, every junction becomes a pinned on-curve point the fit cannot move.
162void fit_curve_reduction(
163 const std::vector<std::vector<Point>>& chains, const std::vector<std::vector<uint8_t>>& fixed,
164 std::vector<std::vector<QuadBezier>>& results, float tolerance
165) {
166 for (size_t i = 0; i < chains.size(); ++i) {
167 const std::vector<Point>& chain = chains[i];
168 const int n = static_cast<int>(chain.size());
169 std::vector<QuadBezier> result;
170 if (n < 2) {
171 results.push_back(result);
172 continue;
173 }
174
175 // Segment boundaries: chain ends plus every interior junction point.
176 std::vector<int> bounds;
177 bounds.push_back(0);
178 for (int k = 1; k < n - 1; ++k)
179 if (k < static_cast<int>(fixed[i].size()) && fixed[i][k])
180 bounds.push_back(k);
181 bounds.push_back(n - 1);
182
183 // Fit each [bounds[s], bounds[s+1]] piece; consecutive pieces share the
184 // junction point, so the curve stays continuous and pinned there.
185 for (size_t s = 0; s + 1 < bounds.size(); ++s) {
186 const int a = bounds[s], b = bounds[s + 1];
187 std::vector<Point> seg(chain.begin() + a, chain.begin() + b + 1);
188 fitRecursive(seg, tolerance, result);
189 }
190 results.push_back(result);
191 }
192}
Definition Point.h:5