按建议,继续下一步工作

结论:已将 LinuxCNC TP 源码纳入 wasm-port 的可复现 vendor 清单和 native probe 构建,新增真实 tpCreate/tpAddLine/tpRunCycle 线性运动验证,并通过 native probes 全量验证。
This commit is contained in:
2026-06-07 07:39:26 +08:00
parent 078f1f6f8e
commit e328394184
72 changed files with 27200 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,281 @@
/********************************************************************
* Description: blendmath.h
* Circular arc blend math functions
*
* Author: Robert W. Ellenberg
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2014 All rights reserved.
*
* Last change:
********************************************************************/
#ifndef BLENDMATH_H
#define BLENDMATH_H
#include <posemath.h>
#include "tc_types.h"
#include "sp_scurve.h"
#define BLEND_ACC_RATIO_TANGENTIAL 0.5
#define BLEND_ACC_RATIO_NORMAL (pmSqrt(1.0 - pmSq(BLEND_ACC_RATIO_TANGENTIAL)))
#define BLEND_KINK_FACTOR 0.25
typedef enum {
BLEND_NONE,
BLEND_LINE_LINE,
BLEND_LINE_ARC,
BLEND_ARC_LINE,
BLEND_ARC_ARC,
} blend_type_t;
/**
* 3D Input geometry for a spherical blend arc.
* This structure contains all of the basic geometry in 3D for a blend arc.
*/
typedef struct {
PmCartesian u1; /* unit vector along line 1 */
PmCartesian u2; /* unit vector along line 2 */
PmCartesian P; /* Intersection point */
PmCartesian normal; /* normal unit vector to plane containing lines */
PmCartesian binormal; /* binormal unit vector to plane containing lines */
PmCartesian u_tan1; /* Actual tangent vector to 1 (used for arcs only) */
PmCartesian u_tan2; /* Actual tangent vector to 2 (used for arcs only) */
PmCartesian center1; /* Local approximation of center for arc 1 */
PmCartesian center2; /* Local approximation of center for arc 2 */
double radius1; /* Local approximation of radius */
double radius2;
double theta_tan;
double v_max1; /* maximum velocity in direction u_tan1 */
double v_max2; /* maximum velocity in direction u_tan2 */
} BlendGeom3;
/**
* 9D Input geometry for a spherical blend arc.
*/
#ifdef BLEND_9D
typedef struct {
//Not implemented yet
} BlendGeom9;
#endif
/**
* Blend arc parameters (abstracted).
* This structure holds blend arc parameters that have been abstracted from the
* physical geometry. This data is used to find the maximum radius given the
* constraints on the blend. By abstracting the parameters from the geometry,
* the same calculations can be used with any input geometry (lines, arcs, 6 or
* 9 dimensional lines).
*/
typedef struct {
double tolerance; /* Net blend tolerance (min of line 1 and 2) */
double L1; /* Available part of line 1 to blend over */
double L2; /* Available part of line 2 to blend over */
double v_req; /* requested velocity for the blend arc */
double a_max; /* max acceleration allowed for blend */
/* These fields are considered "output", and may be refactored into a
* separate structure in the future */
double theta; /* Intersection angle, half of angle between -u1 and u2 */
double phi; /* supplement of intersection angle, angle between u1 and u2 */
double a_n_max; /* max normal acceleration allowed */
double R_plan; /* planned radius for blend arc */
double d_plan; /* distance along each line to arc endpoints */
double v_goal; /* desired velocity at max feed override */
double v_plan; /* planned max velocity at max feed override */
double v_actual; /* velocity at feedscale = 1.0 */
double s_arc; /* arc length */
int consume; /* Consume the previous segment */
double line_length;
//Arc specific stuff
int convex1;
int convex2;
double phi1_max;
double phi2_max;
} BlendParameters;
/**
* Output geometry in 3D.
* Stores the three points representing a simple 3D spherical arc.
*/
typedef struct {
PmCartesian arc_start; /* start point for blend arc */
PmCartesian arc_end; /* end point for blend arc */
PmCartesian arc_center; /* center point for blend arc */
double trim1; /* length (line) or angle (arc) to cut from prev_tc */
double trim2; /* length (line) or angle (arc) to cut from tc */
} BlendPoints3;
#ifdef BLEND_9D
typedef struct {
//Not implemented yet
} BlendPoints9;
#endif
double findMaxTangentAngle(double v, double acc, double cycle_time);
double findKinkAccel(double kink_angle, double v_plan, double cycle_time);
double fsign(double f);
int clip_min(double * const x, double min);
int clip_max(double * const x, double max);
double saturate(double x, double max);
double bisaturate(double x, double max, double min);
int sat_inplace(double * const x, double max);
int checkTangentAngle(PmCircle const * const circ, SphericalArc const * const arc, BlendGeom3 const * const geom, BlendParameters const * const param, double cycle_time, int at_end);
int findIntersectionAngle(PmCartesian const * const u1,
PmCartesian const * const u2, double * const theta);
double pmCartMin(PmCartesian const * const in);
int calculateInscribedDiameter(PmCartesian const * const normal,
PmCartesian const * const bounds, double * const diameter);
int findAccelScale(PmCartesian const * const acc,
PmCartesian const * const bounds,
PmCartesian * const scale);
int pmUnitCartsColinear(PmCartesian const * const u1,
PmCartesian const * const u2);
int pmCartCartParallel(PmCartesian const * const u1,
PmCartesian const * const u2,
double tol);
int pmCartCartAntiParallel(PmCartesian const * const u1,
PmCartesian const * const u2,
double tol);
int pmCircLineCoplanar(PmCircle const * const circ,
PmCartLine const * const line, double tol);
int blendCoplanarCheck(PmCartesian const * const normal,
PmCartesian const * const u1_tan,
PmCartesian const * const u2_tan,
double tol);
int blendCalculateNormals3(BlendGeom3 * const geom);
int blendComputeParameters(BlendParameters * const param);
int blendCheckConsume(BlendParameters * const param,
BlendPoints3 const * const points,
TC_STRUCT const * const prev_tc, int gap_cycles);
int blendFindPoints3(BlendPoints3 * const points, BlendGeom3 const * const geom,
BlendParameters const * const param);
int blendGeom3Init(BlendGeom3 * const geom,
TC_STRUCT const * const prev_tc,
TC_STRUCT const * const tc);
int blendParamKinematics(BlendGeom3 * const geom,
BlendParameters * const param,
TC_STRUCT const * const prev_tc,
TC_STRUCT const * const tc,
PmCartesian const * const acc_bound,
PmCartesian const * const vel_bound,
double maxFeedScale);
int blendInit3FromLineLine(BlendGeom3 * const geom, BlendParameters * const param,
TC_STRUCT const * const prev_tc,
TC_STRUCT const * const tc,
PmCartesian const * const acc_bound,
PmCartesian const * const vel_bound,
double maxFeedScale);
int blendInit3FromLineArc(BlendGeom3 * const geom, BlendParameters * const param,
TC_STRUCT const * const prev_tc,
TC_STRUCT const * const tc,
PmCartesian const * const acc_bound,
PmCartesian const * const vel_bound,
double maxFeedScale);
int blendInit3FromArcLine(BlendGeom3 * const geom, BlendParameters * const param,
TC_STRUCT const * const prev_tc,
TC_STRUCT const * const tc,
PmCartesian const * const acc_bound,
PmCartesian const * const vel_bound,
double maxFeedScale);
int blendInit3FromArcArc(BlendGeom3 * const geom, BlendParameters * const param,
TC_STRUCT const * const prev_tc,
TC_STRUCT const * const tc,
PmCartesian const * const acc_bound,
PmCartesian const * const vel_bound,
double maxFeedScale);
int blendArcArcPostProcess(BlendPoints3 * const points, BlendPoints3 const * const points_in,
BlendParameters * const param, BlendGeom3 const * const geom,
PmCircle const * const circ1, PmCircle const * const circ2);
int blendLineArcPostProcess(BlendPoints3 * const points, BlendPoints3 const * const points_in,
BlendParameters * const param, BlendGeom3 const * const geom,
PmCartLine const * const line1, PmCircle const * const circ2);
int blendArcLinePostProcess(BlendPoints3 * const points, BlendPoints3 const * const points_in,
BlendParameters * const param, BlendGeom3 const * const geom,
PmCircle const * const circ1, PmCartLine const * const line2);
int arcFromBlendPoints3(SphericalArc * const arc, BlendPoints3 const * const points,
BlendGeom3 const * const geom, BlendParameters const * const param);
//Not implemented yet
int blendGeom3Print(BlendGeom3 const * const geom);
int blendParamPrint(BlendParameters const * const param);
int blendPoints3Print(BlendPoints3 const * const points);
double pmCartAbsMax(PmCartesian const * const v);
int findSpiralArcLengthFit(PmCircle const * const circle,
SpiralArcLengthFit * const fit);
int pmCircleAngleFromProgress(PmCircle const * const circle,
SpiralArcLengthFit const * const fit,
double progress,
double * const angle);
double pmCircleEffectiveMinRadius(const PmCircle *circle);
static inline double findVPeak(double a_t_max, double distance)
{
return pmSqrt(a_t_max * distance);
}
static inline double findSCurveVPeak(double a_t_max, double j_t_max, double distance)
{
// Parameter validation
if (a_t_max <= 0.0 || j_t_max <= 0.0 || distance <= 0.0) {
return 0.0;
}
double triangular_v = findVPeak(a_t_max, distance);
double req_v;
int result = findSCurveVSpeed(distance, a_t_max, j_t_max, &req_v);
// If the S-curve calculation fails, revert to the simpler triangular velocity calculation.
if (result != 1) {
return triangular_v;
}
// Take the smaller value between the S-curve velocity and the triangular velocity.
return fmin(req_v, triangular_v);
}
#endif

View File

@@ -0,0 +1,138 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "block.h"
static inline double cruckig_profile_total_duration(const CRuckigProfile *p) {
return p->t_sum[6] + p->brake.duration + p->accel.duration;
}
static void remove_profile(CRuckigProfile *valid_profiles, size_t *valid_profile_counter, size_t index) {
for (size_t i = index; i < *valid_profile_counter - 1; ++i) {
valid_profiles[i] = valid_profiles[i + 1];
}
*valid_profile_counter -= 1;
}
static void interval_from_profiles(CRuckigInterval *iv, const CRuckigProfile *profile_left, const CRuckigProfile *profile_right) {
const double left_duration = cruckig_profile_total_duration(profile_left);
const double right_duration = cruckig_profile_total_duration(profile_right);
if (left_duration < right_duration) {
iv->left = left_duration;
iv->right = right_duration;
iv->profile = *profile_right;
} else {
iv->left = right_duration;
iv->right = left_duration;
iv->profile = *profile_left;
}
iv->valid = true;
}
void cruckig_block_init(CRuckigBlock *block) {
cruckig_profile_init(&block->p_min);
block->t_min = 0.0;
block->a.valid = false;
block->b.valid = false;
}
void cruckig_block_set_min_profile(CRuckigBlock *block, const CRuckigProfile *profile) {
block->p_min = *profile;
block->t_min = cruckig_profile_total_duration(profile);
block->a.valid = false;
block->b.valid = false;
}
bool cruckig_block_calculate(CRuckigBlock *block, CRuckigProfile *valid_profiles,
size_t valid_profile_counter, size_t max_profiles) {
(void)max_profiles;
if (valid_profile_counter == 1) {
cruckig_block_set_min_profile(block, &valid_profiles[0]);
return true;
} else if (valid_profile_counter == 2) {
if (fabs(valid_profiles[0].t_sum[6] - valid_profiles[1].t_sum[6]) < 8 * DBL_EPSILON) {
cruckig_block_set_min_profile(block, &valid_profiles[0]);
return true;
}
/* numerical_robust = true */
{
const size_t idx_min = (valid_profiles[0].t_sum[6] < valid_profiles[1].t_sum[6]) ? 0 : 1;
const size_t idx_else_1 = (idx_min + 1) % 2;
cruckig_block_set_min_profile(block, &valid_profiles[idx_min]);
interval_from_profiles(&block->a, &valid_profiles[idx_min], &valid_profiles[idx_else_1]);
return true;
}
/* Only happens due to numerical issues */
} else if (valid_profile_counter == 4) {
/* Find "identical" profiles */
if (fabs(valid_profiles[0].t_sum[6] - valid_profiles[1].t_sum[6]) < 32 * DBL_EPSILON && valid_profiles[0].direction != valid_profiles[1].direction) {
remove_profile(valid_profiles, &valid_profile_counter, 1);
} else if (fabs(valid_profiles[2].t_sum[6] - valid_profiles[3].t_sum[6]) < 256 * DBL_EPSILON && valid_profiles[2].direction != valid_profiles[3].direction) {
remove_profile(valid_profiles, &valid_profile_counter, 3);
} else if (fabs(valid_profiles[0].t_sum[6] - valid_profiles[3].t_sum[6]) < 256 * DBL_EPSILON && valid_profiles[0].direction != valid_profiles[3].direction) {
remove_profile(valid_profiles, &valid_profile_counter, 3);
} else {
return false;
}
} else if (valid_profile_counter % 2 == 0) {
return false;
}
/* Find index of fastest profile */
size_t idx_min = 0;
for (size_t i = 1; i < valid_profile_counter; ++i) {
if (valid_profiles[i].t_sum[6] < valid_profiles[idx_min].t_sum[6]) {
idx_min = i;
}
}
cruckig_block_set_min_profile(block, &valid_profiles[idx_min]);
if (valid_profile_counter == 3) {
const size_t idx_else_1 = (idx_min + 1) % 3;
const size_t idx_else_2 = (idx_min + 2) % 3;
interval_from_profiles(&block->a, &valid_profiles[idx_else_1], &valid_profiles[idx_else_2]);
return true;
} else if (valid_profile_counter == 5) {
const size_t idx_else_1 = (idx_min + 1) % 5;
const size_t idx_else_2 = (idx_min + 2) % 5;
const size_t idx_else_3 = (idx_min + 3) % 5;
const size_t idx_else_4 = (idx_min + 4) % 5;
if (valid_profiles[idx_else_1].direction == valid_profiles[idx_else_2].direction) {
interval_from_profiles(&block->a, &valid_profiles[idx_else_1], &valid_profiles[idx_else_2]);
interval_from_profiles(&block->b, &valid_profiles[idx_else_3], &valid_profiles[idx_else_4]);
} else {
interval_from_profiles(&block->a, &valid_profiles[idx_else_1], &valid_profiles[idx_else_4]);
interval_from_profiles(&block->b, &valid_profiles[idx_else_2], &valid_profiles[idx_else_3]);
}
return true;
}
return false;
}
/* cruckig_block_is_blocked is now inlined in block.h */
const CRuckigProfile* cruckig_block_get_profile(const CRuckigBlock *block, double t) {
if (block->b.valid && t >= block->b.right) {
return &block->b.profile;
}
if (block->a.valid && t >= block->a.right) {
return &block->a.profile;
}
return &block->p_min;
}

View File

@@ -0,0 +1,43 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_BLOCK_H
#define CRUCKIG_BLOCK_H
#include "cruckig_internal.h"
#include "profile.h"
typedef struct {
double left, right;
CRuckigProfile profile;
bool valid;
} CRuckigInterval;
typedef struct {
CRuckigProfile p_min;
double t_min;
CRuckigInterval a;
CRuckigInterval b;
} CRuckigBlock;
void cruckig_block_init(CRuckigBlock *block);
void cruckig_block_set_min_profile(CRuckigBlock *block, const CRuckigProfile *profile);
/* Calculate block from valid profiles. Returns true if successful. */
bool cruckig_block_calculate(CRuckigBlock *block, CRuckigProfile *valid_profiles,
size_t valid_profile_counter, size_t max_profiles);
/* Inlined for hot-path performance (called in tight synchronization loop) */
CRUCKIG_FORCE_INLINE bool cruckig_block_is_blocked(const CRuckigBlock *block, double t) {
return (t < block->t_min)
|| (block->a.valid && block->a.left < t && t < block->a.right)
|| (block->b.valid && block->b.left < t && t < block->b.right);
}
const CRuckigProfile* cruckig_block_get_profile(const CRuckigBlock *block, double t);
#endif /* CRUCKIG_BLOCK_H */

View File

@@ -0,0 +1,201 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "brake.h"
#include "utils.h"
static const double brake_eps = 2.2e-14;
void cruckig_brake_init(CRuckigBrakeProfile *bp) {
bp->duration = 0.0;
bp->t[0] = 0.0;
bp->t[1] = 0.0;
bp->j[0] = 0.0;
bp->j[1] = 0.0;
bp->a[0] = 0.0;
bp->a[1] = 0.0;
bp->v[0] = 0.0;
bp->v[1] = 0.0;
bp->p[0] = 0.0;
bp->p[1] = 0.0;
}
static inline double brake_v_at_t(double v0, double a0, double j, double t) {
return v0 + t * (a0 + j * t / 2);
}
static inline double brake_v_at_a_zero(double v0, double a0, double j) {
return v0 + (a0 * a0) / (2 * j);
}
static void acceleration_brake(CRuckigBrakeProfile *bp, double v0, double a0,
double vMax, double vMin, double aMax, double aMin, double jMax);
static void velocity_brake(CRuckigBrakeProfile *bp, double v0, double a0,
double vMax, double vMin, double aMax, double aMin, double jMax);
static void acceleration_brake(CRuckigBrakeProfile *bp, double v0, double a0,
double vMax, double vMin, double aMax, double aMin, double jMax) {
bp->j[0] = -jMax;
const double t_to_a_max = (a0 - aMax) / jMax;
const double t_to_a_zero = a0 / jMax;
const double v_at_a_max = brake_v_at_t(v0, a0, -jMax, t_to_a_max);
const double v_at_a_zero_val = brake_v_at_t(v0, a0, -jMax, t_to_a_zero);
if ((v_at_a_zero_val > vMax && jMax > 0) || (v_at_a_zero_val < vMax && jMax < 0)) {
velocity_brake(bp, v0, a0, vMax, vMin, aMax, aMin, jMax);
} else if ((v_at_a_max < vMin && jMax > 0) || (v_at_a_max > vMin && jMax < 0)) {
const double t_to_v_min = -(v_at_a_max - vMin) / aMax;
const double t_to_v_max = -aMax / (2 * jMax) - (v_at_a_max - vMax) / aMax;
bp->t[0] = t_to_a_max + brake_eps;
{
double val = t_to_v_min < (t_to_v_max - brake_eps) ? t_to_v_min : (t_to_v_max - brake_eps);
bp->t[1] = val > 0.0 ? val : 0.0;
}
} else {
bp->t[0] = t_to_a_max + brake_eps;
}
}
static void velocity_brake(CRuckigBrakeProfile *bp, double v0, double a0,
double vMax, double vMin, double aMax, double aMin, double jMax) {
(void)aMax;
bp->j[0] = -jMax;
const double t_to_a_min = (a0 - aMin) / jMax;
const double t_to_v_max = a0 / jMax + sqrt(a0 * a0 + 2 * jMax * (v0 - vMax)) / fabs(jMax);
const double t_to_v_min = a0 / jMax + sqrt(a0 * a0 / 2 + jMax * (v0 - vMin)) / fabs(jMax);
const double t_min_to_v_max = t_to_v_max < t_to_v_min ? t_to_v_max : t_to_v_min;
if (t_to_a_min < t_min_to_v_max) {
const double v_at_a_min = brake_v_at_t(v0, a0, -jMax, t_to_a_min);
const double t_to_v_max_with_constant = -(v_at_a_min - vMax) / aMin;
const double t_to_v_min_with_constant = aMin / (2 * jMax) - (v_at_a_min - vMin) / aMin;
bp->t[0] = (t_to_a_min - brake_eps) > 0.0 ? (t_to_a_min - brake_eps) : 0.0;
{
double val = t_to_v_max_with_constant < t_to_v_min_with_constant ? t_to_v_max_with_constant : t_to_v_min_with_constant;
bp->t[1] = val > 0.0 ? val : 0.0;
}
} else {
bp->t[0] = (t_min_to_v_max - brake_eps) > 0.0 ? (t_min_to_v_max - brake_eps) : 0.0;
}
}
void cruckig_brake_get_position_brake_trajectory(CRuckigBrakeProfile *bp, double v0, double a0,
double vMax, double vMin, double aMax, double aMin, double jMax) {
bp->t[0] = 0.0;
bp->t[1] = 0.0;
bp->j[0] = 0.0;
bp->j[1] = 0.0;
if (jMax == 0.0 || aMax == 0.0 || aMin == 0.0) {
return; /* Ignore braking for zero-limits */
}
if (a0 > aMax) {
acceleration_brake(bp, v0, a0, vMax, vMin, aMax, aMin, jMax);
} else if (a0 < aMin) {
acceleration_brake(bp, v0, a0, vMin, vMax, aMin, aMax, -jMax);
} else if ((v0 > vMax && brake_v_at_a_zero(v0, a0, -jMax) > vMin) || (a0 > 0 && brake_v_at_a_zero(v0, a0, jMax) > vMax)) {
velocity_brake(bp, v0, a0, vMax, vMin, aMax, aMin, jMax);
} else if ((v0 < vMin && brake_v_at_a_zero(v0, a0, jMax) < vMax) || (a0 < 0 && brake_v_at_a_zero(v0, a0, -jMax) < vMin)) {
velocity_brake(bp, v0, a0, vMin, vMax, aMin, aMax, -jMax);
}
}
void cruckig_brake_get_second_order_position_brake_trajectory(CRuckigBrakeProfile *bp, double v0,
double vMax, double vMin, double aMax, double aMin) {
bp->t[0] = 0.0;
bp->t[1] = 0.0;
bp->j[0] = 0.0;
bp->j[1] = 0.0;
bp->a[0] = 0.0;
bp->a[1] = 0.0;
if (aMax == 0.0 || aMin == 0.0) {
return; /* Ignore braking for zero-limits */
}
if (v0 > vMax) {
bp->a[0] = aMin;
bp->t[0] = (vMax - v0) / aMin + brake_eps;
} else if (v0 < vMin) {
bp->a[0] = aMax;
bp->t[0] = (vMin - v0) / aMax + brake_eps;
}
}
void cruckig_brake_get_velocity_brake_trajectory(CRuckigBrakeProfile *bp, double a0,
double aMax, double aMin, double jMax) {
bp->t[0] = 0.0;
bp->t[1] = 0.0;
bp->j[0] = 0.0;
bp->j[1] = 0.0;
if (jMax == 0.0) {
return; /* Ignore braking for zero-limits */
}
if (a0 > aMax) {
bp->j[0] = -jMax;
bp->t[0] = (a0 - aMax) / jMax + brake_eps;
} else if (a0 < aMin) {
bp->j[0] = jMax;
bp->t[0] = -(a0 - aMin) / jMax + brake_eps;
}
}
void cruckig_brake_get_second_order_velocity_brake_trajectory(CRuckigBrakeProfile *bp) {
bp->t[0] = 0.0;
bp->t[1] = 0.0;
bp->j[0] = 0.0;
bp->j[1] = 0.0;
}
void cruckig_brake_finalize(CRuckigBrakeProfile *bp, double *ps, double *vs, double *as) {
if (bp->t[0] <= 0.0 && bp->t[1] <= 0.0) {
bp->duration = 0.0;
return;
}
bp->duration = bp->t[0];
bp->p[0] = *ps;
bp->v[0] = *vs;
bp->a[0] = *as;
cruckig_integrate(bp->t[0], *ps, *vs, *as, bp->j[0], ps, vs, as);
if (bp->t[1] > 0.0) {
bp->duration += bp->t[1];
bp->p[1] = *ps;
bp->v[1] = *vs;
bp->a[1] = *as;
cruckig_integrate(bp->t[1], *ps, *vs, *as, bp->j[1], ps, vs, as);
}
}
void cruckig_brake_finalize_second_order(CRuckigBrakeProfile *bp, double *ps, double *vs, double *as) {
if (bp->t[0] <= 0.0) {
bp->duration = 0.0;
return;
}
bp->duration = bp->t[0];
bp->p[0] = *ps;
bp->v[0] = *vs;
cruckig_integrate(bp->t[0], *ps, *vs, bp->a[0], 0.0, ps, vs, as);
}

View File

@@ -0,0 +1,38 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_BRAKE_H
#define CRUCKIG_BRAKE_H
#include "cruckig_internal.h"
/* Two-phase brake profile */
typedef struct {
double duration;
double t[2];
double j[2];
double a[2];
double v[2];
double p[2];
} CRuckigBrakeProfile;
void cruckig_brake_init(CRuckigBrakeProfile *bp);
/* Calculate brake trajectories */
void cruckig_brake_get_position_brake_trajectory(CRuckigBrakeProfile *bp, double v0, double a0,
double vMax, double vMin, double aMax, double aMin, double jMax);
void cruckig_brake_get_second_order_position_brake_trajectory(CRuckigBrakeProfile *bp, double v0,
double vMax, double vMin, double aMax, double aMin);
void cruckig_brake_get_velocity_brake_trajectory(CRuckigBrakeProfile *bp, double a0,
double aMax, double aMin, double jMax);
void cruckig_brake_get_second_order_velocity_brake_trajectory(CRuckigBrakeProfile *bp);
/* Finalize by integrating */
void cruckig_brake_finalize(CRuckigBrakeProfile *bp, double *ps, double *vs, double *as);
void cruckig_brake_finalize_second_order(CRuckigBrakeProfile *bp, double *ps, double *vs, double *as);
#endif /* CRUCKIG_BRAKE_H */

View File

