Skip to content

Commit 3638e7b

Browse files
committed
C2 curvature matching and path deviation for Bezier blends
bezier9Init() accepts curvature parameters (kappa, normal) at both endpoints. For arc-adjacent blends, P2/P3 are offset in the curvature normal direction by delta = 5*alpha^2*kappa/4 to match adjacent segment curvature, eliminating centripetal acceleration discontinuity at junctions. For line segments (kappa=0) the offset is zero and behavior is unchanged. Add bezier9PathDeviation() which measures maximum distance to the two-segment programmed path (P_start -> corner -> P_end), correct for both symmetric and asymmetric blends. Add bezier9MaxDeviation() for multi-sample diagnostic verification.
1 parent b27451f commit 3638e7b

2 files changed

Lines changed: 157 additions & 5 deletions

File tree

src/emc/motion_planning/bezier9.cc

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,10 @@ int bezier9Init(Bezier9 * const b,
291291
PmCartesian const * const u_end_abc,
292292
PmCartesian const * const u_start_uvw,
293293
PmCartesian const * const u_end_uvw,
294+
double kappa_start,
295+
PmCartesian const * const n_start,
296+
double kappa_end,
297+
PmCartesian const * const n_end,
294298
double alpha)
295299
{
296300
// Validate inputs
@@ -314,14 +318,34 @@ int bezier9Init(Bezier9 * const b,
314318
PmCartesian start_uvw = {start->u, start->v, start->w};
315319
PmCartesian end_uvw = {end->u, end->v, end->w};
316320

317-
// Set quintic control points for each subspace (G2 continuity)
321+
// Set quintic control points for each subspace (zero-curvature baseline)
318322
set_quintic_control_points(b->P, &start_xyz, &end_xyz,
319323
u_start_xyz, u_end_xyz, alpha);
320324
set_quintic_control_points(b->A, &start_abc, &end_abc,
321325
u_start_abc, u_end_abc, alpha);
322326
set_quintic_control_points(b->U, &start_uvw, &end_uvw,
323327
u_start_uvw, u_end_uvw, alpha);
324328

329+
// C2 curvature matching (XYZ only): offset P[2] and P[3] in the
330+
// curvature normal direction to match adjacent segment curvature.
331+
//
332+
// For a quintic Bezier with P0,P1,P2 collinear along tangent u:
333+
// B'(0) = 5α·u, B''(0) = 20·δ·n (after offset)
334+
// κ(0) = |B'×B''| / |B'|³ = 4δ / (5α²)
335+
// Solving for target curvature: δ = 5α²·κ / 4
336+
if (kappa_start > BEZIER9_CURVATURE_EPSILON && n_start) {
337+
double delta = 5.0 * alpha * alpha * kappa_start / 4.0;
338+
b->P[2].x += delta * n_start->x;
339+
b->P[2].y += delta * n_start->y;
340+
b->P[2].z += delta * n_start->z;
341+
}
342+
if (kappa_end > BEZIER9_CURVATURE_EPSILON && n_end) {
343+
double delta = 5.0 * alpha * alpha * kappa_end / 4.0;
344+
b->P[3].x += delta * n_end->x;
345+
b->P[3].y += delta * n_end->y;
346+
b->P[3].z += delta * n_end->z;
347+
}
348+
325349
// Build arc-length parameterization table
326350
build_arc_length_table(b);
327351

@@ -614,3 +638,84 @@ double bezier9Deviation(Bezier9 const * const b,
614638

615639
return sqrt(dx * dx + dy * dy + dz * dz);
616640
}
641+
642+
double bezier9MaxDeviation(Bezier9 const * const b,
643+
PmCartesian const * const intersection_point,
644+
int n_samples)
645+
{
646+
if (!b || !intersection_point || n_samples < 1) {
647+
return 0.0;
648+
}
649+
650+
double max_dev = 0.0;
651+
for (int i = 1; i <= n_samples; i++) {
652+
double t = (double)i / (double)(n_samples + 1);
653+
PmCartesian pt;
654+
bezier5_eval(b->P, t, &pt);
655+
double dx = pt.x - intersection_point->x;
656+
double dy = pt.y - intersection_point->y;
657+
double dz = pt.z - intersection_point->z;
658+
double dev = sqrt(dx * dx + dy * dy + dz * dz);
659+
if (dev > max_dev) max_dev = dev;
660+
}
661+
return max_dev;
662+
}
663+
664+
/**
665+
* Point-to-line-segment distance in 3D.
666+
* Returns the minimum Euclidean distance from point p to the
667+
* line segment from a to b.
668+
*/
669+
static double point_to_segment_dist(PmCartesian const * const p,
670+
PmCartesian const * const a,
671+
PmCartesian const * const b)
672+
{
673+
double abx = b->x - a->x;
674+
double aby = b->y - a->y;
675+
double abz = b->z - a->z;
676+
double apx = p->x - a->x;
677+
double apy = p->y - a->y;
678+
double apz = p->z - a->z;
679+
680+
double ab_sq = abx * abx + aby * aby + abz * abz;
681+
if (ab_sq < 1e-30) {
682+
/* Degenerate segment (a == b): distance to the point */
683+
return sqrt(apx * apx + apy * apy + apz * apz);
684+
}
685+
686+
double t = (apx * abx + apy * aby + apz * abz) / ab_sq;
687+
if (t < 0.0) t = 0.0;
688+
if (t > 1.0) t = 1.0;
689+
690+
double cx = a->x + t * abx - p->x;
691+
double cy = a->y + t * aby - p->y;
692+
double cz = a->z + t * abz - p->z;
693+
694+
return sqrt(cx * cx + cy * cy + cz * cz);
695+
}
696+
697+
double bezier9PathDeviation(Bezier9 const * const b,
698+
PmCartesian const * const seg1_start,
699+
PmCartesian const * const corner,
700+
PmCartesian const * const seg2_end,
701+
int n_samples)
702+
{
703+
if (!b || !seg1_start || !corner || !seg2_end || n_samples < 1) {
704+
return 0.0;
705+
}
706+
707+
double max_dev = 0.0;
708+
for (int i = 1; i <= n_samples; i++) {
709+
double t = (double)i / (double)(n_samples + 1);
710+
PmCartesian pt;
711+
bezier5_eval(b->P, t, &pt);
712+
713+
/* Distance to programmed path = min of distances to both segments */
714+
double d1 = point_to_segment_dist(&pt, seg1_start, corner);
715+
double d2 = point_to_segment_dist(&pt, corner, seg2_end);
716+
double dev = fmin(d1, d2);
717+
718+
if (dev > max_dev) max_dev = dev;
719+
}
720+
return max_dev;
721+
}

src/emc/motion_planning/bezier9.h

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ extern "C" {
2626
* xyz (translation), abc (rotation), and uvw (auxiliary) coordinate spaces.
2727
*
2828
* G2 continuity is achieved by placing P0,P1,P2 collinear along the
29-
* incoming tangent and P3,P4,P5 collinear along the outgoing tangent.
30-
* This gives B''(0) = B''(1) = 0, so curvature is zero at both endpoints
31-
* — matching the zero curvature of adjacent line segments and eliminating
32-
* the centripetal acceleration discontinuity that cubic blends suffer from.
29+
* incoming tangent and P3,P4,P5 collinear along the outgoing tangent,
30+
* then offsetting P2 and P3 in the curvature normal direction to match
31+
* the curvature of adjacent segments. For line segments (κ=0) the
32+
* offset is zero and P0,P1,P2 remain collinear (B''(0)=0). For arcs
33+
* (κ=1/R) the offset δ = 5α²κ/4 gives κ_bezier(endpoint) = κ_arc,
34+
* eliminating the centripetal acceleration discontinuity at junctions.
3335
*
3436
* Arc-length parameterization is precomputed during initialization to enable
3537
* constant-velocity traversal. Curvature analysis is performed on the xyz
@@ -78,6 +80,10 @@ typedef struct {
7880
* @param u_end_abc Unit tangent vector at end (abc)
7981
* @param u_start_uvw Unit tangent vector at start (uvw)
8082
* @param u_end_uvw Unit tangent vector at end (uvw)
83+
* @param kappa_start Curvature of adjacent segment at start (0 for lines)
84+
* @param n_start Unit curvature normal at start (toward center), NULL if kappa=0
85+
* @param kappa_end Curvature of adjacent segment at end (0 for lines)
86+
* @param n_end Unit curvature normal at end (toward center), NULL if kappa=0
8187
* @param alpha Shape parameter controlling control point placement (> 0)
8288
* @return TP_ERR_OK on success, error code otherwise
8389
*/
@@ -90,6 +96,10 @@ int bezier9Init(Bezier9 * const b,
9096
PmCartesian const * const u_end_abc,
9197
PmCartesian const * const u_start_uvw,
9298
PmCartesian const * const u_end_uvw,
99+
double kappa_start,
100+
PmCartesian const * const n_start,
101+
double kappa_end,
102+
PmCartesian const * const n_end,
93103
double alpha);
94104

95105
/**
@@ -192,6 +202,43 @@ double bezier9AccLimit(Bezier9 const * const b,
192202
double bezier9Deviation(Bezier9 const * const b,
193203
PmCartesian const * const intersection_point);
194204

205+
/**
206+
* bezier9MaxDeviation - Compute maximum deviation over N sample points
207+
*
208+
* Samples the xyz Bezier at N equally-spaced t values in (0,1) and returns
209+
* the maximum distance to the intersection point. Used for diagnostic
210+
* verification of the t=0.5 single-point estimate.
211+
*
212+
* @param b Input Bezier9 curve
213+
* @param intersection_point Original corner intersection point (xyz only)
214+
* @param n_samples Number of sample points (e.g. 19 for t = 0.05, 0.10, ... 0.95)
215+
* @return Maximum deviation distance (always >= 0)
216+
*/
217+
double bezier9MaxDeviation(Bezier9 const * const b,
218+
PmCartesian const * const intersection_point,
219+
int n_samples);
220+
221+
/**
222+
* bezier9PathDeviation - Max deviation from two-segment programmed path
223+
*
224+
* Measures the maximum distance from the Bezier curve to the nearest point
225+
* on the two-segment polyline (seg1_start → corner → seg2_end). Unlike
226+
* bezier9Deviation (which measures distance to the corner only), this gives
227+
* the correct path deviation for both symmetric and asymmetric blends.
228+
*
229+
* @param b Input Bezier9 curve
230+
* @param seg1_start Start of first segment (xyz, = blend P_start)
231+
* @param corner Junction point between segments (xyz, = intersection_point)
232+
* @param seg2_end End of second segment (xyz, = blend P_end)
233+
* @param n_samples Number of sample points (9 is sufficient)
234+
* @return Maximum path deviation (always >= 0)
235+
*/
236+
double bezier9PathDeviation(Bezier9 const * const b,
237+
PmCartesian const * const seg1_start,
238+
PmCartesian const * const corner,
239+
PmCartesian const * const seg2_end,
240+
int n_samples);
241+
195242
#ifdef __cplusplus
196243
}
197244
#endif

0 commit comments

Comments
 (0)