@@ -0,0 +1,950 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "calculator.h"
#include "position.h"
#include "velocity.h"
#include "utils.h"
static const double eps = DBL_EPSILON;
CRuckigCalculator* cruckig_calculator_create(size_t dofs) {
CRuckigCalculator *calc = (CRuckigCalculator*)cruckig_calloc(1, sizeof(CRuckigCalculator));
if (!calc) return NULL;
calc->degrees_of_freedom = dofs;
calc->new_phase_control = (double*)cruckig_calloc(dofs, sizeof(double));
calc->pd = (double*)cruckig_calloc(dofs, sizeof(double));
calc->possible_t_syncs = (double*)cruckig_calloc(3 * dofs + 1, sizeof(double));
calc->idx = (size_t*)cruckig_calloc(3 * dofs + 1, sizeof(size_t));
calc->blocks = (CRuckigBlock*)cruckig_calloc(dofs, sizeof(CRuckigBlock));
calc->inp_min_velocity = (double*)cruckig_calloc(dofs, sizeof(double));
calc->inp_min_acceleration = (double*)cruckig_calloc(dofs, sizeof(double));
calc->inp_per_dof_control_interface = (CRuckigControlInterface*)cruckig_calloc(dofs, sizeof(CRuckigControlInterface));
calc->inp_per_dof_synchronization = (CRuckigSynchronization*)cruckig_calloc(dofs, sizeof(CRuckigSynchronization));
calc->segment_input = NULL; /* Created on demand for waypoint calculation */
if (!calc->new_phase_control || !calc->pd || !calc->possible_t_syncs ||
!calc->idx || !calc->blocks || !calc->inp_min_velocity ||
!calc->inp_min_acceleration || !calc->inp_per_dof_control_interface ||
!calc->inp_per_dof_synchronization) {
cruckig_calculator_destroy(calc);
return NULL;
}
for (size_t i = 0; i < dofs; ++i) {
cruckig_block_init(&calc->blocks[i]);
}
return calc;
}
void cruckig_calculator_destroy(CRuckigCalculator *calc) {
if (!calc) return;
cruckig_free(calc->new_phase_control);
cruckig_free(calc->pd);
cruckig_free(calc->possible_t_syncs);
cruckig_free(calc->idx);
cruckig_free(calc->blocks);
cruckig_free(calc->inp_min_velocity);
cruckig_free(calc->inp_min_acceleration);
cruckig_free(calc->inp_per_dof_control_interface);
cruckig_free(calc->inp_per_dof_synchronization);
cruckig_input_destroy(calc->segment_input);
cruckig_free(calc);
}
/* Is the trajectory (in principle) phase synchronizable? */
static bool is_input_collinear(CRuckigCalculator *calc,
const CRuckigInputParameter *inp,
CRuckigDirection limiting_direction,
size_t limiting_dof)
{
const size_t dofs = calc->degrees_of_freedom;
/* Compute pd = target_position - current_position */
for (size_t dof = 0; dof < dofs; ++dof) {
calc->pd[dof] = inp->target_position[dof] - inp->current_position[dof];
}
/* Find scale vector and scale DOF */
const double *scale_vector = NULL;
size_t scale_dof = 0;
bool scale_dof_found = false;
for (size_t dof = 0; dof < dofs; ++dof) {
if (calc->inp_per_dof_synchronization[dof] != CRuckigSyncPhase) {
continue;
}
if (calc->inp_per_dof_control_interface[dof] == CRuckigPosition && fabs(calc->pd[dof]) > eps) {
scale_vector = calc->pd;
scale_dof = dof;
scale_dof_found = true;
break;
} else if (fabs(inp->current_velocity[dof]) > eps) {
scale_vector = inp->current_velocity;
scale_dof = dof;
scale_dof_found = true;
break;
} else if (fabs(inp->current_acceleration[dof]) > eps) {
scale_vector = inp->current_acceleration;
scale_dof = dof;
scale_dof_found = true;
break;
} else if (fabs(inp->target_velocity[dof]) > eps) {
scale_vector = inp->target_velocity;
scale_dof = dof;
scale_dof_found = true;
break;
} else if (fabs(inp->target_acceleration[dof]) > eps) {
scale_vector = inp->target_acceleration;
scale_dof = dof;
scale_dof_found = true;
break;
}
}
if (!scale_dof_found) {
return false;
}
const double scale = scale_vector[scale_dof];
const double pd_scale = calc->pd[scale_dof] / scale;
const double v0_scale = inp->current_velocity[scale_dof] / scale;
const double vf_scale = inp->target_velocity[scale_dof] / scale;
const double a0_scale = inp->current_acceleration[scale_dof] / scale;
const double af_scale = inp->target_acceleration[scale_dof] / scale;
const double scale_limiting = scale_vector[limiting_dof];
double control_limiting;
if (isinf(inp->max_jerk[limiting_dof])) {
control_limiting = (limiting_direction == DirectionUP)
? inp->max_acceleration[limiting_dof]
: calc->inp_min_acceleration[limiting_dof];
} else {
control_limiting = (limiting_direction == DirectionUP)
? inp->max_jerk[limiting_dof]
: -inp->max_jerk[limiting_dof];
}
for (size_t dof = 0; dof < dofs; ++dof) {
if (calc->inp_per_dof_synchronization[dof] != CRuckigSyncPhase) {
continue;
}
const double current_scale = scale_vector[dof];
if (
(calc->inp_per_dof_control_interface[dof] == CRuckigPosition && fabs(calc->pd[dof] - pd_scale * current_scale) > eps)
|| fabs(inp->current_velocity[dof] - v0_scale * current_scale) > eps
|| fabs(inp->current_acceleration[dof] - a0_scale * current_scale) > eps
|| fabs(inp->target_velocity[dof] - vf_scale * current_scale) > eps
|| fabs(inp->target_acceleration[dof] - af_scale * current_scale) > eps
) {
return false;
}
calc->new_phase_control[dof] = control_limiting * current_scale / scale_limiting;
}
return true;
}
/* Simple insertion sort for index array by values */
static void sort_indices(size_t *idx_arr, const double *values, size_t count) {
for (size_t i = 1; i < count; ++i) {
size_t key = idx_arr[i];
double key_val = values[key];
size_t j = i;
while (j > 0 && values[idx_arr[j - 1]] > key_val) {
idx_arr[j] = idx_arr[j - 1];
--j;
}
idx_arr[j] = key;
}
}
/*
* synchronize: Find a valid synchronization time.
* Returns true if found; sets t_sync, limiting_dof, and updates profiles.
*
* has_t_min: whether t_min is valid
* t_min: minimum duration
* limiting_dof_out: set to the limiting DOF index; has_limiting_dof set to true/false
*/
static bool synchronize(CRuckigCalculator *calc,
bool has_t_min, double t_min,
double *t_sync,
bool *has_limiting_dof, size_t *limiting_dof_out,
CRuckigProfile *profiles,
bool discrete_duration, double delta_time)
{
const size_t dofs = calc->degrees_of_freedom;
/* Fill possible_t_syncs */
bool any_interval = false;
for (size_t dof = 0; dof < dofs; ++dof) {
if (calc->inp_per_dof_synchronization[dof] == CRuckigSyncNone) {
calc->possible_t_syncs[dof] = 0.0;
calc->possible_t_syncs[dofs + dof] = INFINITY;
calc->possible_t_syncs[2 * dofs + dof] = INFINITY;
continue;
}
calc->possible_t_syncs[dof] = calc->blocks[dof].t_min;
calc->possible_t_syncs[dofs + dof] = calc->blocks[dof].a.valid
? calc->blocks[dof].a.right : INFINITY;
calc->possible_t_syncs[2 * dofs + dof] = calc->blocks[dof].b.valid
? calc->blocks[dof].b.right : INFINITY;
any_interval = any_interval || calc->blocks[dof].a.valid || calc->blocks[dof].b.valid;
}
calc->possible_t_syncs[3 * dofs] = has_t_min ? t_min : INFINITY;
any_interval = any_interval || has_t_min;
/* Discrete duration rounding */
if (discrete_duration) {
size_t count = 3 * dofs + 1;
for (size_t i = 0; i < count; ++i) {
if (isinf(calc->possible_t_syncs[i])) continue;
double remainder = fmod(calc->possible_t_syncs[i], delta_time);
if (remainder > eps) {
calc->possible_t_syncs[i] += delta_time - remainder;
}
}
}
/* Initialize and sort indices */
size_t idx_end_count = any_interval ? (3 * dofs + 1) : dofs;
for (size_t i = 0; i < idx_end_count; ++i) {
calc->idx[i] = i;
}
sort_indices(calc->idx, calc->possible_t_syncs, idx_end_count);
/* Start at dofs-1 (skip the dofs-1 smallest t_min values since we need ALL dofs at or past their t_min) */
size_t start_idx = (dofs >= 1) ? (dofs - 1) : 0;
for (size_t iter = start_idx; iter < idx_end_count; ++iter) {
size_t i = calc->idx[iter];
double possible_t_sync = calc->possible_t_syncs[i];
/* Check if any DOF is blocked */
bool is_blocked = false;
for (size_t dof = 0; dof < dofs; ++dof) {
if (calc->inp_per_dof_synchronization[dof] == CRuckigSyncNone) {
continue;
}
if (cruckig_block_is_blocked(&calc->blocks[dof], possible_t_sync)) {
is_blocked = true;
break;
}
}
double t_min_or_zero = has_t_min ? t_min : 0.0;
if (is_blocked || possible_t_sync < t_min_or_zero || isinf(possible_t_sync)) {
continue;
}
*t_sync = possible_t_sync;
if (i == 3 * dofs) {
/* Optional t_min was the winning candidate */
*has_limiting_dof = false;
return true;
}
/* Determine which DOF and which block part */
size_t quot = i / dofs;
size_t rem = i % dofs;
*limiting_dof_out = rem;
*has_limiting_dof = true;
switch (quot) {
case 0:
profiles[rem] = calc->blocks[rem].p_min;
break;
case 1:
profiles[rem] = calc->blocks[rem].a.profile;
break;
case 2:
profiles[rem] = calc->blocks[rem].b.profile;
break;
}
return true;
}
return false;
}
CRUCKIG_HOT
/*
* Find the optimal profile for a single DOF (Step 1).
* Separated to keep large Step1 structs (~3.6KB) off the main function's stack,
* which matters for the kernel's limited stack size.
*/
static bool find_profile_step1(
CRuckigCalculator *calc,
const CRuckigInputParameter *inp,
CRuckigProfile *p,
size_t dof)
{
switch (calc->inp_per_dof_control_interface[dof]) {
case CRuckigPosition: {
if (!isinf(inp->max_jerk[dof])) {
CRuckigPositionThirdOrderStep1 *step1 = &calc->step1_workspace.pos3_step1;
cruckig_pos3_step1_init(step1,
p->p[0], p->v[0], p->a[0], p->pf, p->vf, p->af,
inp->max_velocity[dof], calc->inp_min_velocity[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof],
inp->max_jerk[dof]);
return cruckig_pos3_step1_get_profile(step1, p, &calc->blocks[dof]);
} else if (!isinf(inp->max_acceleration[dof])) {
CRuckigPositionSecondOrderStep1 *step1 = &calc->step1_workspace.pos2_step1;
cruckig_pos2_step1_init(step1,
p->p[0], p->v[0], p->pf, p->vf,
inp->max_velocity[dof], calc->inp_min_velocity[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof]);
return cruckig_pos2_step1_get_profile(step1, p, &calc->blocks[dof]);
} else {
CRuckigPositionFirstOrderStep1 *step1 = &calc->step1_workspace.pos1_step1;
cruckig_pos1_step1_init(step1,
p->p[0], p->pf,
inp->max_velocity[dof], calc->inp_min_velocity[dof]);
return cruckig_pos1_step1_get_profile(step1, p, &calc->blocks[dof]);
}
} break;
case CRuckigVelocity: {
if (!isinf(inp->max_jerk[dof])) {
CRuckigVelocityThirdOrderStep1 *step1 = &calc->step1_workspace.vel3_step1;
cruckig_vel3_step1_init(step1,
p->v[0], p->a[0], p->vf, p->af,
inp->max_acceleration[dof], calc->inp_min_acceleration[dof],
inp->max_jerk[dof]);
return cruckig_vel3_step1_get_profile(step1, p, &calc->blocks[dof]);
} else {
CRuckigVelocitySecondOrderStep1 *step1 = &calc->step1_workspace.vel2_step1;
cruckig_vel2_step1_init(step1,
p->v[0], p->vf,
inp->max_acceleration[dof], calc->inp_min_acceleration[dof]);
return cruckig_vel2_step1_get_profile(step1, p, &calc->blocks[dof]);
}
} break;
}
return false;
}
CRuckigResult cruckig_calculator_calculate(CRuckigCalculator *calc,
const CRuckigInputParameter *inp,
CRuckigTrajectory *traj,
double delta_time,
bool *was_interrupted)
{
*was_interrupted = false;
const size_t dofs = calc->degrees_of_freedom;
for (size_t dof = 0; dof < dofs; ++dof) {
CRuckigProfile *p = &traj->profiles[dof];
calc->inp_min_velocity[dof] = inp->min_velocity
? inp->min_velocity[dof] : -inp->max_velocity[dof];
calc->inp_min_acceleration[dof] = inp->min_acceleration
? inp->min_acceleration[dof] : -inp->max_acceleration[dof];
calc->inp_per_dof_control_interface[dof] = inp->per_dof_control_interface
? inp->per_dof_control_interface[dof] : inp->control_interface;
calc->inp_per_dof_synchronization[dof] = inp->per_dof_synchronization
? inp->per_dof_synchronization[dof] : inp->synchronization;
if (!inp->enabled[dof]) {
p->p[7] = inp->current_position[dof];
p->v[7] = inp->current_velocity[dof];
p->a[7] = inp->current_acceleration[dof];
p->t_sum[6] = 0.0;
calc->blocks[dof].t_min = 0.0;
calc->blocks[dof].a.valid = false;
calc->blocks[dof].b.valid = false;
continue;
}
/* Calculate brake (if input exceeds or will exceed limits) */
switch (calc->inp_per_dof_control_interface[dof]) {
case CRuckigPosition: {
if (!isinf(inp->max_jerk[dof])) {
cruckig_brake_get_position_brake_trajectory(&p->brake,
inp->current_velocity[dof], inp->current_acceleration[dof],
inp->max_velocity[dof], calc->inp_min_velocity[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof],
inp->max_jerk[dof]);
} else if (!isinf(inp->max_acceleration[dof])) {
cruckig_brake_get_second_order_position_brake_trajectory(&p->brake,
inp->current_velocity[dof],
inp->max_velocity[dof], calc->inp_min_velocity[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof]);
}
cruckig_profile_set_boundary(p,
inp->current_position[dof], inp->current_velocity[dof],
inp->current_acceleration[dof],
inp->target_position[dof], inp->target_velocity[dof],
inp->target_acceleration[dof]);
} break;
case CRuckigVelocity: {
if (!isinf(inp->max_jerk[dof])) {
cruckig_brake_get_velocity_brake_trajectory(&p->brake,
inp->current_acceleration[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof],
inp->max_jerk[dof]);
} else {
cruckig_brake_get_second_order_velocity_brake_trajectory(&p->brake);
}
cruckig_profile_set_boundary_for_velocity(p,
inp->current_position[dof], inp->current_velocity[dof],
inp->current_acceleration[dof],
inp->target_velocity[dof], inp->target_acceleration[dof]);
} break;
}
/* Finalize pre-trajectory */
if (!isinf(inp->max_jerk[dof])) {
cruckig_brake_finalize(&p->brake, &p->p[0], &p->v[0], &p->a[0]);
} else if (!isinf(inp->max_acceleration[dof])) {
cruckig_brake_finalize_second_order(&p->brake, &p->p[0], &p->v[0], &p->a[0]);
}
if (!find_profile_step1(calc, inp, p, dof)) {
bool has_zero_limits = (inp->max_acceleration[dof] == 0.0 ||
calc->inp_min_acceleration[dof] == 0.0 ||
inp->max_jerk[dof] == 0.0);
if (has_zero_limits) {
return CRuckigErrorZeroLimits;
} else {
return CRuckigErrorExecutionTimeCalculation;
}
}
traj->independent_min_durations[dof] = calc->blocks[dof].t_min;
}
const bool discrete_duration = (inp->duration_discretization == CRuckigDiscrete);
if (dofs == 1 && !inp->has_minimum_duration && !discrete_duration) {
traj->duration = calc->blocks[0].t_min;
traj->profiles[0] = calc->blocks[0].p_min;
traj->cumulative_times[0] = traj->duration;
return CRuckigWorking;
}
/* Synchronize */
bool has_limiting_dof = false;
size_t limiting_dof = 0;
bool found_synchronization = synchronize(calc,
inp->has_minimum_duration, inp->minimum_duration,
&traj->duration, &has_limiting_dof, &limiting_dof,
traj->profiles, discrete_duration, delta_time);
if (!found_synchronization) {
bool has_zero_limits = false;
for (size_t dof = 0; dof < dofs; ++dof) {
if (inp->max_acceleration[dof] == 0.0 ||
calc->inp_min_acceleration[dof] == 0.0 ||
inp->max_jerk[dof] == 0.0) {
has_zero_limits = true;
break;
}
}
if (has_zero_limits) {
return CRuckigErrorZeroLimits;
} else {
return CRuckigErrorSynchronizationCalculation;
}
}
/* None Synchronization */
for (size_t dof = 0; dof < dofs; ++dof) {
if (inp->enabled[dof] && calc->inp_per_dof_synchronization[dof] == CRuckigSyncNone) {
traj->profiles[dof] = calc->blocks[dof].p_min;
if (calc->blocks[dof].t_min > traj->duration) {
traj->duration = calc->blocks[dof].t_min;
has_limiting_dof = true;
limiting_dof = dof;
}
}
}
traj->cumulative_times[0] = traj->duration;
/* Check maximal duration */
if (traj->duration > 7.6e3) {
return CRuckigErrorTrajectoryDuration;
}
if (traj->duration == 0.0) {
/* Copy all profiles for end state */
for (size_t dof = 0; dof < dofs; ++dof) {
traj->profiles[dof] = calc->blocks[dof].p_min;
}
return CRuckigWorking;
}
/* Check if all synchronizations are None */
if (!discrete_duration) {
bool all_none = true;
for (size_t dof = 0; dof < dofs; ++dof) {
if (calc->inp_per_dof_synchronization[dof] != CRuckigSyncNone) {
all_none = false;
break;
}
}
if (all_none) {
return CRuckigWorking;
}
}
/* Phase Synchronization */
if (has_limiting_dof) {
bool any_phase = false;
for (size_t dof = 0; dof < dofs; ++dof) {
if (calc->inp_per_dof_synchronization[dof] == CRuckigSyncPhase) {
any_phase = true;
break;
}
}
if (any_phase) {
const CRuckigProfile *p_limiting = &traj->profiles[limiting_dof];
if (is_input_collinear(calc, inp, p_limiting->direction, limiting_dof)) {
bool found_time_synchronization = true;
for (size_t dof = 0; dof < dofs; ++dof) {
if (!inp->enabled[dof] || dof == limiting_dof ||
calc->inp_per_dof_synchronization[dof] != CRuckigSyncPhase) {
continue;
}
CRuckigProfile *p = &traj->profiles[dof];
double t_profile = traj->duration - p->brake.duration - p->accel.duration;
/* Copy timing information from limiting DOF */
memcpy(p->t, p_limiting->t, sizeof(p->t));
p->control_signs = p_limiting->control_signs;
switch (calc->inp_per_dof_control_interface[dof]) {
case CRuckigPosition: {
switch (p->control_signs) {
case ControlSignsUDDU: {
if (!isinf(inp->max_jerk[dof])) {
found_time_synchronization &= cruckig_profile_check_with_timing_full(p,
ControlSignsUDDU, ReachedLimitsNONE,
t_profile, calc->new_phase_control[dof],
inp->max_velocity[dof], calc->inp_min_velocity[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof],
inp->max_jerk[dof]);
} else if (!isinf(inp->max_acceleration[dof])) {
found_time_synchronization &= cruckig_profile_check_for_second_order_with_timing_full(p,
ControlSignsUDDU, ReachedLimitsNONE,
t_profile, calc->new_phase_control[dof],
-calc->new_phase_control[dof],
inp->max_velocity[dof], calc->inp_min_velocity[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof]);
} else {
found_time_synchronization &= cruckig_profile_check_for_first_order_with_timing_full(p,
ControlSignsUDDU, ReachedLimitsNONE,
t_profile, calc->new_phase_control[dof],
inp->max_velocity[dof], calc->inp_min_velocity[dof]);
}
} break;
case ControlSignsUDUD: {
if (!isinf(inp->max_jerk[dof])) {
found_time_synchronization &= cruckig_profile_check_with_timing_full(p,
ControlSignsUDUD, ReachedLimitsNONE,
t_profile, calc->new_phase_control[dof],
inp->max_velocity[dof], calc->inp_min_velocity[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof],
inp->max_jerk[dof]);
} else {
found_time_synchronization &= cruckig_profile_check_for_second_order_with_timing_full(p,
ControlSignsUDUD, ReachedLimitsNONE,
t_profile, calc->new_phase_control[dof],
-calc->new_phase_control[dof],
inp->max_velocity[dof], calc->inp_min_velocity[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof]);
}
} break;
}
} break;
case CRuckigVelocity: {
switch (p->control_signs) {
case ControlSignsUDDU: {
if (!isinf(inp->max_jerk[dof])) {
found_time_synchronization &= cruckig_profile_check_for_velocity_with_timing_full(p,
ControlSignsUDDU, ReachedLimitsNONE,
t_profile, calc->new_phase_control[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof],
inp->max_jerk[dof]);
} else {
found_time_synchronization &= cruckig_profile_check_for_second_order_velocity_with_timing_full(p,
ControlSignsUDDU, ReachedLimitsNONE,
t_profile, calc->new_phase_control[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof]);
}
} break;
case ControlSignsUDUD: {
if (!isinf(inp->max_jerk[dof])) {
found_time_synchronization &= cruckig_profile_check_for_velocity_with_timing_full(p,
ControlSignsUDUD, ReachedLimitsNONE,
t_profile, calc->new_phase_control[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof],
inp->max_jerk[dof]);
} else {
found_time_synchronization &= cruckig_profile_check_for_second_order_velocity_with_timing_full(p,
ControlSignsUDUD, ReachedLimitsNONE,
t_profile, calc->new_phase_control[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof]);
}
} break;
}
} break;
}
p->limits = p_limiting->limits; /* After check method call */
}
if (found_time_synchronization) {
bool all_phase_or_none = true;
for (size_t dof = 0; dof < dofs; ++dof) {
if (calc->inp_per_dof_synchronization[dof] != CRuckigSyncPhase &&
calc->inp_per_dof_synchronization[dof] != CRuckigSyncNone) {
all_phase_or_none = false;
break;
}
}
if (all_phase_or_none) {
return CRuckigWorking;
}
}
}
}
}
/* Time Synchronization (Step 2) */
for (size_t dof = 0; dof < dofs; ++dof) {
bool skip_synchronization = ((has_limiting_dof && dof == limiting_dof) ||
calc->inp_per_dof_synchronization[dof] == CRuckigSyncNone) &&
!discrete_duration;
if (!inp->enabled[dof] || skip_synchronization) {
continue;
}
CRuckigProfile *p = &traj->profiles[dof];
double t_profile = traj->duration - p->brake.duration - p->accel.duration;
if (calc->inp_per_dof_synchronization[dof] == CRuckigSyncTimeIfNecessary &&
fabs(inp->target_velocity[dof]) < eps &&
fabs(inp->target_acceleration[dof]) < eps) {
*p = calc->blocks[dof].p_min;
continue;
}
/* Check if the final time corresponds to an extremal profile from step 1 */
if (fabs(t_profile - calc->blocks[dof].t_min) < 2 * eps) {
*p = calc->blocks[dof].p_min;
continue;
} else if (calc->blocks[dof].a.valid && fabs(t_profile - calc->blocks[dof].a.right) < 2 * eps) {
*p = calc->blocks[dof].a.profile;
continue;
} else if (calc->blocks[dof].b.valid && fabs(t_profile - calc->blocks[dof].b.right) < 2 * eps) {
*p = calc->blocks[dof].b.profile;
continue;
}
bool found_time_synchronization = false;
switch (calc->inp_per_dof_control_interface[dof]) {
case CRuckigPosition: {
if (!isinf(inp->max_jerk[dof])) {
CRuckigPositionThirdOrderStep2 step2;
cruckig_pos3_step2_init(&step2,
t_profile, p->p[0], p->v[0], p->a[0], p->pf, p->vf, p->af,
inp->max_velocity[dof], calc->inp_min_velocity[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof],
inp->max_jerk[dof]);
found_time_synchronization = cruckig_pos3_step2_get_profile(&step2, p);
} else if (!isinf(inp->max_acceleration[dof])) {
CRuckigPositionSecondOrderStep2 step2;
cruckig_pos2_step2_init(&step2,
t_profile, p->p[0], p->v[0], p->pf, p->vf,
inp->max_velocity[dof], calc->inp_min_velocity[dof],
inp->max_acceleration[dof], calc->inp_min_acceleration[dof]);
found_time_synchronization = cruckig_pos2_step2_get_profile(&step2, p);
} else {
CRuckigPositionFirstOrderStep2 step2;
cruckig_pos1_step2_init(&step2,
t_profile, p->p[0], p->pf,
inp->max_velocity[dof], calc->inp_min_velocity[dof]);
found_time_synchronization = cruckig_pos1_step2_get_profile(&step2, p);
}
} break;
case CRuckigVelocity: {
if (!isinf(inp->max_jerk[dof])) {
CRuckigVelocityThirdOrderStep2 step2;
cruckig_vel3_step2_init(&step2,
t_profile, p->v[0], p->a[0], p->vf, p->af,
inp->max_acceleration[dof], calc->inp_min_acceleration[dof],
inp->max_jerk[dof]);
found_time_synchronization = cruckig_vel3_step2_get_profile(&step2, p);
} else {
CRuckigVelocitySecondOrderStep2 step2;
cruckig_vel2_step2_init(&step2,
t_profile, p->v[0], p->vf,
inp->max_acceleration[dof], calc->inp_min_acceleration[dof]);
found_time_synchronization = cruckig_vel2_step2_get_profile(&step2, p);
}
} break;
}
if (!found_time_synchronization) {
return CRuckigErrorSynchronizationCalculation;
}
}
return CRuckigWorking;
}
/*
* Multi-segment waypoint calculation.
*
* Strategy: sequential segment planning. For each segment between consecutive
* waypoints, use the existing single-segment planner. The end state of segment i
* becomes the start state of segment i+1. At intermediate waypoints, velocity
* and acceleration pass through continuously (zero target velocity at waypoints
* for robustness, with option to optimize).
*/
CRuckigResult cruckig_calculator_calculate_waypoints(CRuckigCalculator *calc,
const CRuckigInputParameter *inp,
CRuckigTrajectory *traj,
double delta_time,
bool *was_interrupted)
{
const size_t dofs = calc->degrees_of_freedom;
const size_t nwp = inp->num_intermediate_waypoints;
const size_t nsec = nwp + 1; /* Number of sections */
/* Resize trajectory for multi-section */
if (!cruckig_trajectory_resize(traj, nsec)) {
return CRuckigError;
}
/* Create reusable segment input if needed */
if (!calc->segment_input) {
calc->segment_input = cruckig_input_create(dofs);
if (!calc->segment_input) return CRuckigError;
}
CRuckigInputParameter *seg = calc->segment_input;
/* Build a temporary single-section trajectory for each segment */
CRuckigTrajectory *seg_traj = cruckig_trajectory_create(dofs);
if (!seg_traj) return CRuckigError;
double cumulative_time = 0.0;
CRuckigResult final_result = CRuckigWorking;
for (size_t s = 0; s < nsec; ++s) {
/* Set segment input: copy global settings */
seg->control_interface = CRuckigPosition;
seg->synchronization = inp->synchronization;
seg->duration_discretization = CRuckigContinuous;
seg->has_minimum_duration = false;
/* Per-section minimum duration */
if (inp->per_section_minimum_duration) {
seg->minimum_duration = inp->per_section_minimum_duration[s];
seg->has_minimum_duration = true;
}
/* Set start state */
if (s == 0) {
/* First segment starts from input current state */
memcpy(seg->current_position, inp->current_position, dofs * sizeof(double));
memcpy(seg->current_velocity, inp->current_velocity, dofs * sizeof(double));
memcpy(seg->current_acceleration, inp->current_acceleration, dofs * sizeof(double));
}
/* else: current state was set by previous iteration's end state */
/* Set target state */
if (s < nwp) {
/* Target is the next intermediate waypoint */
const double *wp = inp->intermediate_positions + s * dofs;
memcpy(seg->target_position, wp, dofs * sizeof(double));
/* Zero velocity/acceleration at intermediate waypoints */
memset(seg->target_velocity, 0, dofs * sizeof(double));
memset(seg->target_acceleration, 0, dofs * sizeof(double));
} else {
/* Last segment targets the final position */
memcpy(seg->target_position, inp->target_position, dofs * sizeof(double));
memcpy(seg->target_velocity, inp->target_velocity, dofs * sizeof(double));
memcpy(seg->target_acceleration, inp->target_acceleration, dofs * sizeof(double));
}
/* Set kinematic constraints (per-section or global) */
if (inp->per_section_max_velocity) {
memcpy(seg->max_velocity, inp->per_section_max_velocity + s * dofs, dofs * sizeof(double));
} else {
memcpy(seg->max_velocity, inp->max_velocity, dofs * sizeof(double));
}
if (inp->per_section_max_acceleration) {
memcpy(seg->max_acceleration, inp->per_section_max_acceleration + s * dofs, dofs * sizeof(double));
} else {
memcpy(seg->max_acceleration, inp->max_acceleration, dofs * sizeof(double));
}
if (inp->per_section_max_jerk) {
memcpy(seg->max_jerk, inp->per_section_max_jerk + s * dofs, dofs * sizeof(double));
} else {
memcpy(seg->max_jerk, inp->max_jerk, dofs * sizeof(double));
}
/* Optional min limits */
if (inp->per_section_min_velocity) {
if (!seg->min_velocity) seg->min_velocity = (double*)cruckig_malloc(dofs * sizeof(double));
memcpy(seg->min_velocity, inp->per_section_min_velocity + s * dofs, dofs * sizeof(double));
} else if (inp->min_velocity) {
if (!seg->min_velocity) seg->min_velocity = (double*)cruckig_malloc(dofs * sizeof(double));
memcpy(seg->min_velocity, inp->min_velocity, dofs * sizeof(double));
} else {
cruckig_free(seg->min_velocity);
seg->min_velocity = NULL;
}
if (inp->per_section_min_acceleration) {
if (!seg->min_acceleration) seg->min_acceleration = (double*)cruckig_malloc(dofs * sizeof(double));
memcpy(seg->min_acceleration, inp->per_section_min_acceleration + s * dofs, dofs * sizeof(double));
} else if (inp->min_acceleration) {
if (!seg->min_acceleration) seg->min_acceleration = (double*)cruckig_malloc(dofs * sizeof(double));
memcpy(seg->min_acceleration, inp->min_acceleration, dofs * sizeof(double));
} else {
cruckig_free(seg->min_acceleration);
seg->min_acceleration = NULL;
}
/* Enable all DOFs for segment */
for (size_t d = 0; d < dofs; ++d) seg->enabled[d] = true;
/* Calculate this segment */
bool seg_interrupted = false;
CRuckigResult seg_result = cruckig_calculator_calculate(calc, seg, seg_traj,
delta_time, &seg_interrupted);
if (seg_result != CRuckigWorking) {
cruckig_trajectory_destroy(seg_traj);
*was_interrupted = false;
return seg_result;
}
/* Copy segment profiles into the multi-section trajectory */
double seg_duration = cruckig_trajectory_get_duration(seg_traj);
cumulative_time += seg_duration;
traj->cumulative_times[s] = cumulative_time;
for (size_t d = 0; d < dofs; ++d) {
traj->profiles[s * dofs + d] = seg_traj->profiles[d];
if (s == 0) {
traj->independent_min_durations[d] = seg_traj->independent_min_durations[d];
}
}
/* Set next segment's start state from this segment's end state */
if (s < nsec - 1) {
for (size_t d = 0; d < dofs; ++d) {
const CRuckigProfile *p = &seg_traj->profiles[d];
seg->current_position[d] = p->p[7];
seg->current_velocity[d] = p->v[7];
seg->current_acceleration[d] = p->a[7];
}
}
}
traj->duration = cumulative_time;
cruckig_trajectory_destroy(seg_traj);
/* Position limits check */
if (inp->max_position || inp->min_position ||
inp->per_section_max_position || inp->per_section_min_position)
{
/* Sample trajectory and check bounds */
double *pos = (double*)cruckig_malloc(dofs * sizeof(double));
double *vel = (double*)cruckig_malloc(dofs * sizeof(double));
double *acc = (double*)cruckig_malloc(dofs * sizeof(double));
size_t sec;
bool violated = false;
/* Check at fine time steps */
double dt_check = (delta_time > 0.0) ? delta_time : 0.001;
for (double t = 0.0; t <= cumulative_time && !violated; t += dt_check) {
cruckig_trajectory_at_time(traj, t, pos, vel, acc, NULL, &sec);
for (size_t d = 0; d < dofs; ++d) {
double p_max = INFINITY, p_min = -INFINITY;
if (inp->max_position) p_max = inp->max_position[d];
if (inp->min_position) p_min = inp->min_position[d];
/* Per-section position limits */
if (sec < nsec) {
if (inp->per_section_max_position) {
double sec_max = inp->per_section_max_position[sec * dofs + d];
if (sec_max < p_max) p_max = sec_max;
}
if (inp->per_section_min_position) {
double sec_min = inp->per_section_min_position[sec * dofs + d];
if (sec_min > p_min) p_min = sec_min;
}
}
if (pos[d] > p_max + 1e-8 || pos[d] < p_min - 1e-8) {
violated = true;
break;
}
}
}
/* Also check position extrema */
if (!violated) {
cruckig_trajectory_get_position_extrema(traj);
for (size_t d = 0; d < dofs; ++d) {
double p_max = INFINITY, p_min = -INFINITY;
if (inp->max_position) p_max = inp->max_position[d];
if (inp->min_position) p_min = inp->min_position[d];
if (traj->position_extrema[d].max > p_max + 1e-8 ||
traj->position_extrema[d].min < p_min - 1e-8) {
violated = true;
break;
}
}
}
cruckig_free(pos);
cruckig_free(vel);
cruckig_free(acc);
if (violated) {
final_result = CRuckigErrorPositionalLimits;
}
}
*was_interrupted = false;
return final_result;
}
CRuckigResult cruckig_calculator_continue(CRuckigCalculator *calc,
const CRuckigInputParameter *inp,
CRuckigTrajectory *traj,
double delta_time,
bool *was_interrupted)
{
/* For now, continue_calculation simply re-runs the full calculation.
* A future optimization could resume from partial state. */
if (inp->num_intermediate_waypoints > 0 && inp->control_interface == CRuckigPosition) {
return cruckig_calculator_calculate_waypoints(calc, inp, traj, delta_time, was_interrupted);
}
return cruckig_calculator_calculate(calc, inp, traj, delta_time, was_interrupted);
}

View File

@@ -0,0 +1,71 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_CALCULATOR_H
#define CRUCKIG_CALCULATOR_H
#include "cruckig_internal.h"
#include "result.h"
#include "block.h"
#include "input_parameter.h"
#include "trajectory.h"
#include "position.h"
#include "velocity.h"
typedef struct {
size_t degrees_of_freedom;
double *new_phase_control;
double *pd;
double *possible_t_syncs;
size_t *idx;
CRuckigBlock *blocks;
double *inp_min_velocity;
double *inp_min_acceleration;
CRuckigControlInterface *inp_per_dof_control_interface;
CRuckigSynchronization *inp_per_dof_synchronization;
/* Scratch space for waypoint calculation */
CRuckigInputParameter *segment_input; /* Reusable per-segment input */
/* Step1 workspace: kept off the stack to stay within kernel frame limits.
* Only one Step1 type is active at a time, so a union suffices. */
union {
CRuckigPositionThirdOrderStep1 pos3_step1;
CRuckigPositionSecondOrderStep1 pos2_step1;
CRuckigPositionFirstOrderStep1 pos1_step1;
CRuckigVelocityThirdOrderStep1 vel3_step1;
CRuckigVelocitySecondOrderStep1 vel2_step1;
} step1_workspace;
} CRuckigCalculator;
CRuckigCalculator* cruckig_calculator_create(size_t dofs);
void cruckig_calculator_destroy(CRuckigCalculator *calc);
/* Single-segment calculation (existing, backward compatible) */
CRuckigResult cruckig_calculator_calculate(CRuckigCalculator *calc,
const CRuckigInputParameter *inp,
CRuckigTrajectory *traj,
double delta_time,
bool *was_interrupted);
/* Multi-segment waypoint calculation */
CRuckigResult cruckig_calculator_calculate_waypoints(CRuckigCalculator *calc,
const CRuckigInputParameter *inp,
CRuckigTrajectory *traj,
double delta_time,
bool *was_interrupted);
/* Continue an interrupted calculation */
CRuckigResult cruckig_calculator_continue(CRuckigCalculator *calc,
const CRuckigInputParameter *inp,
CRuckigTrajectory *traj,
double delta_time,
bool *was_interrupted);
#endif /* CRUCKIG_CALCULATOR_H */

View File

@@ -0,0 +1,173 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "cruckig.h"
static CRuckig* cruckig_create_internal(size_t dofs, double delta_time, size_t max_waypoints) {
CRuckig *r = (CRuckig*)cruckig_calloc(1, sizeof(CRuckig));
if (!r) return NULL;
r->degrees_of_freedom = dofs;
r->delta_time = delta_time;
r->max_number_of_waypoints = max_waypoints;
r->calculator = cruckig_calculator_create(dofs);
if (!r->calculator) {
cruckig_free(r);
return NULL;
}
r->current_input = cruckig_input_create(dofs);
if (!r->current_input) {
cruckig_calculator_destroy(r->calculator);
cruckig_free(r);
return NULL;
}
r->current_input_initialized = false;
return r;
}
CRuckig* cruckig_create(size_t dofs, double delta_time) {
return cruckig_create_internal(dofs, delta_time, 0);
}
CRuckig* cruckig_create_waypoints(size_t dofs, double delta_time, size_t max_waypoints) {
return cruckig_create_internal(dofs, delta_time, max_waypoints);
}
void cruckig_destroy(CRuckig *r) {
if (!r) return;
cruckig_calculator_destroy(r->calculator);
cruckig_input_destroy(r->current_input);
cruckig_free(r);
}
void cruckig_reset(CRuckig *r) {
if (!r) return;
r->current_input_initialized = false;
}
static inline bool use_waypoints(const CRuckigInputParameter *input) {
return input->num_intermediate_waypoints > 0 &&
input->control_interface == CRuckigPosition;
}
bool cruckig_validate_input(const CRuckig *r, const CRuckigInputParameter *input,
bool check_current_within_limits,
bool check_target_within_limits)
{
if (!r || !input) return false;
if (!cruckig_input_validate(input, check_current_within_limits, check_target_within_limits)) {
return false;
}
if (r->delta_time <= 0.0 && input->duration_discretization != CRuckigContinuous) {
return false;
}
/* Validate waypoint count against max */
if (input->num_intermediate_waypoints > r->max_number_of_waypoints &&
r->max_number_of_waypoints > 0) {
return false;
}
return true;
}
static CRuckigResult dispatch_calculate(CRuckig *r, const CRuckigInputParameter *input,
CRuckigTrajectory *trajectory, bool *was_interrupted)
{
if (use_waypoints(input)) {
/* Ensure trajectory has enough capacity */
size_t nsec = input->num_intermediate_waypoints + 1;
if (!cruckig_trajectory_resize(trajectory, nsec)) {
return CRuckigError;
}
return cruckig_calculator_calculate_waypoints(r->calculator, input, trajectory,
r->delta_time, was_interrupted);
} else {
/* Single-segment: ensure single section */
if (trajectory->num_sections != 1) {
cruckig_trajectory_resize(trajectory, 1);
}
return cruckig_calculator_calculate(r->calculator, input, trajectory,
r->delta_time, was_interrupted);
}
}
CRuckigResult cruckig_calculate(CRuckig *r, const CRuckigInputParameter *input,
CRuckigTrajectory *trajectory)
{
if (!r || !input || !trajectory) return CRuckigError;
if (!cruckig_validate_input(r, input, false, true)) {
return CRuckigErrorInvalidInput;
}
bool was_interrupted = false;
return dispatch_calculate(r, input, trajectory, &was_interrupted);
}
static double get_time_us(void) {
/* Timing measurement for interrupt budget feature.
* Not used by LinuxCNC (only cruckig_update, not cruckig_calculate). */
return 0.0;
}
CRUCKIG_HOT
CRuckigResult cruckig_update(CRuckig *r, const CRuckigInputParameter *input,
CRuckigOutputParameter *output)
{
if (CRUCKIG_UNLIKELY(!r || !input || !output)) return CRuckigError;
double start_us = get_time_us();
output->new_calculation = false;
CRuckigResult result = CRuckigWorking;
if (!r->current_input_initialized || !cruckig_input_is_equal(input, r->current_input)) {
if (!cruckig_validate_input(r, input, false, true)) {
return CRuckigErrorInvalidInput;
}
result = dispatch_calculate(r, input, output->trajectory,
&output->was_calculation_interrupted);
if (result != CRuckigWorking && result != CRuckigErrorPositionalLimits) {
return result;
}
cruckig_input_copy(r->current_input, input);
r->current_input_initialized = true;
output->time = 0.0;
output->new_section = 0;
output->new_calculation = true;
}
size_t old_section = output->new_section;
output->time += r->delta_time;
cruckig_trajectory_at_time(output->trajectory, output->time,
output->new_position, output->new_velocity,
output->new_acceleration, output->new_jerk,
&output->new_section);
output->did_section_change = (output->new_section > old_section);
double stop_us = get_time_us();
output->calculation_duration = stop_us - start_us;
cruckig_output_pass_to_input(output, r->current_input);
if (output->time > cruckig_trajectory_get_duration(output->trajectory)) {
return CRuckigFinished;
}
return result;
}

View File

@@ -0,0 +1,54 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_CRUCKIG_H
#define CRUCKIG_CRUCKIG_H
#include "cruckig_internal.h"
#include "result.h"
#include "input_parameter.h"
#include "output_parameter.h"
#include "trajectory.h"
#include "calculator.h"
/* Main cruckig instance */
typedef struct {
size_t degrees_of_freedom;
double delta_time;
size_t max_number_of_waypoints;
CRuckigCalculator *calculator;
CRuckigInputParameter *current_input;
bool current_input_initialized;
} CRuckig;
/* Create and destroy (backward compatible: 0 waypoints) */
CRuckig* cruckig_create(size_t dofs, double delta_time);
/* Create with waypoint support */
CRuckig* cruckig_create_waypoints(size_t dofs, double delta_time, size_t max_waypoints);
void cruckig_destroy(CRuckig *r);
/* Reset (force recalculation on next update) */
void cruckig_reset(CRuckig *r);
/* Calculate trajectory (offline, auto-dispatches to waypoint calculator if needed) */
CRuckigResult cruckig_calculate(CRuckig *r, const CRuckigInputParameter *input,
CRuckigTrajectory *trajectory);
/* Update (online, call every delta_time) */
CRuckigResult cruckig_update(CRuckig *r, const CRuckigInputParameter *input,
CRuckigOutputParameter *output);
/* Validate input */
bool cruckig_validate_input(const CRuckig *r, const CRuckigInputParameter *input,
bool check_current_within_limits,
bool check_target_within_limits);
#endif /* CRUCKIG_CRUCKIG_H */

View File

@@ -0,0 +1,55 @@
/*
* cruckig_internal.h - Internal header for cruckig
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*
* Provides RTAPI-portable types, memory allocation, math, string
* functions, and compiler hint macros for cruckig internals.
*
* All cruckig headers should include this as their first include.
* C files should NOT include this directly -- they get it through
* their corresponding header.
*/
#ifndef CRUCKIG_CRUCKIG_INTERNAL_H
#define CRUCKIG_CRUCKIG_INTERNAL_H
/* RTAPI provides bool, size_t, math, string, and memory allocation
* portably across userspace and kernel builds. */
#include <rtapi.h>
#include <rtapi_bool.h>
#include <rtapi_math.h>
#include <rtapi_string.h>
#include <rtapi_slab.h>
#include <float.h>
/* INFINITY: not provided by rtapi_math.h in kernel space */
#ifndef INFINITY
#define INFINITY __builtin_inf()
#endif
/* Memory allocation: always use rtapi_slab wrappers */
#define cruckig_malloc(sz) rtapi_kmalloc(sz, RTAPI_GFP_KERNEL)
#define cruckig_calloc(n, sz) rtapi_kzalloc((n) * (sz), RTAPI_GFP_KERNEL)
#define cruckig_realloc(p, sz) rtapi_krealloc(p, sz, RTAPI_GFP_KERNEL)
#define cruckig_free(p) rtapi_kfree(p)
/* Branch prediction hints */
#if defined(__GNUC__) || defined(__clang__)
# define CRUCKIG_LIKELY(x) __builtin_expect(!!(x), 1)
# define CRUCKIG_UNLIKELY(x) __builtin_expect(!!(x), 0)
# define CRUCKIG_FORCE_INLINE static inline __attribute__((always_inline))
# define CRUCKIG_HOT __attribute__((hot))
# define CRUCKIG_RESTRICT __restrict__
# define CRUCKIG_PREFETCH(addr) __builtin_prefetch(addr, 0, 1)
#else
# define CRUCKIG_LIKELY(x) (x)
# define CRUCKIG_UNLIKELY(x) (x)
# define CRUCKIG_FORCE_INLINE static inline
# define CRUCKIG_HOT
# define CRUCKIG_RESTRICT restrict
# define CRUCKIG_PREFETCH(addr) ((void)0)
#endif
#endif /* CRUCKIG_CRUCKIG_INTERNAL_H */

View File

@@ -0,0 +1,408 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "input_parameter.h"
static double v_at_a_zero(double v0, double a0, double j) {
return v0 + (a0 * a0) / (2.0 * j);
}
CRuckigInputParameter* cruckig_input_create(size_t dofs) {
CRuckigInputParameter *inp = (CRuckigInputParameter*)cruckig_calloc(1, sizeof(CRuckigInputParameter));
if (!inp) return NULL;
inp->degrees_of_freedom = dofs;
inp->control_interface = CRuckigPosition;
inp->synchronization = CRuckigSyncTime;
inp->duration_discretization = CRuckigContinuous;
inp->current_position = (double*)cruckig_calloc(dofs, sizeof(double));
inp->current_velocity = (double*)cruckig_calloc(dofs, sizeof(double));
inp->current_acceleration = (double*)cruckig_calloc(dofs, sizeof(double));
inp->target_position = (double*)cruckig_calloc(dofs, sizeof(double));
inp->target_velocity = (double*)cruckig_calloc(dofs, sizeof(double));
inp->target_acceleration = (double*)cruckig_calloc(dofs, sizeof(double));
inp->max_velocity = (double*)cruckig_calloc(dofs, sizeof(double));
inp->max_acceleration = (double*)cruckig_malloc(dofs * sizeof(double));
inp->max_jerk = (double*)cruckig_malloc(dofs * sizeof(double));
inp->enabled = (bool*)cruckig_malloc(dofs * sizeof(bool));
if (!inp->current_position || !inp->current_velocity || !inp->current_acceleration ||
!inp->target_position || !inp->target_velocity || !inp->target_acceleration ||
!inp->max_velocity || !inp->max_acceleration || !inp->max_jerk || !inp->enabled) {
cruckig_input_destroy(inp);
return NULL;
}
/* Initialize defaults matching C++ */
for (size_t dof = 0; dof < dofs; ++dof) {
inp->max_acceleration[dof] = INFINITY;
inp->max_jerk[dof] = INFINITY;
inp->enabled[dof] = true;
}
inp->min_velocity = NULL;
inp->min_acceleration = NULL;
inp->per_dof_control_interface = NULL;
inp->per_dof_synchronization = NULL;
inp->minimum_duration = -1.0;
inp->has_minimum_duration = false;
/* Pro fields: initialize to defaults */
inp->intermediate_positions = NULL;
inp->num_intermediate_waypoints = 0;
inp->per_section_max_velocity = NULL;
inp->per_section_max_acceleration = NULL;
inp->per_section_max_jerk = NULL;
inp->per_section_min_velocity = NULL;
inp->per_section_min_acceleration = NULL;
inp->per_section_max_position = NULL;
inp->per_section_min_position = NULL;
inp->max_position = NULL;
inp->min_position = NULL;
inp->per_section_minimum_duration = NULL;
inp->interrupt_calculation_duration = 0.0;
return inp;
}
void cruckig_input_destroy(CRuckigInputParameter *inp) {
if (!inp) return;
cruckig_free(inp->current_position);
cruckig_free(inp->current_velocity);
cruckig_free(inp->current_acceleration);
cruckig_free(inp->target_position);
cruckig_free(inp->target_velocity);
cruckig_free(inp->target_acceleration);
cruckig_free(inp->max_velocity);
cruckig_free(inp->max_acceleration);
cruckig_free(inp->max_jerk);
cruckig_free(inp->enabled);
cruckig_free(inp->min_velocity);
cruckig_free(inp->min_acceleration);
cruckig_free(inp->per_dof_control_interface);
cruckig_free(inp->per_dof_synchronization);
/* Pro fields */
cruckig_free(inp->intermediate_positions);
cruckig_free(inp->per_section_max_velocity);
cruckig_free(inp->per_section_max_acceleration);
cruckig_free(inp->per_section_max_jerk);
cruckig_free(inp->per_section_min_velocity);
cruckig_free(inp->per_section_min_acceleration);
cruckig_free(inp->per_section_max_position);
cruckig_free(inp->per_section_min_position);
cruckig_free(inp->max_position);
cruckig_free(inp->min_position);
cruckig_free(inp->per_section_minimum_duration);
cruckig_free(inp);
}
void cruckig_input_set_intermediate_positions(CRuckigInputParameter *inp,
const double *positions,
size_t num_waypoints)
{
if (!inp) return;
const size_t dofs = inp->degrees_of_freedom;
cruckig_free(inp->intermediate_positions);
if (num_waypoints == 0 || !positions) {
inp->intermediate_positions = NULL;
inp->num_intermediate_waypoints = 0;
return;
}
size_t total = num_waypoints * dofs;
inp->intermediate_positions = (double*)cruckig_malloc(total * sizeof(double));
memcpy(inp->intermediate_positions, positions, total * sizeof(double));
inp->num_intermediate_waypoints = num_waypoints;
}
bool cruckig_input_validate(const CRuckigInputParameter *inp,
bool check_current_within_limits,
bool check_target_within_limits)
{
if (!inp) return false;
const size_t dofs = inp->degrees_of_freedom;
/* Waypoint-specific validation */
if (inp->num_intermediate_waypoints > 0) {
/* Waypoints require Position control interface */
if (inp->control_interface != CRuckigPosition) return false;
/* Waypoints incompatible with Discrete discretization */
if (inp->duration_discretization == CRuckigDiscrete) return false;
/* Waypoints incompatible with minimum_duration */
if (inp->has_minimum_duration) return false;
/* Infinite jerk not supported with waypoints */
for (size_t dof = 0; dof < dofs; ++dof) {
if (isinf(inp->max_jerk[dof])) return false;
if (isinf(inp->max_acceleration[dof])) return false;
}
}
for (size_t dof = 0; dof < dofs; ++dof) {
const double jMax = inp->max_jerk[dof];
if (isnan(jMax) || jMax < 0.0) return false;
const double aMax = inp->max_acceleration[dof];
if (isnan(aMax) || aMax < 0.0) return false;
const double aMin = inp->min_acceleration ? inp->min_acceleration[dof] : -aMax;
if (isnan(aMin) || aMin > 0.0) return false;
const double a0 = inp->current_acceleration[dof];
if (isnan(a0)) return false;
const double af = inp->target_acceleration[dof];
if (isnan(af)) return false;
if (check_current_within_limits) {
if (a0 > aMax) return false;
if (a0 < aMin) return false;
}
if (check_target_within_limits) {
if (af > aMax) return false;
if (af < aMin) return false;
}
const double v0 = inp->current_velocity[dof];
if (isnan(v0)) return false;
const double vf = inp->target_velocity[dof];
if (isnan(vf)) return false;
CRuckigControlInterface ci = inp->per_dof_control_interface
? inp->per_dof_control_interface[dof]
: inp->control_interface;
if (ci == CRuckigPosition) {
const double p0 = inp->current_position[dof];
if (isnan(p0)) return false;
const double pf = inp->target_position[dof];
if (isnan(pf)) return false;
const double vMax = inp->max_velocity[dof];
if (isnan(vMax) || vMax < 0.0) return false;
const double vMin = inp->min_velocity ? inp->min_velocity[dof] : -vMax;
if (isnan(vMin) || vMin > 0.0) return false;
if (check_current_within_limits) {
if (v0 > vMax) return false;
if (v0 < vMin) return false;
}
if (check_target_within_limits) {
if (vf > vMax) return false;
if (vf < vMin) return false;
}
if (check_current_within_limits) {
if (a0 > 0 && jMax > 0 && v_at_a_zero(v0, a0, jMax) > vMax)
return false;
if (a0 < 0 && jMax > 0 && v_at_a_zero(v0, a0, -jMax) < vMin)
return false;
}
if (check_target_within_limits) {
if (af < 0 && jMax > 0 && v_at_a_zero(vf, af, jMax) > vMax)
return false;
if (af > 0 && jMax > 0 && v_at_a_zero(vf, af, -jMax) < vMin)
return false;
}
}
}
return true;
}
bool cruckig_input_is_equal(const CRuckigInputParameter *a, const CRuckigInputParameter *b) {
if (!a || !b) return (a == b);
if (a->degrees_of_freedom != b->degrees_of_freedom) return false;
const size_t dofs = a->degrees_of_freedom;
const size_t dsz = dofs * sizeof(double);
if (memcmp(a->current_position, b->current_position, dsz) != 0) return false;
if (memcmp(a->current_velocity, b->current_velocity, dsz) != 0) return false;
if (memcmp(a->current_acceleration, b->current_acceleration, dsz) != 0) return false;
if (memcmp(a->target_position, b->target_position, dsz) != 0) return false;
if (memcmp(a->target_velocity, b->target_velocity, dsz) != 0) return false;
if (memcmp(a->target_acceleration, b->target_acceleration, dsz) != 0) return false;
if (memcmp(a->max_velocity, b->max_velocity, dsz) != 0) return false;
if (memcmp(a->max_acceleration, b->max_acceleration, dsz) != 0) return false;
if (memcmp(a->max_jerk, b->max_jerk, dsz) != 0) return false;
if (memcmp(a->enabled, b->enabled, dofs * sizeof(bool)) != 0) return false;
/* Compare optional min_velocity */
if ((a->min_velocity == NULL) != (b->min_velocity == NULL)) return false;
if (a->min_velocity && memcmp(a->min_velocity, b->min_velocity, dsz) != 0) return false;
/* Compare optional min_acceleration */
if ((a->min_acceleration == NULL) != (b->min_acceleration == NULL)) return false;
if (a->min_acceleration && memcmp(a->min_acceleration, b->min_acceleration, dsz) != 0) return false;
/* Compare optional per_dof_control_interface */
if ((a->per_dof_control_interface == NULL) != (b->per_dof_control_interface == NULL)) return false;
if (a->per_dof_control_interface &&
memcmp(a->per_dof_control_interface, b->per_dof_control_interface,
dofs * sizeof(CRuckigControlInterface)) != 0) return false;
/* Compare optional per_dof_synchronization */
if ((a->per_dof_synchronization == NULL) != (b->per_dof_synchronization == NULL)) return false;
if (a->per_dof_synchronization &&
memcmp(a->per_dof_synchronization, b->per_dof_synchronization,
dofs * sizeof(CRuckigSynchronization)) != 0) return false;
if (a->control_interface != b->control_interface) return false;
if (a->synchronization != b->synchronization) return false;
if (a->duration_discretization != b->duration_discretization) return false;
if (a->has_minimum_duration != b->has_minimum_duration) return false;
if (a->has_minimum_duration && a->minimum_duration != b->minimum_duration) return false;
/* Compare Pro fields */
if (a->num_intermediate_waypoints != b->num_intermediate_waypoints) return false;
if (a->num_intermediate_waypoints > 0) {
size_t wp_sz = a->num_intermediate_waypoints * dofs * sizeof(double);
if (memcmp(a->intermediate_positions, b->intermediate_positions, wp_sz) != 0) return false;
}
/* Compare position limits */
if ((a->max_position == NULL) != (b->max_position == NULL)) return false;
if (a->max_position && memcmp(a->max_position, b->max_position, dsz) != 0) return false;
if ((a->min_position == NULL) != (b->min_position == NULL)) return false;
if (a->min_position && memcmp(a->min_position, b->min_position, dsz) != 0) return false;
/* Compare per-section constraints */
size_t nsec = a->num_intermediate_waypoints + 1;
size_t sec_dsz = nsec * dofs * sizeof(double);
#define CMP_OPT_SEC(field) \
if ((a->field == NULL) != (b->field == NULL)) return false; \
if (a->field && memcmp(a->field, b->field, sec_dsz) != 0) return false;
CMP_OPT_SEC(per_section_max_velocity)
CMP_OPT_SEC(per_section_max_acceleration)
CMP_OPT_SEC(per_section_max_jerk)
CMP_OPT_SEC(per_section_min_velocity)
CMP_OPT_SEC(per_section_min_acceleration)
CMP_OPT_SEC(per_section_max_position)
CMP_OPT_SEC(per_section_min_position)
#undef CMP_OPT_SEC
if ((a->per_section_minimum_duration == NULL) != (b->per_section_minimum_duration == NULL)) return false;
if (a->per_section_minimum_duration &&
memcmp(a->per_section_minimum_duration, b->per_section_minimum_duration,
nsec * sizeof(double)) != 0) return false;
if (a->interrupt_calculation_duration != b->interrupt_calculation_duration) return false;
return true;
}
/* Helper to copy an optional flat array */
static void copy_opt_array(double **dst, const double *src, size_t count) {
if (src) {
size_t sz = count * sizeof(double);
if (!*dst) {
*dst = (double*)cruckig_malloc(sz);
}
memcpy(*dst, src, sz);
} else {
cruckig_free(*dst);
*dst = NULL;
}
}
void cruckig_input_copy(CRuckigInputParameter *dst, const CRuckigInputParameter *src) {
if (!dst || !src) return;
if (dst == src) return;
const size_t dofs = src->degrees_of_freedom;
const size_t dsz = dofs * sizeof(double);
/* dst must already be allocated with same dofs */
dst->degrees_of_freedom = dofs;
dst->control_interface = src->control_interface;
dst->synchronization = src->synchronization;
dst->duration_discretization = src->duration_discretization;
memcpy(dst->current_position, src->current_position, dsz);
memcpy(dst->current_velocity, src->current_velocity, dsz);
memcpy(dst->current_acceleration, src->current_acceleration, dsz);
memcpy(dst->target_position, src->target_position, dsz);
memcpy(dst->target_velocity, src->target_velocity, dsz);
memcpy(dst->target_acceleration, src->target_acceleration, dsz);
memcpy(dst->max_velocity, src->max_velocity, dsz);
memcpy(dst->max_acceleration, src->max_acceleration, dsz);
memcpy(dst->max_jerk, src->max_jerk, dsz);
memcpy(dst->enabled, src->enabled, dofs * sizeof(bool));
copy_opt_array(&dst->min_velocity, src->min_velocity, dofs);
copy_opt_array(&dst->min_acceleration, src->min_acceleration, dofs);
/* Handle optional per_dof_control_interface */
if (src->per_dof_control_interface) {
if (!dst->per_dof_control_interface) {
dst->per_dof_control_interface = (CRuckigControlInterface*)cruckig_malloc(dofs * sizeof(CRuckigControlInterface));
}
memcpy(dst->per_dof_control_interface, src->per_dof_control_interface,
dofs * sizeof(CRuckigControlInterface));
} else {
cruckig_free(dst->per_dof_control_interface);
dst->per_dof_control_interface = NULL;
}
/* Handle optional per_dof_synchronization */
if (src->per_dof_synchronization) {
if (!dst->per_dof_synchronization) {
dst->per_dof_synchronization = (CRuckigSynchronization*)cruckig_malloc(dofs * sizeof(CRuckigSynchronization));
}
memcpy(dst->per_dof_synchronization, src->per_dof_synchronization,
dofs * sizeof(CRuckigSynchronization));
} else {
cruckig_free(dst->per_dof_synchronization);
dst->per_dof_synchronization = NULL;
}
dst->minimum_duration = src->minimum_duration;
dst->has_minimum_duration = src->has_minimum_duration;
/* Copy Pro fields */
if (src->num_intermediate_waypoints > 0 && src->intermediate_positions) {
size_t wp_sz = src->num_intermediate_waypoints * dofs;
copy_opt_array(&dst->intermediate_positions, src->intermediate_positions, wp_sz);
dst->num_intermediate_waypoints = src->num_intermediate_waypoints;
} else {
cruckig_free(dst->intermediate_positions);
dst->intermediate_positions = NULL;
dst->num_intermediate_waypoints = 0;
}
copy_opt_array(&dst->max_position, src->max_position, dofs);
copy_opt_array(&dst->min_position, src->min_position, dofs);
/* Per-section arrays */
size_t nsec = src->num_intermediate_waypoints + 1;
size_t sec_count = nsec * dofs;
copy_opt_array(&dst->per_section_max_velocity, src->per_section_max_velocity, sec_count);
copy_opt_array(&dst->per_section_max_acceleration, src->per_section_max_acceleration, sec_count);
copy_opt_array(&dst->per_section_max_jerk, src->per_section_max_jerk, sec_count);
copy_opt_array(&dst->per_section_min_velocity, src->per_section_min_velocity, sec_count);
copy_opt_array(&dst->per_section_min_acceleration, src->per_section_min_acceleration, sec_count);
copy_opt_array(&dst->per_section_max_position, src->per_section_max_position, sec_count);
copy_opt_array(&dst->per_section_min_position, src->per_section_min_position, sec_count);
if (src->per_section_minimum_duration) {
copy_opt_array(&dst->per_section_minimum_duration, src->per_section_minimum_duration, nsec);
} else {
cruckig_free(dst->per_section_minimum_duration);
dst->per_section_minimum_duration = NULL;
}
dst->interrupt_calculation_duration = src->interrupt_calculation_duration;
}

View File

@@ -0,0 +1,94 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_INPUT_PARAMETER_H
#define CRUCKIG_INPUT_PARAMETER_H
#include "cruckig_internal.h"
#include "result.h"
typedef struct {
size_t degrees_of_freedom;
CRuckigControlInterface control_interface;
CRuckigSynchronization synchronization;
CRuckigDurationDiscretization duration_discretization;
/* Current state */
double *current_position;
double *current_velocity;
double *current_acceleration;
/* Target state */
double *target_position;
double *target_velocity;
double *target_acceleration;
/* Kinematic constraints */
double *max_velocity;
double *max_acceleration;
double *max_jerk;
/* Optional min limits (NULL = use -max) */
double *min_velocity; /* NULL or array of dofs */
double *min_acceleration; /* NULL or array of dofs */
/* Per-DOF enable flags */
bool *enabled;
/* Optional per-DOF control interface / synchronization (NULL = use global) */
CRuckigControlInterface *per_dof_control_interface; /* NULL or array of dofs */
CRuckigSynchronization *per_dof_synchronization; /* NULL or array of dofs */
/* Optional minimum trajectory duration (-1 = not set) */
double minimum_duration;
bool has_minimum_duration;
/* ---- Pro features ---- */
/* Intermediate waypoints: flat array of num_waypoints * dofs doubles.
* Each waypoint is dofs consecutive doubles. NULL if no waypoints. */
double *intermediate_positions;
size_t num_intermediate_waypoints;
/* Per-section kinematic constraints: flat arrays of (num_waypoints+1) * dofs.
* Section i constraints at offset i*dofs. NULL = use global. */
double *per_section_max_velocity;
double *per_section_max_acceleration;
double *per_section_max_jerk;
double *per_section_min_velocity;
double *per_section_min_acceleration;
/* Per-section position limits: flat arrays of (num_waypoints+1) * dofs. */
double *per_section_max_position;
double *per_section_min_position;
/* Global position limits during trajectory (NULL = no limits) */
double *max_position; /* NULL or array of dofs */
double *min_position; /* NULL or array of dofs */
/* Per-section minimum duration: array of (num_waypoints+1). NULL = no constraint. */
double *per_section_minimum_duration;
/* Calculation interruption budget in microseconds. 0 = no interruption. */
double interrupt_calculation_duration;
} CRuckigInputParameter;
CRuckigInputParameter* cruckig_input_create(size_t dofs);
void cruckig_input_destroy(CRuckigInputParameter *inp);
bool cruckig_input_validate(const CRuckigInputParameter *inp,
bool check_current_within_limits,
bool check_target_within_limits);
bool cruckig_input_is_equal(const CRuckigInputParameter *a, const CRuckigInputParameter *b);
void cruckig_input_copy(CRuckigInputParameter *dst, const CRuckigInputParameter *src);
/* Set intermediate waypoints. Copies the data. positions is num_waypoints * dofs doubles. */
void cruckig_input_set_intermediate_positions(CRuckigInputParameter *inp,
const double *positions,
size_t num_waypoints);
#endif /* CRUCKIG_INPUT_PARAMETER_H */

View File

@@ -0,0 +1,104 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "output_parameter.h"
CRuckigOutputParameter* cruckig_output_create(size_t dofs) {
CRuckigOutputParameter *out = (CRuckigOutputParameter*)cruckig_calloc(1, sizeof(CRuckigOutputParameter));
if (!out) return NULL;
out->degrees_of_freedom = dofs;
out->trajectory = cruckig_trajectory_create(dofs);
if (!out->trajectory) {
cruckig_free(out);
return NULL;
}
out->new_position = (double*)cruckig_calloc(dofs, sizeof(double));
out->new_velocity = (double*)cruckig_calloc(dofs, sizeof(double));
out->new_acceleration = (double*)cruckig_calloc(dofs, sizeof(double));
out->new_jerk = (double*)cruckig_calloc(dofs, sizeof(double));
if (!out->new_position || !out->new_velocity ||
!out->new_acceleration || !out->new_jerk) {
cruckig_output_destroy(out);
return NULL;
}
out->time = 0.0;
out->new_section = 0;
out->did_section_change = false;
out->new_calculation = false;
out->was_calculation_interrupted = false;
out->calculation_duration = 0.0;
return out;
}
void cruckig_output_destroy(CRuckigOutputParameter *out) {
if (!out) return;
cruckig_trajectory_destroy(out->trajectory);
cruckig_free(out->new_position);
cruckig_free(out->new_velocity);
cruckig_free(out->new_acceleration);
cruckig_free(out->new_jerk);
cruckig_free(out);
}
void cruckig_output_pass_to_input(const CRuckigOutputParameter *out, CRuckigInputParameter *inp) {
if (!out || !inp) return;
const size_t dofs = out->degrees_of_freedom;
const size_t dsz = dofs * sizeof(double);
memcpy(inp->current_position, out->new_position, dsz);
memcpy(inp->current_velocity, out->new_velocity, dsz);
memcpy(inp->current_acceleration, out->new_acceleration, dsz);
/* If section changed and we have intermediate waypoints, remove the first waypoint */
if (out->did_section_change && inp->num_intermediate_waypoints > 0) {
size_t remaining = inp->num_intermediate_waypoints - 1;
if (remaining == 0) {
cruckig_free(inp->intermediate_positions);
inp->intermediate_positions = NULL;
inp->num_intermediate_waypoints = 0;
} else {
/* Shift waypoints forward by one */
memmove(inp->intermediate_positions,
inp->intermediate_positions + dofs,
remaining * dofs * sizeof(double));
inp->num_intermediate_waypoints = remaining;
}
/* Also shift per-section constraints if present */
size_t old_nsec = remaining + 2; /* was num_waypoints+1 sections */
size_t new_nsec = remaining + 1;
#define SHIFT_PER_SEC(field) \
if (inp->field) { \
memmove(inp->field, inp->field + dofs, new_nsec * dofs * sizeof(double)); \
}
SHIFT_PER_SEC(per_section_max_velocity)
SHIFT_PER_SEC(per_section_max_acceleration)
SHIFT_PER_SEC(per_section_max_jerk)
SHIFT_PER_SEC(per_section_min_velocity)
SHIFT_PER_SEC(per_section_min_acceleration)
SHIFT_PER_SEC(per_section_max_position)
SHIFT_PER_SEC(per_section_min_position)
#undef SHIFT_PER_SEC
if (inp->per_section_minimum_duration) {
memmove(inp->per_section_minimum_duration,
inp->per_section_minimum_duration + 1,
new_nsec * sizeof(double));
}
(void)old_nsec;
}
}

View File

@@ -0,0 +1,37 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_OUTPUT_PARAMETER_H
#define CRUCKIG_OUTPUT_PARAMETER_H
#include "cruckig_internal.h"
#include "trajectory.h"
#include "input_parameter.h"
typedef struct {
size_t degrees_of_freedom;
CRuckigTrajectory *trajectory;
double *new_position;
double *new_velocity;
double *new_acceleration;
double *new_jerk;
double time;
size_t new_section;
bool did_section_change;
bool new_calculation;
bool was_calculation_interrupted;
double calculation_duration; /* microseconds */
} CRuckigOutputParameter;
CRuckigOutputParameter* cruckig_output_create(size_t dofs);
void cruckig_output_destroy(CRuckigOutputParameter *out);
void cruckig_output_pass_to_input(const CRuckigOutputParameter *out, CRuckigInputParameter *inp);
#endif /* CRUCKIG_OUTPUT_PARAMETER_H */

View File

@@ -0,0 +1,103 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_POSITION_H
#define CRUCKIG_POSITION_H
#include "cruckig_internal.h"
#include "profile.h"
#include "block.h"
/* ---- Third Order Step 1 ---- */
typedef struct {
double v0, a0, vf, af;
double _vMax, _vMin, _aMax, _aMin, _jMax;
double pd;
double v0_v0, vf_vf;
double a0_a0, a0_p3, a0_p4;
double af_af, af_p3, af_p4;
double jMax_jMax;
CRuckigProfile valid_profiles[6];
} CRuckigPositionThirdOrderStep1;
void cruckig_pos3_step1_init(CRuckigPositionThirdOrderStep1 *s,
double p0, double v0, double a0,
double pf, double vf, double af,
double vMax, double vMin, double aMax, double aMin, double jMax);
bool cruckig_pos3_step1_get_profile(CRuckigPositionThirdOrderStep1 *s,
const CRuckigProfile *input, CRuckigBlock *block);
/* ---- Third Order Step 2 ---- */
typedef struct {
double v0, a0, tf, vf, af;
double _vMax, _vMin, _aMax, _aMin, _jMax;
double pd;
double tf_tf, tf_p3, tf_p4;
double vd, vd_vd;
double ad, ad_ad;
double v0_v0, vf_vf;
double a0_a0, a0_p3, a0_p4, a0_p5, a0_p6;
double af_af, af_p3, af_p4, af_p5, af_p6;
double jMax_jMax;
double g1, g2;
} CRuckigPositionThirdOrderStep2;
void cruckig_pos3_step2_init(CRuckigPositionThirdOrderStep2 *s,
double tf, double p0, double v0, double a0,
double pf, double vf, double af,
double vMax, double vMin, double aMax, double aMin, double jMax);
bool cruckig_pos3_step2_get_profile(CRuckigPositionThirdOrderStep2 *s, CRuckigProfile *profile);
/* ---- Second Order Step 1 ---- */
typedef struct {
double v0, vf;
double _vMax, _vMin, _aMax, _aMin;
double pd;
CRuckigProfile valid_profiles[4];
} CRuckigPositionSecondOrderStep1;
void cruckig_pos2_step1_init(CRuckigPositionSecondOrderStep1 *s,
double p0, double v0, double pf, double vf,
double vMax, double vMin, double aMax, double aMin);
bool cruckig_pos2_step1_get_profile(CRuckigPositionSecondOrderStep1 *s,
const CRuckigProfile *input, CRuckigBlock *block);
/* ---- Second Order Step 2 ---- */
typedef struct {
double v0, tf, vf;
double _vMax, _vMin, _aMax, _aMin;
double pd, vd;
} CRuckigPositionSecondOrderStep2;
void cruckig_pos2_step2_init(CRuckigPositionSecondOrderStep2 *s,
double tf, double p0, double v0, double pf, double vf,
double vMax, double vMin, double aMax, double aMin);
bool cruckig_pos2_step2_get_profile(CRuckigPositionSecondOrderStep2 *s, CRuckigProfile *profile);
/* ---- First Order Step 1 ---- */
typedef struct {
double _vMax, _vMin;
double pd;
} CRuckigPositionFirstOrderStep1;
void cruckig_pos1_step1_init(CRuckigPositionFirstOrderStep1 *s,
double p0, double pf, double vMax, double vMin);
bool cruckig_pos1_step1_get_profile(CRuckigPositionFirstOrderStep1 *s,
const CRuckigProfile *input, CRuckigBlock *block);
/* ---- First Order Step 2 ---- */
typedef struct {
double tf;
double _vMax, _vMin;
double pd;
} CRuckigPositionFirstOrderStep2;
void cruckig_pos1_step2_init(CRuckigPositionFirstOrderStep2 *s,
double tf, double p0, double pf, double vMax, double vMin);
bool cruckig_pos1_step2_get_profile(CRuckigPositionFirstOrderStep2 *s, CRuckigProfile *profile);
#endif /* CRUCKIG_POSITION_H */

View File

@@ -0,0 +1,41 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "position.h"
#include "block.h"
#include "profile.h"
void cruckig_pos1_step1_init(CRuckigPositionFirstOrderStep1 *s,
double p0, double pf, double vMax, double vMin)
{
s->_vMax = vMax;
s->_vMin = vMin;
s->pd = pf - p0;
}
bool cruckig_pos1_step1_get_profile(CRuckigPositionFirstOrderStep1 *s,
const CRuckigProfile *input, CRuckigBlock *block)
{
CRuckigProfile *p = &block->p_min;
cruckig_profile_set_boundary_from_profile(p, input);
const double vf = (s->pd > 0) ? s->_vMax : s->_vMin;
p->t[0] = 0;
p->t[1] = 0;
p->t[2] = 0;
p->t[3] = s->pd / vf;
p->t[4] = 0;
p->t[5] = 0;
p->t[6] = 0;
if (cruckig_profile_check_for_first_order(p, ControlSignsUDDU, ReachedLimitsVEL, vf)) {
block->t_min = p->t_sum[6] + p->brake.duration + p->accel.duration;
return true;
}
return false;
}

View File

@@ -0,0 +1,37 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "position.h"
#include "block.h"
#include "profile.h"
#include "roots.h"
void cruckig_pos1_step2_init(CRuckigPositionFirstOrderStep2 *s,
double tf, double p0, double pf, double vMax, double vMin)
{
s->tf = tf;
s->_vMax = vMax;
s->_vMin = vMin;
s->pd = pf - p0;
}
bool cruckig_pos1_step2_get_profile(CRuckigPositionFirstOrderStep2 *s, CRuckigProfile *profile)
{
const double vf = s->pd / s->tf;
profile->t[0] = 0;
profile->t[1] = 0;
profile->t[2] = 0;
profile->t[3] = s->tf;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
return cruckig_profile_check_for_first_order_with_timing_full(profile, ControlSignsUDDU, ReachedLimitsNONE,
s->tf, vf, s->_vMax, s->_vMin);
}

View File

@@ -0,0 +1,179 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "position.h"
#include "block.h"
#include "profile.h"
void cruckig_pos2_step1_init(CRuckigPositionSecondOrderStep1 *s,
double p0, double v0, double pf, double vf,
double vMax, double vMin, double aMax, double aMin)
{
s->v0 = v0;
s->vf = vf;
s->_vMax = vMax;
s->_vMin = vMin;
s->_aMax = aMax;
s->_aMin = aMin;
s->pd = pf - p0;
}
static void time_acc0(CRuckigPositionSecondOrderStep1 *s,
CRuckigProfile *valid_profiles, size_t *counter,
double vMax, double vMin, double aMax, double aMin, bool return_after_found)
{
CRuckigProfile *profile = &valid_profiles[*counter];
profile->t[0] = (-s->v0 + vMax) / aMax;
profile->t[1] = (aMin * s->v0 * s->v0 - aMax * s->vf * s->vf) / (2 * aMax * aMin * vMax) + vMax * (aMax - aMin) / (2 * aMax * aMin) + s->pd / vMax;
profile->t[2] = (s->vf - vMax) / aMin;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check_for_second_order(profile, ControlSignsUDDU, ReachedLimitsACC0, aMax, aMin, vMax, vMin)) {
++(*counter);
if (*counter < 4) {
cruckig_profile_set_boundary_from_profile(&valid_profiles[*counter], profile);
}
}
(void)return_after_found;
}
static void time_none(CRuckigPositionSecondOrderStep1 *s,
CRuckigProfile *valid_profiles, size_t *counter,
double vMax, double vMin, double aMax, double aMin, bool return_after_found)
{
double h1 = (aMax * s->vf * s->vf - aMin * s->v0 * s->v0 - 2 * aMax * aMin * s->pd) / (aMax - aMin);
if (h1 >= 0.0) {
h1 = sqrt(h1);
/* Solution 1 */
{
CRuckigProfile *profile = &valid_profiles[*counter];
profile->t[0] = -(s->v0 + h1) / aMax;
profile->t[1] = 0;
profile->t[2] = (s->vf + h1) / aMin;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check_for_second_order(profile, ControlSignsUDDU, ReachedLimitsNONE, aMax, aMin, vMax, vMin)) {
++(*counter);
if (*counter < 4) {
cruckig_profile_set_boundary_from_profile(&valid_profiles[*counter], profile);
}
if (return_after_found) {
return;
}
}
}
/* Solution 2 */
{
CRuckigProfile *profile = &valid_profiles[*counter];
profile->t[0] = (-s->v0 + h1) / aMax;
profile->t[1] = 0;
profile->t[2] = (s->vf - h1) / aMin;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check_for_second_order(profile, ControlSignsUDDU, ReachedLimitsNONE, aMax, aMin, vMax, vMin)) {
++(*counter);
if (*counter < 4) {
cruckig_profile_set_boundary_from_profile(&valid_profiles[*counter], profile);
}
}
}
}
}
static bool time_all_single_step(CRuckigPositionSecondOrderStep1 *s,
CRuckigProfile *profile, double vMax, double vMin)
{
if (fabs(s->vf - s->v0) > DBL_EPSILON) {
return false;
}
profile->t[0] = 0;
profile->t[1] = 0;
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (fabs(s->v0) > DBL_EPSILON) {
profile->t[3] = s->pd / s->v0;
if (cruckig_profile_check_for_second_order(profile, ControlSignsUDDU, ReachedLimitsNONE, 0.0, 0.0, vMax, vMin)) {
return true;
}
} else if (fabs(s->pd) < DBL_EPSILON) {
if (cruckig_profile_check_for_second_order(profile, ControlSignsUDDU, ReachedLimitsNONE, 0.0, 0.0, vMax, vMin)) {
return true;
}
}
return false;
}
bool cruckig_pos2_step1_get_profile(CRuckigPositionSecondOrderStep1 *s,
const CRuckigProfile *input, CRuckigBlock *block)
{
/* Zero-limits special case */
if (s->_vMax == 0.0 && s->_vMin == 0.0) {
CRuckigProfile *p = &block->p_min;
cruckig_profile_set_boundary_from_profile(p, input);
if (time_all_single_step(s, p, s->_vMax, s->_vMin)) {
block->t_min = p->t_sum[6] + p->brake.duration + p->accel.duration;
if (fabs(s->v0) > DBL_EPSILON) {
block->a.valid = true;
block->a.left = block->t_min;
block->a.right = INFINITY;
}
return true;
}
return false;
}
size_t valid_profile_counter = 0;
cruckig_profile_set_boundary_from_profile(&s->valid_profiles[0], input);
if (fabs(s->vf) < DBL_EPSILON) {
/* There is no blocked interval when vf==0, so return after first found profile */
const double vMax = (s->pd >= 0) ? s->_vMax : s->_vMin;
const double vMin = (s->pd >= 0) ? s->_vMin : s->_vMax;
const double aMax = (s->pd >= 0) ? s->_aMax : s->_aMin;
const double aMin = (s->pd >= 0) ? s->_aMin : s->_aMax;
time_none(s, s->valid_profiles, &valid_profile_counter, vMax, vMin, aMax, aMin, true);
if (valid_profile_counter > 0) { goto return_block; }
time_acc0(s, s->valid_profiles, &valid_profile_counter, vMax, vMin, aMax, aMin, true);
if (valid_profile_counter > 0) { goto return_block; }
time_none(s, s->valid_profiles, &valid_profile_counter, vMin, vMax, aMin, aMax, true);
if (valid_profile_counter > 0) { goto return_block; }
time_acc0(s, s->valid_profiles, &valid_profile_counter, vMin, vMax, aMin, aMax, true);
} else {
time_none(s, s->valid_profiles, &valid_profile_counter, s->_vMax, s->_vMin, s->_aMax, s->_aMin, false);
time_none(s, s->valid_profiles, &valid_profile_counter, s->_vMin, s->_vMax, s->_aMin, s->_aMax, false);
time_acc0(s, s->valid_profiles, &valid_profile_counter, s->_vMax, s->_vMin, s->_aMax, s->_aMin, false);
time_acc0(s, s->valid_profiles, &valid_profile_counter, s->_vMin, s->_vMax, s->_aMin, s->_aMax, false);
}
return_block:
return cruckig_block_calculate(block, s->valid_profiles, valid_profile_counter, 4);
}

View File

@@ -0,0 +1,146 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "position.h"
#include "block.h"
#include "profile.h"
#include "roots.h"
void cruckig_pos2_step2_init(CRuckigPositionSecondOrderStep2 *s,
double tf, double p0, double v0, double pf, double vf,
double vMax, double vMin, double aMax, double aMin)
{
s->v0 = v0;
s->tf = tf;
s->vf = vf;
s->_vMax = vMax;
s->_vMin = vMin;
s->_aMax = aMax;
s->_aMin = aMin;
s->pd = pf - p0;
s->vd = vf - v0;
}
static bool time_acc0(CRuckigPositionSecondOrderStep2 *s, CRuckigProfile *profile,
double vMax, double vMin, double aMax, double aMin)
{
/* UD Solution 1/2 */
{
const double h1 = sqrt((2 * aMax * (s->pd - s->tf * s->vf) - 2 * aMin * (s->pd - s->tf * s->v0) + s->vd * s->vd) / (aMax * aMin) + s->tf * s->tf);
profile->t[0] = (aMax * s->vd - aMax * aMin * (s->tf - h1)) / (aMax * (aMax - aMin));
profile->t[1] = h1;
profile->t[2] = s->tf - (profile->t[0] + h1);
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check_for_second_order_with_timing(profile, ControlSignsUDDU, ReachedLimitsACC0, s->tf, aMax, aMin, vMax, vMin)) {
profile->pf = profile->p[7];
return true;
}
}
/* UU Solution */
{
const double h1 = (-s->vd + aMax * s->tf);
profile->t[0] = -s->vd * s->vd / (2 * aMax * h1) + (s->pd - s->v0 * s->tf) / h1;
profile->t[1] = -s->vd / aMax + s->tf;
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = s->tf - (profile->t[0] + profile->t[1]);
if (cruckig_profile_check_for_second_order_with_timing(profile, ControlSignsUDDU, ReachedLimitsACC0, s->tf, aMax, aMin, vMax, vMin)) {
profile->pf = profile->p[7];
return true;
}
}
/* UU Solution - 2 step */
{
profile->t[0] = 0;
profile->t[1] = -s->vd / aMax + s->tf;
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = s->vd / aMax;
if (cruckig_profile_check_for_second_order_with_timing(profile, ControlSignsUDDU, ReachedLimitsACC0, s->tf, aMax, aMin, vMax, vMin)) {
profile->pf = profile->p[7];
return true;
}
}
return false;
}
static bool time_none(CRuckigPositionSecondOrderStep2 *s, CRuckigProfile *profile,
double vMax, double vMin, double aMax, double aMin)
{
if (fabs(s->v0) < DBL_EPSILON && fabs(s->vf) < DBL_EPSILON && fabs(s->pd) < DBL_EPSILON) {
profile->t[0] = 0;
profile->t[1] = s->tf;
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check_for_second_order_with_timing(profile, ControlSignsUDDU, ReachedLimitsNONE, s->tf, aMax, aMin, vMax, vMin)) {
profile->pf = profile->p[7];
return true;
}
}
/* UD Solution 1/2 */
{
const double h1 = 2 * (s->vf * s->tf - s->pd);
profile->t[0] = h1 / s->vd;
profile->t[1] = s->tf - profile->t[0];
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
const double af = s->vd * s->vd / h1;
if ((aMin - 1e-12 < af) && (af < aMax + 1e-12) &&
cruckig_profile_check_for_second_order_with_timing(profile, ControlSignsUDDU, ReachedLimitsNONE, s->tf, af, -af, vMax, vMin)) {
profile->pf = profile->p[7];
return true;
}
}
return false;
}
static bool check_all(CRuckigPositionSecondOrderStep2 *s, CRuckigProfile *profile,
double vMax, double vMin, double aMax, double aMin)
{
return time_acc0(s, profile, vMax, vMin, aMax, aMin) ||
time_none(s, profile, vMax, vMin, aMax, aMin);
}
bool cruckig_pos2_step2_get_profile(CRuckigPositionSecondOrderStep2 *s, CRuckigProfile *profile)
{
/* Test all cases to get ones that match */
if (s->pd > 0) {
return check_all(s, profile, s->_vMax, s->_vMin, s->_aMax, s->_aMin) ||
check_all(s, profile, s->_vMin, s->_vMax, s->_aMin, s->_aMax);
}
return check_all(s, profile, s->_vMin, s->_vMax, s->_aMin, s->_aMax) ||
check_all(s, profile, s->_vMax, s->_vMin, s->_aMax, s->_aMin);
}

View File

@@ -0,0 +1,705 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "position.h"
#include "block.h"
#include "profile.h"
#include "roots.h"
void cruckig_pos3_step1_init(CRuckigPositionThirdOrderStep1 *s,
double p0, double v0, double a0,
double pf, double vf, double af,
double vMax, double vMin, double aMax, double aMin, double jMax)
{
s->v0 = v0;
s->a0 = a0;
s->vf = vf;
s->af = af;
s->_vMax = vMax;
s->_vMin = vMin;
s->_aMax = aMax;
s->_aMin = aMin;
s->_jMax = jMax;
s->pd = pf - p0;
s->v0_v0 = v0 * v0;
s->vf_vf = vf * vf;
s->a0_a0 = a0 * a0;
s->af_af = af * af;
s->a0_p3 = a0 * s->a0_a0;
s->a0_p4 = s->a0_a0 * s->a0_a0;
s->af_p3 = af * s->af_af;
s->af_p4 = s->af_af * s->af_af;
s->jMax_jMax = jMax * jMax;
}
/* Helper: add_profile equivalent - increment counter, copy boundary to next */
static inline void add_profile(CRuckigProfile *valid_profiles, size_t *counter, size_t max_profiles)
{
const size_t prev = *counter;
++(*counter);
if (*counter < max_profiles) {
cruckig_profile_set_boundary_from_profile(&valid_profiles[*counter], &valid_profiles[prev]);
}
}
static void time_all_vel(CRuckigPositionThirdOrderStep1 *s,
CRuckigProfile *valid_profiles, size_t *counter,
double vMax, double vMin, double aMax, double aMin, double jMax,
bool return_after_found)
{
CRuckigProfile *profile = &valid_profiles[*counter];
const double v0 = s->v0, a0 = s->a0, vf = s->vf, af = s->af;
const double v0_v0 = s->v0_v0, vf_vf = s->vf_vf;
const double a0_a0 = s->a0_a0, af_af = s->af_af;
const double a0_p3 = s->a0_p3, af_p3 = s->af_p3;
const double a0_p4 = s->a0_p4, af_p4 = s->af_p4;
const double jMax_jMax = s->jMax_jMax;
const double pd = s->pd;
(void)return_after_found;
/* ACC0_ACC1_VEL */
profile->t[0] = (-a0 + aMax) / jMax;
profile->t[1] = (a0_a0 / 2 - aMax * aMax - jMax * (v0 - vMax)) / (aMax * jMax);
profile->t[2] = aMax / jMax;
profile->t[3] = (3 * (a0_p4 * aMin - af_p4 * aMax) + 8 * aMax * aMin * (af_p3 - a0_p3 + 3 * jMax * (a0 * v0 - af * vf)) + 6 * a0_a0 * aMin * (aMax * aMax - 2 * jMax * v0) - 6 * af_af * aMax * (aMin * aMin - 2 * jMax * vf) - 12 * jMax * (aMax * aMin * (aMax * (v0 + vMax) - aMin * (vf + vMax) - 2 * jMax * pd) + (aMin - aMax) * jMax * vMax * vMax + jMax * (aMax * vf_vf - aMin * v0_v0))) / (24 * aMax * aMin * jMax_jMax * vMax);
profile->t[4] = -aMin / jMax;
profile->t[5] = -(af_af / 2 - aMin * aMin - jMax * (vf - vMax)) / (aMin * jMax);
profile->t[6] = profile->t[4] + af / jMax;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsACC0_ACC1_VEL, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
return;
}
/* ACC1_VEL */
{
const double t_acc0 = sqrt(a0_a0 / (2 * jMax_jMax) + (vMax - v0) / jMax);
profile->t[0] = t_acc0 - a0 / jMax;
profile->t[1] = 0;
profile->t[2] = t_acc0;
profile->t[3] = -(3 * af_p4 - 8 * aMin * (af_p3 - a0_p3) - 24 * aMin * jMax * (a0 * v0 - af * vf) + 6 * af_af * (aMin * aMin - 2 * jMax * vf) - 12 * jMax * (2 * aMin * jMax * pd + aMin * aMin * (vf + vMax) + jMax * (vMax * vMax - vf_vf) + aMin * t_acc0 * (a0_a0 - 2 * jMax * (v0 + vMax)))) / (24 * aMin * jMax_jMax * vMax);
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsACC1_VEL, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
return;
}
}
/* ACC0_VEL */
{
const double t_acc1 = sqrt(af_af / (2 * jMax_jMax) + (vMax - vf) / jMax);
profile->t[0] = (-a0 + aMax) / jMax;
profile->t[1] = (a0_a0 / 2 - aMax * aMax - jMax * (v0 - vMax)) / (aMax * jMax);
profile->t[2] = aMax / jMax;
profile->t[3] = (3 * a0_p4 + 8 * aMax * (af_p3 - a0_p3) + 24 * aMax * jMax * (a0 * v0 - af * vf) + 6 * a0_a0 * (aMax * aMax - 2 * jMax * v0) - 12 * jMax * (-2 * aMax * jMax * pd + aMax * aMax * (v0 + vMax) + jMax * (vMax * vMax - v0_v0) + aMax * t_acc1 * (-af_af + 2 * (vf + vMax) * jMax))) / (24 * aMax * jMax_jMax * vMax);
profile->t[4] = t_acc1;
profile->t[5] = 0;
profile->t[6] = t_acc1 + af / jMax;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsACC0_VEL, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
return;
}
}
/* VEL */
{
const double t_acc0 = sqrt(a0_a0 / (2 * jMax_jMax) + (vMax - v0) / jMax);
const double t_acc1 = sqrt(af_af / (2 * jMax_jMax) + (vMax - vf) / jMax);
/* Solution 3/4 */
profile->t[0] = t_acc0 - a0 / jMax;
profile->t[1] = 0;
profile->t[2] = t_acc0;
profile->t[3] = (af_p3 - a0_p3) / (3 * jMax_jMax * vMax) + (a0 * v0 - af * vf + (af_af * t_acc1 + a0_a0 * t_acc0) / 2) / (jMax * vMax) - (v0 / vMax + 1.0) * t_acc0 - (vf / vMax + 1.0) * t_acc1 + pd / vMax;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsVEL, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
}
}
}
static void time_acc0_acc1(CRuckigPositionThirdOrderStep1 *s,
CRuckigProfile *valid_profiles, size_t *counter,
double vMax, double vMin, double aMax, double aMin, double jMax,
bool return_after_found)
{
CRuckigProfile *profile = &valid_profiles[*counter];
const double a0 = s->a0, af = s->af;
const double a0_a0 = s->a0_a0, af_af = s->af_af;
const double a0_p3 = s->a0_p3, af_p3 = s->af_p3;
const double a0_p4 = s->a0_p4, af_p4 = s->af_p4;
const double v0 = s->v0, vf = s->vf;
const double v0_v0 = s->v0_v0, vf_vf = s->vf_vf;
const double jMax_jMax = s->jMax_jMax;
const double pd = s->pd;
double h1 = (3 * (af_p4 * aMax - a0_p4 * aMin) + aMax * aMin * (8 * (a0_p3 - af_p3) + 3 * aMax * aMin * (aMax - aMin) + 6 * aMin * af_af - 6 * aMax * a0_a0) + 12 * jMax * (aMax * aMin * ((aMax - 2 * a0) * v0 - (aMin - 2 * af) * vf) + aMin * a0_a0 * v0 - aMax * af_af * vf)) / (3 * (aMax - aMin) * jMax_jMax) + 4 * (aMax * vf_vf - aMin * v0_v0 - 2 * aMin * aMax * pd) / (aMax - aMin);
if (h1 >= 0) {
h1 = sqrt(h1) / 2;
const double h2 = a0_a0 / (2 * aMax * jMax) + (aMin - 2 * aMax) / (2 * jMax) - v0 / aMax;
const double h3 = -af_af / (2 * aMin * jMax) - (aMax - 2 * aMin) / (2 * jMax) + vf / aMin;
/* UDDU: Solution 2 */
if (h2 > h1 / aMax && h3 > -h1 / aMin) {
profile->t[0] = (-a0 + aMax) / jMax;
profile->t[1] = h2 - h1 / aMax;
profile->t[2] = aMax / jMax;
profile->t[3] = 0;
profile->t[4] = -aMin / jMax;
profile->t[5] = h3 + h1 / aMin;
profile->t[6] = profile->t[4] + af / jMax;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsACC0_ACC1, true, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
if (return_after_found) {
return;
}
}
}
/* UDDU: Solution 1 */
profile = &valid_profiles[*counter];
if (h2 > -h1 / aMax && h3 > h1 / aMin) {
profile->t[0] = (-a0 + aMax) / jMax;
profile->t[1] = h2 + h1 / aMax;
profile->t[2] = aMax / jMax;
profile->t[3] = 0;
profile->t[4] = -aMin / jMax;
profile->t[5] = h3 - h1 / aMin;
profile->t[6] = profile->t[4] + af / jMax;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsACC0_ACC1, true, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
}
}
}
}
static void time_all_none_acc0_acc1(CRuckigPositionThirdOrderStep1 *s,
CRuckigProfile *valid_profiles, size_t *counter,
double vMax, double vMin, double aMax, double aMin, double jMax,
bool return_after_found)
{
CRuckigProfile *profile = &valid_profiles[*counter];
const double v0 = s->v0, a0 = s->a0, vf = s->vf, af = s->af;
const double v0_v0 = s->v0_v0, vf_vf = s->vf_vf;
const double a0_a0 = s->a0_a0, af_af = s->af_af;
const double a0_p3 = s->a0_p3, af_p3 = s->af_p3;
const double a0_p4 = s->a0_p4, af_p4 = s->af_p4;
const double jMax_jMax = s->jMax_jMax;
const double pd = s->pd;
/* NONE UDDU / UDUD Strategy */
const double h2_none = (a0_a0 - af_af) / (2 * jMax) + (vf - v0);
const double h2_h2 = h2_none * h2_none;
const double t_min_none = (a0 - af) / jMax;
const double t_max_none = (aMax - aMin) / jMax;
double polynom_none[4];
polynom_none[0] = 0;
polynom_none[1] = -2 * (a0_a0 + af_af - 2 * jMax * (v0 + vf)) / jMax_jMax;
polynom_none[2] = 4 * (a0_p3 - af_p3 + 3 * jMax * (af * vf - a0 * v0)) / (3 * jMax * jMax_jMax) - 4 * pd / jMax;
polynom_none[3] = -h2_h2 / jMax_jMax;
/* ACC0 */
const double h3_acc0 = (a0_a0 - af_af) / (2 * aMax * jMax) + (vf - v0) / aMax;
const double t_min_acc0 = (aMax - af) / jMax;
const double t_max_acc0 = (aMax - aMin) / jMax;
const double h0_acc0 = 3 * (af_p4 - a0_p4) + 8 * (a0_p3 - af_p3) * aMax + 24 * aMax * jMax * (af * vf - a0 * v0) - 6 * a0_a0 * (aMax * aMax - 2 * jMax * v0) + 6 * af_af * (aMax * aMax - 2 * jMax * vf) + 12 * jMax * (jMax * (vf_vf - v0_v0 - 2 * aMax * pd) - aMax * aMax * (vf - v0));
const double h2_acc0 = -af_af + aMax * aMax + 2 * jMax * vf;
double polynom_acc0[4];
polynom_acc0[0] = -2 * aMax / jMax;
polynom_acc0[1] = h2_acc0 / jMax_jMax;
polynom_acc0[2] = 0;
polynom_acc0[3] = h0_acc0 / (12 * jMax_jMax * jMax_jMax);
/* ACC1 */
const double h3_acc1 = -(a0_a0 + af_af) / (2 * jMax * aMin) + aMin / jMax + (vf - v0) / aMin;
const double t_min_acc1 = (aMin - a0) / jMax;
const double t_max_acc1 = (aMax - a0) / jMax;
const double h0_acc1 = (a0_p4 - af_p4) / 4 + 2 * (af_p3 - a0_p3) * aMin / 3 + (a0_a0 - af_af) * aMin * aMin / 2 + jMax * (af_af * vf + a0_a0 * v0 + 2 * aMin * (jMax * pd - a0 * v0 - af * vf) + aMin * aMin * (v0 + vf) + jMax * (v0_v0 - vf_vf));
const double h2_acc1 = a0_a0 - a0 * aMin + 2 * jMax * v0;
double polynom_acc1[4];
polynom_acc1[0] = 2 * (2 * a0 - aMin) / jMax;
polynom_acc1[1] = (5 * a0_a0 + aMin * (aMin - 6 * a0) + 2 * jMax * v0) / jMax_jMax;
polynom_acc1[2] = 2 * (a0 - aMin) * h2_acc1 / (jMax_jMax * jMax);
polynom_acc1[3] = h0_acc1 / (jMax_jMax * jMax_jMax);
CRuckigRootSet roots_none = cruckig_roots_solve_quart_monic(polynom_none[0], polynom_none[1], polynom_none[2], polynom_none[3]);
CRuckigRootSet roots_acc0 = cruckig_roots_solve_quart_monic(polynom_acc0[0], polynom_acc0[1], polynom_acc0[2], polynom_acc0[3]);
CRuckigRootSet roots_acc1 = cruckig_roots_solve_quart_monic(polynom_acc1[0], polynom_acc1[1], polynom_acc1[2], polynom_acc1[3]);
cruckig_root_set_sort(&roots_none);
cruckig_root_set_sort(&roots_acc0);
cruckig_root_set_sort(&roots_acc1);
for (size_t i = 0; i < roots_none.size; ++i) {
double t = roots_none.data[i];
if (t < t_min_none || t > t_max_none) {
continue;
}
/* Single Newton-step (regarding pd) */
if (t > DBL_EPSILON) {
const double h1 = jMax * t * t;
const double orig = -h2_h2 / (4 * jMax * t) + h2_none * (af / jMax + t) + (4 * a0_p3 + 2 * af_p3 - 6 * a0_a0 * (af + 2 * jMax * t) + 12 * (af - a0) * jMax * v0 + 3 * jMax_jMax * (-4 * pd + (h1 + 8 * v0) * t)) / (12 * jMax_jMax);
const double deriv = h2_none + 2 * v0 - a0_a0 / jMax + h2_h2 / (4 * h1) + (3 * h1) / 4;
t -= orig / deriv;
}
const double h0 = h2_none / (2 * jMax * t);
profile = &valid_profiles[*counter];
profile->t[0] = h0 + t / 2 - a0 / jMax;
profile->t[1] = 0;
profile->t[2] = t;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = -h0 + t / 2 + af / jMax;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsNONE, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
if (return_after_found) {
return;
}
}
}
for (size_t i = 0; i < roots_acc0.size; ++i) {
double t = roots_acc0.data[i];
if (t < t_min_acc0 || t > t_max_acc0) {
continue;
}
/* Single Newton step (regarding pd) */
if (t > DBL_EPSILON) {
const double h1 = jMax * t;
const double orig = h0_acc0 / (12 * jMax_jMax * t) + t * (h2_acc0 + h1 * (h1 - 2 * aMax));
const double deriv = 2 * (h2_acc0 + h1 * (2 * h1 - 3 * aMax));
t -= orig / deriv;
}
profile = &valid_profiles[*counter];
profile->t[0] = (-a0 + aMax) / jMax;
profile->t[1] = h3_acc0 - 2 * t + jMax / aMax * t * t;
profile->t[2] = t;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = (af - aMax) / jMax + t;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsACC0, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
if (return_after_found) {
return;
}
}
}
for (size_t i = 0; i < roots_acc1.size; ++i) {
double t = roots_acc1.data[i];
if (t < t_min_acc1 || t > t_max_acc1) {
continue;
}
/* Double Newton step (regarding pd) */
if (t > DBL_EPSILON) {
const double h5 = a0_p3 + 2 * jMax * a0 * v0;
double h1 = jMax * t;
double orig = -(h0_acc1 / 2 + h1 * (h5 + a0 * (aMin - 2 * h1) * (aMin - h1) + a0_a0 * (5 * h1 / 2 - 2 * aMin) + aMin * aMin * h1 / 2 + jMax * (h1 / 2 - aMin) * (h1 * t + 2 * v0))) / jMax;
double deriv = (aMin - a0 - h1) * (h2_acc1 + h1 * (4 * a0 - aMin + 2 * h1));
{
double correction = orig / deriv;
if (correction > t) correction = t;
t -= correction;
}
h1 = jMax * t;
orig = -(h0_acc1 / 2 + h1 * (h5 + a0 * (aMin - 2 * h1) * (aMin - h1) + a0_a0 * (5 * h1 / 2 - 2 * aMin) + aMin * aMin * h1 / 2 + jMax * (h1 / 2 - aMin) * (h1 * t + 2 * v0))) / jMax;
if (fabs(orig) > 1e-9) {
deriv = (aMin - a0 - h1) * (h2_acc1 + h1 * (4 * a0 - aMin + 2 * h1));
t -= orig / deriv;
h1 = jMax * t;
orig = -(h0_acc1 / 2 + h1 * (h5 + a0 * (aMin - 2 * h1) * (aMin - h1) + a0_a0 * (5 * h1 / 2 - 2 * aMin) + aMin * aMin * h1 / 2 + jMax * (h1 / 2 - aMin) * (h1 * t + 2 * v0))) / jMax;
if (fabs(orig) > 1e-9) {
deriv = (aMin - a0 - h1) * (h2_acc1 + h1 * (4 * a0 - aMin + 2 * h1));
t -= orig / deriv;
}
}
}
profile = &valid_profiles[*counter];
profile->t[0] = t;
profile->t[1] = 0;
profile->t[2] = (a0 - aMin) / jMax + t;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = h3_acc1 - (2 * a0 + jMax * t) * t / aMin;
profile->t[6] = (af - aMin) / jMax;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsACC1, true, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
if (return_after_found) {
return;
}
}
}
}
static void time_acc1_vel_two_step(CRuckigPositionThirdOrderStep1 *s,
CRuckigProfile *valid_profiles, size_t *counter,
double vMax, double vMin, double aMax, double aMin, double jMax)
{
CRuckigProfile *profile = &valid_profiles[*counter];
const double v0 = s->v0, a0 = s->a0, vf = s->vf, af = s->af;
const double vf_vf = s->vf_vf;
const double a0_a0 = s->a0_a0, af_af = s->af_af;
const double a0_p3 = s->a0_p3, af_p3 = s->af_p3, af_p4 = s->af_p4;
const double jMax_jMax = s->jMax_jMax;
const double pd = s->pd;
profile->t[0] = 0;
profile->t[1] = 0;
profile->t[2] = a0 / jMax;
profile->t[3] = -(3 * af_p4 - 8 * aMin * (af_p3 - a0_p3) - 24 * aMin * jMax * (a0 * v0 - af * vf) + 6 * af_af * (aMin * aMin - 2 * jMax * vf) - 12 * jMax * (2 * aMin * jMax * pd + aMin * aMin * (vf + vMax) + jMax * (vMax * vMax - vf_vf) + aMin * a0 * (a0_a0 - 2 * jMax * (v0 + vMax)) / jMax)) / (24 * aMin * jMax_jMax * vMax);
profile->t[4] = -aMin / jMax;
profile->t[5] = -(af_af / 2 - aMin * aMin + jMax * (vMax - vf)) / (aMin * jMax);
profile->t[6] = profile->t[4] + af / jMax;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsACC1_VEL, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
}
}
static void time_acc0_two_step(CRuckigPositionThirdOrderStep1 *s,
CRuckigProfile *valid_profiles, size_t *counter,
double vMax, double vMin, double aMax, double aMin, double jMax)
{
CRuckigProfile *profile = &valid_profiles[*counter];
const double v0 = s->v0, a0 = s->a0, vf = s->vf, af = s->af;
const double v0_v0 = s->v0_v0, vf_vf = s->vf_vf;
const double a0_a0 = s->a0_a0, af_af = s->af_af;
const double a0_p3 = s->a0_p3, af_p3 = s->af_p3;
const double a0_p4 = s->a0_p4, af_p4 = s->af_p4;
const double jMax_jMax = s->jMax_jMax;
const double pd = s->pd;
/* Two step */
{
profile->t[0] = 0;
profile->t[1] = (af_af - a0_a0 + 2 * jMax * (vf - v0)) / (2 * a0 * jMax);
profile->t[2] = (a0 - af) / jMax;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsACC0, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
return;
}
}
/* Three step - Removed pf */
{
profile = &valid_profiles[*counter];
profile->t[0] = (-a0 + aMax) / jMax;
profile->t[1] = (a0_a0 + af_af - 2 * aMax * aMax + 2 * jMax * (vf - v0)) / (2 * aMax * jMax);
profile->t[2] = (-af + aMax) / jMax;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsACC0, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
return;
}
}
/* Three step - Removed aMax */
{
profile = &valid_profiles[*counter];
const double h0 = 3 * (af_af - a0_a0 + 2 * jMax * (v0 + vf));
const double h2 = a0_p3 + 2 * af_p3 + 6 * jMax_jMax * pd + 6 * (af - a0) * jMax * vf - 3 * a0 * af_af;
const double h1_sq = 2 * (2 * h2 * h2 + h0 * (a0_p4 - 6 * a0_a0 * (af_af + 2 * jMax * vf) + 8 * a0 * (af_p3 + 3 * jMax_jMax * pd + 3 * af * jMax * vf) - 3 * (af_p4 + 4 * af_af * jMax * vf + 4 * jMax_jMax * (vf_vf - v0_v0))));
const double h1 = sqrt(h1_sq) * fabs(jMax) / jMax;
profile->t[0] = (4 * af_p3 + 2 * a0_p3 - 6 * a0 * af_af + 12 * jMax_jMax * pd + 12 * (af - a0) * jMax * vf + h1) / (2 * jMax * h0);
profile->t[1] = -h1 / (jMax * h0);
profile->t[2] = (-4 * a0_p3 - 2 * af_p3 + 6 * a0_a0 * af + 12 * jMax_jMax * pd - 12 * (af - a0) * jMax * v0 + h1) / (2 * jMax * h0);
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsACC0, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
return;
}
}
/* Three step - t=(aMax - aMin)/jMax */
{
profile = &valid_profiles[*counter];
const double t = (aMax - aMin) / jMax;
profile->t[0] = (-a0 + aMax) / jMax;
profile->t[1] = (a0_a0 - af_af) / (2 * aMax * jMax) + (vf - v0 + jMax * t * t) / aMax - 2 * t;
profile->t[2] = t;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = (af - aMin) / jMax;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsACC0, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
return;
}
}
}
static void time_vel_two_step(CRuckigPositionThirdOrderStep1 *s,
CRuckigProfile *valid_profiles, size_t *counter,
double vMax, double vMin, double aMax, double aMin, double jMax)
{
CRuckigProfile *profile;
const double v0 = s->v0, a0 = s->a0, vf = s->vf, af = s->af;
const double af_af = s->af_af;
const double a0_p3 = s->a0_p3, af_p3 = s->af_p3;
const double jMax_jMax = s->jMax_jMax;
const double pd = s->pd;
const double h1 = sqrt(af_af / (2 * jMax_jMax) + (vMax - vf) / jMax);
/* Four step - Solution 3/4 */
{
profile = &valid_profiles[*counter];
profile->t[0] = -a0 / jMax;
profile->t[1] = 0;
profile->t[2] = 0;
profile->t[3] = (af_p3 - a0_p3) / (3 * jMax_jMax * vMax) + (a0 * v0 - af * vf + (af_af * h1) / 2) / (jMax * vMax) - (vf / vMax + 1.0) * h1 + pd / vMax;
profile->t[4] = h1;
profile->t[5] = 0;
profile->t[6] = h1 + af / jMax;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsVEL, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
return;
}
}
/* Four step */
{
profile = &valid_profiles[*counter];
profile->t[0] = 0;
profile->t[1] = 0;
profile->t[2] = a0 / jMax;
profile->t[3] = (af_p3 - a0_p3) / (3 * jMax_jMax * vMax) + (a0 * v0 - af * vf + (af_af * h1 + a0_p3 / jMax) / 2) / (jMax * vMax) - (v0 / vMax + 1.0) * a0 / jMax - (vf / vMax + 1.0) * h1 + pd / vMax;
profile->t[4] = h1;
profile->t[5] = 0;
profile->t[6] = h1 + af / jMax;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsVEL, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
return;
}
}
}
static void time_none_two_step(CRuckigPositionThirdOrderStep1 *s,
CRuckigProfile *valid_profiles, size_t *counter,
double vMax, double vMin, double aMax, double aMin, double jMax)
{
CRuckigProfile *profile;
const double v0 = s->v0, a0 = s->a0, vf = s->vf, af = s->af;
const double a0_a0 = s->a0_a0, af_af = s->af_af;
/* Two step */
{
profile = &valid_profiles[*counter];
const double h0 = sqrt((a0_a0 + af_af) / 2 + jMax * (vf - v0)) * fabs(jMax) / jMax;
profile->t[0] = (h0 - a0) / jMax;
profile->t[1] = 0;
profile->t[2] = (h0 - af) / jMax;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsNONE, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
return;
}
}
/* Single step */
{
profile = &valid_profiles[*counter];
profile->t[0] = (af - a0) / jMax;
profile->t[1] = 0;
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsNONE, false, jMax, vMax, vMin, aMax, aMin)) {
add_profile(valid_profiles, counter, 6);
return;
}
}
}
static bool time_all_single_step(CRuckigPositionThirdOrderStep1 *s,
CRuckigProfile *profile, double vMax, double vMin, double aMax, double aMin)
{
const double v0 = s->v0, a0 = s->a0, af = s->af;
const double v0_v0 = s->v0_v0;
const double pd = s->pd;
if (fabs(af - a0) > DBL_EPSILON) {
return false;
}
profile->t[0] = 0;
profile->t[1] = 0;
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (fabs(a0) > DBL_EPSILON) {
const double q = sqrt(2 * a0 * pd + v0_v0);
/* Solution 1 */
profile->t[3] = (-v0 + q) / a0;
if (profile->t[3] >= 0.0 && cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsNONE, false, 0.0, vMax, vMin, aMax, aMin)) {
return true;
}
/* Solution 2 */
profile->t[3] = -(v0 + q) / a0;
if (profile->t[3] >= 0.0 && cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsNONE, false, 0.0, vMax, vMin, aMax, aMin)) {
return true;
}
} else if (fabs(v0) > DBL_EPSILON) {
profile->t[3] = pd / v0;
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsNONE, false, 0.0, vMax, vMin, aMax, aMin)) {
return true;
}
} else if (fabs(pd) < DBL_EPSILON) {
if (cruckig_profile_check(profile, ControlSignsUDDU, ReachedLimitsNONE, false, 0.0, vMax, vMin, aMax, aMin)) {
return true;
}
}
return false;
}
CRUCKIG_HOT
bool cruckig_pos3_step1_get_profile(CRuckigPositionThirdOrderStep1 *s,
const CRuckigProfile *input, CRuckigBlock *block)
{
/* Zero-limits special case */
if (s->_jMax == 0.0 || s->_aMax == 0.0 || s->_aMin == 0.0) {
CRuckigProfile *p = &block->p_min;
cruckig_profile_set_boundary_from_profile(p, input);
if (time_all_single_step(s, p, s->_vMax, s->_vMin, s->_aMax, s->_aMin)) {
block->t_min = p->t_sum[6] + p->brake.duration + p->accel.duration;
if (fabs(s->v0) > DBL_EPSILON || fabs(s->a0) > DBL_EPSILON) {
block->a.valid = true;
block->a.left = block->t_min;
block->a.right = INFINITY;
}
return true;
}
return false;
}
size_t valid_profile_counter = 0;
cruckig_profile_set_boundary_from_profile(&s->valid_profiles[0], input);
if (fabs(s->vf) < DBL_EPSILON && fabs(s->af) < DBL_EPSILON) {
const double vMax = (s->pd >= 0) ? s->_vMax : s->_vMin;
const double vMin = (s->pd >= 0) ? s->_vMin : s->_vMax;
const double aMax = (s->pd >= 0) ? s->_aMax : s->_aMin;
const double aMin = (s->pd >= 0) ? s->_aMin : s->_aMax;
const double jMax = (s->pd >= 0) ? s->_jMax : -s->_jMax;
if (fabs(s->v0) < DBL_EPSILON && fabs(s->a0) < DBL_EPSILON && fabs(s->pd) < DBL_EPSILON) {
time_all_none_acc0_acc1(s, s->valid_profiles, &valid_profile_counter, vMax, vMin, aMax, aMin, jMax, true);
} else {
/* There is no blocked interval when vf==0 && af==0, so return after first found profile */
time_all_vel(s, s->valid_profiles, &valid_profile_counter, vMax, vMin, aMax, aMin, jMax, true);
if (valid_profile_counter > 0) { goto return_block; }
time_all_none_acc0_acc1(s, s->valid_profiles, &valid_profile_counter, vMax, vMin, aMax, aMin, jMax, true);
if (valid_profile_counter > 0) { goto return_block; }
time_acc0_acc1(s, s->valid_profiles, &valid_profile_counter, vMax, vMin, aMax, aMin, jMax, true);
if (valid_profile_counter > 0) { goto return_block; }
time_all_vel(s, s->valid_profiles, &valid_profile_counter, vMin, vMax, aMin, aMax, -jMax, true);
if (valid_profile_counter > 0) { goto return_block; }
time_all_none_acc0_acc1(s, s->valid_profiles, &valid_profile_counter, vMin, vMax, aMin, aMax, -jMax, true);
if (valid_profile_counter > 0) { goto return_block; }
time_acc0_acc1(s, s->valid_profiles, &valid_profile_counter, vMin, vMax, aMin, aMax, -jMax, true);
}
} else {
time_all_none_acc0_acc1(s, s->valid_profiles, &valid_profile_counter, s->_vMax, s->_vMin, s->_aMax, s->_aMin, s->_jMax, false);
time_all_none_acc0_acc1(s, s->valid_profiles, &valid_profile_counter, s->_vMin, s->_vMax, s->_aMin, s->_aMax, -s->_jMax, false);
time_acc0_acc1(s, s->valid_profiles, &valid_profile_counter, s->_vMax, s->_vMin, s->_aMax, s->_aMin, s->_jMax, false);
time_acc0_acc1(s, s->valid_profiles, &valid_profile_counter, s->_vMin, s->_vMax, s->_aMin, s->_aMax, -s->_jMax, false);
time_all_vel(s, s->valid_profiles, &valid_profile_counter, s->_vMax, s->_vMin, s->_aMax, s->_aMin, s->_jMax, false);
time_all_vel(s, s->valid_profiles, &valid_profile_counter, s->_vMin, s->_vMax, s->_aMin, s->_aMax, -s->_jMax, false);
}
if (valid_profile_counter == 0) {
time_none_two_step(s, s->valid_profiles, &valid_profile_counter, s->_vMax, s->_vMin, s->_aMax, s->_aMin, s->_jMax);
if (valid_profile_counter > 0) { goto return_block; }
time_none_two_step(s, s->valid_profiles, &valid_profile_counter, s->_vMin, s->_vMax, s->_aMin, s->_aMax, -s->_jMax);
if (valid_profile_counter > 0) { goto return_block; }
time_acc0_two_step(s, s->valid_profiles, &valid_profile_counter, s->_vMax, s->_vMin, s->_aMax, s->_aMin, s->_jMax);
if (valid_profile_counter > 0) { goto return_block; }
time_acc0_two_step(s, s->valid_profiles, &valid_profile_counter, s->_vMin, s->_vMax, s->_aMin, s->_aMax, -s->_jMax);
if (valid_profile_counter > 0) { goto return_block; }
time_vel_two_step(s, s->valid_profiles, &valid_profile_counter, s->_vMax, s->_vMin, s->_aMax, s->_aMin, s->_jMax);
if (valid_profile_counter > 0) { goto return_block; }
time_vel_two_step(s, s->valid_profiles, &valid_profile_counter, s->_vMin, s->_vMax, s->_aMin, s->_aMax, -s->_jMax);
if (valid_profile_counter > 0) { goto return_block; }
time_acc1_vel_two_step(s, s->valid_profiles, &valid_profile_counter, s->_vMax, s->_vMin, s->_aMax, s->_aMin, s->_jMax);
if (valid_profile_counter > 0) { goto return_block; }
time_acc1_vel_two_step(s, s->valid_profiles, &valid_profile_counter, s->_vMin, s->_vMax, s->_aMin, s->_aMax, -s->_jMax);
}
return_block:
return cruckig_block_calculate(block, s->valid_profiles, valid_profile_counter, 6);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,539 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "profile.h"
#include "roots.h"
#include "utils.h"
void cruckig_profile_init(CRuckigProfile *p) {
memset(p->t, 0, sizeof(p->t));
memset(p->t_sum, 0, sizeof(p->t_sum));
memset(p->j, 0, sizeof(p->j));
memset(p->a, 0, sizeof(p->a));
memset(p->v, 0, sizeof(p->v));
memset(p->p, 0, sizeof(p->p));
cruckig_brake_init(&p->brake);
cruckig_brake_init(&p->accel);
p->pf = 0.0;
p->vf = 0.0;
p->af = 0.0;
p->limits = ReachedLimitsNONE;
p->direction = DirectionUP;
p->control_signs = ControlSignsUDDU;
}
void cruckig_profile_set_boundary(CRuckigProfile *p, double p0, double v0, double a0,
double pf, double vf, double af) {
p->a[0] = a0;
p->v[0] = v0;
p->p[0] = p0;
p->af = af;
p->vf = vf;
p->pf = pf;
}
void cruckig_profile_set_boundary_from_profile(CRuckigProfile *p, const CRuckigProfile *src) {
p->a[0] = src->a[0];
p->v[0] = src->v[0];
p->p[0] = src->p[0];
p->af = src->af;
p->vf = src->vf;
p->pf = src->pf;
p->brake = src->brake;
p->accel = src->accel;
}
void cruckig_profile_set_boundary_for_velocity(CRuckigProfile *p, double p0, double v0, double a0,
double vf, double af) {
p->a[0] = a0;
p->v[0] = v0;
p->p[0] = p0;
p->af = af;
p->vf = vf;
}
/* Third-order position check */
CRUCKIG_HOT
bool cruckig_profile_check(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
bool set_limits, double jf, double vMax, double vMin, double aMax, double aMin) {
if (CRUCKIG_UNLIKELY(p->t[0] < 0)) {
return false;
}
p->t_sum[0] = p->t[0];
for (size_t i = 0; i < 6; ++i) {
if (CRUCKIG_UNLIKELY(p->t[i + 1] < 0)) {
return false;
}
p->t_sum[i + 1] = p->t_sum[i] + p->t[i + 1];
}
if (lim == ReachedLimitsACC0_ACC1_VEL || lim == ReachedLimitsACC0_VEL || lim == ReachedLimitsACC1_VEL || lim == ReachedLimitsVEL) {
if (CRUCKIG_UNLIKELY(p->t[3] < DBL_EPSILON)) {
return false;
}
}
if (lim == ReachedLimitsACC0 || lim == ReachedLimitsACC0_ACC1) {
if (CRUCKIG_UNLIKELY(p->t[1] < DBL_EPSILON)) {
return false;
}
}
if (lim == ReachedLimitsACC1 || lim == ReachedLimitsACC0_ACC1) {
if (CRUCKIG_UNLIKELY(p->t[5] < DBL_EPSILON)) {
return false;
}
}
if (CRUCKIG_UNLIKELY(p->t_sum[6] > PROFILE_T_MAX)) {
return false;
}
if (cs == ControlSignsUDDU) {
p->j[0] = (p->t[0] > 0 ? jf : 0);
p->j[1] = 0;
p->j[2] = (p->t[2] > 0 ? -jf : 0);
p->j[3] = 0;
p->j[4] = (p->t[4] > 0 ? -jf : 0);
p->j[5] = 0;
p->j[6] = (p->t[6] > 0 ? jf : 0);
} else {
p->j[0] = (p->t[0] > 0 ? jf : 0);
p->j[1] = 0;
p->j[2] = (p->t[2] > 0 ? -jf : 0);
p->j[3] = 0;
p->j[4] = (p->t[4] > 0 ? jf : 0);
p->j[5] = 0;
p->j[6] = (p->t[6] > 0 ? -jf : 0);
}
p->direction = (vMax > 0) ? DirectionUP : DirectionDOWN;
const double vUppLim = (p->direction == DirectionUP ? vMax : vMin) + PROFILE_V_EPS;
const double vLowLim = (p->direction == DirectionUP ? vMin : vMax) - PROFILE_V_EPS;
for (size_t i = 0; i < 7; ++i) {
p->a[i + 1] = p->a[i] + p->t[i] * p->j[i];
p->v[i + 1] = p->v[i] + p->t[i] * (p->a[i] + p->t[i] * p->j[i] / 2);
p->p[i + 1] = p->p[i] + p->t[i] * (p->v[i] + p->t[i] * (p->a[i] / 2 + p->t[i] * p->j[i] / 6));
if (lim == ReachedLimitsACC0_ACC1_VEL || lim == ReachedLimitsACC0_ACC1 || lim == ReachedLimitsACC0_VEL || lim == ReachedLimitsACC1_VEL || lim == ReachedLimitsVEL) {
if (i == 2) {
p->a[3] = 0.0;
}
}
if (set_limits) {
if (lim == ReachedLimitsACC1) {
if (i == 2) {
p->a[3] = aMin;
}
}
if (lim == ReachedLimitsACC0_ACC1) {
if (i == 0) {
p->a[1] = aMax;
}
if (i == 4) {
p->a[5] = aMin;
}
}
}
if (i > 1 && p->a[i + 1] * p->a[i] < -DBL_EPSILON) {
const double v_a_zero = p->v[i] - (p->a[i] * p->a[i]) / (2 * p->j[i]);
if (v_a_zero > vUppLim || v_a_zero < vLowLim) {
return false;
}
}
}
p->control_signs = cs;
p->limits = lim;
const double aUppLim = (p->direction == DirectionUP ? aMax : aMin) + PROFILE_A_EPS;
const double aLowLim = (p->direction == DirectionUP ? aMin : aMax) - PROFILE_A_EPS;
return fabs(p->p[7] - p->pf) < PROFILE_P_PREC && fabs(p->v[7] - p->vf) < PROFILE_V_PREC && fabs(p->a[7] - p->af) < PROFILE_A_PREC
&& p->a[1] >= aLowLim && p->a[3] >= aLowLim && p->a[5] >= aLowLim
&& p->a[1] <= aUppLim && p->a[3] <= aUppLim && p->a[5] <= aUppLim
&& p->v[3] <= vUppLim && p->v[4] <= vUppLim && p->v[5] <= vUppLim && p->v[6] <= vUppLim
&& p->v[3] >= vLowLim && p->v[4] >= vLowLim && p->v[5] >= vLowLim && p->v[6] >= vLowLim;
}
bool cruckig_profile_check_with_timing(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double jf, double vMax, double vMin, double aMax, double aMin) {
(void)tf;
/* Time doesn't need to be checked as every profile has a: tf - ... equation */
return cruckig_profile_check(p, cs, lim, false, jf, vMax, vMin, aMax, aMin);
}
bool cruckig_profile_check_with_timing_full(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double jf, double vMax, double vMin, double aMax, double aMin, double jMax) {
return (fabs(jf) < fabs(jMax) + PROFILE_J_EPS) && cruckig_profile_check_with_timing(p, cs, lim, tf, jf, vMax, vMin, aMax, aMin);
}
/* Third-order velocity check */
CRUCKIG_HOT
bool cruckig_profile_check_for_velocity(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double jf, double aMax, double aMin) {
if (CRUCKIG_UNLIKELY(p->t[0] < 0)) {
return false;
}
p->t_sum[0] = p->t[0];
for (size_t i = 0; i < 6; ++i) {
if (p->t[i + 1] < 0) {
return false;
}
p->t_sum[i + 1] = p->t_sum[i] + p->t[i + 1];
}
if (lim == ReachedLimitsACC0) {
if (p->t[1] < DBL_EPSILON) {
return false;
}
}
if (p->t_sum[6] > PROFILE_T_MAX) {
return false;
}
if (cs == ControlSignsUDDU) {
p->j[0] = (p->t[0] > 0 ? jf : 0);
p->j[1] = 0;
p->j[2] = (p->t[2] > 0 ? -jf : 0);
p->j[3] = 0;
p->j[4] = (p->t[4] > 0 ? -jf : 0);
p->j[5] = 0;
p->j[6] = (p->t[6] > 0 ? jf : 0);
} else {
p->j[0] = (p->t[0] > 0 ? jf : 0);
p->j[1] = 0;
p->j[2] = (p->t[2] > 0 ? -jf : 0);
p->j[3] = 0;
p->j[4] = (p->t[4] > 0 ? jf : 0);
p->j[5] = 0;
p->j[6] = (p->t[6] > 0 ? -jf : 0);
}
for (size_t i = 0; i < 7; ++i) {
p->a[i + 1] = p->a[i] + p->t[i] * p->j[i];
p->v[i + 1] = p->v[i] + p->t[i] * (p->a[i] + p->t[i] * p->j[i] / 2);
p->p[i + 1] = p->p[i] + p->t[i] * (p->v[i] + p->t[i] * (p->a[i] / 2 + p->t[i] * p->j[i] / 6));
}
p->control_signs = cs;
p->limits = lim;
p->direction = (aMax > 0) ? DirectionUP : DirectionDOWN;
const double aUppLim = (p->direction == DirectionUP ? aMax : aMin) + PROFILE_A_EPS;
const double aLowLim = (p->direction == DirectionUP ? aMin : aMax) - PROFILE_A_EPS;
return fabs(p->v[7] - p->vf) < PROFILE_V_PREC && fabs(p->a[7] - p->af) < PROFILE_A_PREC
&& p->a[1] >= aLowLim && p->a[3] >= aLowLim && p->a[5] >= aLowLim
&& p->a[1] <= aUppLim && p->a[3] <= aUppLim && p->a[5] <= aUppLim;
}
bool cruckig_profile_check_for_velocity_with_timing(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double jf, double aMax, double aMin) {
(void)tf;
return cruckig_profile_check_for_velocity(p, cs, lim, jf, aMax, aMin);
}
bool cruckig_profile_check_for_velocity_with_timing_full(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double jf, double aMax, double aMin, double jMax) {
return (fabs(jf) < fabs(jMax) + PROFILE_J_EPS) && cruckig_profile_check_for_velocity_with_timing(p, cs, lim, tf, jf, aMax, aMin);
}
/* Second-order position check */
bool cruckig_profile_check_for_second_order(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double aUp, double aDown, double vMax, double vMin) {
if (p->t[0] < 0) {
return false;
}
p->t_sum[0] = p->t[0];
for (size_t i = 0; i < 6; ++i) {
if (p->t[i + 1] < 0) {
return false;
}
p->t_sum[i + 1] = p->t_sum[i] + p->t[i + 1];
}
if (p->t_sum[6] > PROFILE_T_MAX) {
return false;
}
p->j[0] = 0; p->j[1] = 0; p->j[2] = 0; p->j[3] = 0;
p->j[4] = 0; p->j[5] = 0; p->j[6] = 0;
if (cs == ControlSignsUDDU) {
p->a[0] = (p->t[0] > 0 ? aUp : 0);
p->a[1] = 0;
p->a[2] = (p->t[2] > 0 ? aDown : 0);
p->a[3] = 0;
p->a[4] = (p->t[4] > 0 ? aDown : 0);
p->a[5] = 0;
p->a[6] = (p->t[6] > 0 ? aUp : 0);
p->a[7] = p->af;
} else {
p->a[0] = (p->t[0] > 0 ? aUp : 0);
p->a[1] = 0;
p->a[2] = (p->t[2] > 0 ? aDown : 0);
p->a[3] = 0;
p->a[4] = (p->t[4] > 0 ? aUp : 0);
p->a[5] = 0;
p->a[6] = (p->t[6] > 0 ? aDown : 0);
p->a[7] = p->af;
}
p->direction = (vMax > 0) ? DirectionUP : DirectionDOWN;
const double vUppLim = (p->direction == DirectionUP ? vMax : vMin) + PROFILE_V_EPS;
const double vLowLim = (p->direction == DirectionUP ? vMin : vMax) - PROFILE_V_EPS;
for (size_t i = 0; i < 7; ++i) {
p->v[i + 1] = p->v[i] + p->t[i] * p->a[i];
p->p[i + 1] = p->p[i] + p->t[i] * (p->v[i] + p->t[i] * p->a[i] / 2);
}
p->control_signs = cs;
p->limits = lim;
return fabs(p->p[7] - p->pf) < PROFILE_P_PREC && fabs(p->v[7] - p->vf) < PROFILE_V_PREC
&& p->v[2] <= vUppLim && p->v[3] <= vUppLim && p->v[4] <= vUppLim && p->v[5] <= vUppLim && p->v[6] <= vUppLim
&& p->v[2] >= vLowLim && p->v[3] >= vLowLim && p->v[4] >= vLowLim && p->v[5] >= vLowLim && p->v[6] >= vLowLim;
}
bool cruckig_profile_check_for_second_order_with_timing(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double aUp, double aDown, double vMax, double vMin) {
(void)tf;
return cruckig_profile_check_for_second_order(p, cs, lim, aUp, aDown, vMax, vMin);
}
bool cruckig_profile_check_for_second_order_with_timing_full(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double aUp, double aDown, double vMax, double vMin,
double aMax, double aMin) {
return (aMin - PROFILE_A_EPS < aUp) && (aUp < aMax + PROFILE_A_EPS) && (aMin - PROFILE_A_EPS < aDown) && (aDown < aMax + PROFILE_A_EPS)
&& cruckig_profile_check_for_second_order_with_timing(p, cs, lim, tf, aUp, aDown, vMax, vMin);
}
/* Second-order velocity check */
bool cruckig_profile_check_for_second_order_velocity(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double aUp) {
/* ReachedLimits::ACC0 */
if (p->t[1] < 0.0) {
return false;
}
p->t_sum[0] = 0;
p->t_sum[1] = p->t[1];
p->t_sum[2] = p->t[1];
p->t_sum[3] = p->t[1];
p->t_sum[4] = p->t[1];
p->t_sum[5] = p->t[1];
p->t_sum[6] = p->t[1];
if (p->t_sum[6] > PROFILE_T_MAX) {
return false;
}
p->j[0] = 0; p->j[1] = 0; p->j[2] = 0; p->j[3] = 0;
p->j[4] = 0; p->j[5] = 0; p->j[6] = 0;
p->a[0] = 0;
p->a[1] = (p->t[1] > 0) ? aUp : 0;
p->a[2] = 0; p->a[3] = 0; p->a[4] = 0; p->a[5] = 0; p->a[6] = 0;
p->a[7] = p->af;
for (size_t i = 0; i < 7; ++i) {
p->v[i + 1] = p->v[i] + p->t[i] * p->a[i];
p->p[i + 1] = p->p[i] + p->t[i] * (p->v[i] + p->t[i] * p->a[i] / 2);
}
p->control_signs = cs;
p->limits = lim;
p->direction = (aUp > 0) ? DirectionUP : DirectionDOWN;
return fabs(p->v[7] - p->vf) < PROFILE_V_PREC;
}
bool cruckig_profile_check_for_second_order_velocity_with_timing(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double aUp) {
(void)tf;
return cruckig_profile_check_for_second_order_velocity(p, cs, lim, aUp);
}
bool cruckig_profile_check_for_second_order_velocity_with_timing_full(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double aUp, double aMax, double aMin) {
return (aMin - PROFILE_A_EPS < aUp) && (aUp < aMax + PROFILE_A_EPS)
&& cruckig_profile_check_for_second_order_velocity_with_timing(p, cs, lim, tf, aUp);
}
/* First-order position check */
bool cruckig_profile_check_for_first_order(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double vUp) {
/* ReachedLimits::VEL */
if (p->t[3] < 0.0) {
return false;
}
p->t_sum[0] = 0; p->t_sum[1] = 0; p->t_sum[2] = 0;
p->t_sum[3] = p->t[3];
p->t_sum[4] = p->t[3]; p->t_sum[5] = p->t[3]; p->t_sum[6] = p->t[3];
if (p->t_sum[6] > PROFILE_T_MAX) {
return false;
}
p->j[0] = 0; p->j[1] = 0; p->j[2] = 0; p->j[3] = 0;
p->j[4] = 0; p->j[5] = 0; p->j[6] = 0;
p->a[0] = 0; p->a[1] = 0; p->a[2] = 0; p->a[3] = 0;
p->a[4] = 0; p->a[5] = 0; p->a[6] = 0; p->a[7] = p->af;
p->v[0] = 0; p->v[1] = 0; p->v[2] = 0;
p->v[3] = (p->t[3] > 0 ? vUp : 0);
p->v[4] = 0; p->v[5] = 0; p->v[6] = 0; p->v[7] = p->vf;
for (size_t i = 0; i < 7; ++i) {
p->p[i + 1] = p->p[i] + p->t[i] * (p->v[i] + p->t[i] * p->a[i] / 2);
}
p->control_signs = cs;
p->limits = lim;
p->direction = (vUp > 0) ? DirectionUP : DirectionDOWN;
return fabs(p->p[7] - p->pf) < PROFILE_P_PREC;
}
bool cruckig_profile_check_for_first_order_with_timing(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double vUp) {
(void)tf;
return cruckig_profile_check_for_first_order(p, cs, lim, vUp);
}
bool cruckig_profile_check_for_first_order_with_timing_full(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double vUp, double vMax, double vMin) {
return (vMin - PROFILE_V_EPS < vUp) && (vUp < vMax + PROFILE_V_EPS)
&& cruckig_profile_check_for_first_order_with_timing(p, cs, lim, tf, vUp);
}
/* Position extrema helpers */
static void check_position_extremum(double t_ext, double t_sum_val, double t_seg, double pos, double vel, double acc, double jrk, CRuckigBound *ext) {
if (0 < t_ext && t_ext < t_seg) {
double p_ext, v_ext, a_ext;
cruckig_integrate(t_ext, pos, vel, acc, jrk, &p_ext, &v_ext, &a_ext);
(void)v_ext;
if (a_ext > 0 && p_ext < ext->min) {
ext->min = p_ext;
ext->t_min = t_sum_val + t_ext;
} else if (a_ext < 0 && p_ext > ext->max) {
ext->max = p_ext;
ext->t_max = t_sum_val + t_ext;
}
}
}
static void check_step_for_position_extremum(double t_sum_val, double t_seg, double pos, double vel, double acc, double jrk, CRuckigBound *ext) {
if (pos < ext->min) {
ext->min = pos;
ext->t_min = t_sum_val;
}
if (pos > ext->max) {
ext->max = pos;
ext->t_max = t_sum_val;
}
if (jrk != 0) {
const double D = acc * acc - 2 * jrk * vel;
if (fabs(D) < DBL_EPSILON) {
check_position_extremum(-acc / jrk, t_sum_val, t_seg, pos, vel, acc, jrk, ext);
} else if (D > 0.0) {
const double D_sqrt = sqrt(D);
check_position_extremum((-acc - D_sqrt) / jrk, t_sum_val, t_seg, pos, vel, acc, jrk, ext);
check_position_extremum((-acc + D_sqrt) / jrk, t_sum_val, t_seg, pos, vel, acc, jrk, ext);
}
}
}
CRuckigBound cruckig_profile_get_position_extrema(const CRuckigProfile *p) {
CRuckigBound extrema;
extrema.min = INFINITY;
extrema.max = -INFINITY;
extrema.t_min = 0.0;
extrema.t_max = 0.0;
if (p->brake.duration > 0.0) {
if (p->brake.t[0] > 0.0) {
check_step_for_position_extremum(0.0, p->brake.t[0], p->brake.p[0], p->brake.v[0], p->brake.a[0], p->brake.j[0], &extrema);
if (p->brake.t[1] > 0.0) {
check_step_for_position_extremum(p->brake.t[0], p->brake.t[1], p->brake.p[1], p->brake.v[1], p->brake.a[1], p->brake.j[1], &extrema);
}
}
}
double t_current_sum = 0.0;
for (size_t i = 0; i < 7; ++i) {
if (i > 0) {
t_current_sum = p->t_sum[i - 1];
}
check_step_for_position_extremum(t_current_sum + p->brake.duration, p->t[i], p->p[i], p->v[i], p->a[i], p->j[i], &extrema);
}
if (p->pf < extrema.min) {
extrema.min = p->pf;
extrema.t_min = p->t_sum[6] + p->brake.duration;
}
if (p->pf > extrema.max) {
extrema.max = p->pf;
extrema.t_max = p->t_sum[6] + p->brake.duration;
}
return extrema;
}
bool cruckig_profile_get_first_state_at_position(const CRuckigProfile *p, double pt, double *time, double time_after) {
double t_cum = 0.0;
for (size_t i = 0; i < 7; ++i) {
if (p->t[i] == 0.0) {
continue;
}
if (fabs(p->p[i] - pt) < DBL_EPSILON && t_cum >= time_after) {
*time = t_cum;
return true;
}
CRuckigRootSet cubic_roots = cruckig_roots_solve_cubic(p->j[i] / 6, p->a[i] / 2, p->v[i], p->p[i] - pt);
cruckig_root_set_sort(&cubic_roots);
for (size_t r = 0; r < cubic_roots.size; ++r) {
double _t = cubic_roots.data[r];
if (0 < _t && time_after - t_cum <= _t && _t <= p->t[i]) {
*time = _t + t_cum;
return true;
}
}
t_cum += p->t[i];
}
if ((p->t[6] > 0.0 || p->t_sum[6] == 0.0) && fabs(p->pf - pt) < 1e-9 && p->t_sum[6] >= time_after) {
*time = p->t_sum[6];
return true;
}
return false;
}

View File

@@ -0,0 +1,126 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_PROFILE_H
#define CRUCKIG_PROFILE_H
#include "cruckig_internal.h"
#include "brake.h"
/* Constants */
#define PROFILE_V_EPS 1e-12
#define PROFILE_A_EPS 1e-12
#define PROFILE_J_EPS 1e-12
#define PROFILE_P_PREC 1e-8
#define PROFILE_V_PREC 1e-8
#define PROFILE_A_PREC 1e-10
#define PROFILE_T_PREC 1e-12
#define PROFILE_T_MAX 1e12
typedef enum {
ReachedLimitsACC0_ACC1_VEL = 0,
ReachedLimitsVEL,
ReachedLimitsACC0,
ReachedLimitsACC1,
ReachedLimitsACC0_ACC1,
ReachedLimitsACC0_VEL,
ReachedLimitsACC1_VEL,
ReachedLimitsNONE
} CRuckigReachedLimits;
typedef enum {
DirectionUP = 0,
DirectionDOWN
} CRuckigDirection;
typedef enum {
ControlSignsUDDU = 0,
ControlSignsUDUD
} CRuckigControlSigns;
/* Position extrema info */
typedef struct {
double min, max;
double t_min, t_max;
} CRuckigBound;
/* Single-DOF kinematic profile */
typedef struct {
double t[7];
double t_sum[7];
double j[7];
double a[8];
double v[8];
double p[8];
CRuckigBrakeProfile brake;
CRuckigBrakeProfile accel;
double pf, vf, af;
CRuckigReachedLimits limits;
CRuckigDirection direction;
CRuckigControlSigns control_signs;
} CRuckigProfile;
void cruckig_profile_init(CRuckigProfile *p);
/* Set boundary conditions */
void cruckig_profile_set_boundary(CRuckigProfile *p, double p0, double v0, double a0,
double pf, double vf, double af);
void cruckig_profile_set_boundary_from_profile(CRuckigProfile *p, const CRuckigProfile *src);
void cruckig_profile_set_boundary_for_velocity(CRuckigProfile *p, double p0, double v0, double a0,
double vf, double af);
/* Third-order position check */
bool cruckig_profile_check(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
bool set_limits, double jf, double vMax, double vMin, double aMax, double aMin);
bool cruckig_profile_check_with_timing(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double jf, double vMax, double vMin, double aMax, double aMin);
bool cruckig_profile_check_with_timing_full(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double jf, double vMax, double vMin, double aMax, double aMin, double jMax);
/* Third-order velocity check */
bool cruckig_profile_check_for_velocity(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double jf, double aMax, double aMin);
bool cruckig_profile_check_for_velocity_with_timing(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double jf, double aMax, double aMin);
bool cruckig_profile_check_for_velocity_with_timing_full(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double jf, double aMax, double aMin, double jMax);
/* Second-order position check */
bool cruckig_profile_check_for_second_order(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double aUp, double aDown, double vMax, double vMin);
bool cruckig_profile_check_for_second_order_with_timing(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double aUp, double aDown, double vMax, double vMin);
bool cruckig_profile_check_for_second_order_with_timing_full(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double aUp, double aDown, double vMax, double vMin,
double aMax, double aMin);
/* Second-order velocity check */
bool cruckig_profile_check_for_second_order_velocity(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double aUp);
bool cruckig_profile_check_for_second_order_velocity_with_timing(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double aUp);
bool cruckig_profile_check_for_second_order_velocity_with_timing_full(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double aUp, double aMax, double aMin);
/* First-order position check */
bool cruckig_profile_check_for_first_order(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double vUp);
bool cruckig_profile_check_for_first_order_with_timing(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double vUp);
bool cruckig_profile_check_for_first_order_with_timing_full(CRuckigProfile *p, CRuckigControlSigns cs, CRuckigReachedLimits lim,
double tf, double vUp, double vMax, double vMin);
/* Position extrema */
CRuckigBound cruckig_profile_get_position_extrema(const CRuckigProfile *p);
/* First time at position */
bool cruckig_profile_get_first_state_at_position(const CRuckigProfile *p, double pt, double *time, double time_after);
#endif /* CRUCKIG_PROFILE_H */

View File

@@ -0,0 +1,40 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_RESULT_H
#define CRUCKIG_RESULT_H
typedef enum {
CRuckigWorking = 0,
CRuckigFinished = 1,
CRuckigError = -1,
CRuckigErrorInvalidInput = -100,
CRuckigErrorTrajectoryDuration = -101,
CRuckigErrorPositionalLimits = -102,
CRuckigErrorZeroLimits = -104,
CRuckigErrorExecutionTimeCalculation = -110,
CRuckigErrorSynchronizationCalculation = -111
} CRuckigResult;
typedef enum {
CRuckigPosition = 0,
CRuckigVelocity = 1
} CRuckigControlInterface;
typedef enum {
CRuckigSyncTime = 0,
CRuckigSyncTimeIfNecessary = 1,
CRuckigSyncPhase = 2,
CRuckigSyncNone = 3
} CRuckigSynchronization;
typedef enum {
CRuckigContinuous = 0,
CRuckigDiscrete = 1
} CRuckigDurationDiscretization;
#endif /* CRUCKIG_RESULT_H */

View File

@@ -0,0 +1,408 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "roots.h"
/*
* cruckig_cbrt() - Cube root, used for cubic/quartic polynomial solving.
* Optimized implementation from musl libc / FreeBSD libmsun.
* Polynomial approximation to 23 bits + one Newton step to 53 bits.
* Error < 0.667 ulps.
*
* Copyright (c) 1993 Sun Microsystems, Inc. All rights reserved.
* Developed at SunPro, a Sun Microsystems, Inc. business.
* Copyright (c) 2005-2020 Rich Felker, et al. (musl libc)
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*/
double cruckig_cbrt(double x) {
static const unsigned B1 = 715094163;
static const unsigned B2 = 696219795;
static const double P0 = 1.87595182427177009643;
static const double P1 = -1.88497979543377169875;
static const double P2 = 1.621429720105354466140;
static const double P3 = -0.758397934778766047437;
static const double P4 = 0.145996192886612446982;
union { double f; unsigned long long i; } u = {x};
double r, s, t, w;
unsigned hx = u.i >> 32 & 0x7fffffff;
if (hx >= 0x7ff00000)
return x + x;
if (hx < 0x00100000) {
u.f = x * 0x1p54;
hx = u.i >> 32 & 0x7fffffff;
if (hx == 0) return x;
hx = hx / 3 + B2;
} else {
hx = hx / 3 + B1;
}
u.i &= 1ULL << 63;
u.i |= (unsigned long long)hx << 32;
t = u.f;
r = (t * t) * (t / x);
t = t * ((P0 + r * (P1 + r * P2)) + ((r * r) * r) * (P3 + r * P4));
u.f = t;
u.i = (u.i + 0x80000000) & 0xffffffffc0000000ULL;
t = u.f;
s = t * t;
r = x / s;
w = t + t;
r = (r - t) / (w + r);
t = t + t * r;
return t;
}
void cruckig_root_set_sort(CRuckigRootSet *s) {
/* Insertion sort for small arrays (max 4 elements) */
for (size_t i = 1; i < s->size; ++i) {
double key = s->data[i];
size_t j = i;
while (j > 0 && s->data[j - 1] > key) {
s->data[j] = s->data[j - 1];
--j;
}
s->data[j] = key;
}
}
CRUCKIG_HOT
CRuckigRootSet cruckig_roots_solve_cubic(double a, double b, double c, double d) {
CRuckigRootSet roots;
cruckig_root_set_init(&roots);
if (fabs(d) < DBL_EPSILON) {
/* First solution is x = 0 */
cruckig_root_set_insert(&roots, 0.0);
/* Converting to a quadratic equation */
d = c;
c = b;
b = a;
a = 0.0;
}
if (fabs(a) < DBL_EPSILON) {
if (fabs(b) < DBL_EPSILON) {
/* Linear equation */
if (fabs(c) > DBL_EPSILON) {
cruckig_root_set_insert(&roots, -d / c);
}
} else {
/* Quadratic equation */
const double discriminant = c * c - 4 * b * d;
if (discriminant >= 0) {
const double inv2b = 1.0 / (2 * b);
const double y = sqrt(discriminant);
cruckig_root_set_insert(&roots, (-c + y) * inv2b);
cruckig_root_set_insert(&roots, (-c - y) * inv2b);
}
}
} else {
/* Cubic equation */
const double inva = 1.0 / a;
const double invaa = inva * inva;
const double bb = b * b;
const double bover3a = b * inva / 3;
const double p = (a * c - bb / 3) * invaa;
const double halfq = (2 * bb * b - 9 * a * b * c + 27 * a * a * d) / 54 * invaa * inva;
const double yy = p * p * p / 27 + halfq * halfq;
const double cos120 = -0.50;
const double sin120 = 0.866025403784438646764;
if (yy > DBL_EPSILON) {
/* Sqrt is positive: one real solution */
const double y = sqrt(yy);
const double uuu = -halfq + y;
const double vvv = -halfq - y;
const double www = fabs(uuu) > fabs(vvv) ? uuu : vvv;
const double w = cruckig_cbrt(www);
cruckig_root_set_insert(&roots, w - p / (3 * w) - bover3a);
} else if (yy < -DBL_EPSILON) {
/* Sqrt is negative: three real solutions */
const double x = -halfq;
const double y = sqrt(-yy);
double theta;
double r;
/* Convert to polar form */
if (fabs(x) > DBL_EPSILON) {
theta = (x > 0.0) ? atan(y / x) : (atan(y / x) + M_PI);
r = sqrt(x * x - yy);
} else {
/* Vertical line */
theta = M_PI / 2;
r = y;
}
/* Calculate cube root */
theta /= 3;
r = 2 * cruckig_cbrt(r);
/* Convert to complex coordinate */
const double ux = cos(theta) * r;
const double uyi = sin(theta) * r;
cruckig_root_set_insert(&roots, ux - bover3a);
cruckig_root_set_insert(&roots, ux * cos120 - uyi * sin120 - bover3a);
cruckig_root_set_insert(&roots, ux * cos120 + uyi * sin120 - bover3a);
} else {
/* Sqrt is zero: two real solutions */
const double www = -halfq;
const double w = 2 * cruckig_cbrt(www);
cruckig_root_set_insert(&roots, w - bover3a);
cruckig_root_set_insert(&roots, w * cos120 - bover3a);
}
}
return roots;
}
int cruckig_roots_solve_resolvent(double x[3], double a, double b, double c) {
const double cos120 = -0.50;
const double sin120 = 0.866025403784438646764;
a /= 3;
const double a2 = a * a;
double q = a2 - b / 3;
const double r = (a * (2 * a2 - b) + c) / 2;
const double r2 = r * r;
const double q3 = q * q * q;
if (r2 < q3) {
const double qsqrt = sqrt(q);
double t_val = r / (q * qsqrt);
if (t_val < -1.0) t_val = -1.0;
if (t_val > 1.0) t_val = 1.0;
q = -2 * qsqrt;
const double theta = acos(t_val) / 3;
const double ux = cos(theta) * q;
const double uyi = sin(theta) * q;
x[0] = ux - a;
x[1] = ux * cos120 - uyi * sin120 - a;
x[2] = ux * cos120 + uyi * sin120 - a;
return 3;
} else {
double A = -cruckig_cbrt(fabs(r) + sqrt(r2 - q3));
if (r < 0.0) {
A = -A;
}
const double B = (0.0 == A ? 0.0 : q / A);
x[0] = (A + B) - a;
x[1] = -(A + B) / 2 - a;
x[2] = sqrt(3.0) * (A - B) / 2;
if (fabs(x[2]) < DBL_EPSILON) {
x[2] = x[1];
return 2;
}
return 1;
}
}
CRUCKIG_HOT
CRuckigRootSet cruckig_roots_solve_quart_monic(double a, double b, double c, double d) {
CRuckigRootSet roots;
cruckig_root_set_init(&roots);
if (fabs(d) < DBL_EPSILON) {
if (fabs(c) < DBL_EPSILON) {
cruckig_root_set_insert(&roots, 0.0);
const double D = a * a - 4 * b;
if (fabs(D) < DBL_EPSILON) {
cruckig_root_set_insert(&roots, -a / 2);
} else if (D > 0.0) {
const double sqrtD = sqrt(D);
cruckig_root_set_insert(&roots, (-a - sqrtD) / 2);
cruckig_root_set_insert(&roots, (-a + sqrtD) / 2);
}
return roots;
}
if (fabs(a) < DBL_EPSILON && fabs(b) < DBL_EPSILON) {
cruckig_root_set_insert(&roots, 0.0);
cruckig_root_set_insert(&roots, -cruckig_cbrt(c));
return roots;
}
}
const double a3 = -b;
const double b3 = a * c - 4 * d;
const double c3 = -a * a * d - c * c + 4 * b * d;
double x3[3];
const int number_zeroes = cruckig_roots_solve_resolvent(x3, a3, b3, c3);
double y = x3[0];
/* Choosing Y with maximal absolute value */
if (number_zeroes != 1) {
if (fabs(x3[1]) > fabs(y)) {
y = x3[1];
}
if (fabs(x3[2]) > fabs(y)) {
y = x3[2];
}
}
double q1, q2, p1, p2;
double D;
D = y * y - 4 * d;
if (fabs(D) < DBL_EPSILON) {
q1 = q2 = y / 2;
D = a * a - 4 * (b - y);
if (fabs(D) < DBL_EPSILON) {
p1 = p2 = a / 2;
} else {
const double sqrtD = sqrt(D);
p1 = (a + sqrtD) / 2;
p2 = (a - sqrtD) / 2;
}
} else {
const double sqrtD = sqrt(D);
q1 = (y + sqrtD) / 2;
q2 = (y - sqrtD) / 2;
p1 = (a * q1 - c) / (q1 - q2);
p2 = (c - a * q2) / (q1 - q2);
}
{
const double eps = 16 * DBL_EPSILON;
D = p1 * p1 - 4 * q1;
if (fabs(D) < eps) {
cruckig_root_set_insert(&roots, -p1 / 2);
} else if (D > 0.0) {
const double sqrtD = sqrt(D);
cruckig_root_set_insert(&roots, (-p1 - sqrtD) / 2);
cruckig_root_set_insert(&roots, (-p1 + sqrtD) / 2);
}
D = p2 * p2 - 4 * q2;
if (fabs(D) < eps) {
cruckig_root_set_insert(&roots, -p2 / 2);
} else if (D > 0.0) {
const double sqrtD = sqrt(D);
cruckig_root_set_insert(&roots, (-p2 - sqrtD) / 2);
cruckig_root_set_insert(&roots, (-p2 + sqrtD) / 2);
}
}
return roots;
}
double cruckig_roots_poly_eval(const double *p, size_t n, double x) {
if (n == 0) {
return 0.0;
}
double retVal = 0.0;
if (fabs(x) < DBL_EPSILON) {
retVal = p[n - 1];
} else if (x == 1.0) {
for (int i = (int)n - 1; i >= 0; i--) {
retVal += p[i];
}
} else {
double xn = 1.0;
for (int i = (int)n - 1; i >= 0; i--) {
retVal += p[i] * xn;
xn *= x;
}
}
return retVal;
}
void cruckig_roots_poly_derivative(const double *coeffs, size_t n, double *deriv) {
for (size_t i = 0; i < n - 1; ++i) {
deriv[i] = (double)(n - 1 - i) * coeffs[i];
}
}
double cruckig_roots_shrink_interval(const double *p, size_t n, double l, double h) {
const size_t maxIts = 128;
const double tolerance = 1e-14;
const double fl = cruckig_roots_poly_eval(p, n, l);
const double fh = cruckig_roots_poly_eval(p, n, h);
if (fl == 0.0) {
return l;
}
if (fh == 0.0) {
return h;
}
if (fl > 0.0) {
/* swap l and h */
double tmp = l;
l = h;
h = tmp;
}
double rts = (l + h) / 2;
double dxold = fabs(h - l);
double dx = dxold;
/* Compute derivative coefficients (n-1 elements) */
double deriv[16]; /* max polynomial degree supported */
cruckig_roots_poly_derivative(p, n, deriv);
size_t dn = n - 1;
double f = cruckig_roots_poly_eval(p, n, rts);
double df = cruckig_roots_poly_eval(deriv, dn, rts);
double temp;
for (size_t j = 0; j < maxIts; j++) {
if ((((rts - h) * df - f) * ((rts - l) * df - f) > 0.0) || (fabs(2 * f) > fabs(dxold * df))) {
dxold = dx;
dx = (h - l) / 2;
rts = l + dx;
if (l == rts) {
break;
}
} else {
dxold = dx;
dx = f / df;
temp = rts;
rts -= dx;
if (temp == rts) {
break;
}
}
if (fabs(dx) < tolerance) {
break;
}
f = cruckig_roots_poly_eval(p, n, rts);
df = cruckig_roots_poly_eval(deriv, dn, rts);
if (f < 0.0) {
l = rts;
} else {
h = rts;
}
}
return rts;
}

View File

@@ -0,0 +1,57 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_ROOTS_H
#define CRUCKIG_ROOTS_H
#include "cruckig_internal.h"
/* A set of positive double roots, stored on the stack */
typedef struct {
double data[4];
size_t size;
} CRuckigRootSet;
CRUCKIG_FORCE_INLINE void cruckig_root_set_init(CRuckigRootSet *s) {
s->size = 0;
}
CRUCKIG_FORCE_INLINE void cruckig_root_set_insert(CRuckigRootSet *s, double value) {
if (value >= 0.0) {
s->data[s->size] = value;
s->size++;
}
}
/*
* Cube root, portable replacement for cbrt() (not available in kernel).
* Optimized implementation from musl libc / FreeBSD libmsun.
*/
double cruckig_cbrt(double x);
/* Sort the root set (simple insertion sort for small N) */
void cruckig_root_set_sort(CRuckigRootSet *s);
/* Solve a*x^3 + b*x^2 + c*x + d = 0, returning positive roots */
CRuckigRootSet cruckig_roots_solve_cubic(double a, double b, double c, double d);
/* Solve resolvent equation, returns number of zeros */
int cruckig_roots_solve_resolvent(double x[3], double a, double b, double c);
/* Solve monic quartic x^4 + a*x^3 + b*x^2 + c*x + d = 0 */
CRuckigRootSet cruckig_roots_solve_quart_monic(double a, double b, double c, double d);
/* Evaluate polynomial of order N at x. Coefficients in descending order: p[0]*x^(N-1) + ... + p[N-1] */
double cruckig_roots_poly_eval(const double *p, size_t n, double x);
/* Calculate derivative coefficients */
void cruckig_roots_poly_derivative(const double *coeffs, size_t n, double *deriv);
/* Safe Newton method: find root in [l, h] where p(l)*p(h) < 0 */
double cruckig_roots_shrink_interval(const double *p, size_t n, double l, double h);
#endif /* CRUCKIG_ROOTS_H */

View File

@@ -0,0 +1,315 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "trajectory.h"
#include "utils.h"
CRuckigTrajectory* cruckig_trajectory_create(size_t dofs) {
CRuckigTrajectory *traj = (CRuckigTrajectory*)cruckig_calloc(1, sizeof(CRuckigTrajectory));
if (!traj) return NULL;
traj->degrees_of_freedom = dofs;
traj->num_sections = 1;
traj->section_capacity = 1;
traj->duration = 0.0;
traj->profiles = (CRuckigProfile*)cruckig_calloc(dofs, sizeof(CRuckigProfile));
traj->cumulative_times = (double*)cruckig_calloc(1, sizeof(double));
traj->independent_min_durations = (double*)cruckig_calloc(dofs, sizeof(double));
traj->position_extrema = (CRuckigBound*)cruckig_calloc(dofs, sizeof(CRuckigBound));
if (!traj->profiles || !traj->cumulative_times ||
!traj->independent_min_durations || !traj->position_extrema) {
cruckig_trajectory_destroy(traj);
return NULL;
}
for (size_t dof = 0; dof < dofs; ++dof) {
cruckig_profile_init(&traj->profiles[dof]);
}
return traj;
}
void cruckig_trajectory_destroy(CRuckigTrajectory *traj) {
if (!traj) return;
cruckig_free(traj->profiles);
cruckig_free(traj->cumulative_times);
cruckig_free(traj->independent_min_durations);
cruckig_free(traj->position_extrema);
cruckig_free(traj);
}
bool cruckig_trajectory_resize(CRuckigTrajectory *traj, size_t num_sections) {
if (!traj || num_sections == 0) return false;
const size_t dofs = traj->degrees_of_freedom;
if (num_sections > traj->section_capacity) {
CRuckigProfile *new_profiles = (CRuckigProfile*)cruckig_realloc(
traj->profiles, num_sections * dofs * sizeof(CRuckigProfile));
double *new_times = (double*)cruckig_realloc(
traj->cumulative_times, num_sections * sizeof(double));
if (!new_profiles || !new_times) {
/* Restore on failure */
if (new_profiles) traj->profiles = new_profiles;
if (new_times) traj->cumulative_times = new_times;
return false;
}
traj->profiles = new_profiles;
traj->cumulative_times = new_times;
traj->section_capacity = num_sections;
/* Initialize new profiles */
for (size_t s = traj->num_sections; s < num_sections; ++s) {
for (size_t d = 0; d < dofs; ++d) {
cruckig_profile_init(&traj->profiles[s * dofs + d]);
}
traj->cumulative_times[s] = 0.0;
}
}
traj->num_sections = num_sections;
return true;
}
/*
* state_to_integrate_from: Determine the integration base state at a given time.
* Supports multi-section trajectories via binary search on cumulative_times.
*/
static void state_to_integrate_from(const CRuckigTrajectory *traj, double time,
size_t *new_section,
double *t_out, double *p_out, double *v_out,
double *a_out, double *j_out)
{
const size_t dofs = traj->degrees_of_freedom;
const size_t nsec = traj->num_sections;
if (time >= traj->duration) {
/* Past the end of trajectory */
*new_section = nsec;
size_t last = nsec - 1;
for (size_t dof = 0; dof < dofs; ++dof) {
const CRuckigProfile *prof = &traj->profiles[last * dofs + dof];
double t_pre = prof->brake.duration;
double t_diff = time - (traj->duration - (t_pre + prof->t_sum[6]) + t_pre + prof->t_sum[6]);
/* Simplify: time past the end of last section's profile */
double section_start = (last > 0) ? traj->cumulative_times[last - 1] : 0.0;
t_diff = time - section_start - t_pre - prof->t_sum[6];
t_out[dof] = t_diff;
p_out[dof] = prof->p[7];
v_out[dof] = prof->v[7];
a_out[dof] = prof->a[7];
j_out[dof] = 0.0;
}
return;
}
/* Binary search to find current section */
size_t section = 0;
if (nsec > 1) {
size_t lo = 0, hi = nsec;
while (lo < hi) {
size_t mid = lo + (hi - lo) / 2;
if (traj->cumulative_times[mid] <= time) {
lo = mid + 1;
} else {
hi = mid;
}
}
section = lo;
if (section >= nsec) section = nsec - 1;
}
*new_section = section;
/* Time offset within this section */
double section_start = (section > 0) ? traj->cumulative_times[section - 1] : 0.0;
double t_diff = time - section_start;
for (size_t dof = 0; dof < dofs; ++dof) {
const CRuckigProfile *prof = &traj->profiles[section * dofs + dof];
double t_diff_dof = t_diff;
/* Brake pre-trajectory (only in first section, or in each section for waypoints) */
if (prof->brake.duration > 0.0) {
if (t_diff_dof < prof->brake.duration) {
size_t index = (t_diff_dof < prof->brake.t[0]) ? 0 : 1;
if (index > 0) {
t_diff_dof -= prof->brake.t[index - 1];
}
t_out[dof] = t_diff_dof;
p_out[dof] = prof->brake.p[index];
v_out[dof] = prof->brake.v[index];
a_out[dof] = prof->brake.a[index];
j_out[dof] = prof->brake.j[index];
continue;
} else {
t_diff_dof -= prof->brake.duration;
}
}
/* Non-time synchronization: past the end of this DOF's profile */
if (t_diff_dof >= prof->t_sum[6]) {
t_out[dof] = t_diff_dof - prof->t_sum[6];
p_out[dof] = prof->p[7];
v_out[dof] = prof->v[7];
a_out[dof] = prof->a[7];
j_out[dof] = 0.0;
continue;
}
/* Binary search in t_sum[0..6] */
size_t index_dof = 0;
{
size_t lo = 0, hi = 7;
while (lo < hi) {
size_t mid = lo + (hi - lo) / 2;
if (prof->t_sum[mid] <= t_diff_dof) {
lo = mid + 1;
} else {
hi = mid;
}
}
index_dof = lo;
}
if (index_dof > 0) {
t_diff_dof -= prof->t_sum[index_dof - 1];
}
t_out[dof] = t_diff_dof;
p_out[dof] = prof->p[index_dof];
v_out[dof] = prof->v[index_dof];
a_out[dof] = prof->a[index_dof];
j_out[dof] = prof->j[index_dof];
}
}
CRUCKIG_HOT
void cruckig_trajectory_at_time(const CRuckigTrajectory *traj, double time,
double * CRUCKIG_RESTRICT new_position,
double * CRUCKIG_RESTRICT new_velocity,
double * CRUCKIG_RESTRICT new_acceleration,
double * CRUCKIG_RESTRICT new_jerk,
size_t *new_section)
{
const size_t dofs = traj->degrees_of_freedom;
/* Implementation limit: max 16 DOF (stack-allocated work arrays) */
double t_buf[16], p_buf[16], v_buf[16], a_buf[16], j_buf[16];
const size_t ndofs = (dofs > 16) ? 16 : dofs;
state_to_integrate_from(traj, time, new_section, t_buf, p_buf, v_buf, a_buf, j_buf);
for (size_t dof = 0; dof < ndofs; ++dof) {
double p_out, v_out, a_out;
cruckig_integrate(t_buf[dof], p_buf[dof], v_buf[dof], a_buf[dof], j_buf[dof],
&p_out, &v_out, &a_out);
new_position[dof] = p_out;
new_velocity[dof] = v_out;
new_acceleration[dof] = a_out;
if (new_jerk) {
new_jerk[dof] = j_buf[dof];
}
}
}
void cruckig_trajectory_at_time_simple(const CRuckigTrajectory *traj, double time,
double *new_position, double *new_velocity,
double *new_acceleration)
{
size_t new_section;
cruckig_trajectory_at_time(traj, time, new_position, new_velocity,
new_acceleration, NULL, &new_section);
}
double cruckig_trajectory_get_duration(const CRuckigTrajectory *traj) {
return traj->duration;
}
size_t cruckig_trajectory_get_intermediate_durations(const CRuckigTrajectory *traj,
double *out_durations)
{
for (size_t s = 0; s < traj->num_sections; ++s) {
out_durations[s] = traj->cumulative_times[s];
}
return traj->num_sections;
}
void cruckig_trajectory_get_position_extrema(CRuckigTrajectory *traj) {
const size_t dofs = traj->degrees_of_freedom;
for (size_t dof = 0; dof < dofs; ++dof) {
/* Initialize from first section */
CRuckigBound bound = cruckig_profile_get_position_extrema(&traj->profiles[dof]);
/* Merge across all sections */
for (size_t s = 1; s < traj->num_sections; ++s) {
double section_start = traj->cumulative_times[s - 1];
CRuckigBound sb = cruckig_profile_get_position_extrema(
&traj->profiles[s * dofs + dof]);
if (sb.min < bound.min) {
bound.min = sb.min;
bound.t_min = sb.t_min + section_start;
}
if (sb.max > bound.max) {
bound.max = sb.max;
bound.t_max = sb.t_max + section_start;
}
}
traj->position_extrema[dof] = bound;
}
}
bool cruckig_trajectory_get_first_time_at_position(const CRuckigTrajectory *traj,
size_t dof, double position,
double *time, double time_after)
{
if (dof >= traj->degrees_of_freedom) return false;
const size_t dofs = traj->degrees_of_freedom;
/* Search through all sections */
for (size_t s = 0; s < traj->num_sections; ++s) {
double section_start = (s > 0) ? traj->cumulative_times[s - 1] : 0.0;
double adjusted_time_after = time_after - section_start;
if (adjusted_time_after < 0.0) adjusted_time_after = 0.0;
if (cruckig_profile_get_first_state_at_position(
&traj->profiles[s * dofs + dof], position, time, adjusted_time_after)) {
*time += section_start;
return true;
}
}
return false;
}
void cruckig_trajectory_get_independent_min_durations(const CRuckigTrajectory *traj,
double *out_durations)
{
for (size_t dof = 0; dof < traj->degrees_of_freedom; ++dof) {
out_durations[dof] = traj->independent_min_durations[dof];
}
}
const CRuckigProfile* cruckig_trajectory_get_profile(const CRuckigTrajectory *traj, size_t dof)
{
if (dof >= traj->degrees_of_freedom) return NULL;
return &traj->profiles[dof];
}
const CRuckigProfile* cruckig_trajectory_get_section_profile(const CRuckigTrajectory *traj,
size_t section, size_t dof)
{
if (section >= traj->num_sections || dof >= traj->degrees_of_freedom) return NULL;
return &traj->profiles[section * traj->degrees_of_freedom + dof];
}

View File

@@ -0,0 +1,71 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_TRAJECTORY_H
#define CRUCKIG_TRAJECTORY_H
#include "cruckig_internal.h"
#include "profile.h"
typedef struct {
size_t degrees_of_freedom;
/* Multi-section support: profiles[section * dofs + dof] */
CRuckigProfile *profiles; /* Array of num_sections * dofs profiles */
size_t num_sections; /* Number of sections (1 for state-to-state) */
size_t section_capacity; /* Allocated capacity for sections */
double duration;
double *cumulative_times; /* Array of num_sections cumulative durations */
double *independent_min_durations; /* Array of dofs */
CRuckigBound *position_extrema; /* Array of dofs */
} CRuckigTrajectory;
/* Create trajectory for single-section (backward compatible) */
CRuckigTrajectory* cruckig_trajectory_create(size_t dofs);
void cruckig_trajectory_destroy(CRuckigTrajectory *traj);
/* Resize trajectory for multi-section (num_sections = max_waypoints + 1) */
bool cruckig_trajectory_resize(CRuckigTrajectory *traj, size_t num_sections);
/* Query trajectory state at time */
void cruckig_trajectory_at_time(const CRuckigTrajectory *traj, double time,
double *new_position, double *new_velocity,
double *new_acceleration, double *new_jerk,
size_t *new_section);
/* Simplified version without jerk/section */
void cruckig_trajectory_at_time_simple(const CRuckigTrajectory *traj, double time,
double *new_position, double *new_velocity,
double *new_acceleration);
double cruckig_trajectory_get_duration(const CRuckigTrajectory *traj);
/* Get intermediate durations (cumulative times array). Returns num_sections. */
size_t cruckig_trajectory_get_intermediate_durations(const CRuckigTrajectory *traj,
double *out_durations);
/* Get position extrema for all DOFs */
void cruckig_trajectory_get_position_extrema(CRuckigTrajectory *traj);
/* Get first time at position for a DOF. Returns true if found. */
bool cruckig_trajectory_get_first_time_at_position(const CRuckigTrajectory *traj,
size_t dof, double position,
double *time, double time_after);
/* Get independent minimum durations (one per DOF). Caller provides array of dofs. */
void cruckig_trajectory_get_independent_min_durations(const CRuckigTrajectory *traj,
double *out_durations);
/* Get the underlying profile for a specific DOF in a section (read-only). */
const CRuckigProfile* cruckig_trajectory_get_profile(const CRuckigTrajectory *traj, size_t dof);
/* Get profile for specific section and DOF. */
const CRuckigProfile* cruckig_trajectory_get_section_profile(const CRuckigTrajectory *traj,
size_t section, size_t dof);
#endif /* CRUCKIG_TRAJECTORY_H */

View File

@@ -0,0 +1,26 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_UTILS_H
#define CRUCKIG_UTILS_H
#include "cruckig_internal.h"
CRUCKIG_FORCE_INLINE void cruckig_integrate(double t, double p0, double v0, double a0, double j,
double * CRUCKIG_RESTRICT p_out,
double * CRUCKIG_RESTRICT v_out,
double * CRUCKIG_RESTRICT a_out) {
*p_out = p0 + t * (v0 + t * (a0 / 2.0 + t * j / 6.0));
*v_out = v0 + t * (a0 + t * j / 2.0);
*a_out = a0 + t * j;
}
CRUCKIG_FORCE_INLINE double cruckig_pow2(double v) {
return v * v;
}
#endif /* CRUCKIG_UTILS_H */

View File

@@ -0,0 +1,63 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#ifndef CRUCKIG_VELOCITY_H
#define CRUCKIG_VELOCITY_H
#include "cruckig_internal.h"
#include "profile.h"
#include "block.h"
/* ---- Third Order Step 1 ---- */
typedef struct {
double a0, af;
double _aMax, _aMin, _jMax;
double vd;
CRuckigProfile valid_profiles[3];
} CRuckigVelocityThirdOrderStep1;
void cruckig_vel3_step1_init(CRuckigVelocityThirdOrderStep1 *s,
double v0, double a0, double vf, double af,
double aMax, double aMin, double jMax);
bool cruckig_vel3_step1_get_profile(CRuckigVelocityThirdOrderStep1 *s,
const CRuckigProfile *input, CRuckigBlock *block);
/* ---- Third Order Step 2 ---- */
typedef struct {
double a0, tf, af;
double _aMax, _aMin, _jMax;
double vd, ad;
} CRuckigVelocityThirdOrderStep2;
void cruckig_vel3_step2_init(CRuckigVelocityThirdOrderStep2 *s,
double tf, double v0, double a0, double vf, double af,
double aMax, double aMin, double jMax);
bool cruckig_vel3_step2_get_profile(CRuckigVelocityThirdOrderStep2 *s, CRuckigProfile *profile);
/* ---- Second Order Step 1 ---- */
typedef struct {
double _aMax, _aMin;
double vd;
} CRuckigVelocitySecondOrderStep1;
void cruckig_vel2_step1_init(CRuckigVelocitySecondOrderStep1 *s,
double v0, double vf, double aMax, double aMin);
bool cruckig_vel2_step1_get_profile(CRuckigVelocitySecondOrderStep1 *s,
const CRuckigProfile *input, CRuckigBlock *block);
/* ---- Second Order Step 2 ---- */
typedef struct {
double tf;
double _aMax, _aMin;
double vd;
} CRuckigVelocitySecondOrderStep2;
void cruckig_vel2_step2_init(CRuckigVelocitySecondOrderStep2 *s,
double tf, double v0, double vf, double aMax, double aMin);
bool cruckig_vel2_step2_get_profile(CRuckigVelocitySecondOrderStep2 *s, CRuckigProfile *profile);
#endif /* CRUCKIG_VELOCITY_H */

View File

@@ -0,0 +1,40 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "velocity.h"
#include "block.h"
#include "profile.h"
void cruckig_vel2_step1_init(CRuckigVelocitySecondOrderStep1 *s,
double v0, double vf, double aMax, double aMin)
{
s->_aMax = aMax;
s->_aMin = aMin;
s->vd = vf - v0;
}
bool cruckig_vel2_step1_get_profile(CRuckigVelocitySecondOrderStep1 *s,
const CRuckigProfile *input, CRuckigBlock *block)
{
CRuckigProfile *p = &block->p_min;
cruckig_profile_set_boundary_from_profile(p, input);
const double af = (s->vd > 0) ? s->_aMax : s->_aMin;
p->t[0] = 0;
p->t[1] = s->vd / af;
p->t[2] = 0;
p->t[3] = 0;
p->t[4] = 0;
p->t[5] = 0;
p->t[6] = 0;
if (cruckig_profile_check_for_second_order_velocity(p, ControlSignsUDDU, ReachedLimitsACC0, af)) {
block->t_min = p->t_sum[6] + p->brake.duration + p->accel.duration;
return true;
}
return false;
}

View File

@@ -0,0 +1,39 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "velocity.h"
#include "block.h"
#include "profile.h"
void cruckig_vel2_step2_init(CRuckigVelocitySecondOrderStep2 *s,
double tf, double v0, double vf, double aMax, double aMin)
{
s->tf = tf;
s->_aMax = aMax;
s->_aMin = aMin;
s->vd = vf - v0;
}
bool cruckig_vel2_step2_get_profile(CRuckigVelocitySecondOrderStep2 *s, CRuckigProfile *profile)
{
const double af = s->vd / s->tf;
profile->t[0] = 0;
profile->t[1] = s->tf;
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check_for_second_order_velocity_with_timing_full(profile, ControlSignsUDDU, ReachedLimitsNONE, s->tf, af, s->_aMax, s->_aMin)) {
profile->pf = profile->p[7];
return true;
}
return false;
}

View File

@@ -0,0 +1,187 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "velocity.h"
#include "block.h"
#include "profile.h"
/* ---- Internal helper functions ---- */
static void time_acc0(const CRuckigVelocityThirdOrderStep1 *s,
CRuckigProfile *valid_profiles, size_t *counter,
double aMax, double aMin, double jMax, bool return_after_found)
{
(void)return_after_found;
CRuckigProfile *profile = &valid_profiles[*counter];
profile->t[0] = (-s->a0 + aMax) / jMax;
profile->t[1] = (s->a0 * s->a0 + s->af * s->af) / (2 * aMax * jMax) - aMax / jMax + s->vd / aMax;
profile->t[2] = (-s->af + aMax) / jMax;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check_for_velocity(profile, ControlSignsUDDU, ReachedLimitsACC0, jMax, aMax, aMin)) {
(*counter)++;
if (*counter < 3) {
cruckig_profile_set_boundary_from_profile(&valid_profiles[*counter], profile);
}
}
}
static void time_none(const CRuckigVelocityThirdOrderStep1 *s,
CRuckigProfile *valid_profiles, size_t *counter,
double aMax, double aMin, double jMax, bool return_after_found)
{
double h1 = (s->a0 * s->a0 + s->af * s->af) / 2 + jMax * s->vd;
if (h1 >= 0.0) {
h1 = sqrt(h1);
/* Solution 1 */
{
CRuckigProfile *profile = &valid_profiles[*counter];
profile->t[0] = -(s->a0 + h1) / jMax;
profile->t[1] = 0;
profile->t[2] = -(s->af + h1) / jMax;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check_for_velocity(profile, ControlSignsUDDU, ReachedLimitsNONE, jMax, aMax, aMin)) {
(*counter)++;
if (*counter < 3) {
cruckig_profile_set_boundary_from_profile(&valid_profiles[*counter], profile);
}
if (return_after_found) {
return;
}
}
}
/* Solution 2 */
{
CRuckigProfile *profile = &valid_profiles[*counter];
profile->t[0] = (-s->a0 + h1) / jMax;
profile->t[1] = 0;
profile->t[2] = (-s->af + h1) / jMax;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check_for_velocity(profile, ControlSignsUDDU, ReachedLimitsNONE, jMax, aMax, aMin)) {
(*counter)++;
if (*counter < 3) {
cruckig_profile_set_boundary_from_profile(&valid_profiles[*counter], profile);
}
}
}
}
}
static bool time_all_single_step(const CRuckigVelocityThirdOrderStep1 *s,
CRuckigProfile *profile,
double aMax, double aMin, double jMax)
{
(void)jMax;
if (fabs(s->af - s->a0) > DBL_EPSILON) {
return false;
}
profile->t[0] = 0;
profile->t[1] = 0;
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (fabs(s->a0) > DBL_EPSILON) {
profile->t[3] = s->vd / s->a0;
if (cruckig_profile_check_for_velocity(profile, ControlSignsUDDU, ReachedLimitsNONE, 0.0, aMax, aMin)) {
return true;
}
} else if (fabs(s->vd) < DBL_EPSILON) {
if (cruckig_profile_check_for_velocity(profile, ControlSignsUDDU, ReachedLimitsNONE, 0.0, aMax, aMin)) {
return true;
}
}
return false;
}
/* ---- Public interface ---- */
void cruckig_vel3_step1_init(CRuckigVelocityThirdOrderStep1 *s,
double v0, double a0, double vf, double af,
double aMax, double aMin, double jMax)
{
s->a0 = a0;
s->af = af;
s->_aMax = aMax;
s->_aMin = aMin;
s->_jMax = jMax;
s->vd = vf - v0;
}
bool cruckig_vel3_step1_get_profile(CRuckigVelocityThirdOrderStep1 *s,
const CRuckigProfile *input, CRuckigBlock *block)
{
/* Zero-limits special case */
if (s->_jMax == 0.0) {
CRuckigProfile *p = &block->p_min;
cruckig_profile_set_boundary_from_profile(p, input);
if (time_all_single_step(s, p, s->_aMax, s->_aMin, s->_jMax)) {
block->t_min = p->t_sum[6] + p->brake.duration + p->accel.duration;
if (fabs(s->a0) > DBL_EPSILON) {
block->a.valid = true;
block->a.left = block->t_min;
block->a.right = INFINITY;
}
return true;
}
return false;
}
size_t valid_profile_counter = 0;
cruckig_profile_set_boundary_from_profile(&s->valid_profiles[0], input);
if (fabs(s->af) < DBL_EPSILON) {
/* There is no blocked interval when af==0, so return after first found profile */
const double aMax = (s->vd >= 0) ? s->_aMax : s->_aMin;
const double aMin = (s->vd >= 0) ? s->_aMin : s->_aMax;
const double jMax = (s->vd >= 0) ? s->_jMax : -s->_jMax;
time_none(s, s->valid_profiles, &valid_profile_counter, aMax, aMin, jMax, true);
if (valid_profile_counter > 0) { goto return_block; }
time_acc0(s, s->valid_profiles, &valid_profile_counter, aMax, aMin, jMax, true);
if (valid_profile_counter > 0) { goto return_block; }
time_none(s, s->valid_profiles, &valid_profile_counter, aMin, aMax, -jMax, true);
if (valid_profile_counter > 0) { goto return_block; }
time_acc0(s, s->valid_profiles, &valid_profile_counter, aMin, aMax, -jMax, true);
} else {
time_none(s, s->valid_profiles, &valid_profile_counter, s->_aMax, s->_aMin, s->_jMax, false);
time_none(s, s->valid_profiles, &valid_profile_counter, s->_aMin, s->_aMax, -s->_jMax, false);
time_acc0(s, s->valid_profiles, &valid_profile_counter, s->_aMax, s->_aMin, s->_jMax, false);
time_acc0(s, s->valid_profiles, &valid_profile_counter, s->_aMin, s->_aMax, -s->_jMax, false);
}
return_block:
return cruckig_block_calculate(block, s->valid_profiles, valid_profile_counter, 3);
}

View File

@@ -0,0 +1,146 @@
/*
* cruckig - Pure C99 port of the Ruckig trajectory generation library
* Copyright (c) 2025 Yang Yang <mika-net@outlook.com>
* Copyright (c) 2021 Lars Berscheid (original C++ Ruckig)
*
* License: MIT, see the LICENSE file in this directory.
*/
#include "velocity.h"
#include "block.h"
#include "profile.h"
/* ---- Internal helper functions ---- */
static bool time_acc0(CRuckigVelocityThirdOrderStep2 *s, CRuckigProfile *profile,
double aMax, double aMin, double jMax)
{
/* UD Solution 1/2 */
{
const double h1 = sqrt((-s->ad * s->ad + 2 * jMax * ((s->a0 + s->af) * s->tf - 2 * s->vd)) / (jMax * jMax) + s->tf * s->tf);
profile->t[0] = s->ad / (2 * jMax) + (s->tf - h1) / 2;
profile->t[1] = h1;
profile->t[2] = s->tf - (profile->t[0] + h1);
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check_for_velocity_with_timing(profile, ControlSignsUDDU, ReachedLimitsACC0, s->tf, jMax, aMax, aMin)) {
profile->pf = profile->p[7];
return true;
}
}
/* UU Solution */
{
const double h1 = (-s->ad + jMax * s->tf);
profile->t[0] = -s->ad * s->ad / (2 * jMax * h1) + (s->vd - s->a0 * s->tf) / h1;
profile->t[1] = -s->ad / jMax + s->tf;
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = s->tf - (profile->t[0] + profile->t[1]);
if (cruckig_profile_check_for_velocity_with_timing(profile, ControlSignsUDDU, ReachedLimitsACC0, s->tf, jMax, aMax, aMin)) {
profile->pf = profile->p[7];
return true;
}
}
/* UU Solution - 2 step */
{
profile->t[0] = 0;
profile->t[1] = -s->ad / jMax + s->tf;
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = s->ad / jMax;
if (cruckig_profile_check_for_velocity_with_timing(profile, ControlSignsUDDU, ReachedLimitsACC0, s->tf, jMax, aMax, aMin)) {
profile->pf = profile->p[7];
return true;
}
}
return false;
}
static bool time_none(CRuckigVelocityThirdOrderStep2 *s, CRuckigProfile *profile,
double aMax, double aMin, double jMax)
{
if (fabs(s->a0) < DBL_EPSILON && fabs(s->af) < DBL_EPSILON && fabs(s->vd) < DBL_EPSILON) {
profile->t[0] = 0;
profile->t[1] = s->tf;
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
if (cruckig_profile_check_for_velocity_with_timing(profile, ControlSignsUDDU, ReachedLimitsNONE, s->tf, jMax, aMax, aMin)) {
profile->pf = profile->p[7];
return true;
}
}
/* UD Solution 1/2 */
{
const double h1 = 2 * (s->af * s->tf - s->vd);
profile->t[0] = h1 / s->ad;
profile->t[1] = s->tf - profile->t[0];
profile->t[2] = 0;
profile->t[3] = 0;
profile->t[4] = 0;
profile->t[5] = 0;
profile->t[6] = 0;
const double jf = s->ad * s->ad / h1;
if (fabs(jf) < fabs(jMax) + 1e-12 && cruckig_profile_check_for_velocity_with_timing(profile, ControlSignsUDDU, ReachedLimitsNONE, s->tf, jf, aMax, aMin)) {
profile->pf = profile->p[7];
return true;
}
}
return false;
}
static bool check_all(CRuckigVelocityThirdOrderStep2 *s, CRuckigProfile *profile,
double aMax, double aMin, double jMax)
{
return time_acc0(s, profile, aMax, aMin, jMax) || time_none(s, profile, aMax, aMin, jMax);
}
/* ---- Public interface ---- */
void cruckig_vel3_step2_init(CRuckigVelocityThirdOrderStep2 *s,
double tf, double v0, double a0, double vf, double af,
double aMax, double aMin, double jMax)
{
s->a0 = a0;
s->tf = tf;
s->af = af;
s->_aMax = aMax;
s->_aMin = aMin;
s->_jMax = jMax;
s->vd = vf - v0;
s->ad = af - a0;
}
bool cruckig_vel3_step2_get_profile(CRuckigVelocityThirdOrderStep2 *s, CRuckigProfile *profile)
{
/* Test all cases to get ones that match */
/* However we should guess which one is correct and try them first... */
if (s->vd > 0) {
return check_all(s, profile, s->_aMax, s->_aMin, s->_jMax) || check_all(s, profile, s->_aMin, s->_aMax, -s->_jMax);
}
return check_all(s, profile, s->_aMin, s->_aMax, -s->_jMax) || check_all(s, profile, s->_aMax, s->_aMin, s->_jMax);
}

View File

@@ -0,0 +1,680 @@
/********************************************************************
* Description: ruckig_wrapper.c
* Cruckig (pure C) trajectory planning library wrapper implementation
*
* This file provides a C wrapper around the Cruckig C library
* for S-curve trajectory planning in LinuxCNC.
* Replaces the C++ Ruckig implementation to enable RTAI kernel builds.
*
* License: GPL Version 2
* System: Linux
* Original Author: 杨阳 (mika-net@outlook.com)
* Cruckig port: LinuxCNC contributors
*
* Copyright (c) 2024-2026 All rights reserved.
********************************************************************/
#include "ruckig_wrapper.h"
#include <rtapi.h>
#include <rtapi_math.h>
#include <rtapi_slab.h>
/* LinuxCNC precision constants (consistent with tp_types.h) */
#ifndef TP_POS_EPSILON
#define TP_POS_EPSILON 1e-12
#endif
#ifndef TP_VEL_EPSILON
#define TP_VEL_EPSILON 1e-8
#endif
/* Cruckig C headers */
#include "cruckig/cruckig.h"
/* Internal implementation struct */
struct RuckigPlannerImpl {
CRuckig *otg; /* cruckig planner instance */
CRuckigInputParameter *input; /* input parameters */
CRuckigTrajectory *trajectory; /* trajectory result */
double cycle_time; /* cycle time */
int planned; /* whether planning has been done */
double start_time; /* trajectory start time */
double target_pos; /* target position (used for precision correction) */
double target_vel; /* target velocity (used for precision correction) */
double target_acc; /* target acceleration (used for precision correction) */
int use_position_control; /* 1=position control, 0=velocity control */
double last_actual_acc; /* previous actual acceleration (for jerk calculation) */
int is_first_cycle; /* first cycle after replanning */
int enable_logging; /* 1=enabled, 0=disabled */
};
/* Helper macro: conditionally output log based on planner's logging setting */
#define RUCKIG_LOG_IF_ENABLED(planner, level, fmt, ...) \
do { \
if (planner) { \
struct RuckigPlannerImpl *_impl = (struct RuckigPlannerImpl *)planner; \
if (_impl->enable_logging) { \
rtapi_print_msg(level, fmt, ##__VA_ARGS__); \
} \
} else { \
rtapi_print_msg(level, fmt, ##__VA_ARGS__); \
} \
} while (0)
RuckigPlanner ruckig_create(double cycle_time) {
if (cycle_time <= 0.0) {
rtapi_print_msg(RTAPI_MSG_ERR, "ruckig_create: invalid cycle_time %f\n", cycle_time);
return NULL;
}
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)rtapi_kmalloc(sizeof(struct RuckigPlannerImpl), RTAPI_GFP_KERNEL);
if (!impl) {
rtapi_print_msg(RTAPI_MSG_ERR, "ruckig_create: memory allocation failed\n");
return NULL;
}
impl->otg = cruckig_create(1, cycle_time);
impl->input = cruckig_input_create(1);
impl->trajectory = cruckig_trajectory_create(1);
if (!impl->otg || !impl->input || !impl->trajectory) {
rtapi_print_msg(RTAPI_MSG_ERR, "ruckig_create: cruckig allocation failed\n");
if (impl->otg) cruckig_destroy(impl->otg);
if (impl->input) cruckig_input_destroy(impl->input);
if (impl->trajectory) cruckig_trajectory_destroy(impl->trajectory);
rtapi_kfree(impl);
return NULL;
}
impl->cycle_time = cycle_time;
impl->planned = 0;
impl->start_time = 0.0;
impl->target_pos = 0.0;
impl->target_vel = 0.0;
impl->target_acc = 0.0;
impl->use_position_control = 0;
impl->last_actual_acc = 0.0;
impl->is_first_cycle = 0;
impl->enable_logging = 1;
return (RuckigPlanner)impl;
}
void ruckig_destroy(RuckigPlanner planner) {
if (planner) {
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
if (impl->otg) cruckig_destroy(impl->otg);
if (impl->input) cruckig_input_destroy(impl->input);
if (impl->trajectory) cruckig_trajectory_destroy(impl->trajectory);
rtapi_kfree(impl);
}
}
/* Helper: copy trajectory state for backup/restore on planning failure.
* We cannot just memcpy the CRuckigTrajectory because it contains owned pointers.
* Instead we save/restore the profile data and scalar fields. */
struct TrajectoryBackup {
CRuckigProfile profile; /* single-DOF single-section profile copy */
double duration;
double cumulative_time;
double independent_min_duration;
CRuckigBound position_extremum;
};
static void backup_trajectory(const CRuckigTrajectory *traj, struct TrajectoryBackup *bk) {
bk->duration = traj->duration;
if (traj->profiles)
bk->profile = traj->profiles[0]; /* 1 DOF, 1 section */
if (traj->cumulative_times)
bk->cumulative_time = traj->cumulative_times[0];
if (traj->independent_min_durations)
bk->independent_min_duration = traj->independent_min_durations[0];
if (traj->position_extrema)
bk->position_extremum = traj->position_extrema[0];
}
static void restore_trajectory(CRuckigTrajectory *traj, const struct TrajectoryBackup *bk) {
traj->duration = bk->duration;
if (traj->profiles)
traj->profiles[0] = bk->profile;
if (traj->cumulative_times)
traj->cumulative_times[0] = bk->cumulative_time;
if (traj->independent_min_durations)
traj->independent_min_durations[0] = bk->independent_min_duration;
if (traj->position_extrema)
traj->position_extrema[0] = bk->position_extremum;
}
/* Helper: handle cruckig result codes, return 0 on success, -1 or -2 on failure.
* On failure with a previous plan, restores the backup. */
static int handle_result(CRuckigResult result, RuckigPlanner planner,
const char *func_name,
int had_previous_plan,
const struct TrajectoryBackup *bk,
double bk_target_pos, double bk_target_vel,
int bk_use_position_control, double bk_last_actual_acc) {
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
if (result == CRuckigWorking || result == CRuckigFinished) {
if (result == CRuckigFinished) {
double duration = cruckig_trajectory_get_duration(impl->trajectory);
if (duration < 0.001) {
RUCKIG_LOG_IF_ENABLED(planner, RTAPI_MSG_INFO,
"%s: already at target (duration=%f)\n", func_name, duration);
} else {
RUCKIG_LOG_IF_ENABLED(planner, RTAPI_MSG_INFO,
"%s: trajectory finished (duration=%f)\n", func_name, duration);
}
}
return 0; /* success */
}
/* Planning failed: restore previous trajectory if it exists */
if (had_previous_plan) {
restore_trajectory(impl->trajectory, bk);
impl->target_pos = bk_target_pos;
impl->target_vel = bk_target_vel;
impl->use_position_control = bk_use_position_control;
impl->last_actual_acc = bk_last_actual_acc;
RUCKIG_LOG_IF_ENABLED(planner, RTAPI_MSG_INFO,
"%s: planning failed, restored previous trajectory\n", func_name);
}
/* Log error */
switch (result) {
case CRuckigErrorInvalidInput:
RUCKIG_LOG_IF_ENABLED(planner, RTAPI_MSG_ERR,
"%s: invalid input parameters\n", func_name);
break;
case CRuckigErrorTrajectoryDuration:
RUCKIG_LOG_IF_ENABLED(planner, RTAPI_MSG_ERR,
"%s: trajectory duration exceeds numerical limits\n", func_name);
break;
case CRuckigErrorPositionalLimits:
RUCKIG_LOG_IF_ENABLED(planner, RTAPI_MSG_ERR,
"%s: positional limits exceeded\n", func_name);
break;
case CRuckigErrorZeroLimits:
RUCKIG_LOG_IF_ENABLED(planner, RTAPI_MSG_ERR,
"%s: zero limits conflict\n", func_name);
break;
case CRuckigErrorExecutionTimeCalculation:
return -2;
case CRuckigErrorSynchronizationCalculation:
RUCKIG_LOG_IF_ENABLED(planner, RTAPI_MSG_ERR,
"%s: synchronization calculation error\n", func_name);
break;
case CRuckigError:
RUCKIG_LOG_IF_ENABLED(planner, RTAPI_MSG_ERR,
"%s: general error\n", func_name);
break;
default:
RUCKIG_LOG_IF_ENABLED(planner, RTAPI_MSG_ERR,
"%s: unknown error result %d\n", func_name, (int)result);
break;
}
return -1;
}
int ruckig_plan_position(RuckigPlanner planner,
double current_pos,
double current_vel,
double current_acc,
double target_pos,
double target_vel,
double target_acc,
double min_vel,
double max_vel,
double max_acc,
double max_jerk) {
if (!planner) {
return -1;
}
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
/* Parameter validation */
if (max_vel <= 0.0 || max_acc <= 0.0 || max_jerk <= 0.0) {
RUCKIG_LOG_IF_ENABLED(planner, RTAPI_MSG_ERR,
"ruckig_plan_position: invalid limits (v=%f, a=%f, j=%f)\n",
max_vel, max_acc, max_jerk);
return -1;
}
/* Set input parameters (position control mode) */
impl->input->control_interface = CRuckigPosition;
impl->input->synchronization = CRuckigSyncTime;
impl->input->current_position[0] = current_pos;
impl->input->current_velocity[0] = current_vel;
impl->input->current_acceleration[0] = current_acc;
impl->input->target_position[0] = target_pos;
impl->input->target_velocity[0] = target_vel;
impl->input->target_acceleration[0] = target_acc;
impl->input->max_velocity[0] = max_vel;
impl->input->max_acceleration[0] = max_acc;
impl->input->max_jerk[0] = max_jerk;
/* Set min_velocity: cruckig uses NULL for default (-max), or a pointer for explicit */
if (impl->input->min_velocity == NULL) {
impl->input->min_velocity = (double *)rtapi_kmalloc(sizeof(double), RTAPI_GFP_KERNEL);
if (!impl->input->min_velocity) return -1;
}
impl->input->min_velocity[0] = min_vel;
/* Backup trajectory on failure */
int had_previous_plan = impl->planned;
struct TrajectoryBackup bk;
double bk_target_pos = 0.0, bk_target_vel = 0.0, bk_last_actual_acc = 0.0;
int bk_use_position_control = 0;
if (had_previous_plan) {
backup_trajectory(impl->trajectory, &bk);
bk_target_pos = impl->target_pos;
bk_target_vel = impl->target_vel;
bk_use_position_control = impl->use_position_control;
bk_last_actual_acc = impl->last_actual_acc;
}
/* Execute planning */
CRuckigResult result = cruckig_calculate(impl->otg, impl->input, impl->trajectory);
int rc = handle_result(result, planner, "ruckig_plan_position",
had_previous_plan, &bk,
bk_target_pos, bk_target_vel,
bk_use_position_control, bk_last_actual_acc);
if (rc != 0) return rc;
/* Update state on success */
int was_planned = impl->planned;
if (!was_planned) {
impl->last_actual_acc = current_acc;
}
impl->planned = 1;
impl->start_time = 0.0;
impl->target_pos = target_pos;
impl->target_vel = target_vel;
impl->target_acc = target_acc;
impl->use_position_control = 1;
impl->is_first_cycle = 1;
return 0;
}
int ruckig_plan_velocity(RuckigPlanner planner,
double current_vel,
double current_acc,
double target_vel,
double target_acc,
double min_vel,
double max_acc,
double max_jerk) {
if (!planner) {
return -1;
}
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
/* Parameter validation */
if (max_acc <= 0.0 || max_jerk <= 0.0) {
RUCKIG_LOG_IF_ENABLED(planner, RTAPI_MSG_ERR,
"ruckig_plan_velocity: invalid limits (a=%f, j=%f)\n",
max_acc, max_jerk);
return -1;
}
/* Set input parameters (velocity control mode) */
impl->input->control_interface = CRuckigVelocity;
impl->input->synchronization = CRuckigSyncNone;
impl->input->current_position[0] = 0.0;
impl->input->current_velocity[0] = current_vel;
impl->input->current_acceleration[0] = current_acc;
impl->input->target_position[0] = 0.0;
impl->input->target_velocity[0] = target_vel;
impl->input->target_acceleration[0] = target_acc;
impl->input->max_velocity[0] = INFINITY;
impl->input->max_acceleration[0] = max_acc;
impl->input->max_jerk[0] = max_jerk;
/* Set min_velocity */
if (impl->input->min_velocity == NULL) {
impl->input->min_velocity = (double *)rtapi_kmalloc(sizeof(double), RTAPI_GFP_KERNEL);
if (!impl->input->min_velocity) return -1;
}
impl->input->min_velocity[0] = min_vel;
/* Backup trajectory on failure */
int had_previous_plan = impl->planned;
struct TrajectoryBackup bk;
double bk_target_pos = 0.0, bk_target_vel = 0.0, bk_last_actual_acc = 0.0;
int bk_use_position_control = 0;
if (had_previous_plan) {
backup_trajectory(impl->trajectory, &bk);
bk_target_pos = impl->target_pos;
bk_target_vel = impl->target_vel;
bk_use_position_control = impl->use_position_control;
bk_last_actual_acc = impl->last_actual_acc;
}
/* Execute planning */
CRuckigResult result = cruckig_calculate(impl->otg, impl->input, impl->trajectory);
int rc = handle_result(result, planner, "ruckig_plan_velocity",
had_previous_plan, &bk,
bk_target_pos, bk_target_vel,
bk_use_position_control, bk_last_actual_acc);
if (rc != 0) return rc;
/* Update state on success */
int was_planned = impl->planned;
if (!was_planned) {
impl->last_actual_acc = current_acc;
}
impl->planned = 1;
impl->start_time = 0.0;
impl->target_pos = 0.0;
impl->target_vel = target_vel;
impl->target_acc = target_acc;
impl->use_position_control = 0;
impl->is_first_cycle = 1;
return 0;
}
int ruckig_at_time(RuckigPlanner planner,
double time,
double *pos,
double *vel,
double *acc,
double *jerk) {
if (!planner || !pos || !vel || !acc || !jerk) {
return -1;
}
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
if (!impl->planned) {
rtapi_print_msg(RTAPI_MSG_ERR, "ruckig_at_time: trajectory not planned\n");
return -1;
}
double duration = cruckig_trajectory_get_duration(impl->trajectory);
/* Clamp time */
double query_time = time;
if (time < 0.0) {
rtapi_print_msg(RTAPI_MSG_ERR, "ruckig_at_time: time %f is negative\n", time);
return -1;
}
if (time > duration) {
query_time = duration;
}
/* Get state at specified time */
double new_pos, new_vel, new_acc, new_jerk_unused;
size_t new_section;
cruckig_trajectory_at_time(impl->trajectory, query_time,
&new_pos, &new_vel, &new_acc, &new_jerk_unused,
&new_section);
*pos = new_pos;
*vel = new_vel;
*acc = new_acc;
/* Precision correction: ensure position and velocity exactly match target values */
if (impl->use_position_control) {
const double TIME_THRESHOLD = fmax(duration * 0.1, impl->cycle_time * 10.0);
const double POS_ERROR_THRESHOLD = 1e-6;
if (time >= duration - TIME_THRESHOLD || time >= duration) {
double pos_error = fabs(*pos - impl->target_pos);
if (pos_error < POS_ERROR_THRESHOLD) {
*pos = impl->target_pos;
}
if (time >= duration) {
*vel = impl->target_vel;
}
/* During trajectory: let S-curve complete naturally */
}
} else {
/* Velocity control mode: only correct at trajectory end */
if (time >= duration) {
*vel = impl->target_vel;
*acc = impl->target_acc;
}
}
/* Calculate jerk */
if (time > duration) {
if (impl->use_position_control) {
double pos_error = fabs(*pos - impl->target_pos);
double vel_error = fabs(*vel - impl->target_vel);
double acc_threshold = 1e-6;
int acc_near_zero = (fabs(*acc) < acc_threshold);
if (pos_error < TP_POS_EPSILON * 100.0 && vel_error < TP_VEL_EPSILON * 10.0 && acc_near_zero) {
*jerk = 0.0;
*acc = 0.0;
}
} else {
double vel_error = fabs(*vel - impl->target_vel);
double acc_threshold = 1e-6;
int acc_near_zero = (fabs(*acc) < acc_threshold);
if (vel_error < TP_VEL_EPSILON * 10.0 && acc_near_zero) {
*jerk = 0.0;
*acc = 0.0;
}
}
} else if (query_time > impl->cycle_time) {
/* Compute jerk from acceleration difference */
double prev_pos, prev_vel, prev_acc_val, prev_jerk_unused;
size_t prev_section;
double prev_time = query_time - impl->cycle_time;
if (prev_time < 0.0) prev_time = 0.0;
cruckig_trajectory_at_time(impl->trajectory, prev_time,
&prev_pos, &prev_vel, &prev_acc_val, &prev_jerk_unused,
&prev_section);
*jerk = (new_acc - prev_acc_val) / impl->cycle_time;
} else {
/* First cycle after replanning */
if (impl->is_first_cycle) {
double base_acc = impl->last_actual_acc;
*jerk = (new_acc - base_acc) / impl->cycle_time;
impl->is_first_cycle = 0;
} else {
/* Use initial acceleration from planning time */
*jerk = (query_time > 0.0) ?
(new_acc - impl->input->current_acceleration[0]) / query_time : 0.0;
}
}
/* Save current acceleration for jerk calculation in next cycle */
impl->last_actual_acc = *acc;
return 0;
}
int ruckig_next_cycle(RuckigPlanner planner,
double current_time,
double cycle_time,
double *pos,
double *vel,
double *acc,
double *jerk) {
if (!planner) {
return -1;
}
double next_time = current_time + cycle_time;
return ruckig_at_time(planner, next_time, pos, vel, acc, jerk);
}
double ruckig_get_duration(RuckigPlanner planner) {
if (!planner) {
return -1.0;
}
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
if (!impl->planned) {
return -1.0;
}
return cruckig_trajectory_get_duration(impl->trajectory);
}
int ruckig_is_finished(RuckigPlanner planner, double current_time) {
if (!planner) {
return -1;
}
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
if (!impl->planned) {
return -1;
}
double duration = ruckig_get_duration(planner);
if (duration < 0.0) {
return -1;
}
return (current_time >= duration) ? 1 : 0;
}
void ruckig_reset(RuckigPlanner planner) {
if (!planner) {
return;
}
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
/* Reset all state fields */
impl->planned = 0;
impl->start_time = 0.0;
impl->target_pos = 0.0;
impl->target_vel = 0.0;
impl->target_acc = 0.0;
impl->use_position_control = 0;
impl->last_actual_acc = 0.0;
impl->is_first_cycle = 0;
/* Note: do not reset enable_logging, preserve user setting */
/* Reset cruckig objects */
cruckig_reset(impl->otg);
}
void ruckig_set_logging(RuckigPlanner planner, int enable) {
if (!planner) {
return;
}
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
impl->enable_logging = (enable != 0) ? 1 : 0;
}
int ruckig_get_decelerate_phases(RuckigPlanner planner, double *t1, double *t2) {
if (!planner) {
return -1;
}
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
if (!impl->planned) {
rtapi_print_msg(RTAPI_MSG_ERR, "ruckig_get_decelerate_phases: trajectory not planned\n");
return -1;
}
/* Get Profile (1 DOF, section 0) */
const CRuckigProfile *profile = cruckig_trajectory_get_profile(impl->trajectory, 0);
if (!profile) {
rtapi_print_msg(RTAPI_MSG_ERR, "ruckig_get_decelerate_phases: no profile available\n");
return -1;
}
/* Deceleration phases: t[4]=T1 (jerk), t[5]=T2 (constant accel) */
if (t1 != NULL) {
*t1 = (profile->t[4] > 0.0) ? profile->t[4] : 0.0;
}
if (t2 != NULL) {
*t2 = (profile->t[5] > 0.0) ? profile->t[5] : 0.0;
}
return 0;
}
int ruckig_get_peak_velocity(RuckigPlanner planner, double *peak_vel) {
if (!planner || !peak_vel) {
return -1;
}
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
if (!impl->planned) {
rtapi_print_msg(RTAPI_MSG_ERR, "ruckig_get_peak_velocity: trajectory not planned\n");
return -1;
}
const CRuckigProfile *profile = cruckig_trajectory_get_profile(impl->trajectory, 0);
if (!profile) {
rtapi_print_msg(RTAPI_MSG_ERR, "ruckig_get_peak_velocity: no profile available\n");
return -1;
}
/* Peak velocity is the maximum of v[0] through v[7] */
double max_v = 0.0;
size_t i;
for (i = 0; i < 8; i++) {
if (profile->v[i] > max_v) {
max_v = profile->v[i];
}
}
*peak_vel = max_v;
return 0;
}
int ruckig_get_start_velocity(RuckigPlanner planner, double *start_vel) {
if (!planner || !start_vel) {
return -1;
}
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
if (!impl->planned) {
rtapi_print_msg(RTAPI_MSG_ERR, "ruckig_get_start_velocity: trajectory not planned\n");
return -1;
}
const CRuckigProfile *profile = cruckig_trajectory_get_profile(impl->trajectory, 0);
if (!profile) {
rtapi_print_msg(RTAPI_MSG_ERR, "ruckig_get_start_velocity: no profile available\n");
return -1;
}
*start_vel = profile->v[0];
return 0;
}
int ruckig_get_time_at_position(RuckigPlanner planner, double position, double time_after, double *time) {
if (!planner || time == NULL) {
return -1;
}
struct RuckigPlannerImpl *impl = (struct RuckigPlannerImpl *)planner;
if (!impl->planned) {
rtapi_print_msg(RTAPI_MSG_ERR, "ruckig_get_time_at_position: trajectory not planned\n");
return -1;
}
double result_time;
if (cruckig_trajectory_get_first_time_at_position(impl->trajectory, 0, position,
&result_time, time_after)) {
*time = result_time;
return 0;
} else {
return -1;
}
}

View File

@@ -0,0 +1,221 @@
/********************************************************************
* Description: ruckig_wrapper.h
* Ruckig trajectory planning library wrapper for LinuxCNC
*
* This wrapper provides a C interface to Ruckig C++ library
* for S-curve trajectory planning.
*
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2024 All rights reserved.
********************************************************************/
#ifndef RUCKIG_WRAPPER_H
#define RUCKIG_WRAPPER_H
#include <rtapi.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Ruckig trajectory planner handle (opaque pointer)
*/
typedef void* RuckigPlanner;
/**
* Create a Ruckig trajectory planner.
*
* @param cycle_time cycle time in seconds
* @return planner handle, or NULL on failure
*/
RuckigPlanner ruckig_create(double cycle_time);
/**
* Destroy a Ruckig trajectory planner.
*
* @param planner planner handle
*/
void ruckig_destroy(RuckigPlanner planner);
/**
* Plan an S-curve trajectory in position control mode.
*
* Given the initial and target states, plan a complete S-curve trajectory.
*
* @param planner planner handle
* @param current_pos current position
* @param current_vel current velocity
* @param current_acc current acceleration
* @param target_pos target position
* @param target_vel target velocity (usually 0)
* @param target_acc target acceleration (usually 0)
* @param min_vel minimum velocity limit (set to 0 for unidirectional motion)
* @param max_vel maximum velocity limit
* @param max_acc maximum acceleration limit
* @param max_jerk maximum jerk limit
* @return 0 on success, -1 on failure (insufficient distance or invalid params)
*/
int ruckig_plan_position(RuckigPlanner planner,
double current_pos,
double current_vel,
double current_acc,
double target_pos,
double target_vel,
double target_acc,
double min_vel,
double max_vel,
double max_acc,
double max_jerk);
/**
* Plan an S-curve trajectory in velocity control mode (for stop/pause).
*
* Uses velocity control mode, ignoring target position.
* Suitable for stop or pause scenarios where deceleration may span segments.
*
* @param planner planner handle
* @param current_vel current velocity
* @param current_acc current acceleration
* @param target_vel target velocity (0 for stop)
* @param target_acc target acceleration (usually 0)
* @param min_vel minimum velocity limit (set to 0 for unidirectional motion)
* @param max_acc maximum acceleration limit
* @param max_jerk maximum jerk limit
* @return 0 on success, -1 on failure (invalid params)
*/
int ruckig_plan_velocity(RuckigPlanner planner,
double current_vel,
double current_acc,
double target_vel,
double target_acc,
double min_vel,
double max_acc,
double max_jerk);
/**
* Get the motion state at a specified time.
*
* @param planner planner handle
* @param time time in seconds (from trajectory start)
* @param pos [out] position
* @param vel [out] velocity
* @param acc [out] acceleration
* @param jerk [out] jerk
* @return 0 on success, -1 on failure (time out of range)
*/
int ruckig_at_time(RuckigPlanner planner,
double time,
double *pos,
double *vel,
double *acc,
double *jerk);
/**
* Get the motion state at the next cycle.
*
* Computes the state at (current_time + cycle_time).
*
* @param planner planner handle
* @param current_time current time in seconds (from trajectory start)
* @param cycle_time cycle time in seconds
* @param pos [out] position
* @param vel [out] velocity
* @param acc [out] acceleration
* @param jerk [out] jerk
* @return 0 on success, -1 on failure (time out of range or not planned)
*/
int ruckig_next_cycle(RuckigPlanner planner,
double current_time,
double cycle_time,
double *pos,
double *vel,
double *acc,
double *jerk);
/**
* Get total trajectory duration.
*
* @param planner planner handle
* @return total time in seconds, or -1.0 on failure
*/
double ruckig_get_duration(RuckigPlanner planner);
/**
* Check if the trajectory has completed.
*
* @param planner planner handle
* @param current_time current time in seconds
* @return 1 if finished, 0 if not, -1 on error
*/
int ruckig_is_finished(RuckigPlanner planner, double current_time);
/**
* Reset the planner state.
*
* Clears previous planning results, preparing for new planning.
*
* @param planner planner handle
*/
void ruckig_reset(RuckigPlanner planner);
/**
* Enable or disable log output.
*
* Controls whether the planner outputs error and warning messages.
* For velocity planning scenarios (e.g. sp_scurve.c), logging can be
* disabled to avoid unnecessary warnings.
*
* @param planner planner handle
* @param enable 1=enable logging, 0=disable logging
*/
void ruckig_set_logging(RuckigPlanner planner, int enable);
/**
* Get the deceleration phase durations (T1 and T2) from the Ruckig profile.
*
* T1: time for acceleration to change from 0 to -amax (jerk phase)
* T2: time at constant -amax acceleration (constant accel phase)
*
* @param planner planner handle (must have completed planning)
* @param t1 [out] T1 time (jerk phase), NULL if not needed
* @param t2 [out] T2 time (constant accel phase), NULL if not needed
* @return 0 on success, -1 on failure (not planned or cannot retrieve)
*/
int ruckig_get_decelerate_phases(RuckigPlanner planner, double *t1, double *t2);
/**
* Get the peak velocity of the trajectory.
*
* @param planner planner handle (must have completed planning)
* @param peak_vel [out] peak velocity
* @return 0 on success, -1 on failure (not planned or cannot retrieve)
*/
int ruckig_get_peak_velocity(RuckigPlanner planner, double *peak_vel);
/**
* Get the start velocity of the trajectory.
*
* @param planner planner handle (must have completed planning)
* @param start_vel [out] start velocity
* @return 0 on success, -1 on failure (not planned or cannot retrieve)
*/
int ruckig_get_start_velocity(RuckigPlanner planner, double *start_vel);
/**
* Get the time at which the trajectory first reaches a given position.
*
* @param planner planner handle (must have completed planning)
* @param position target position
* @param time_after start query time (optional, default 0.0)
* @param time [out] time at which position is reached
* @return 0 on success, -1 on failure (not planned, position unreachable)
*/
int ruckig_get_time_at_position(RuckigPlanner planner, double position, double time_after, double *time);
#ifdef __cplusplus
}
#endif
#endif /* RUCKIG_WRAPPER_H */

View File

@@ -0,0 +1,653 @@
/*!
********************************************************************
* Description: sp_scurve.c
*\brief Ruckig-based S-curve trajectory planning with legacy helpers
*
*\author Derived from a work by Yang Yang
*
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2004 All rights reserved.
*
* Last change:
********************************************************************/
#include <rtapi.h>
#include <rtapi_math.h>
#include "sp_scurve.h"
#include "tp_types.h"
#include "ruckig_wrapper.h"
#ifndef __KERNEL__
#include <stdio.h>
#include <string.h>
#endif
/* ========== Cached Ruckig planner ==========
* Use a static variable to cache the planner, avoiding creation and
* destruction on every call.
*/
static RuckigPlanner cached_planner = NULL;
static double cached_cycle_time = 0.0; /* cycle time used by the current planner */
/**
* @brief Initialize the S-curve planner (call at program entry).
*
* @param cycle_time cycle time in seconds
* @return 0 on success, -1 on failure
*/
int sp_scurve_init(double cycle_time) {
/* Parameter validation */
if (cycle_time <= 0.0) {
rtapi_print_msg(RTAPI_MSG_ERR, "sp_scurve_init: invalid cycle_time=%f\n", cycle_time);
return -1;
}
/* If planner already exists with the same cycle time, nothing to do */
if (cached_planner != NULL && fabs(cached_cycle_time - cycle_time) < 1e-12) {
return 0;
}
/* If planner exists but cycle time changed, destroy the old one first */
if (cached_planner != NULL) {
rtapi_print_msg(RTAPI_MSG_INFO, "sp_scurve_init: cycle time changed from %f to %f, recreating planner\n",
cached_cycle_time, cycle_time);
ruckig_destroy(cached_planner);
cached_planner = NULL;
}
/* Create new planner */
cached_planner = ruckig_create(cycle_time);
if (cached_planner == NULL) {
rtapi_print_msg(RTAPI_MSG_ERR, "sp_scurve_init: ruckig_create() failed with cycle_time=%f\n", cycle_time);
return -1;
}
/* Disable log output (used for velocity planning — avoids unnecessary warnings) */
ruckig_set_logging(cached_planner, 0);
cached_cycle_time = cycle_time;
rtapi_print_msg(RTAPI_MSG_INFO, "sp_scurve_init: planner created with cycle_time=%f (logging disabled)\n", cycle_time);
return 0;
}
/**
* @brief Clean up the S-curve planner (call at program exit).
*/
void sp_scurve_cleanup(void) {
if (cached_planner != NULL) {
ruckig_destroy(cached_planner);
cached_planner = NULL;
cached_cycle_time = 0.0;
}
}
/**
* @brief Get the cached Ruckig planner.
*
* Note: sp_scurve_init() must be called before using this.
*
* @return RuckigPlanner handle, or NULL if not initialized
*/
static RuckigPlanner get_cached_planner(void) {
/* If planner is not initialized, return NULL.
* Callers should check the return value and handle the error. */
return cached_planner;
}
/* ================================================================
* Ruckig-based S-curve functions
* ================================================================ */
/**
* @brief Compute the S-curve peak velocity from rest to end-speed
* (using Ruckig planning).
*
* Given total distance and end velocity, plan a complete trajectory
* from (0, 0, 0) to (distance, Ve, 0), then read the peak velocity
* directly from the profile — no iteration required.
*
* @param distance total distance
* @param Ve end velocity
* @param maxA maximum acceleration
* @param maxJ maximum jerk
* @param req_v [out] computed peak velocity
* @return 1 on success, -1 on failure
*/
int findSCurveVSpeedWithEndSpeed(double distance, double Ve,
double maxA, double maxJ, double* req_v) {
/* Parameter validation */
if (distance <= 0 || maxA <= 0 || maxJ <= 0) {
*req_v = fabs(Ve);
return -1;
}
/* When Ve is approximately zero, use the symmetric function */
if (fabs(Ve) <= TP_VEL_EPSILON) {
return findSCurveVSpeed(distance, maxA, maxJ, req_v);
}
/* Use the cached planner */
RuckigPlanner planner = get_cached_planner();
if (!planner) {
rtapi_print_msg(RTAPI_MSG_ERR, "findSCurveVSpeedWithEndSpeed: planner not initialized, call sp_scurve_init() first\n");
*req_v = fabs(Ve);
return -1;
}
/* Reset planner state */
ruckig_reset(planner);
/* Plan a complete trajectory from (0, 0, 0) to (distance, Ve, 0).
* Ruckig will automatically find the peak velocity that satisfies
* the distance and end-velocity constraints. */
int result = ruckig_plan_position(planner,
0.0, /* start position */
0.0, /* start velocity */
0.0, /* start acceleration */
distance, /* target position */
Ve, /* target velocity */
0.0, /* target acceleration */
0.0, /* min velocity (unidirectional) */
sqrt(maxA * distance + Ve * Ve) * 2.0, /* max velocity (conservative, ensures no limiting) */
maxA, /* max acceleration */
maxJ); /* max jerk */
if (result != 0) {
rtapi_print_msg(RTAPI_MSG_ERR, "findSCurveVSpeedWithEndSpeed: ruckig_plan_position failed (result=%d)\n", result);
*req_v = fabs(Ve);
return -1;
}
/* Read the peak velocity directly from the profile */
double peak_vel = 0.0;
result = ruckig_get_peak_velocity(planner, &peak_vel);
if (result != 0) {
rtapi_print_msg(RTAPI_MSG_ERR, "findSCurveVSpeedWithEndSpeed: ruckig_get_peak_velocity failed\n");
*req_v = fabs(Ve);
return -1;
}
*req_v = peak_vel;
return 1;
}
/**
* @brief Compute the maximum start speed that can decelerate to Ve within
* a given distance (jerk-constrained).
*
* Find the largest Vs such that a trajectory exists from (0, Vs, 0) to
* (distance, Ve, 0) under (maxA, maxJ) constraints.
*
* Method: use the constant-acceleration upper bound
* Vs_estimate = sqrt(Ve^2 + 2*maxA*distance)
* as an initial guess and pass it to Ruckig. If planning succeeds,
* Vs_estimate is feasible. If it fails, the jerk constraint requires
* more distance — return a guaranteed-feasible upper bound instead.
*
* On failure, instead of returning 0.9*Vs_estimate (which may still
* exceed the jerk-feasible value), return the 0->0 S-curve peak for
* the same distance. That value is always jerk-feasible and prevents
* downstream planning failures. On success the same peak is used as
* an upper-bound clamp.
*
* @param distance total distance
* @param Ve end velocity
* @param maxA maximum acceleration
* @param maxJ maximum jerk
* @param req_v [out] computed maximum start speed
* @return 1 on success, -1 on failure
*/
int findSCurveMaxStartSpeed(double distance, double Ve,
double maxA, double maxJ, double* req_v) {
if (distance <= 0 || maxA <= 0 || maxJ <= 0) {
*req_v = fabs(Ve);
return -1;
}
if (fabs(Ve) <= TP_VEL_EPSILON) {
return findSCurveVSpeed(distance, maxA, maxJ, req_v);
}
/* 0->0 S-curve peak for this distance — reliable jerk-constrained upper bound,
* used as fallback on failure and as a clamp on success. */
double v_0_to_0_peak = 0.0;
if (findSCurveVSpeed(distance, maxA, maxJ, &v_0_to_0_peak) != 1) {
/* findSCurveVSpeed failed: use triangular upper bound to avoid unbounded result */
v_0_to_0_peak = sqrt(maxA * distance);
}
RuckigPlanner planner = get_cached_planner();
if (!planner) {
rtapi_print_msg(RTAPI_MSG_ERR, "findSCurveMaxStartSpeed: planner not initialized, call sp_scurve_init() first\n");
*req_v = fmin(fabs(Ve) * 2.0, v_0_to_0_peak);
return -1;
}
ruckig_reset(planner);
double Vs_estimate = sqrt(Ve * Ve + 2.0 * maxA * distance);
if (Vs_estimate < fabs(Ve)) {
Vs_estimate = fabs(Ve) * 2.0;
}
int result = ruckig_plan_position(planner,
0.0,
Vs_estimate,
0.0,
distance,
Ve,
0.0,
0.0,
Vs_estimate * 2.0,
maxA,
maxJ);
if (result == 0) {
double duration = ruckig_get_duration(planner);
if (duration > 0.0) {
double actual_pos, actual_vel, actual_acc, actual_jerk;
int query_result = ruckig_at_time(planner, duration,
&actual_pos, &actual_vel,
&actual_acc, &actual_jerk);
if (query_result == 0) {
double pos_error = fabs(actual_pos - distance);
if (pos_error < 1e-6) {
double start_vel = 0.0;
if (ruckig_get_start_velocity(planner, &start_vel) == 0) {
*req_v = fmin(start_vel, v_0_to_0_peak);
return 1;
}
}
}
}
*req_v = fmin(Vs_estimate, v_0_to_0_peak);
return 1;
}
/* Planning failed: jerk constraint makes Vs_estimate infeasible.
* Return the guaranteed-feasible 0->0 peak to avoid downstream failures. */
*req_v = fmax(fabs(Ve), v_0_to_0_peak);
return 1;
}
/**
* @brief Compute the rest-to-rest S-curve peak velocity (using Ruckig planning).
*
* Given a total distance, plan a complete trajectory from (0, 0, 0) to
* (distance, 0, 0), then read the peak velocity directly from the
* profile — no iteration required.
*
* @param distence total distance (rest to rest)
* @param maxA maximum acceleration
* @param maxJ maximum jerk
* @param req_v [out] computed peak velocity
* @return 1 on success, -1 on failure
*/
int findSCurveVSpeed(double distence, double maxA, double maxJ, double* req_v){
/* Parameter validation */
if (distence <= 0 || maxA <= 0 || maxJ <= 0) {
*req_v = 0.0;
return -1;
}
/* Use the cached planner */
RuckigPlanner planner = get_cached_planner();
if (!planner) {
rtapi_print_msg(RTAPI_MSG_ERR, "findSCurveVSpeed: planner not initialized, call sp_scurve_init() first\n");
*req_v = 0.0;
return -1;
}
/* Reset planner state */
ruckig_reset(planner);
/* Plan a complete trajectory from (0, 0, 0) to (distance, 0, 0) */
int result = ruckig_plan_position(planner,
0.0, /* start position */
0.0, /* start velocity */
0.0, /* start acceleration */
distence, /* target position */
0.0, /* target velocity */
0.0, /* target acceleration */
0.0, /* min velocity (unidirectional) */
sqrt(maxA * distence) * 2.0, /* max velocity (conservative, ensures no limiting) */
maxA, /* max acceleration */
maxJ); /* max jerk */
if (result != 0) {
rtapi_print_msg(RTAPI_MSG_ERR, "findSCurveVSpeed: ruckig_plan_position failed (result=%d)\n", result);
*req_v = 0.0;
return -1;
}
/* Read the peak velocity directly from the profile */
double peak_vel = 0.0;
result = ruckig_get_peak_velocity(planner, &peak_vel);
if (result != 0) {
rtapi_print_msg(RTAPI_MSG_ERR, "findSCurveVSpeed: ruckig_get_peak_velocity failed\n");
*req_v = 0.0;
return -1;
}
*req_v = peak_vel;
return 1;
}
/**
* @brief Compute S-curve deceleration time parameters using analytical formulas
* (real-time optimized version).
*
* S-curve deceleration consists of three phases:
* T1: jerk ramp-up phase (j = -jerk), acceleration goes from 0 to -amax
* T2: constant deceleration phase (j = 0), acceleration stays at -amax
* T1: jerk ramp-down phase (j = +jerk), acceleration goes from -amax to 0
*
* ========== Velocity-time curve ==========
*
* velocity v
* ^
* V |------\
* | \
* | \____
* | \
* | \
* +---------------\----> time t
* 0 T1 T1+T2 2T1+T2
*
* ========== Analytical formula derivation ==========
*
* For S-curve deceleration:
* - T1 = amax / jerk (time for acceleration to go from 0 to -amax)
* - Phase 1 velocity loss: dv1 = 0.5 * jerk * T1^2 = 0.5 * amax^2 / jerk
* - Phase 3 velocity loss: dv3 = 0.5 * jerk * T1^2 = 0.5 * amax^2 / jerk (same as phase 1)
* - Phase 2 velocity loss: dv2 = amax * T2
* - Total velocity loss: v = dv1 + dv2 + dv3 = amax^2 / jerk + amax * T2
* - Therefore: T2 = (v - amax^2 / jerk) / amax
*
* Special case (triangular profile):
* - If v < amax^2 / jerk, the velocity is too small for a full S-curve
* (no constant deceleration phase)
* - For triangular profile: v = jerk * T1^2, so T1 = sqrt(v / jerk), T2 = 0
*
* ========== Optimization notes ==========
*
* This function uses analytical formulas for direct computation, avoiding
* frequent trajectory planning — suitable for real-time system calls.
* Compared to using Ruckig, performance is significantly better and results
* are fully consistent.
*
* @param v initial velocity (absolute value is taken)
* @param amax maximum acceleration
* @param jerk maximum jerk
* @param t1 [out, optional] jerk phase time T1
* @param t2 [out, optional] constant deceleration phase time T2
* @return total deceleration time = 2*T1 + T2
*/
double calcDecelerateTimes(double v, double amax, double jerk, double* t1, double* t2){
v = fabs(v);
/* Parameter validation */
if (v < TP_VEL_EPSILON) {
if (t1 != NULL) *t1 = 0.0;
if (t2 != NULL) *t2 = 0.0;
return 0.0;
}
if (amax <= 0.0 || jerk <= 0.0) {
if (t1 != NULL) *t1 = 0.0;
if (t2 != NULL) *t2 = 0.0;
return 0.0;
}
/* Compute T1 (jerk phase time) */
double T1 = amax / jerk;
/* Total velocity loss from phase 1 and phase 3:
* dv1 + dv3 = 2 * (0.5 * amax^2 / jerk) = amax^2 / jerk */
double v_loss_jerk_phases = amax * amax / jerk;
double T2 = 0.0;
/* Determine whether this is a full S-curve or a triangular profile */
if (v >= v_loss_jerk_phases) {
/* Full S-curve: constant deceleration phase exists */
T2 = (v - v_loss_jerk_phases) / amax;
if (T2 < 0.0) {
T2 = 0.0; /* guard against numerical error */
}
} else {
/* Triangular profile: no constant deceleration phase, recompute T1.
* v = jerk * T1^2, so T1 = sqrt(v / jerk) */
T1 = sqrt(v / jerk);
T2 = 0.0;
}
/* Output results */
if (t1 != NULL) *t1 = T1;
if (t2 != NULL) *t2 = T2;
/* Total time: 2*T1 + T2
* (T1 to ramp accel to -amax, T2 at constant -amax, T1 to ramp back to 0) */
return T1 * 2.0 + T2;
}
/**
* @brief Compute the maximum speed reachable from rest in time T using
* an S-curve profile (via Ruckig planning).
*
* Given maximum acceleration amax, maximum jerk, and time T, compute the
* maximum velocity achievable from rest using an S-curve acceleration
* profile within time T.
*
* Algorithm: use Ruckig position-control mode to plan toward a sufficiently
* large target position (ensuring the target is not reached within time T),
* then sample the velocity at time T.
*
* @param amax maximum acceleration
* @param jerk maximum jerk
* @param T time in seconds
* @return maximum velocity at time T, or 0.0 on failure
*/
double calcSCurveSpeedWithT(double amax, double jerk, double T) {
/* Parameter validation */
if (amax <= 0.0 || jerk <= 0.0 || T <= 0.0) {
return 0.0;
}
/* Use the cached planner */
RuckigPlanner planner = get_cached_planner();
if (!planner) {
rtapi_print_msg(RTAPI_MSG_ERR, "calcSCurveSpeedWithT: planner not initialized, call sp_scurve_init() first\n");
return 0.0;
}
/* Reset planner state */
ruckig_reset(planner);
/* Estimate a target position large enough that the trajectory will not
* reach it within time T. Use the trapezoidal formula as a conservative
* estimate: s = 0.5 * amax * T^2. Double it for safety. */
double target_pos = 0.5 * amax * T * T * 2.0;
/* Set a max velocity large enough to not be the limiting factor */
double max_vel = amax * T * 2.0; /* conservative estimate */
int result = ruckig_plan_position(planner,
0.0, /* start position */
0.0, /* start velocity */
0.0, /* start acceleration */
target_pos, /* target position (large enough) */
max_vel, /* target velocity (large, not limiting) */
0.0, /* target acceleration */
0.0, /* min velocity (unidirectional) */
max_vel * 2.0, /* max velocity (ensures no limiting) */
amax, /* max acceleration */
jerk); /* max jerk */
if (result != 0) {
/* Planning failed — use conservative fallback estimate.
* For an S-curve the velocity upper bound at time T is amax*T
* (trapezoidal), but the S-curve value is smaller. */
return fmin(amax * T, sqrt(amax * amax * T / jerk));
}
/* Sample velocity at time T */
double pos, vel, acc, jerk_val;
result = ruckig_at_time(planner, T, &pos, &vel, &acc, &jerk_val);
if (result != 0) {
/* Sampling failed — use conservative fallback */
return fmin(amax * T, sqrt(amax * amax * T / jerk));
}
return vel;
}
/* ================================================================
* Legacy functions kept for simple_tp.c compatibility
* ================================================================ */
/* PT = P0 + V0 * T + 0.5 * A0 * T^2 + J * T^3 / 6
* VT = V0 + A0 * T + J * T^2 / 2
* AT = A0 + J * T
*/
double nextAccel(double t, double targetV, double v, double a, double maxA,
double maxJ) {
double max_da, tiny_da, vel_err, acc_req;
max_da = delta_accel(t, maxJ);
tiny_da = max_da * t * 0.001;
vel_err = targetV - v;
if (vel_err > tiny_da){
acc_req = -max_da +
sqrt(2.0 * maxJ * vel_err + max_da * max_da);
}else if (vel_err < -tiny_da){
acc_req = max_da -
sqrt(-2.0 * maxJ * vel_err + max_da * max_da);
}else{
/* within 'tiny_da' of desired velocity, no need to move */
acc_req = 0.0;
}
/* limit acceleration request */
if (acc_req > maxA){
acc_req = maxA;
}else if (acc_req < -maxA){
acc_req = -maxA;
}
/* ramp acceleration toward request at jerk limit */
if (acc_req > a + max_da){
return a + max_da;
}else if (acc_req < a - max_da){
return a - max_da;
}else{
return acc_req;
}
}
/* PT = P0 + V0 * T + 0.5 * A0 * T^2 + J * T^3 / 6
* VT = V0 + A0 * T + J * T^2 / 2
* AT = A0 + J * T
*/
double nextSpeed(double v, double a, double t, double targetV, double maxA, double maxJ, double* req_v, double* req_a, double* req_j) {
/* Compute next acceleration */
double nextA = nextAccel(t, targetV, v, a, maxA, maxJ);
/* Compute next velocity using trapezoidal rule:
* VT - V0 = (A0 + AT) * T / 2 */
double deltaV = (a + nextA) * t / 2.0;
if ((deltaV < 0 && targetV < v && v + deltaV < targetV) ||
(0 < deltaV && v < targetV && targetV < v + deltaV)) {
/* Would overshoot target velocity — clamp */
nextA = 2.0 * (targetV - v) / t - a;
if(nextA >= maxA){
nextA = maxA;
targetV = (a + nextA) * t / 2.0;
}
v = targetV;
} else {
v += deltaV;
}
/* Compute jerk = delta accel / time */
*req_j = (nextA - a) / t;
if(*req_j > maxJ){
*req_j = maxJ;
nextA = a + maxJ * t;
} else if (*req_j < -maxJ) {
*req_j = -maxJ;
nextA = a - maxJ * t;
}
*req_a = nextA;
*req_v = v;
return v;
}
double stoppingDist(double v, double a, double maxA, double maxJ) {
/* Already stopped */
if (fabs(v) < 0.0001) return 0;
/* Handle negative velocity */
if (v < 0) {
v = -v;
a = -a;
}
double d = 0;
/* Compute distance and velocity change to bring acceleration to 0 */
if (0 < a) {
double t = a / maxJ;
d += sc_distance(t, v, a, -maxJ);
v += delta_velocity(t, a, -maxJ);
a = 0;
}
/* Compute maximum deceleration.
*
* At target velocity, both velocity and acceleration are 0.
* VT = 0 + 0*T1 + J*T1^2/2, and because Amax = J*T1:
* VT = Amax^2 / (2*J)
* From the other side: VT = v + (a + Amax)*T2/2
* Combining: Amax^2 = v*J + 0.5*a*a
*/
double maxDeccel = -sqrt(v * maxJ + 0.5 * a * a);
if (maxDeccel < -maxA) maxDeccel = -maxA;
/* Compute distance and velocity change to reach max deceleration */
if (maxDeccel < a) {
double t = (a - maxDeccel) / maxJ;
d += sc_distance(t, v, a, -maxJ);
v += delta_velocity(t, a, -maxJ);
a = maxDeccel;
}
/* Velocity remaining when entering final jerk phase:
* VT = Amax^2 / (2*J) */
double deltaV = 0.5 * a * a / maxJ;
/* Constant deceleration phase (if needed) */
if (deltaV < v) {
double t = (v - deltaV) / -a;
d += sc_distance(t, v, a, 0);
v += delta_velocity(t, a, 0);
}
/* Distance to zero velocity (final jerk phase) */
d += sc_distance(-a / maxJ, v, a, maxJ);
return d;
}
/* S-curve displacement: P = v*t + (1/2)*a*t^2 + (1/6)*j*t^3 */
double sc_distance(double t, double v, double a, double j) {
return t * (v + t * (0.5 * a + 1.0 / 6.0 * j * t));
}
/* Velocity change: dV = a*t + (1/2)*j*t^2 */
double delta_velocity(double t, double a, double j) {
return t * (a + 0.5 * j * t);
}
/* Acceleration change: dA = j*t */
double delta_accel(double t, double j) {return j * t;}

View File

@@ -0,0 +1,60 @@
/********************************************************************
* Description: sp_scurve.h
* Discriminate-based trajectory planning
*
* Derived from a work by Yang Yang
*
* Author: Yang Yang
* Contact: mika-net@outlook.com
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2004 All rights reserved.
*
* Last change:
********************************************************************/
#ifndef SP_SCURVE_H
#define SP_SCURVE_H
#include <rtapi_math.h>
#include "../motion/simple_tp.h"
/**
* Initialize the S-curve planner (call at program entry).
*
* @param cycle_time cycle time in seconds
* @return 0 on success, -1 on failure
*/
int sp_scurve_init(double cycle_time);
/**
* Clean up the S-curve planner (call at program exit).
*/
void sp_scurve_cleanup(void);
/* Legacy functions kept for simple_tp.c compatibility */
double nextAccel(double t, double targetV, double v, double a, double maxA, double maxJ);
double sc_distance(double t, double v, double a, double j);
double delta_velocity(double t, double a, double j);
double delta_accel(double t, double j);
double nextSpeed(double v, double a, double t, double targetV, double maxA, double maxJ, double* req_v, double* req_a, double* req_j);
double stoppingDist(double v, double a, double maxA, double maxJ);
int findSCurveVSpeed(double distence,/* double maxV, */double maxA, double maxJ, double *req_v);
int findSCurveVSpeedWithEndSpeed(double distence, double Ve, double maxA, double maxJ, double* req_v);
int findSCurveMaxStartSpeed(double distance, double Ve, double maxA, double maxJ, double* req_v);
double calcDecelerateTimes(double v, double amax, double jerk, double* t1, double* t2);
double calcSCurveSpeedWithT(double amax, double jerk, double T);
/**
* tpCalculateSCurveAccel return value definitions
*
* TP_SCURVE_ACCEL_ERROR - calculation failed (maxjerk invalid or less than/equal to 1)
* TP_SCURVE_ACCEL_ACCEL - acceleration or normal state (no deceleration needed)
* TP_SCURVE_ACCEL_DECEL - deceleration needed
*/
#define TP_SCURVE_ACCEL_ERROR -5
#define TP_SCURVE_ACCEL_ACCEL 0
#define TP_SCURVE_ACCEL_DECEL 1
#endif

View File

@@ -0,0 +1,202 @@
/********************************************************************
* Description: spherical_arc.c
*
* A simple spherical linear interpolation library and related functions.
*
* Author: Robert W. Ellenberg
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2014 All rights reserved.
*
********************************************************************/
#include <rtapi_math.h>
#include "spherical_arc.h"
#include "tp_types.h"
#include "tp_debug.h"
int arcInitFromPoints(SphericalArc * const arc, PmCartesian const * const start,
PmCartesian const * const end,
PmCartesian const * const center)
{
#ifdef ARC_PEDANTIC
if (!P0 || !P1 || !center)
return TP_ERR_MISSING_INPUT;
if (!arc)
return TP_ERR_MISSING_OUTPUT;
#endif
// Store the start, end, and center
arc->start = *start;
arc->end = *end;
arc->center = *center;
pmCartCartSub(start, center, &arc->rStart);
pmCartCartSub(end, center, &arc->rEnd);
// Find the radii at start and end. These are identical for a perfect spherical arc
double radius0, radius1;
pmCartMag(&arc->rStart, &radius0);
pmCartMag(&arc->rEnd, &radius1);
tp_debug_print("radii are %g and %g\n",
radius0,
radius1);
if (radius0 < ARC_MIN_RADIUS || radius1 < ARC_MIN_RADIUS) {
tp_debug_print("radius below min radius %f, aborting arc\n",
ARC_MIN_RADIUS);
return TP_ERR_RADIUS_TOO_SMALL;
}
// Choose initial radius as nominal radius
arc->radius = radius0;
// Get unit vectors from center to start and center to end
PmCartesian u0, u1;
pmCartScalMult(&arc->rStart, 1.0 / radius0, &u0);
pmCartScalMult(&arc->rEnd, 1.0 / radius1, &u1);
// Find arc angle
double dot;
pmCartCartDot(&u0, &u1, &dot);
arc->angle = acos(dot);
tp_debug_print("spherical arc angle = %f\n", arc->angle);
// Store spiral factor as radial difference. Archimedean spiral coef. a = spiral / angle
arc->spiral = (radius1 - radius0 );
if (arc->angle < ARC_MIN_ANGLE) {
tp_debug_print("angle %f below min angle %f, aborting arc\n",
arc->angle,
ARC_MIN_ANGLE);
return TP_ERR_GEOM;
}
// Store sin of arc angle since it is reused many times for SLERP
arc->Sangle = sin(arc->angle);
return TP_ERR_OK;
}
int arcPoint(SphericalArc const * const arc, double progress, PmCartesian * const out)
{
//TODO pedantic
//Convert progress to actual progress around the arc
double net_progress = progress - arc->line_length;
if (net_progress <= 0.0 && arc->line_length > 0) {
tc_debug_print("net_progress = %f, line_length = %f\n", net_progress, arc->line_length);
//Get position on line (not actually an angle in this case)
pmCartScalMult(&arc->uTan, net_progress, out);
pmCartCartAdd(out, &arc->start, out);
} else {
double angle_in = net_progress / arc->radius;
tc_debug_print("angle_in = %f, angle_total = %f\n", angle_in, arc->angle);
double scale0 = sin(arc->angle - angle_in) / arc->Sangle;
double scale1 = sin(angle_in) / arc->Sangle;
PmCartesian interp0,interp1;
pmCartScalMult(&arc->rStart, scale0, &interp0);
pmCartScalMult(&arc->rEnd, scale1, &interp1);
pmCartCartAdd(&interp0, &interp1, out);
pmCartCartAdd(&arc->center, out, out);
}
return TP_ERR_OK;
}
int arcLength(SphericalArc const * const arc, double * const length)
{
*length = arc->radius * arc->angle + arc->line_length;
tp_debug_print("arc length = %g\n", *length);
return TP_ERR_OK;
}
int arcFromLines(SphericalArc * const arc, PmCartLine const * const line1,
PmCartLine const * const line2, double radius,
double blend_dist, double center_dist, PmCartesian * const start, PmCartesian * const end, int consume) {
(void)radius;
PmCartesian center, normal, binormal;
// Pointer to middle point of line segment pair
PmCartesian const * const middle = &line1->end;
//TODO assert line1 end = line2 start?
//Calculate the normal direction of the arc from the difference
//between the unit vectors
pmCartCartSub(&line2->uVec, &line1->uVec, &normal);
pmCartUnitEq(&normal);
pmCartScalMultEq(&normal, center_dist);
pmCartCartAdd(middle, &normal, &center);
//Calculate the binormal (vector perpendicular to the plane of the
//arc)
pmCartCartCross(&line1->uVec, &line2->uVec, &binormal);
pmCartUnitEq(&binormal);
// Start point is blend_dist away from middle point in the
// negative direction of line1
pmCartScalMult(&line1->uVec, -blend_dist, start);
pmCartCartAdd(start, middle, start);
// End point is blend_dist away from middle point in the positive
// direction of line2
pmCartScalMult(&line2->uVec, blend_dist, end);
pmCartCartAddEq(end, middle);
//Handle line portion of line-arc
arc->uTan = line1->uVec;
if (consume) {
arc->line_length = line1->tmag - blend_dist;
} else {
arc->line_length = 0;
}
return arcInitFromPoints(arc, start, end, &center);
}
int arcConvexTest(PmCartesian const * const center,
PmCartesian const * const P, PmCartesian const * const uVec, int reverse_dir)
{
//Check if an arc-line intersection is concave or convex
double dot;
PmCartesian diff;
pmCartCartSub(P, center, &diff);
pmCartCartDot(&diff, uVec, &dot);
tp_debug_print("convex test: dot = %f, reverse_dir = %d\n", dot, reverse_dir);
int convex = (reverse_dir != 0) ^ (dot < 0);
return convex;
}
int arcTangent(SphericalArc const * const arc, PmCartesian * const tan, int at_end)
{
PmCartesian r_perp;
PmCartesian r_tan;
if (at_end) {
r_perp = arc->rEnd;
} else {
r_perp = arc->rStart;
}
pmCartCartCross(&arc->binormal, &r_perp, &r_tan);
//Get spiral component
double dr = arc->spiral / arc->angle;
//Get perpendicular component due to spiral
PmCartesian d_perp;
pmCartUnit(&r_perp, &d_perp);
pmCartScalMultEq(&d_perp, dr);
//TODO error checks
pmCartCartAdd(&d_perp, &r_tan, tan);
pmCartUnitEq(tan);
return TP_ERR_OK;
}

View File

@@ -0,0 +1,67 @@
/********************************************************************
* Description: spherical_arc.h
*
* A simple spherical linear interpolation library and related functions.
*
* Author: Robert W. Ellenberg
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2014 All rights reserved.
*
********************************************************************/
#ifndef SPHERICAL_ARC_H
#define SPHERICAL_ARC_H
#include <posemath.h>
#define ARC_POS_EPSILON 1e-12
#define ARC_MIN_RADIUS 1e-12
#define ARC_MIN_ANGLE 1e-6
//FIXME relate this to cornering acceleration?
#define ARC_ABS_ERR 5e-4
#define ARC_REL_ERR 5e-4
typedef struct {
// Three defining points for the arc
PmCartesian start;
PmCartesian end;
PmCartesian center;
// Relative vectors from center to start and center to end
// These are cached here since they'll be reused during SLERP
PmCartesian rStart;
PmCartesian rEnd;
PmCartesian uTan; /* Tangent vector at start of arc (copied from
prev. tangent line)*/
PmCartesian binormal;
double radius;
double spiral;
// Angle that the arc encloses
double angle;
double Sangle;
double line_length;
} SphericalArc;
int arcInitFromPoints(SphericalArc * const arc, PmCartesian const * const start,
PmCartesian const * const end, PmCartesian const * const center);
int arcInitFromVectors(SphericalArc * const arc, PmCartesian const * const vec0,
PmCartesian const * const vec1,
PmCartesian const * const center);
int arcPoint(SphericalArc const * const arc, double angle_in, PmCartesian * const out);
int arcNormalizedSlerp(SphericalArc const * const arc, double t, PmCartesian * const out);
int arcLength(SphericalArc const * const arc, double * const length);
int arcFromLines(SphericalArc * const arc, PmCartLine const * const line1,
PmCartLine const * const line2, double radius,
double blend_dist, double center_dist, PmCartesian * const start, PmCartesian * const end, int consume);
int arcConvexTest(PmCartesian const * const center,
PmCartesian const * const P, PmCartesian const * const uVec, int reverse_dir);
int arcTangent(SphericalArc const * const arc, PmCartesian * const tan, int at_end);
#endif

1109
wasm-port/vendor/linuxcnc/src/emc/tp/tc.c vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,128 @@
/********************************************************************
* Description: tc.h
* Discriminate-based trajectory planning
*
* Derived from a work by Fred Proctor & Will Shackleford
*
* Author:
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2004 All rights reserved.
*
* Last change:
********************************************************************/
#ifndef TC_H
#define TC_H
#include <posemath.h>
#include <emcpos.h>
#include <emcmotcfg.h>
#include "spherical_arc.h"
#include "tc_types.h"
#include "tp_types.h"
double tcGetMaxTargetVel(TC_STRUCT const * const tc,
double max_scale);
double tcGetOverallMaxAccel(TC_STRUCT const * tc);
double tcGetTangentialMaxAccel(TC_STRUCT const * const tc);
int tcSetKinkProperties(TC_STRUCT *prev_tc, TC_STRUCT *tc, double kink_vel, double accel_reduction);
int tcInitKinkProperties(TC_STRUCT *tc);
int tcRemoveKinkProperties(TC_STRUCT *prev_tc, TC_STRUCT *tc);
int tcGetEndpoint(TC_STRUCT const * const tc, EmcPose * const out);
int tcGetStartpoint(TC_STRUCT const * const tc, EmcPose * const out);
int tcGetPos(TC_STRUCT const * const tc, EmcPose * const out);
int tcGetPosReal(TC_STRUCT const * const tc, int of_endpoint, EmcPose * const out);
int tcGetEndAccelUnitVector(TC_STRUCT const * const tc, PmCartesian * const out);
int tcGetStartAccelUnitVector(TC_STRUCT const * const tc, PmCartesian * const out);
int tcGetEndTangentUnitVector(TC_STRUCT const * const tc, PmCartesian * const out);
int tcGetStartTangentUnitVector(TC_STRUCT const * const tc, PmCartesian * const out);
int tcGetCurrentTangentUnitVector(TC_STRUCT const * const tc, PmCartesian * const out);
double tcGetDistanceToGo(TC_STRUCT const * const tc, int direction);
double tcGetTarget(TC_STRUCT const * const tc, int direction);
int tcGetIntersectionPoint(TC_STRUCT const * const prev_tc,
TC_STRUCT const * const tc, PmCartesian * const point);
int tcCanConsume(TC_STRUCT const * const tc);
int tcSetTermCond(TC_STRUCT * prev_tc, TC_STRUCT * tc, int term_cond);
int tcConnectBlendArc(TC_STRUCT * const prev_tc, TC_STRUCT * const tc,
PmCartesian const * const circ_start,
PmCartesian const * const circ_end);
int tcIsBlending(TC_STRUCT * const tc);
int tcFindBlendTolerance(TC_STRUCT const * const prev_tc,
TC_STRUCT const * const tc, double * const T_blend, double * const nominal_tolerance);
int pmCircleTangentVector(PmCircle const * const circle,
double angle_in, PmCartesian * const out);
int tcFlagEarlyStop(TC_STRUCT * const tc,
TC_STRUCT * const nexttc);
double pmLine9Target(PmLine9 * const line9);
int pmLine9Init(PmLine9 * const line9,
EmcPose const * const start,
EmcPose const * const end);
double pmCircle9Target(PmCircle9 const * const circ9);
int pmCircle9Init(PmCircle9 * const circ9,
EmcPose const * const start,
EmcPose const * const end,
PmCartesian const * const center,
PmCartesian const * const normal,
int turn);
int pmRigidTapInit(PmRigidTap * const tap,
EmcPose const * const start,
EmcPose const * const end,
double reversal_scale);
double pmRigidTapTarget(PmRigidTap * const tap, double uu_per_rev);
int tcInit(TC_STRUCT * const tc,
int motion_type,
int canon_motion_type,
double cycle_time,
unsigned char enables,
char atspeed);
int tcSetupFromTP(TC_STRUCT * const tc, TP_STRUCT const * const tp);
int tcSetupMotion(TC_STRUCT * const tc,
double vel,
double ini_maxvel,
double acc,
double ini_maxjerk);
int tcSetupState(TC_STRUCT * const tc, TP_STRUCT const * const tp);
int tcUpdateArcLimits(TC_STRUCT * tc);
int tcFinalizeLength(TC_STRUCT * const tc);
int tcClampVelocityByLength(TC_STRUCT * const tc);
int tcPureRotaryCheck(TC_STRUCT const * const tc);
int tcSetCircleXYZ(TC_STRUCT * const tc, PmCircle const * const circ);
int tcClearFlags(TC_STRUCT * const tc);
/**
* Clean up Ruckig planner resources in a TC_STRUCT.
* Called when the trajectory segment is removed or reset.
*/
void tcCleanupRuckig(TC_STRUCT * const tc);
#endif /* TC_H */

View File

@@ -0,0 +1,215 @@
/********************************************************************
* Description: tc.h
* Discriminate-based trajectory planning
*
* Derived from a work by Fred Proctor & Will Shackleford
*
* Author:
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2004 All rights reserved.
********************************************************************/
#ifndef TC_TYPES_H
#define TC_TYPES_H
#include <posemath.h>
#include <emcpos.h>
#include <emcmotcfg.h>
#include "spherical_arc.h"
#include "../motion/state_tag.h"
#define BLEND_DIST_FRACTION 0.5
/* values for endFlag */
typedef enum {
TC_TERM_COND_STOP = 0,
TC_TERM_COND_EXACT = 1,
TC_TERM_COND_PARABOLIC = 2,
TC_TERM_COND_TANGENT = 3
} tc_term_cond_t;
typedef enum {
TC_LINEAR = 1,
TC_CIRCULAR = 2,
TC_RIGIDTAP = 3,
TC_SPHERICAL = 4
} tc_motion_type_t;
typedef enum {
TC_SYNC_NONE = 0,
TC_SYNC_VELOCITY,
TC_SYNC_POSITION
} tc_spindle_sync_t;
typedef enum {
TC_DIR_FORWARD = 0,
TC_DIR_REVERSE
} tc_direction_t;
#define TC_GET_PROGRESS 0
#define TC_GET_STARTPOINT 1
#define TC_GET_ENDPOINT 2
#define TC_OPTIM_UNTOUCHED 0
#define TC_OPTIM_AT_MAX 1
#define TC_ACCEL_TRAPZ 0
#define TC_ACCEL_RAMP 1
/**
* Spiral arc length approximation by quadratic fit.
*/
typedef struct {
double b0; /* 2nd order coefficient */
double b1; /* 1st order coefficient */
double total_planar_length; /* total arc length in plane */
int spiral_in; /* flag indicating spiral is inward,
rather than outward */
} SpiralArcLengthFit;
/* structure for individual trajectory elements */
typedef struct {
PmCartLine xyz;
PmCartLine abc;
PmCartLine uvw;
} PmLine9;
typedef struct {
PmCircle xyz;
PmCartLine abc;
PmCartLine uvw;
SpiralArcLengthFit fit;
} PmCircle9;
typedef struct {
SphericalArc xyz;
PmCartesian abc;
PmCartesian uvw;
} Arc9;
typedef enum {
RIGIDTAP_START,
TAPPING, REVERSING, RETRACTION, FINAL_REVERSAL, FINAL_PLACEMENT
} RIGIDTAP_STATE;
typedef unsigned long long iomask_t; // 64 bits on both x86 and x86_64
typedef struct {
char anychanged;
iomask_t dio_mask;
iomask_t aio_mask;
signed char dios[EMCMOT_MAX_DIO];
double aios[EMCMOT_MAX_AIO];
} syncdio_t;
typedef struct {
PmCartLine xyz; // original, but elongated, move down
PmCartLine aux_xyz; // this will be generated on the fly, for the other
// two moves: retraction, final placement
PmCartesian abc;
PmCartesian uvw;
double reversal_target;
double reversal_scale;
double spindlerevs_at_reversal;
RIGIDTAP_STATE state;
} PmRigidTap;
typedef struct {
double cycle_time;
//Position stuff
double target; // actual segment length
double progress; // where are we in the segment? 0..target
double nominal_length;
//Velocity
double reqvel; // vel requested by F word, calc'd by task
double target_vel; // velocity to actually track, limited by other factors
double maxvel; // max possible vel (feed override stops here)
double currentvel; // keep track of current step (vel * cycle_time)
double last_move_length;// last move length
double finalvel; // velocity to aim for at end of segment
double term_vel; // actual velocity at termination of segment
double kink_vel; // Temporary way to store our calculation of maximum velocity we can handle if this segment is declared tangent with the next
double kink_accel_reduce_prev; // How much to reduce the allowed tangential acceleration to account for the extra acceleration at an approximate tangent intersection.
double kink_accel_reduce; // How much to reduce the allowed tangential acceleration to account for the extra acceleration at an approximate tangent intersection.
double factor;
double targetvel;
double vt;
//Jerk
double maxjerk; // max jerk for S-curve motion
double blend_maxjerk; // max jerk during blend (set by look-ahead)
double currentjerk; // current jerk for S-curve planning
double currentacc; // current acceleration for S-curve planning
double lastacc;
//Acceleration
double maxaccel; // accel calc'd by task
double acc_ratio_tan;// ratio between normal and tangential accel
int id; // segment's serial number
struct state_tag_t tag; // state tag corresponding to running motion
union { // describes the segment's start and end positions
PmLine9 line;
PmCircle9 circle;
PmRigidTap rigidtap;
Arc9 arc;
} coords;
int motion_type; // TC_LINEAR (coords.line) or
// TC_CIRCULAR (coords.circle) or
// TC_RIGIDTAP (coords.rigidtap)
int active; // this motion is being executed
int canon_motion_type; // this motion is due to which canon function?
int term_cond; // gcode requests continuous feed at the end of
// this segment (g64 mode)
int blending_next; // segment is being blended into following segment
double blend_vel; // velocity below which we should start blending
double tolerance; // during the blend at the end of this move,
// stay within this distance from the path.
int synchronized; // spindle sync state
double uu_per_rev; // for sync, user units per rev (e.g. 0.0625 for 16tpi)
double vel_at_blend_start;
int sync_accel; // we're accelerating up to sync with the spindle
unsigned char enables; // Feed scale, etc, enable bits for this move
int atspeed; // wait for the spindle to be at-speed before starting this move
syncdio_t syncdio; // synched DIO's for this move. what to turn on/off
int indexer_jnum; // which joint to unlock (for a locking indexer) to make this move, -1 for none
int optimization_state; // At peak velocity during blends)
int on_final_decel;
int blend_prev;
int accel_mode;
int splitting; // the segment is less than 1 cycle time
// away from the end.
int remove; // Flag to remove the segment from the queue
int active_depth; /* Active depth (i.e. how many segments
* after this will it take to slow to zero
* speed) */
int finalized;
// Temporary status flags (reset each cycle)
int is_blending;
// Ruckig trajectory planner support
void *ruckig_planner; // Ruckig planner handle (opaque pointer)
double ruckig_trajectory_time; // current trajectory time (seconds from trajectory start)
int ruckig_planned; // whether Ruckig planning completed (1=planned, 0=not)
// Store last planning parameters for detecting parameter changes
double ruckig_last_maxaccel; // max acceleration used in last planning
double ruckig_last_maxjerk; // max jerk used in last planning
double ruckig_last_target_vel; // target velocity used in last planning
double ruckig_last_final_vel; // final velocity used in last planning
double ruckig_last_target_pos; // target position used in last planning
int ruckig_last_use_velocity_control; // control mode used in last planning (1=velocity, 0=position)
double ruckig_last_req_pos; // last req_pos value from Ruckig (for velocity control incremental calc)
double ruckig_last_feed_override; // feed override value at last planning (for debug and change detection)
} TC_STRUCT;
#endif /* TC_TYPES_H */

View File

@@ -0,0 +1,355 @@
/*!
********************************************************************
* Description: tcq.c
*\brief queue handling functions for trajectory planner
* These following functions implement the motion queue that
* is fed by tpAddLine/tpAddCircle and consumed by tpRunCycle.
* They have been fully working for a long time and a wise programmer
* won't mess with them.
*
*\author Derived from a work by Fred Proctor & Will Shackleford
*\author rewritten by Chris Radek
*
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2004 All rights reserved.
*
********************************************************************/
#include <stddef.h>
#include "tcq.h"
/** Return 0 if queue is valid, -1 if not */
static inline int tcqCheck(TC_QUEUE_STRUCT const * const tcq)
{
if ((0 == tcq) || (0 == tcq->queue))
{
return -1;
}
return 0;
}
/*! tcqCreate() function
*
* \brief Creates a new queue for TC elements.
*
* This function creates a new queue for TC elements.
* It gets called by tpCreate()
*
* @param tcq pointer to the new TC_QUEUE_STRUCT
* @param _size size of the new queue
* @param tcSpace holds the space allocated for the new queue, allocated in motion.c
*
* @return int returns success or failure
*/
int tcqCreate(TC_QUEUE_STRUCT * const tcq, int _size, TC_STRUCT * const tcSpace)
{
if (!tcq || !tcSpace || _size < 1) {
return -1;
}
tcq->queue = tcSpace;
tcq->size = _size;
tcqInit(tcq);
return 0;
}
/*! tcqDelete() function
*
* \brief Deletes a queue holding TC elements.
*
* This function creates deletes a queue. It doesn't free the space
* only throws the pointer away.
* It gets called by tpDelete()
* \todo FIXME, it seems tpDelete() is gone, and this function isn't used.
*
* @param tcq pointer to the TC_QUEUE_STRUCT
*
* @return int returns success
*/
int tcqDelete(TC_QUEUE_STRUCT * const tcq)
{
if (!tcqCheck(tcq)) {
/* free(tcq->queue); */
tcq->queue = 0;
}
return 0;
}
/*! tcqInit() function
*
* \brief Initializes a queue with TC elements.
*
* This function initializes a queue with TC elements.
* It gets called by tpClear() and
* by tpRunCycle() when we are aborting
*
* @param tcq pointer to the TC_QUEUE_STRUCT
*
* @return int returns success or failure (if no tcq found)
*/
int tcqInit(TC_QUEUE_STRUCT * const tcq)
{
if (tcqCheck(tcq)) return -1;
tcq->_len = 0;
tcq->start = tcq->end = 0;
tcq->rend = 0;
tcq->_rlen = 0;
tcq->allFull = 0;
return 0;
}
/*! tcqPut() function
*
* \brief puts a TC element at the end of the queue
*
* This function adds a tc element at the end of the queue.
* It gets called by tpAddLine() and tpAddCircle()
*
* @param tcq pointer to the new TC_QUEUE_STRUCT
* @param tc the new TC element to be added
*
* @return int returns success or failure
*/
int tcqPut(TC_QUEUE_STRUCT * const tcq, TC_STRUCT const * const tc)
{
/* check for initialized */
if (tcqCheck(tcq)) return -1;
/* check for allFull, so we don't overflow the queue */
if (tcq->allFull) {
return -1;
}
/* add it */
tcq->queue[tcq->end] = *tc;
tcq->_len++;
/* update end ptr, modulo size of queue */
tcq->end = (tcq->end + 1) % tcq->size;
/* set allFull flag if we're really full */
if (tcq->end == tcq->start) {
tcq->allFull = 1;
}
return 0;
}
/*! tcqPopBack() function
*
* \brief removes the newest TC element (converse of tcqRemove)
*
* @param tcq pointer to the TC_QUEUE_STRUCT
*
* @return int returns success or failure
*/
int tcqPopBack(TC_QUEUE_STRUCT * const tcq)
{
/* check for initialized */
if (tcqCheck(tcq)) return -1;
/* Too short to pop! */
if (tcq->_len < 1) {
return -1;
}
int n = tcq->end - 1 + tcq->size;
tcq->end = n % tcq->size;
tcq->_len--;
return 0;
}
#define TCQ_REVERSE_MARGIN 200
int tcqPop(TC_QUEUE_STRUCT * const tcq)
{
if (tcqCheck(tcq)) {
return -1;
}
if (tcq->_len < 1 && !tcq->allFull) {
return -1;
}
/* update start ptr and reset allFull flag and len */
tcq->start = (tcq->start + 1) % tcq->size;
tcq->allFull = 0;
tcq->_len--;
if (tcq->_rlen < TCQ_REVERSE_MARGIN) {
//If we're not overwriting the history yet, then we have another segment added to the reverse history
tcq->_rlen++;
} else {
//If we're run out of spare reverse history, then advance rend
tcq->rend = (tcq->rend + 1) % tcq->size;
}
return 0;
}
/*! tcqRemove() function
*
* \brief removes n items from the queue
*
* This function removes the first n items from the queue,
* after checking that they can be removed
* (queue initialized, queue not empty, enough elements in it)
* Function gets called by tpRunCycle() with n=1
* \todo FIXME: Optimize the code to remove only 1 element, might speed it up
*
* @param tcq pointer to the new TC_QUEUE_STRUCT
* @param n the number of TC elements to be removed
*
* @return int returns success or failure
*/
int tcqRemove(TC_QUEUE_STRUCT * const tcq, int n)
{
if (n <= 0) {
return 0; /* okay to remove 0 or fewer */
}
if (tcqCheck(tcq) || ((tcq->start == tcq->end) && !tcq->allFull) ||
(n > tcq->_len)) { /* too many requested */
return -1;
}
/* update start ptr and reset allFull flag and len */
tcq->start = (tcq->start + n) % tcq->size;
tcq->allFull = 0;
tcq->_len -= n;
return 0;
}
/**
* Step backward into the reverse history.
*/
int tcqBackStep(TC_QUEUE_STRUCT * const tcq)
{
if (tcqCheck(tcq)) {
return -1;
}
// start == end means that queue is empty
if ( tcq->start == tcq->rend) {
return -1;
}
/* update start ptr and reset allFull flag and len */
tcq->start = (tcq->start - 1 + tcq->size) % tcq->size;
tcq->_len++;
tcq->_rlen--;
return 0;
}
/*! tcqLen() function
*
* \brief returns the number of elements in the queue
*
* Function gets called by tpSetVScale(), tpAddLine(), tpAddCircle()
*
* @param tcq pointer to the TC_QUEUE_STRUCT
*
* @return int returns number of elements
*/
int tcqLen(TC_QUEUE_STRUCT const * const tcq)
{
if (tcqCheck(tcq)) return -1;
return tcq->_len;
}
/*! tcqItem() function
*
* \brief gets the n-th TC element in the queue, without removing it
*
* Function gets called by tpSetVScale(), tpRunCycle(), tpIsPaused()
*
* @param tcq pointer to the TC_QUEUE_STRUCT
*
* @return TC_STRUCT returns the TC elements
*/
TC_STRUCT * tcqItem(TC_QUEUE_STRUCT const * const tcq, int n)
{
if (tcqCheck(tcq) || (n < 0) || (n >= tcq->_len)) return NULL;
return &(tcq->queue[(tcq->start + n) % tcq->size]);
}
/*!
* \def TC_QUEUE_MARGIN
* sets up a margin at the end of the queue, to reduce effects of race conditions
*/
#define TC_QUEUE_MARGIN (TCQ_REVERSE_MARGIN+20)
/*! tcqFull() function
*
* \brief get the full status of the queue
* Function returns full if the count is closer to the end of the queue than TC_QUEUE_MARGIN
*
* Function called by update_status() in control.c
*
* @param tcq pointer to the TC_QUEUE_STRUCT
*
* @return int returns status (0==not full, 1==full)
*/
int tcqFull(TC_QUEUE_STRUCT const * const tcq)
{
if (tcqCheck(tcq)) {
return 1; /* null queue is full, for safety */
}
/* call the queue full if the length is into the margin, so reduce the
effect of a race condition where the appending process may not see the
full status immediately and send another motion */
if (tcq->size <= TC_QUEUE_MARGIN) {
/* no margin available, so full means really all full */
return tcq->allFull;
}
if (tcq->_len >= tcq->size - TC_QUEUE_MARGIN) {
/* we're into the margin, so call it full */
return 1;
}
/* we're not into the margin */
return 0;
}
/*! tcqLast() function
*
* \brief gets the last TC element in the queue, without removing it
*
*
* @param tcq pointer to the TC_QUEUE_STRUCT
*
* @return TC_STRUCT returns the TC element
*/
TC_STRUCT *tcqLast(TC_QUEUE_STRUCT const * const tcq)
{
if (tcqCheck(tcq)) {
return NULL;
}
if (tcq->_len == 0) {
return NULL;
}
//Fix for negative modulus error
int n = tcq->end-1 + tcq->size;
return &(tcq->queue[n % tcq->size]);
}

View File

@@ -0,0 +1,75 @@
/********************************************************************
* Description: tcq.c
*\brief queue handling functions for trajectory planner
* These following functions implement the motion queue that
* is fed by tpAddLine/tpAddCircle and consumed by tpRunCycle.
* They have been fully working for a long time and a wise programmer
* won't mess with them.
*
* Derived from a work by Fred Proctor & Will Shackleford
*
* Author:
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2004 All rights reserved.
*
* Last change:
********************************************************************/
/* queue of TC_STRUCT elements*/
#ifndef TCQ_H
#define TCQ_H
#include "tc_types.h"
typedef struct {
TC_STRUCT *queue; /* ptr to the tcs */
int size; /* size of queue */
int _len; /* number of tcs now in queue */
int _rlen; /* number of tcs now in reverse history */
int start, end; /* indices to next to get, next to put */
int rend;
int allFull; /* flag meaning it's actually full */
} TC_QUEUE_STRUCT;
/* TC_QUEUE_STRUCT functions */
/* create queue of _size */
extern int tcqCreate(TC_QUEUE_STRUCT * const tcq, int _size,
TC_STRUCT * const tcSpace);
/* free up queue */
extern int tcqDelete(TC_QUEUE_STRUCT * const tcq);
/* reset queue to empty */
extern int tcqInit(TC_QUEUE_STRUCT * const tcq);
/* put tc on end */
extern int tcqPut(TC_QUEUE_STRUCT * const tcq, TC_STRUCT const * const tc);
/* remove a single tc from the back of the queue */
extern int tcqPopBack(TC_QUEUE_STRUCT * const tcq);
extern int tcqPop(TC_QUEUE_STRUCT * const tcq);
/* remove n tcs from front */
extern int tcqRemove(TC_QUEUE_STRUCT * const tcq, int n);
extern int tcqBackStep(TC_QUEUE_STRUCT * const tcq);
/* how many tcs on queue */
extern int tcqLen(TC_QUEUE_STRUCT const * const tcq);
/* look at nth item, first is 0 */
extern TC_STRUCT * tcqItem(TC_QUEUE_STRUCT const * const tcq, int n);
/**
* Get the "end" of the queue, the most recently added item.
*/
extern TC_STRUCT * tcqLast(TC_QUEUE_STRUCT const * const tcq);
/* get full status */
extern int tcqFull(TC_QUEUE_STRUCT const * const tcq);
#endif

4388
wasm-port/vendor/linuxcnc/src/emc/tp/tp.c vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
/********************************************************************
* Description: tp.h
* Trajectory planner based on TC elements
*
* Derived from a work by Fred Proctor & Will Shackleford
*
* Author:
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2004 All rights reserved.
*
********************************************************************/
#ifndef TP_H
#define TP_H
#include <posemath.h>
#include "tc_types.h"
#include "tp_types.h"
#include "tcq.h"
// functions not used by motmod:
int tpAddCurrentPos(TP_STRUCT * const tp, EmcPose const * const disp);
int tpSetCurrentPos(TP_STRUCT * const tp, EmcPose const * const pos);
void tpToggleDIOs(TC_STRUCT * const tc); //gets called when a new tc is
//taken from the queue. it checks
//and toggles all needed DIO's
int tpIsMoving(TP_STRUCT const * const tp);
int tpInit(TP_STRUCT * const tp);
// functions used by motmod:
int tpCreate(TP_STRUCT * const tp, int _queueSize,int id);
int tpClear(TP_STRUCT * const tp);
int tpClearDIOs(TP_STRUCT * const tp);
int tpSetCycleTime(TP_STRUCT * tp, double secs);
int tpSetVmax(TP_STRUCT * tp, double vmax, double ini_maxvel);
int tpSetVlimit(TP_STRUCT * tp, double limit);
int tpSetAmax(TP_STRUCT * tp, double amax);
int tpSetId(TP_STRUCT * tp, int id);
int tpGetExecId(TP_STRUCT * tp);
struct state_tag_t tpGetExecTag(TP_STRUCT * const tp);
int tpSetTermCond(TP_STRUCT * tp, int cond, double tolerance);
int tpSetPos(TP_STRUCT * tp, EmcPose const * const pos);
int tpRunCycle(TP_STRUCT * tp, long period);
int tpPause(TP_STRUCT * tp);
int tpResume(TP_STRUCT * tp);
int tpAbort(TP_STRUCT * tp);
int tpAddRigidTap(TP_STRUCT * const tp,
EmcPose end,
double vel,
double ini_maxvel,
double acc,
double ini_maxjerk,
unsigned char enables,
double scale,
struct state_tag_t tag);
int tpAddLine(TP_STRUCT * const tp, EmcPose end, int canon_motion_type,
double vel, double ini_maxvel, double acc, double ini_maxjerk, unsigned char enables,
char atspeed, int indexrotary, struct state_tag_t tag);
int tpAddCircle(TP_STRUCT * const tp, EmcPose end, PmCartesian center,
PmCartesian normal, int turn, int canon_motion_type, double vel,
double ini_maxvel, double acc, double ini_maxjerk, unsigned char enables,
char atspeed, struct state_tag_t tag);
int tpGetPos(TP_STRUCT const * const tp, EmcPose * const pos);
int tpIsDone(TP_STRUCT * const tp);
int tpQueueDepth(TP_STRUCT * const tp);
int tpActiveDepth(TP_STRUCT * const tp);
int tpGetMotionType(TP_STRUCT * const tp);
int tpSetSpindleSync(TP_STRUCT * const tp, int spindle, double sync, int wait);
int tpSetAout(TP_STRUCT * const tp, unsigned char index, double start, double end);
int tpSetDout(TP_STRUCT * const tp, int index, unsigned char start, unsigned char end); //gets called to place DIO toggles on the TC queue
int tpSetRunDir(TP_STRUCT * const tp, tc_direction_t dir);
//---------------------------------------------------------------------
// Module interface
void tpMotFunctions(void(*pDioWrite)(int,char)
,void(*pAioWrite)(int,double)
,void(*pSetRotaryUnlock)(int,int)
,int( *pGetRotaryUnlock)(int)
,double(*paxis_get_vel_limit)(int)
,double(*paxis_get_acc_limit)(int)
);
// These are here so we don't need to include "motion/motion.h"
// because that feels very wrong. The real solution is to untangle
// motion controller and trajectory planner sources. Only the shared
// data should be exposed to each other.
typedef struct emcmot_status_t emcmot_status_t;
typedef struct emcmot_config_t emcmot_config_t;
void tpMotData(emcmot_status_t *
,emcmot_config_t *
);
//---------------------------------------------------------------------
#endif /* TP_H */

View File

@@ -0,0 +1,65 @@
/********************************************************************
* Description: tc_debug.h
*
*
* Author: Robert W. Ellenberg
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2013 All rights reserved.
*
* Last change:
********************************************************************/
#ifndef TP_DEBUG_H
#define TP_DEBUG_H
#include <rtapi.h> /* printing functions */
/** TP debug stuff */
#ifdef TP_DEBUG
//Kludge because I didn't know any better at the time
//FIXME replace these with better names?
#define tp_debug_print(...) rtapi_print(__VA_ARGS__)
#elif defined(UNIT_TEST)
#include <stdio.h>
#define tp_debug_print(...) printf(__VA_ARGS__)
#else
#define tp_debug_print(...)
#endif
// Verbose but effective wrappers for building faux-JSON debug output for a function
#define tp_debug_json_double(varname_) tp_debug_print("%s: %g, ", #varname_, varname_)
#define tp_debug_json_start(fname_) tp_debug_print("%s: {", #fname_)
#define tp_debug_json_end() tp_debug_print("}\n")
/** Use for profiling to make static function names visible */
#ifdef TP_PROFILE
#define STATIC
#else
#define STATIC static
#endif
/** "TC" debug info for inspecting trajectory planner output at each timestep */
#ifdef TC_DEBUG
#define tc_debug_print(...) rtapi_print(__VA_ARGS__)
#else
#define tc_debug_print(...)
#endif
/** TP position data output to debug acceleration spikes */
#ifdef TP_POSEMATH_DEBUG
#define tp_posemath_debug(...) rtapi_print(__VA_ARGS__)
#else
#define tp_posemath_debug(...)
#endif
/** TP misc data logging */
#ifdef TP_INFO_LOGGING
#define tp_info_print(...) rtapi_print(__VA_ARGS__)
#else
#define tp_info_print(...)
#endif
int gdb_fake_catch(int condition);
int gdb_fake_assert(int condition);
#endif

View File

@@ -0,0 +1,154 @@
/********************************************************************
* Description: tp_types.h
* Trajectory planner types and constants
*
* Derived from a work by Fred Proctor & Will Shackleford
*
* Author:
* License: GPL Version 2
* System: Linux
*
* Copyright (c) 2004 All rights reserved.
*
********************************************************************/
#ifndef TP_TYPES_H
#define TP_TYPES_H
#include <rtapi_bool.h>
#include <posemath.h>
#include "tc_types.h"
#include "tcq.h"
#define TP_DEFAULT_QUEUE_SIZE 32
/* Minimum length of a segment in cycles (must be greater than 1 to ensure each
* segment is hit at least once.) */
#define TP_MIN_SEGMENT_CYCLES 1.02
/* Values chosen for accel ratio to match parabolic blend acceleration
* limits. */
#define TP_OPTIMIZATION_CUTOFF 4
/* If the queue is shorter than the threshold, assume that we're approaching
* the end of the program */
#define TP_QUEUE_THRESHOLD 3
/* closeness to zero, for determining if a move is pure rotation */
#define TP_PURE_ROTATION_EPSILON 1e-6
/* "neighborhood" size (if two values differ by less than the epsilon,
* then they are effectively equal.)*/
#define TP_ACCEL_EPSILON 1e-4
#define TP_VEL_EPSILON 1e-8
#define TP_POS_EPSILON 1e-12
#define TP_TIME_EPSILON 1e-12
#define TP_ANGLE_EPSILON 1e-6
#define TP_ANGLE_EPSILON_SQ (TP_ANGLE_EPSILON * TP_ANGLE_EPSILON)
#define TP_MIN_ARC_ANGLE 1e-3
#define TP_MIN_ARC_LENGTH 1e-6
#define TP_BIG_NUM 1e10
/**
* TP return codes.
* This enum is a catch-all for useful return statuses from TP
* internal functions. This may be replaced with a better system in
* the future.
*/
typedef enum {
TP_ERR_INVALID = -9,
TP_ERR_INPUT_TYPE = -8,
TP_ERR_TOLERANCE = -7,
TP_ERR_RADIUS_TOO_SMALL = -6,
TP_ERR_GEOM = -5,
TP_ERR_RANGE = -4,
TP_ERR_MISSING_OUTPUT = -3,
TP_ERR_MISSING_INPUT = -2,
TP_ERR_FAIL = -1,
TP_ERR_OK = 0,
TP_ERR_NO_ACTION,
TP_ERR_SLOWING,
TP_ERR_STOPPED,
TP_ERR_WAITING,
TP_ERR_ZERO_LENGTH,
TP_ERR_REVERSE_EMPTY,
TP_ERR_LAST
} tp_err_t;
/**
* Persistent data for spindle status within tpRunCycle.
* This structure encapsulates some static variables to simplify refactoring of
* synchronized motion code.
*/
typedef struct {
int spindle_num;
double offset;
double revs;
int waiting_for_index;
int waiting_for_atspeed;
} tp_spindle_t;
/**
* Trajectory planner state structure.
* Stores persistent data for the trajectory planner that should be accessible
* by outside functions.
*/
typedef struct {
TC_QUEUE_STRUCT queue;
tp_spindle_t spindle; //Spindle data
EmcPose currentPos;
EmcPose goalPos;
int queueSize;
double cycleTime;
double vMax; /* vel for subsequent moves */
double ini_maxvel; /* max velocity allowed by machine
constraints (INI file) for
subsequent moves */
double vLimit; /* absolute upper limit on all vels */
double aMax; /* max accel (unused) */
double ini_maxjerk;
//FIXME this shouldn't be a separate limit,
double aMaxCartesian; /* max cartesian acceleration by machine bounds */
double aLimit; /* max accel (unused) */
double wMax; /* rotational velocity max */
double wDotMax; /* rotational acceleration max */
int nextId;
int execId;
struct state_tag_t execTag; /* state tag corresponding to running motion */
int termCond;
int done;
int depth; /* number of total queued motions */
int activeDepth; /* number of motions blending */
int aborting;
int pausing;
int reverse_run; /* Indicates that TP is running in reverse */
int motionType;
double tolerance; /* for subsequent motions, stay within this
distance of the programmed path during
blends */
int synchronized; // spindle sync required for this move
int velocity_mode; /* TRUE if spindle sync is in velocity mode,
FALSE if in position mode */
double uu_per_rev; /* user units per spindle revolution */
syncdio_t syncdio; //record tpSetDout's here
} TP_STRUCT;
/**
* Describes blend modes used in the trajectory planner.
* @note these values are used as array indices, so make sure valid options
* start at 0 and increase by one.
*/
typedef enum {
NO_BLEND = -1,
PARABOLIC_BLEND,
TANGENT_SEGMENTS_BLEND,
ARC_BLEND
} tc_blend_type_t;
#endif /* TP_TYPES_H */