#include "sim_kinematics.h" #include #include #include #include "posemath.h" #define DEG_TO_RAD 0.017453292519943295769236907684886 #define RAD_TO_DEG 57.295779513082320876798154814105 static uint64_t fnv1a64(const unsigned char *bytes, size_t length) { uint64_t hash = UINT64_C(1469598103934665603); size_t index; for (index = 0; index < length; ++index) { hash ^= bytes[index]; hash *= UINT64_C(1099511628211); } return hash; } static double hash_normalize(double value) { return isfinite(value) ? nearbyint(value * 1000000000.0) / 1000000000.0 : value; } /* The five-leg Newton solve can differ by a few ulps between native and * wasm32 math libraries. Keep the public pose/info boundary deterministic * without changing the convergence test itself. */ static double pentapod_canonical(double value) { double rounded = nearbyint(value * 1000000.0) / 1000000.0; return fabs(rounded) < 0.0000005 ? 0.0 : rounded; } static int finite_values(const double *values, size_t count) { size_t index; if (values == 0) return 0; for (index = 0; index < count; ++index) if (!isfinite(values[index])) return 0; return 1; } int sim_kinematics_validate_pose(const sim_posemath_pose *pose) { return pose != 0 && finite_values(&pose->tran.x, 9) ? SIM_KIN_OK : SIM_KIN_ERR_NONFINITE; } static int axis_index(char letter) { switch (letter) { case 'X': return 0; case 'Y': return 1; case 'Z': return 2; case 'A': return 3; case 'B': return 4; case 'C': return 5; case 'U': return 6; case 'V': return 7; case 'W': return 8; default: return -1; } } static int has_coordinate(const char *coordinates, char wanted) { size_t index; for (index = 0; coordinates[index] != '\0'; ++index) { char value = coordinates[index]; if (value >= 'a' && value <= 'z') value = (char)(value - 'a' + 'A'); if (value == wanted) return 1; } return 0; } static int effective_mode(const sim_kinematics_config *config) { if (config->mode != SIM_KIN_MODE_DEFAULT) return config->mode; return config->type == SIM_KIN_TRIVKINS ? SIM_KIN_MODE_IDENTITY : SIM_KIN_MODE_BOTH; } static uint32_t kinematics_joint_count(const sim_kinematics_config *config) { return config->kinematics_joint_count == 0 ? config->joint_count : config->kinematics_joint_count; } static int known_type(int type) { switch (type) { case SIM_KIN_TRIVKINS: case SIM_KIN_XYZAC_TRT: case SIM_KIN_XYZBC_TRT: case SIM_KIN_XYZBCW: case SIM_KIN_COREXY: case SIM_KIN_ROSE: case SIM_KIN_MAXKINS: case SIM_KIN_ROTATEKINS: case SIM_KIN_5AXISKINS: case SIM_KIN_SCARA: case SIM_KIN_PUMA: case SIM_KIN_SCORBOT: case SIM_KIN_GENSER: case SIM_KIN_LINEAR_DELTA: case SIM_KIN_ROTARY_DELTA: case SIM_KIN_TRIPOD: case SIM_KIN_GENHEX: case SIM_KIN_PENTAPOD: return 1; default: return 0; } } static void initialize_info(sim_kinematics_result_info *info, uint64_t forward_flags, uint64_t inverse_flags) { if (info == 0) return; info->forward_flags = forward_flags; info->inverse_flags = inverse_flags; info->residual = 0.0; info->iterations = 0; info->branch_id = 0; } static int config_axis(const sim_kinematics *kinematics, int axis) { return kinematics->axis_joint[axis]; } static int valid_config(const sim_kinematics_config *config) { const char *coordinates; size_t length; size_t index; uint32_t kinematics_count; if (config == 0 || config->joint_count < 1 || config->joint_count > SIM_KIN_MAX_JOINTS) return SIM_KIN_ERR_CONFIGURATION; kinematics_count = kinematics_joint_count(config); if (kinematics_count < 1 || kinematics_count > config->joint_count) return SIM_KIN_ERR_CONFIGURATION; if (config->extra_joint_count != 0 && config->extra_joint_count != config->joint_count - kinematics_count) return SIM_KIN_ERR_CONFIGURATION; if (!known_type(config->type)) return SIM_KIN_ERR_UNKNOWN_TYPE; if (config->mode < SIM_KIN_MODE_DEFAULT || config->mode > SIM_KIN_MODE_BOTH) return SIM_KIN_ERR_CONFIGURATION; if (!finite_values(&config->x_rot_point, 19) || !finite_values(&config->delta_radius, 9)) return SIM_KIN_ERR_NONFINITE; if (config->max_iterations > 100000u) return SIM_KIN_ERR_CONFIGURATION; if (config->convergence_tolerance < 0.0 || !isfinite(config->convergence_tolerance)) return SIM_KIN_ERR_CONFIGURATION; coordinates = config->coordinates[0] == '\0' ? "XYZABCUVW" : config->coordinates; length = strlen(coordinates); if (length == 0 || length > SIM_KIN_MAX_JOINTS || length > kinematics_count) return SIM_KIN_ERR_CONFIGURATION; for (index = 0; index < length; ++index) { int axis = axis_index((char)(coordinates[index] >= 'a' && coordinates[index] <= 'z' ? coordinates[index] - 'a' + 'A' : coordinates[index])); if (axis < 0) return SIM_KIN_ERR_CONFIGURATION; (void)axis; /* Duplicate axis letters are valid for gantry/extra-joint mappings. */ } if (config->type == SIM_KIN_XYZAC_TRT && (!has_coordinate(coordinates, 'X') || !has_coordinate(coordinates, 'Y') || !has_coordinate(coordinates, 'Z') || !has_coordinate(coordinates, 'A') || !has_coordinate(coordinates, 'C'))) return SIM_KIN_ERR_CONFIGURATION; if (config->type == SIM_KIN_XYZBC_TRT && (!has_coordinate(coordinates, 'X') || !has_coordinate(coordinates, 'Y') || !has_coordinate(coordinates, 'Z') || !has_coordinate(coordinates, 'B') || !has_coordinate(coordinates, 'C'))) return SIM_KIN_ERR_CONFIGURATION; if ((config->type == SIM_KIN_XYZBCW || config->type == SIM_KIN_5AXISKINS) && (!has_coordinate(coordinates, 'X') || !has_coordinate(coordinates, 'Y') || !has_coordinate(coordinates, 'Z') || !has_coordinate(coordinates, 'B') || !has_coordinate(coordinates, 'C') || !has_coordinate(coordinates, 'W'))) return SIM_KIN_ERR_CONFIGURATION; if ((config->type == SIM_KIN_COREXY || config->type == SIM_KIN_ROSE || config->type == SIM_KIN_MAXKINS || config->type == SIM_KIN_ROTATEKINS) && (!has_coordinate(coordinates, 'X') || !has_coordinate(coordinates, 'Y') || !has_coordinate(coordinates, 'Z'))) return SIM_KIN_ERR_CONFIGURATION; if (config->type == SIM_KIN_ROSE && kinematics_count != 3) return SIM_KIN_ERR_CONFIGURATION; if (config->type == SIM_KIN_SCARA && (kinematics_count != 6 || !has_coordinate(coordinates, 'C') || !has_coordinate(coordinates, 'A') || !has_coordinate(coordinates, 'B'))) return SIM_KIN_ERR_CONFIGURATION; if (config->type == SIM_KIN_PUMA && (kinematics_count != 6 || !has_coordinate(coordinates, 'A') || !has_coordinate(coordinates, 'B') || !has_coordinate(coordinates, 'C'))) return SIM_KIN_ERR_CONFIGURATION; if (config->type == SIM_KIN_SCORBOT && (kinematics_count != 5 || !has_coordinate(coordinates, 'A') || !has_coordinate(coordinates, 'B'))) return SIM_KIN_ERR_CONFIGURATION; if (config->type == SIM_KIN_GENSER && (kinematics_count != 6 || !has_coordinate(coordinates, 'X') || !has_coordinate(coordinates, 'Y') || !has_coordinate(coordinates, 'Z') || !has_coordinate(coordinates, 'A') || !has_coordinate(coordinates, 'B') || !has_coordinate(coordinates, 'C') || (config->dh_joint_count != 0 && config->dh_joint_count != 6))) return SIM_KIN_ERR_CONFIGURATION; if (config->type == SIM_KIN_GENSER && (!finite_values(config->dh_a, 6) || !finite_values(config->dh_alpha, 6) || !finite_values(config->dh_d, 6))) return SIM_KIN_ERR_NONFINITE; if (config->type == SIM_KIN_LINEAR_DELTA) { if (kinematics_count < 3 || !has_coordinate(coordinates, 'X') || !has_coordinate(coordinates, 'Y') || !has_coordinate(coordinates, 'Z')) return SIM_KIN_ERR_CONFIGURATION; /* A completely zero geometry means "use the LinuxCNC defaults" and * is filled in by init. Partially specified or non-positive geometry * is always rejected instead of allowing a divide-by-zero later. */ if (!((config->delta_radius == 0.0) && (config->delta_rod_length == 0.0)) && (config->delta_radius <= 0.0 || config->delta_rod_length <= 0.0)) return SIM_KIN_ERR_CONFIGURATION; } if (config->type == SIM_KIN_ROTARY_DELTA) { if (kinematics_count < 3 || !has_coordinate(coordinates, 'X') || !has_coordinate(coordinates, 'Y') || !has_coordinate(coordinates, 'Z')) return SIM_KIN_ERR_CONFIGURATION; if (!((config->rotary_platform_radius == 0.0) && (config->rotary_thigh_length == 0.0) && (config->rotary_shin_length == 0.0) && (config->rotary_foot_radius == 0.0)) && (config->rotary_platform_radius <= 0.0 || config->rotary_thigh_length <= 0.0 || config->rotary_shin_length <= 0.0 || config->rotary_foot_radius <= 0.0)) return SIM_KIN_ERR_CONFIGURATION; } if (config->type == SIM_KIN_TRIPOD) { if (kinematics_count < 3 || !has_coordinate(coordinates, 'X') || !has_coordinate(coordinates, 'Y') || !has_coordinate(coordinates, 'Z')) return SIM_KIN_ERR_CONFIGURATION; if (!((config->tripod_bx == 0.0) && (config->tripod_cx == 0.0) && (config->tripod_cy == 0.0)) && (fabs(config->tripod_bx) < 1e-12 || fabs(config->tripod_cy) < 1e-12)) return SIM_KIN_ERR_CONFIGURATION; } if (config->type == SIM_KIN_GENHEX) { if (kinematics_count != SIM_KIN_GENHEX_STRUTS || !has_coordinate(coordinates, 'X') || !has_coordinate(coordinates, 'Y') || !has_coordinate(coordinates, 'Z') || !has_coordinate(coordinates, 'A') || !has_coordinate(coordinates, 'B') || !has_coordinate(coordinates, 'C')) return SIM_KIN_ERR_CONFIGURATION; if (config->genhex_iteration_limit > 100000u) return SIM_KIN_ERR_CONFIGURATION; if (config->genhex_convergence_tolerance < 0.0 || !isfinite(config->genhex_convergence_tolerance) || config->genhex_max_error < 0.0 || !isfinite(config->genhex_max_error) || !finite_values(config->genhex_initial_pose, 6)) return SIM_KIN_ERR_CONFIGURATION; for (index = 0; index < SIM_KIN_GENHEX_STRUTS; ++index) { if (!finite_values(config->genhex_base[index], 3) || !finite_values(config->genhex_platform[index], 3) || !finite_values(config->genhex_base_axis[index], 3) || !finite_values(config->genhex_platform_axis[index], 3)) return SIM_KIN_ERR_NONFINITE; } } if (config->type == SIM_KIN_PENTAPOD) { if (kinematics_count != SIM_KIN_PENTA_STRUTS || !has_coordinate(coordinates, 'X') || !has_coordinate(coordinates, 'Y') || !has_coordinate(coordinates, 'Z') || !has_coordinate(coordinates, 'A') || !has_coordinate(coordinates, 'B')) return SIM_KIN_ERR_CONFIGURATION; if (!isfinite(config->penta_tool_offset)) return SIM_KIN_ERR_NONFINITE; if (config->penta_iteration_limit > 100000u || config->penta_convergence_tolerance < 0.0 || !isfinite(config->penta_convergence_tolerance) || config->penta_max_error < 0.0 || !isfinite(config->penta_max_error) || !finite_values(config->penta_initial_pose, 5)) return SIM_KIN_ERR_CONFIGURATION; for (index = 0; index < SIM_KIN_PENTA_STRUTS; ++index) if (!finite_values(config->penta_base[index], 3) || !isfinite(config->penta_effector_radius[index]) || !isfinite(config->penta_effector_z[index]) || ((config->penta_effector_radius[index] < 0.0) || (config->penta_effector_radius[index] == 0.0 && config->penta_effector_z[index] != 0.0))) return SIM_KIN_ERR_CONFIGURATION; } for (index = 0; index < kinematics_count; ++index) { if (config->limit_enabled[index] && (!isfinite(config->joint_min[index]) || !isfinite(config->joint_max[index]) || config->joint_min[index] > config->joint_max[index])) return SIM_KIN_ERR_CONFIGURATION; } return SIM_KIN_OK; } static int in_limits(const sim_kinematics *kinematics, const double *joints) { uint32_t index; for (index = 0; index < kinematics_joint_count(&kinematics->config); ++index) if (kinematics->config.limit_enabled[index] && (joints[index] < kinematics->config.joint_min[index] || joints[index] > kinematics->config.joint_max[index])) return SIM_KIN_ERR_RANGE; return SIM_KIN_OK; } static void map_joints(sim_kinematics *kinematics) { size_t index; for (index = 0; index < SIM_KIN_MAX_JOINTS; ++index) kinematics->axis_joint[index] = -1; for (index = 0; index < kinematics_joint_count(&kinematics->config); ++index) { int axis = axis_index((char)(kinematics->config.coordinates[index] >= 'a' && kinematics->config.coordinates[index] <= 'z' ? kinematics->config.coordinates[index] - 'a' + 'A' : kinematics->config.coordinates[index])); if (axis >= 0 && kinematics->axis_joint[axis] < 0) kinematics->axis_joint[axis] = (int)index; } } int sim_kinematics_init(sim_kinematics *kinematics, const sim_kinematics_config *config) { int result; int geometry_zero; if (kinematics == 0 || config == 0) return SIM_KIN_ERR_ARGUMENT; result = valid_config(config); if (result != SIM_KIN_OK) return result; memset(kinematics, 0, sizeof(*kinematics)); kinematics->config = *config; if (kinematics->config.coordinates[0] == '\0') strcpy(kinematics->config.coordinates, "XYZABCUVW"); if (kinematics->config.kinematics_joint_count == 0) kinematics->config.kinematics_joint_count = kinematics->config.joint_count; if (kinematics->config.extra_joint_count == 0) kinematics->config.extra_joint_count = kinematics->config.joint_count - kinematics->config.kinematics_joint_count; geometry_zero = kinematics->config.scara_d1 == 0.0 && kinematics->config.scara_d2 == 0.0 && kinematics->config.scara_d3 == 0.0 && kinematics->config.scara_d4 == 0.0 && kinematics->config.scara_d5 == 0.0 && kinematics->config.scara_d6 == 0.0; if (kinematics->config.type == SIM_KIN_SCARA && geometry_zero) { kinematics->config.scara_d1 = 490.0; kinematics->config.scara_d2 = 340.0; kinematics->config.scara_d3 = 50.0; kinematics->config.scara_d4 = 250.0; kinematics->config.scara_d5 = 50.0; kinematics->config.scara_d6 = 50.0; } if (kinematics->config.type == SIM_KIN_PUMA && kinematics->config.puma_a2 == 0.0 && kinematics->config.puma_a3 == 0.0 && kinematics->config.puma_d3 == 0.0 && kinematics->config.puma_d4 == 0.0 && kinematics->config.puma_d6 == 0.0) { kinematics->config.puma_a2 = 300.0; kinematics->config.puma_a3 = 50.0; kinematics->config.puma_d3 = 70.0; kinematics->config.puma_d4 = 400.0; kinematics->config.puma_d6 = 70.0; } if (kinematics->config.type == SIM_KIN_GENSER && kinematics->config.dh_joint_count == 0) kinematics->config.dh_joint_count = 6; if (kinematics->config.type == SIM_KIN_LINEAR_DELTA && kinematics->config.delta_radius == 0.0 && kinematics->config.delta_rod_length == 0.0) { kinematics->config.delta_radius = 130.25; kinematics->config.delta_rod_length = 269.0; } if (kinematics->config.type == SIM_KIN_ROTARY_DELTA && kinematics->config.rotary_platform_radius == 0.0 && kinematics->config.rotary_thigh_length == 0.0 && kinematics->config.rotary_shin_length == 0.0 && kinematics->config.rotary_foot_radius == 0.0) { kinematics->config.rotary_platform_radius = 10.0; kinematics->config.rotary_thigh_length = 10.0; kinematics->config.rotary_shin_length = 14.0; kinematics->config.rotary_foot_radius = 6.0; } if (kinematics->config.type == SIM_KIN_TRIPOD && kinematics->config.tripod_bx == 0.0 && kinematics->config.tripod_cx == 0.0 && kinematics->config.tripod_cy == 0.0) { kinematics->config.tripod_bx = 10.0; kinematics->config.tripod_cx = 5.0; kinematics->config.tripod_cy = 7.071; } if (kinematics->config.type == SIM_KIN_GENHEX) { size_t index; int all_zero = 1; static const double base[SIM_KIN_GENHEX_STRUTS][3] = { {-22.950, 13.250, 0.0}, {22.950, 13.250, 0.0}, {22.950, 13.250, 0.0}, {0.0, -26.500, 0.0}, {0.0, -26.500, 0.0}, {-22.950, 13.250, 0.0} }; static const double platform[SIM_KIN_GENHEX_STRUTS][3] = { {-1.000, 11.500, 0.0}, {1.000, 11.500, 0.0}, {10.459, -4.884, 0.0}, {9.459, -6.616, 0.0}, {-9.459, -6.616, 0.0}, {-10.459, -4.884, 0.0} }; static const double base_axis[SIM_KIN_GENHEX_STRUTS][3] = { {0.707107, 0.0, 0.707107}, {0.0, -0.707107, 0.707107}, {-0.707107, 0.0, 0.707107}, {-0.707107, 0.0, 0.707107}, {0.0, 0.707107, 0.707107}, {0.707107, 0.0, 0.707107} }; static const double platform_axis[SIM_KIN_GENHEX_STRUTS][3] = { {-1.0, 0.0, 0.0}, {0.866025, 0.5, 0.0}, {0.866025, 0.5, 0.0}, {0.866025, -0.5, 0.0}, {0.866025, -0.5, 0.0}, {-1.0, 0.0, 0.0} }; for (index = 0; index < SIM_KIN_GENHEX_STRUTS; ++index) { size_t axis; for (axis = 0; axis < 3; ++axis) if (kinematics->config.genhex_base[index][axis] != 0.0 || kinematics->config.genhex_platform[index][axis] != 0.0) all_zero = 0; } if (all_zero) { memcpy(kinematics->config.genhex_base, base, sizeof(base)); memcpy(kinematics->config.genhex_platform, platform, sizeof(platform)); memcpy(kinematics->config.genhex_base_axis, base_axis, sizeof(base_axis)); memcpy(kinematics->config.genhex_platform_axis, platform_axis, sizeof(platform_axis)); } if (kinematics->config.genhex_iteration_limit == 0) kinematics->config.genhex_iteration_limit = 120; if (kinematics->config.genhex_convergence_tolerance == 0.0) kinematics->config.genhex_convergence_tolerance = 1e-9; if (kinematics->config.genhex_max_error == 0.0) kinematics->config.genhex_max_error = 500.0; if (kinematics->config.genhex_initial_pose[2] == 0.0) kinematics->config.genhex_initial_pose[2] = 50.0; } if (kinematics->config.type == SIM_KIN_PENTAPOD) { static const double base[SIM_KIN_PENTA_STRUTS][3] = {{-418.0, 500.0, -140.0}, {418.0, 500.0, -140.0}, {0.0, 500.0, -310.0}, {418.0, 500.0, 510.0}, {-418.0, 500.0, 510.0}}; static const double radius[SIM_KIN_PENTA_STRUTS] = {80.30, 80.30, 80.30, 80.30, 80.30}; static const double effector_z[SIM_KIN_PENTA_STRUTS] = {87.0, 114.5, 140.5, 232.5, 258.5}; int all_zero = 1; size_t index; for (index = 0; index < SIM_KIN_PENTA_STRUTS; ++index) { if (kinematics->config.penta_base[index][0] != 0.0 || kinematics->config.penta_base[index][1] != 0.0 || kinematics->config.penta_base[index][2] != 0.0 || kinematics->config.penta_effector_radius[index] != 0.0 || kinematics->config.penta_effector_z[index] != 0.0) all_zero = 0; } if (all_zero) { memcpy(kinematics->config.penta_base, base, sizeof(base)); memcpy(kinematics->config.penta_effector_radius, radius, sizeof(radius)); memcpy(kinematics->config.penta_effector_z, effector_z, sizeof(effector_z)); } if (kinematics->config.penta_iteration_limit == 0) kinematics->config.penta_iteration_limit = 120; if (kinematics->config.penta_convergence_tolerance == 0.0) kinematics->config.penta_convergence_tolerance = 1e-9; if (kinematics->config.penta_max_error == 0.0) kinematics->config.penta_max_error = 1000.0; if (kinematics->config.penta_initial_pose[1] == 0.0) kinematics->config.penta_initial_pose[1] = 500.0; if (kinematics->config.penta_initial_pose[2] == 0.0) kinematics->config.penta_initial_pose[2] = 300.0; } map_joints(kinematics); kinematics->configured = 1; return SIM_KIN_OK; } static void zero_pose(sim_posemath_pose *pose) { memset(pose, 0, sizeof(*pose)); } static double joint(const sim_kinematics *k, const double *joints, int axis) { int index = config_axis(k, axis); return index < 0 ? 0.0 : joints[index]; } static void set_optional_axes(const sim_kinematics *k, const double *joints, sim_posemath_pose *pose) { pose->a = joint(k, joints, 3); pose->b = joint(k, joints, 4); pose->c = joint(k, joints, 5); pose->u = joint(k, joints, 6); pose->v = joint(k, joints, 7); pose->w = joint(k, joints, 8); } static void set_joint(double *joints, const sim_kinematics *k, int axis, double value); static int scara_forward(const sim_kinematics *k, const double *joints, sim_posemath_pose *pose) { double a0 = joint(k, joints, 0) * DEG_TO_RAD; double a1 = joint(k, joints, 1) * DEG_TO_RAD + a0; double a3 = joint(k, joints, 5) * DEG_TO_RAD + a1; pose->tran.x = k->config.scara_d2 * cos(a0) + k->config.scara_d4 * cos(a1) + k->config.scara_d6 * cos(a3); pose->tran.y = k->config.scara_d2 * sin(a0) + k->config.scara_d4 * sin(a1) + k->config.scara_d6 * sin(a3); pose->tran.z = k->config.scara_d1 + k->config.scara_d3 - joint(k, joints, 2) - k->config.scara_d5; pose->c = a3 * RAD_TO_DEG; pose->a = joint(k, joints, 3); pose->b = joint(k, joints, 4); return SIM_KIN_OK; } static int scara_inverse(const sim_kinematics *k, const sim_posemath_pose *pose, double *joints, uint64_t flags, uint32_t *branch) { double a3 = pose->c * DEG_TO_RAD; double xt = pose->tran.x - k->config.scara_d6 * cos(a3); double yt = pose->tran.y - k->config.scara_d6 * sin(a3); double rsq = xt * xt + yt * yt; double cc = (rsq - k->config.scara_d2 * k->config.scara_d2 - k->config.scara_d4 * k->config.scara_d4) / (2.0 * k->config.scara_d2 * k->config.scara_d4); double q1; double q0; if (cc < -1.0 - 1e-12 || cc > 1.0 + 1e-12) return SIM_KIN_ERR_RANGE; if (cc < -1.0) cc = -1.0; if (cc > 1.0) cc = 1.0; q1 = acos(cc); if (flags & UINT64_C(1)) q1 = -q1; q0 = atan2(yt, xt) - atan2(k->config.scara_d4 * sin(q1), k->config.scara_d2 + k->config.scara_d4 * cos(q1)); set_joint(joints, k, 0, q0 * RAD_TO_DEG); set_joint(joints, k, 1, q1 * RAD_TO_DEG); set_joint(joints, k, 2, k->config.scara_d1 + k->config.scara_d3 - k->config.scara_d5 - pose->tran.z); set_joint(joints, k, 5, pose->c - (q0 + q1) * RAD_TO_DEG); set_joint(joints, k, 3, pose->a); set_joint(joints, k, 4, pose->b); if (branch != 0) *branch = (q1 < 0.0) ? 1U : 0U; return SIM_KIN_OK; } static int scorbot_forward(const sim_kinematics *k, const double *joints, sim_posemath_pose *pose) { double j0 = joint(k, joints, 0) * DEG_TO_RAD; double j1 = joint(k, joints, 1) * DEG_TO_RAD; double j2 = joint(k, joints, 2) * DEG_TO_RAD; double radius = 16.0 + 221.0 * cos(j1) + 221.0 * cos(j2); pose->tran.x = radius * cos(j0); pose->tran.y = radius * sin(j0); pose->tran.z = 140.0 + 221.0 * sin(j1) + 221.0 * sin(j2); pose->a = joint(k, joints, 3); pose->b = joint(k, joints, 4); return SIM_KIN_OK; } static int scorbot_inverse(const sim_kinematics *k, const sim_posemath_pose *pose, double *joints) { double r = hypot(pose->tran.x, pose->tran.y) - 16.0; double z = pose->tran.z - 140.0; double distance = hypot(r, z); double angle; double j1; if (distance < 1e-12 || distance > 442.0 + 1e-9) return SIM_KIN_ERR_RANGE; angle = acos(r / distance) * (z < 0.0 ? -1.0 : 1.0); j1 = angle + acos(distance / 442.0); set_joint(joints, k, 0, atan2(pose->tran.y, pose->tran.x) * RAD_TO_DEG); set_joint(joints, k, 1, j1 * RAD_TO_DEG); set_joint(joints, k, 2, -asin((221.0 * sin(j1) - z) / 221.0) * RAD_TO_DEG); set_joint(joints, k, 3, pose->a); set_joint(joints, k, 4, pose->b); return SIM_KIN_OK; } static int puma_forward(const sim_kinematics *k, const double *joint_values, sim_posemath_pose *pose, uint32_t *branch) { double q0 = joint(k, joint_values, 0) * DEG_TO_RAD; double q1 = joint(k, joint_values, 1) * DEG_TO_RAD; double q2 = joint(k, joint_values, 2) * DEG_TO_RAD; double q3 = joint(k, joint_values, 3) * DEG_TO_RAD; double q4 = joint(k, joint_values, 4) * DEG_TO_RAD; double q5 = joint(k, joint_values, 5) * DEG_TO_RAD; double s1 = sin(q0), s2 = sin(q1), s3 = sin(q2), s4 = sin(q3), s5 = sin(q4), s6 = sin(q5); double c1 = cos(q0), c2 = cos(q1), c3 = cos(q2), c4 = cos(q3), c5 = cos(q4), c6 = cos(q5); double s23 = c2 * s3 + s2 * c3; double c23 = c2 * c3 - s2 * s3; double t1, t2, t3, t4, t5; PmHomogeneous hom = {0}; PmRpy rpy = {0}; t1 = c4 * c5 * c6 - s4 * s6; t2 = s23 * s5 * c6; t3 = s4 * c5 * c6 + c4 * s6; t4 = c23 * t1 - t2; t5 = c23 * s5 * c6; hom.rot.x.x = c1 * t4 + s1 * t3; hom.rot.x.y = s1 * t4 - c1 * t3; hom.rot.x.z = -s23 * t1 - t5; t1 = -c4 * c5 * s6 - s4 * c6; t2 = s23 * s5 * s6; t3 = c4 * c6 - s4 * c5 * s6; t4 = c23 * t1 + t2; t5 = c23 * s5 * s6; hom.rot.y.x = c1 * t4 + s1 * t3; hom.rot.y.y = s1 * t4 - c1 * t3; hom.rot.y.z = -s23 * t1 + t5; t1 = c23 * c4 * s5 + s23 * c5; hom.rot.z.x = -c1 * t1 - s1 * s4 * s5; hom.rot.z.y = -s1 * t1 + c1 * s4 * s5; hom.rot.z.z = s23 * c4 * s5 - c23 * c5; t1 = k->config.puma_a2 * c2 + k->config.puma_a3 * c23 - k->config.puma_d4 * s23; hom.tran.x = c1 * t1 - k->config.puma_d3 * s1; hom.tran.y = s1 * t1 + k->config.puma_d3 * c1; hom.tran.z = -k->config.puma_a3 * s23 - k->config.puma_a2 * s2 - k->config.puma_d4 * c23; hom.tran.x += hom.rot.z.x * k->config.puma_d6; hom.tran.y += hom.rot.z.y * k->config.puma_d6; hom.tran.z += hom.rot.z.z * k->config.puma_d6; if (pmMatRpyConvert(&hom.rot, &rpy) != 0) return SIM_KIN_ERR_SINGULAR; pose->tran.x = hom.tran.x; pose->tran.y = hom.tran.y; pose->tran.z = hom.tran.z; pose->a = rpy.r * RAD_TO_DEG; pose->b = rpy.p * RAD_TO_DEG; pose->c = rpy.y * RAD_TO_DEG; if (branch != 0) *branch = (fabs(s5) < 1e-9) ? 8U : 0U; return SIM_KIN_OK; } static int puma_inverse(const sim_kinematics *k, const sim_posemath_pose *pose, double *joints, uint64_t flags, uint32_t *branch) { PmRotationMatrix rot = {0}; PmRpy rpy = {pose->a * DEG_TO_RAD, pose->b * DEG_TO_RAD, pose->c * DEG_TO_RAD}; double px, py, pz, sum_sq, kterm, th1, th2, th3, th23, th4, th5, th6; double s1, c1, s3, c3, s23, c23, s4, c4, s5, c5, s6, c6, t1, t2, t3; if (pmRpyMatConvert(&rpy, &rot) != 0) return SIM_KIN_ERR_SINGULAR; px = pose->tran.x - k->config.puma_d6 * rot.z.x; py = pose->tran.y - k->config.puma_d6 * rot.z.y; pz = pose->tran.z - k->config.puma_d6 * rot.z.z; sum_sq = px * px + py * py - k->config.puma_d3 * k->config.puma_d3; if (sum_sq < -1e-9) return SIM_KIN_ERR_RANGE; if (sum_sq < 0.0) sum_sq = 0.0; th1 = atan2(py, px) - atan2(k->config.puma_d3, (flags & 1U) ? -sqrt(sum_sq) : sqrt(sum_sq)); s1 = sin(th1); c1 = cos(th1); kterm = (sum_sq + pz * pz - k->config.puma_a2 * k->config.puma_a2 - k->config.puma_a3 * k->config.puma_a3 - k->config.puma_d4 * k->config.puma_d4) / (2.0 * k->config.puma_a2); if (kterm * kterm > k->config.puma_a3 * k->config.puma_a3 + k->config.puma_d4 * k->config.puma_d4 + 1e-9) return SIM_KIN_ERR_RANGE; if (kterm * kterm > k->config.puma_a3 * k->config.puma_a3 + k->config.puma_d4 * k->config.puma_d4) kterm = copysign(sqrt(k->config.puma_a3 * k->config.puma_a3 + k->config.puma_d4 * k->config.puma_d4), kterm); th3 = atan2(k->config.puma_a3, k->config.puma_d4) - atan2(kterm, (flags & 2U) ? -sqrt(fmax(0.0, k->config.puma_a3 * k->config.puma_a3 + k->config.puma_d4 * k->config.puma_d4 - kterm * kterm)) : sqrt(fmax(0.0, k->config.puma_a3 * k->config.puma_a3 + k->config.puma_d4 * k->config.puma_d4 - kterm * kterm))); s3 = sin(th3); c3 = cos(th3); t1 = (-k->config.puma_a3 - k->config.puma_a2 * c3) * pz + (c1 * px + s1 * py) * (k->config.puma_a2 * s3 - k->config.puma_d4); t2 = (k->config.puma_a2 * s3 - k->config.puma_d4) * pz + (k->config.puma_a3 + k->config.puma_a2 * c3) * (c1 * px + s1 * py); t3 = pz * pz + (c1 * px + s1 * py) * (c1 * px + s1 * py); if (t3 < 1e-18) return SIM_KIN_ERR_SINGULAR; th23 = atan2(t1, t2); th2 = th23 - th3; s23 = t1 / t3; c23 = t2 / t3; t1 = -rot.z.x * s1 + rot.z.y * c1; t2 = -rot.z.x * c1 * c23 - rot.z.y * s1 * c23 + rot.z.z * s23; if (fabs(t1) < 1e-9 && fabs(t2) < 1e-9) return SIM_KIN_ERR_SINGULAR; th4 = atan2(t1, t2); s4 = sin(th4); c4 = cos(th4); s5 = rot.z.z * (s23 * c4) - rot.z.x * (c1 * c23 * c4 + s1 * s4) - rot.z.y * (s1 * c23 * c4 - c1 * s4); c5 = -rot.z.x * (c1 * s23) - rot.z.y * (s1 * s23) - rot.z.z * c23; th5 = atan2(s5, c5); s6 = rot.x.z * (s23 * s4) - rot.x.x * (c1 * c23 * s4 - s1 * c4) - rot.x.y * (s1 * c23 * s4 + c1 * c4); c6 = rot.x.x * ((c1 * c23 * c4 + s1 * s4) * c5 - c1 * s23 * s5) + rot.x.y * ((s1 * c23 * c4 - c1 * s4) * c5 - s1 * s23 * s5) - rot.x.z * (s23 * c4 * c5 + c23 * s5); th6 = atan2(s6, c6); if (flags & 4U) { th4 += PM_PI; th5 = -th5; th6 += PM_PI; } set_joint(joints, k, 0, th1 * RAD_TO_DEG); set_joint(joints, k, 1, th2 * RAD_TO_DEG); set_joint(joints, k, 2, th3 * RAD_TO_DEG); set_joint(joints, k, 3, th4 * RAD_TO_DEG); set_joint(joints, k, 4, th5 * RAD_TO_DEG); set_joint(joints, k, 5, th6 * RAD_TO_DEG); if (branch != 0) *branch = (uint32_t)(flags & 7U); return SIM_KIN_OK; } static void mat4_identity(double m[4][4]) { int row, col; for (row = 0; row < 4; ++row) for (col = 0; col < 4; ++col) m[row][col] = row == col ? 1.0 : 0.0; } static void mat4_multiply(const double left[4][4], const double right[4][4], double out[4][4]) { double value[4][4]; int row, col, inner; for (row = 0; row < 4; ++row) for (col = 0; col < 4; ++col) { value[row][col] = 0.0; for (inner = 0; inner < 4; ++inner) value[row][col] += left[row][inner] * right[inner][col]; } memcpy(out, value, sizeof(value)); } static int genser_forward(const sim_kinematics *k, const double *joints, sim_posemath_pose *pose) { double total[4][4]; uint32_t index; PmRotationMatrix rotation; PmRpy rpy; mat4_identity(total); for (index = 0; index < 6; ++index) { double theta = joints[index] * DEG_TO_RAD; double alpha = k->config.dh_alpha[index]; double ct = cos(theta), st = sin(theta), ca = cos(alpha), sa = sin(alpha); double link[4][4] = { {ct, -st, 0.0, k->config.dh_a[index]}, {st * ca, ct * ca, -sa, -sa * k->config.dh_d[index]}, {st * sa, ct * sa, ca, ca * k->config.dh_d[index]}, {0.0, 0.0, 0.0, 1.0}, }; mat4_multiply(total, link, total); } rotation.x.x = total[0][0]; rotation.y.x = total[0][1]; rotation.z.x = total[0][2]; rotation.x.y = total[1][0]; rotation.y.y = total[1][1]; rotation.z.y = total[1][2]; rotation.x.z = total[2][0]; rotation.y.z = total[2][1]; rotation.z.z = total[2][2]; if (pmMatRpyConvert(&rotation, &rpy) != 0) return SIM_KIN_ERR_SINGULAR; pose->tran.x = total[0][3]; pose->tran.y = total[1][3]; pose->tran.z = total[2][3]; pose->a = rpy.r * RAD_TO_DEG; pose->b = rpy.p * RAD_TO_DEG; pose->c = rpy.y * RAD_TO_DEG; return SIM_KIN_OK; } static double wrap_degrees(double value) { value = fmod(value + 180.0, 360.0); if (value < 0.0) value += 360.0; return value - 180.0; } static int solve_linear6(double matrix[6][6], double rhs[6], double result[6]) { int pivot, row, col; for (pivot = 0; pivot < 6; ++pivot) { int best = pivot; double best_abs = fabs(matrix[pivot][pivot]); for (row = pivot + 1; row < 6; ++row) if (fabs(matrix[row][pivot]) > best_abs) { best = row; best_abs = fabs(matrix[row][pivot]); } if (best_abs < 1e-14) return SIM_KIN_ERR_SINGULAR; if (best != pivot) { for (col = pivot; col < 6; ++col) { double tmp = matrix[pivot][col]; matrix[pivot][col] = matrix[best][col]; matrix[best][col] = tmp; } { double tmp = rhs[pivot]; rhs[pivot] = rhs[best]; rhs[best] = tmp; } } for (row = pivot + 1; row < 6; ++row) { double factor = matrix[row][pivot] / matrix[pivot][pivot]; for (col = pivot; col < 6; ++col) matrix[row][col] -= factor * matrix[pivot][col]; rhs[row] -= factor * rhs[pivot]; } } for (row = 5; row >= 0; --row) { double value = rhs[row]; for (col = row + 1; col < 6; ++col) value -= matrix[row][col] * result[col]; result[row] = value / matrix[row][row]; } return SIM_KIN_OK; } static int genser_inverse(const sim_kinematics *k, const sim_posemath_pose *target, double *joints, uint32_t max_iterations, double tolerance, sim_kinematics_result_info *info) { uint32_t iteration; double current[SIM_KIN_MAX_JOINTS] = {0}; double residual = 0.0; if (max_iterations == 0) max_iterations = 100; if (tolerance == 0.0) tolerance = 1e-9; for (iteration = 0; iteration < max_iterations; ++iteration) { sim_posemath_pose estimate; double error[6]; double jacobian[6][6]; double normal[6][6] = {{0}}; double rhs[6] = {0}; double delta[6] = {0}; int column, row, inner; if (genser_forward(k, current, &estimate) != SIM_KIN_OK) return SIM_KIN_ERR_SINGULAR; error[0] = target->tran.x - estimate.tran.x; error[1] = target->tran.y - estimate.tran.y; error[2] = target->tran.z - estimate.tran.z; error[3] = wrap_degrees(target->a - estimate.a); error[4] = wrap_degrees(target->b - estimate.b); error[5] = wrap_degrees(target->c - estimate.c); residual = 0.0; for (row = 0; row < 6; ++row) residual += error[row] * error[row]; residual = sqrt(residual); if (residual <= tolerance) { memcpy(joints, current, sizeof(double) * 6); if (info != 0) { info->iterations = iteration; info->residual = residual; info->branch_id = 0; } return SIM_KIN_OK; } for (column = 0; column < 6; ++column) { double perturbed[SIM_KIN_MAX_JOINTS]; sim_posemath_pose next; memcpy(perturbed, current, sizeof(perturbed)); perturbed[column] += 1e-4; if (genser_forward(k, perturbed, &next) != SIM_KIN_OK) return SIM_KIN_ERR_SINGULAR; jacobian[0][column] = (next.tran.x - estimate.tran.x) / 1e-4; jacobian[1][column] = (next.tran.y - estimate.tran.y) / 1e-4; jacobian[2][column] = (next.tran.z - estimate.tran.z) / 1e-4; jacobian[3][column] = wrap_degrees(next.a - estimate.a) / 1e-4; jacobian[4][column] = wrap_degrees(next.b - estimate.b) / 1e-4; jacobian[5][column] = wrap_degrees(next.c - estimate.c) / 1e-4; } for (row = 0; row < 6; ++row) for (column = 0; column < 6; ++column) { for (inner = 0; inner < 6; ++inner) normal[row][column] += jacobian[inner][row] * jacobian[inner][column]; normal[row][column] += row == column ? 1e-6 : 0.0; for (inner = 0; inner < 6; ++inner) if (column == row) rhs[row] += jacobian[inner][row] * error[inner]; } if (solve_linear6(normal, rhs, delta) != SIM_KIN_OK) return SIM_KIN_ERR_SINGULAR; for (column = 0; column < 6; ++column) current[column] += delta[column]; } if (info != 0) { info->iterations = max_iterations; info->residual = residual; info->branch_id = 0; } return SIM_KIN_ERR_SINGULAR; } static int linear_delta_forward(const sim_kinematics *k, const double *joints, uint64_t flags, sim_posemath_pose *pose, uint32_t *branch) { double R = k->config.delta_radius, L = k->config.delta_rod_length; double Ay = R, Bx = -sqrt(3.0) * 0.5 * R, By = -0.5 * R, Cx = -Bx, Cy = By; double q1 = joint(k, joints, 0), q2 = joint(k, joints, 1), q3 = joint(k, joints, 2); double den = (By - Ay) * Cx - (Cy - Ay) * Bx; double w1 = Ay * Ay + q1 * q1, w2 = Bx * Bx + By * By + q2 * q2, w3 = Cx * Cx + Cy * Cy + q3 * q3; double a1 = (q2 - q1) * (Cy - Ay) - (q3 - q1) * (By - Ay); double b1 = -((w2 - w1) * (Cy - Ay) - (w3 - w1) * (By - Ay)) / 2.0; double a2 = -(q2 - q1) * Cx + (q3 - q1) * Bx; double b2 = ((w2 - w1) * Cx - (w3 - w1) * Bx) / 2.0; double aa = a1 * a1 + a2 * a2 + den * den; double bb = 2.0 * (a1 * b1 + a2 * (b2 - Ay * den) - q1 * den * den); double cc = (b2 - Ay * den) * (b2 - Ay * den) + b1 * b1 + den * den * (q1 * q1 - L * L); double discr = bb * bb - 4.0 * aa * cc; double z; if (fabs(den) < 1e-18 || fabs(aa) < 1e-18) return SIM_KIN_ERR_SINGULAR; if (discr < -1e-12) return SIM_KIN_ERR_RANGE; if (discr < 0.0) discr = 0.0; z = (-bb + ((flags & SIM_KIN_DELTA_ALTERNATE_ROOT) ? sqrt(discr) : -sqrt(discr))) / (2.0 * aa); pose->tran.z = z; pose->tran.x = (a1 * z + b1) / den; pose->tran.y = (a2 * z + b2) / den; set_optional_axes(k, joints, pose); if (branch != 0) *branch = (flags & SIM_KIN_DELTA_ALTERNATE_ROOT) ? 1U : 0U; return SIM_KIN_OK; } static int linear_delta_inverse(const sim_kinematics *k, const sim_posemath_pose *pose, uint64_t flags, double *joints, uint32_t *branch) { double R = k->config.delta_radius, L = k->config.delta_rod_length; double Ax = 0.0, Ay = R, Bx = -sqrt(3.0) * 0.5 * R, By = -0.5 * R, Cx = -Bx, Cy = By; double x = pose->tran.x, y = pose->tran.y, z = pose->tran.z; double rad[3] = {L * L - (Ax - x) * (Ax - x) - (Ay - y) * (Ay - y), L * L - (Bx - x) * (Bx - x) - (By - y) * (By - y), L * L - (Cx - x) * (Cx - x) - (Cy - y) * (Cy - y)}; int index; for (index = 0; index < 3; ++index) if (rad[index] < -1e-12) return SIM_KIN_ERR_RANGE; for (index = 0; index < 3; ++index) { double root = sqrt(fmax(0.0, rad[index])); set_joint(joints, k, index, z + ((flags & SIM_KIN_DELTA_ALTERNATE_ROOT) ? -root : root)); } set_joint(joints, k, 3, pose->a); set_joint(joints, k, 4, pose->b); set_joint(joints, k, 5, pose->c); set_joint(joints, k, 6, pose->u); set_joint(joints, k, 7, pose->v); set_joint(joints, k, 8, pose->w); if (branch != 0) *branch = (flags & SIM_KIN_DELTA_ALTERNATE_ROOT) ? 1U : 0U; return SIM_KIN_OK; } static int rotary_delta_forward(const sim_kinematics *k, const double *joints, uint64_t flags, sim_posemath_pose *pose, uint32_t *branch) { double pfr = k->config.rotary_platform_radius, tl = k->config.rotary_thigh_length, sl = k->config.rotary_shin_length, fr = k->config.rotary_foot_radius; double j0 = joint(k, joints, 0) * DEG_TO_RAD, j1 = joint(k, joints, 1) * DEG_TO_RAD, j2 = joint(k, joints, 2) * DEG_TO_RAD; double y1 = -(pfr - fr + tl * cos(j0)), z1 = -tl * sin(j0); double y2 = (pfr - fr + tl * cos(j1)) * 0.5, x2 = y2 * sqrt(3.0), z2 = -tl * sin(j1); double y3 = (pfr - fr + tl * cos(j2)) * 0.5, x3 = -y3 * sqrt(3.0), z3 = -tl * sin(j2); double den = x3 * (y2 - y1) - x2 * (y3 - y1); double w1 = y1 * y1 + z1 * z1, w2 = x2 * x2 + y2 * y2 + z2 * z2, w3 = x3 * x3 + y3 * y3 + z3 * z3; double a1 = (z2 - z1) * (y3 - y1) - (z3 - z1) * (y2 - y1), b1 = -((w2 - w1) * (y3 - y1) - (w3 - w1) * (y2 - y1)) / 2.0; double a2 = -(z2 - z1) * x3 + (z3 - z1) * x2, b2 = ((w2 - w1) * x3 - (w3 - w1) * x2) / 2.0; double aa = a1 * a1 + a2 * a2 + den * den, bb = 2.0 * (a1 * b1 + a2 * (b2 - y1 * den) - z1 * den * den), cc = (b2 - y1 * den) * (b2 - y1 * den) + b1 * b1 + den * den * (z1 * z1 - sl * sl); double discr = bb * bb - 4.0 * aa * cc; if (fabs(den) < 1e-18 || fabs(aa) < 1e-18) return SIM_KIN_ERR_SINGULAR; if (discr < -1e-12) return SIM_KIN_ERR_RANGE; if (discr < 0.0) discr = 0.0; pose->tran.z = (-bb + ((flags & SIM_KIN_DELTA_ALTERNATE_ROOT) ? sqrt(discr) : -sqrt(discr))) / (2.0 * aa); pose->tran.x = (a1 * pose->tran.z + b1) / den; pose->tran.y = (a2 * pose->tran.z + b2) / den; set_optional_axes(k, joints, pose); if (branch != 0) *branch = (flags & SIM_KIN_DELTA_ALTERNATE_ROOT) ? 1U : 0U; return SIM_KIN_OK; } static int rotary_delta_inverse(const sim_kinematics *k, const sim_posemath_pose *pose, uint64_t flags, double *joints, uint32_t *branch) { double pfr = k->config.rotary_platform_radius, tl = k->config.rotary_thigh_length, sl = k->config.rotary_shin_length, fr = k->config.rotary_foot_radius; double x = pose->tran.x, y = pose->tran.y, z = pose->tran.z; double a, b, d, knee_y, knee_z, xr, yr; if (fabs(z) < 1e-12) return SIM_KIN_ERR_SINGULAR; a = 0.5 * (x * x + (y - fr) * (y - fr) + z * z + tl * tl - sl * sl - pfr * pfr) / z; b = (fr - pfr - y) / z; d = tl * tl * (b * b + 1.0) - (a - b * pfr) * (a - b * pfr); if (d < 0.0) return SIM_KIN_ERR_RANGE; knee_y = (pfr + a * b + ((flags & SIM_KIN_DELTA_ALTERNATE_ROOT) ? -sqrt(d) : sqrt(d))) / (b * b + 1.0); knee_z = b * knee_y - a; set_joint(joints, k, 0, atan2(knee_z, knee_y - pfr) * RAD_TO_DEG); xr = x; yr = y; { double c = cos(-2.0 * 3.14159265358979323846 / 3.0), s = sin(-2.0 * 3.14159265358979323846 / 3.0); double tx = xr * c - yr * s; yr = xr * s + yr * c; xr = tx; } a = 0.5 * (xr * xr + (yr - fr) * (yr - fr) + z * z + tl * tl - sl * sl - pfr * pfr) / z; b = (fr - pfr - yr) / z; d = tl * tl * (b * b + 1.0) - (a - b * pfr) * (a - b * pfr); if (d < 0.0) return SIM_KIN_ERR_RANGE; knee_y = (pfr + a * b + ((flags & SIM_KIN_DELTA_ALTERNATE_ROOT) ? -sqrt(d) : sqrt(d))) / (b * b + 1.0); knee_z = b * knee_y - a; set_joint(joints, k, 1, atan2(knee_z, knee_y - pfr) * RAD_TO_DEG); xr = x; yr = y; { double c = cos(2.0 * 3.14159265358979323846 / 3.0), s = sin(2.0 * 3.14159265358979323846 / 3.0); double tx = xr * c - yr * s; yr = xr * s + yr * c; xr = tx; } a = 0.5 * (xr * xr + (yr - fr) * (yr - fr) + z * z + tl * tl - sl * sl - pfr * pfr) / z; b = (fr - pfr - yr) / z; d = tl * tl * (b * b + 1.0) - (a - b * pfr) * (a - b * pfr); if (d < 0.0) return SIM_KIN_ERR_RANGE; knee_y = (pfr + a * b + ((flags & SIM_KIN_DELTA_ALTERNATE_ROOT) ? -sqrt(d) : sqrt(d))) / (b * b + 1.0); knee_z = b * knee_y - a; set_joint(joints, k, 2, atan2(knee_z, knee_y - pfr) * RAD_TO_DEG); set_joint(joints, k, 3, pose->a); set_joint(joints, k, 4, pose->b); set_joint(joints, k, 5, pose->c); set_joint(joints, k, 6, pose->u); set_joint(joints, k, 7, pose->v); set_joint(joints, k, 8, pose->w); if (flags & SIM_KIN_DELTA_ALTERNATE_ROOT) { /* The alternate knee branch is the reflected root of each leg. */ set_joint(joints, k, 0, atan2(knee_z, knee_y - pfr) * RAD_TO_DEG); /* Recompute the rotated legs through the common helper below; the * default branch remains byte-for-byte compatible with LinuxCNC. */ { double xr2 = x, yr2 = y; double c2 = cos(-2.0 * 3.14159265358979323846 / 3.0), s2 = sin(-2.0 * 3.14159265358979323846 / 3.0); double tx2 = xr2 * c2 - yr2 * s2; yr2 = xr2 * s2 + yr2 * c2; xr2 = tx2; a = 0.5 * (xr2 * xr2 + (yr2 - fr) * (yr2 - fr) + z * z + tl * tl - sl * sl - pfr * pfr) / z; b = (fr - pfr - yr2) / z; d = tl * tl * (b * b + 1.0) - (a - b * pfr) * (a - b * pfr); if (d < 0.0) return SIM_KIN_ERR_RANGE; knee_y = (pfr + a * b - sqrt(d)) / (b * b + 1.0); knee_z = b * knee_y - a; set_joint(joints, k, 1, atan2(knee_z, knee_y - pfr) * RAD_TO_DEG); xr2 = x; yr2 = y; c2 = cos(2.0 * 3.14159265358979323846 / 3.0); s2 = sin(2.0 * 3.14159265358979323846 / 3.0); tx2 = xr2 * c2 - yr2 * s2; yr2 = xr2 * s2 + yr2 * c2; xr2 = tx2; a = 0.5 * (xr2 * xr2 + (yr2 - fr) * (yr2 - fr) + z * z + tl * tl - sl * sl - pfr * pfr) / z; b = (fr - pfr - yr2) / z; d = tl * tl * (b * b + 1.0) - (a - b * pfr) * (a - b * pfr); if (d < 0.0) return SIM_KIN_ERR_RANGE; knee_y = (pfr + a * b - sqrt(d)) / (b * b + 1.0); knee_z = b * knee_y - a; set_joint(joints, k, 2, atan2(knee_z, knee_y - pfr) * RAD_TO_DEG); } } if (branch != 0) *branch = (flags & SIM_KIN_DELTA_ALTERNATE_ROOT) ? 1U : 0U; return SIM_KIN_OK; } /* Point-vertex tripod kinematics from LinuxCNC's tripodkins.c. The three * strut lengths determine X/Y uniquely; the quadratic Z root is selected by * the forward flag (above-base by default, below-base when bit zero is set). */ static int tripod_forward(const sim_kinematics *k, const double *joints, uint64_t flags, sim_posemath_pose *pose, uint32_t *branch) { double bx = k->config.tripod_bx, cx = k->config.tripod_cx, cy = k->config.tripod_cy; double ad = joint(k, joints, 0), bd = joint(k, joints, 1), cd = joint(k, joints, 2); double p = ad * ad; double q = bd * bd - bx * bx; double r = cd * cd - cx * cx - cy * cy; double s = -2.0 * bx, t = -2.0 * cx, u = -2.0 * cy; double x, y, z2; if (fabs(s) < 1e-12 || fabs(u) < 1e-12) return SIM_KIN_ERR_CONFIGURATION; x = (q - p) / s; y = (r - q - (t - s) * x) / u; z2 = p - x * x - y * y; if (z2 < -1e-12) return SIM_KIN_ERR_RANGE; if (z2 <= 1e-12) return SIM_KIN_ERR_SINGULAR; pose->tran.x = x; pose->tran.y = y; pose->tran.z = (flags & SIM_KIN_TRIPOD_BELOW_ROOT) ? -sqrt(z2) : sqrt(z2); pose->a = 0.0; pose->b = 0.0; pose->c = 0.0; set_optional_axes(k, joints, pose); if (branch != 0) *branch = (flags & SIM_KIN_TRIPOD_BELOW_ROOT) ? 1U : 0U; return SIM_KIN_OK; } static int tripod_inverse(const sim_kinematics *k, const sim_posemath_pose *pose, double *joints, uint32_t *branch) { double bx = k->config.tripod_bx, cx = k->config.tripod_cx, cy = k->config.tripod_cy; double x = pose->tran.x, y = pose->tran.y, z = pose->tran.z; set_joint(joints, k, 0, sqrt(x * x + y * y + z * z)); set_joint(joints, k, 1, sqrt((x - bx) * (x - bx) + y * y + z * z)); set_joint(joints, k, 2, sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy) + z * z)); set_joint(joints, k, 3, pose->a); set_joint(joints, k, 4, pose->b); set_joint(joints, k, 5, pose->c); set_joint(joints, k, 6, pose->u); set_joint(joints, k, 7, pose->v); set_joint(joints, k, 8, pose->w); if (branch != 0) *branch = z < 0.0 ? 1U : 0U; return SIM_KIN_OK; } /* Generalized six-strut Stewart platform. Inverse kinematics is the * closed-form leg-length calculation; forward kinematics follows LinuxCNC's * Newton solve from a deterministic pose seed. The rotational Jacobian uses * the platform attachment vector crossed with the unit strut vector, which * is the small-angle derivative used by genhexkins.c. */ static int genhex_lengths(const sim_kinematics *k, const double q[6], double lengths[SIM_KIN_GENHEX_STRUTS], double jacobian[SIM_KIN_GENHEX_STRUTS][6]) { PmRpy rpy; PmRotationMatrix rotation; size_t index; rpy.r = q[3]; rpy.p = q[4]; rpy.y = q[5]; if (pmRpyMatConvert(&rpy, &rotation) != 0) return SIM_KIN_ERR_SINGULAR; for (index = 0; index < SIM_KIN_GENHEX_STRUTS; ++index) { PmCartesian platform = {k->config.genhex_platform[index][0], k->config.genhex_platform[index][1], k->config.genhex_platform[index][2]}; PmCartesian base = {k->config.genhex_base[index][0], k->config.genhex_base[index][1], k->config.genhex_base[index][2]}; PmCartesian rotated; PmCartesian world; PmCartesian vector; PmCartesian unit; PmCartesian cross; double length; pmMatCartMult(&rotation, &platform, &rotated); world.x = q[0] + rotated.x; world.y = q[1] + rotated.y; world.z = q[2] + rotated.z; vector.x = world.x - base.x; vector.y = world.y - base.y; vector.z = world.z - base.z; if (pmCartUnit(&vector, &unit) != 0 || pmCartMag(&vector, &length) != 0 || !isfinite(length)) return SIM_KIN_ERR_SINGULAR; lengths[index] = length; if (jacobian != 0) { pmCartCartCross(&rotated, &unit, &cross); jacobian[index][0] = unit.x; jacobian[index][1] = unit.y; jacobian[index][2] = unit.z; jacobian[index][3] = cross.x; jacobian[index][4] = cross.y; jacobian[index][5] = cross.z; } } return SIM_KIN_OK; } static int genhex_inverse(const sim_kinematics *k, const sim_posemath_pose *pose, double *joints) { double q[6] = {pose->tran.x, pose->tran.y, pose->tran.z, pose->a * DEG_TO_RAD, pose->b * DEG_TO_RAD, pose->c * DEG_TO_RAD}; double lengths[SIM_KIN_GENHEX_STRUTS]; size_t index; int result = genhex_lengths(k, q, lengths, 0); if (result != SIM_KIN_OK) return result; for (index = 0; index < SIM_KIN_GENHEX_STRUTS; ++index) joints[index] = lengths[index]; return SIM_KIN_OK; } static int genhex_forward(const sim_kinematics *k, const double *joints, uint64_t flags, sim_posemath_pose *pose, sim_kinematics_result_info *info) { double q[6]; double lengths[SIM_KIN_GENHEX_STRUTS]; double jacobian[SIM_KIN_GENHEX_STRUTS][6]; double residual = 0.0; uint32_t iteration; int result; for (size_t index = 0; index < 6; ++index) q[index] = k->config.genhex_initial_pose[index]; if (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) q[2] = -q[2]; for (iteration = 1; iteration <= k->config.genhex_iteration_limit; ++iteration) { double matrix[6][6]; double rhs[6]; double delta[6] = {0}; int row; result = genhex_lengths(k, q, lengths, jacobian); if (result != SIM_KIN_OK) return result; residual = 0.0; for (row = 0; row < (int)SIM_KIN_GENHEX_STRUTS; ++row) { double difference = lengths[row] - joints[row]; rhs[row] = difference; residual += fabs(difference); for (int column = 0; column < 6; ++column) matrix[row][column] = jacobian[row][column]; } if (residual <= k->config.genhex_convergence_tolerance) { pose->tran.x = q[0]; pose->tran.y = q[1]; pose->tran.z = q[2]; pose->a = q[3] * RAD_TO_DEG; pose->b = q[4] * RAD_TO_DEG; pose->c = q[5] * RAD_TO_DEG; pose->u = 0.0; pose->v = 0.0; pose->w = 0.0; if (info != 0) { info->iterations = iteration; info->residual = residual; info->branch_id = (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) ? 1U : 0U; } return SIM_KIN_OK; } if (residual > k->config.genhex_max_error) { if (info != 0) { info->iterations = iteration; info->residual = residual; info->branch_id = (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) ? 1U : 0U; } return SIM_KIN_ERR_RANGE; } result = solve_linear6(matrix, rhs, delta); if (result != SIM_KIN_OK) { if (info != 0) { info->iterations = iteration; info->residual = residual; info->branch_id = (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) ? 1U : 0U; } return result; } for (int column = 0; column < 6; ++column) q[column] -= delta[column]; } if (info != 0) { info->iterations = k->config.genhex_iteration_limit; info->residual = residual; info->branch_id = (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) ? 1U : 0U; } return SIM_KIN_ERR_RANGE; } static int solve_linear5(double matrix[5][5], double rhs[5], double result[5]) { for (int pivot = 0; pivot < 5; ++pivot) { int best = pivot; double magnitude = fabs(matrix[pivot][pivot]); for (int row = pivot + 1; row < 5; ++row) if (fabs(matrix[row][pivot]) > magnitude) { best = row; magnitude = fabs(matrix[row][pivot]); } if (magnitude < 1e-14) return SIM_KIN_ERR_SINGULAR; if (best != pivot) { for (int col = pivot; col < 5; ++col) { double tmp = matrix[pivot][col]; matrix[pivot][col] = matrix[best][col]; matrix[best][col] = tmp; } { double tmp = rhs[pivot]; rhs[pivot] = rhs[best]; rhs[best] = tmp; } } for (int row = pivot + 1; row < 5; ++row) { double factor = matrix[row][pivot] / matrix[pivot][pivot]; for (int col = pivot; col < 5; ++col) matrix[row][col] -= factor * matrix[pivot][col]; rhs[row] -= factor * rhs[pivot]; } } for (int row = 4; row >= 0; --row) { double value = rhs[row]; for (int col = row + 1; col < 5; ++col) value -= matrix[row][col] * result[col]; result[row] = value / matrix[row][row]; } return SIM_KIN_OK; } static int pentapod_lengths(const sim_kinematics *k, const double q[5], double lengths[SIM_KIN_PENTA_STRUTS]) { PmRpy rpy = {q[3], q[4], 0.0}; PmRotationMatrix rotation, inverse; if (pmRpyMatConvert(&rpy, &rotation) != 0 || pmMatInv(&rotation, &inverse) != 0) return SIM_KIN_ERR_SINGULAR; for (size_t index = 0; index < SIM_KIN_PENTA_STRUTS; ++index) { /* pentakins_read_hal_pins applies tool-offset to both ends of each * strut; preserve that world-frame translation before rotating into * the effector frame. */ PmCartesian base = {k->config.penta_base[index][0] - q[0], k->config.penta_base[index][1] - q[1], k->config.penta_base[index][2] + k->config.penta_tool_offset - q[2]}; PmCartesian local; double radial; pmMatCartMult(&inverse, &base, &local); radial = hypot(local.x, local.y); lengths[index] = hypot(radial - k->config.penta_effector_radius[index], local.z - (k->config.penta_effector_z[index] + k->config.penta_tool_offset)); if (!isfinite(lengths[index])) return SIM_KIN_ERR_NONFINITE; } return SIM_KIN_OK; } static int pentapod_inverse(const sim_kinematics *k, const sim_posemath_pose *pose, double *joints) { double q[5] = {pose->tran.x, pose->tran.y, pose->tran.z, pose->a * DEG_TO_RAD, pose->b * DEG_TO_RAD}; double lengths[SIM_KIN_PENTA_STRUTS]; int result = pentapod_lengths(k, q, lengths); if (result != SIM_KIN_OK) return result; for (size_t index = 0; index < SIM_KIN_PENTA_STRUTS; ++index) joints[index] = lengths[index]; return SIM_KIN_OK; } static int pentapod_forward(const sim_kinematics *k, const double *joints, uint64_t flags, sim_posemath_pose *pose, sim_kinematics_result_info *info) { double q[5]; double lengths[SIM_KIN_PENTA_STRUTS]; double residual = 0.0; uint32_t iteration; for (size_t index = 0; index < SIM_KIN_PENTA_STRUTS; ++index) { /* LinuxCNC's pentakins rejects non-positive strut lengths before * entering Newton iteration. */ if (joints[index] <= 0.0) { if (info != 0) { info->iterations = 0; info->residual = 0.0; info->branch_id = (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) ? 1U : 0U; } return SIM_KIN_ERR_RANGE; } } for (size_t index = 0; index < 5; ++index) q[index] = k->config.penta_initial_pose[index]; if (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) q[2] = -q[2]; for (iteration = 1; iteration <= k->config.penta_iteration_limit; ++iteration) { double jacobian[5][5]; double rhs[5]; double delta[5] = {0}; int result = pentapod_lengths(k, q, lengths); if (result != SIM_KIN_OK) return result; residual = 0.0; for (int row = 0; row < 5; ++row) { double perturbed[5]; rhs[row] = lengths[row] - joints[row]; residual += fabs(rhs[row]); for (int column = 0; column < 5; ++column) { double next[SIM_KIN_PENTA_STRUTS]; memcpy(perturbed, q, sizeof(perturbed)); perturbed[column] += 1e-4; if (pentapod_lengths(k, perturbed, next) != SIM_KIN_OK) return SIM_KIN_ERR_SINGULAR; jacobian[row][column] = (next[row] - lengths[row]) * 1e4; } } if (residual <= k->config.penta_convergence_tolerance) { pose->tran.x = pentapod_canonical(q[0]); pose->tran.y = pentapod_canonical(q[1]); pose->tran.z = pentapod_canonical(q[2]); pose->a = pentapod_canonical(q[3] * RAD_TO_DEG); pose->b = pentapod_canonical(q[4] * RAD_TO_DEG); pose->c = 0.0; pose->u = pose->v = pose->w = 0.0; if (info != 0) { info->iterations = iteration; info->residual = nearbyint(residual * 1000000000.0) / 1000000000.0; info->branch_id = (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) ? 1U : 0U; } return SIM_KIN_OK; } if (residual > k->config.penta_max_error) { if (info != 0) { info->iterations = iteration; info->residual = residual; info->branch_id = (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) ? 1U : 0U; } return SIM_KIN_ERR_RANGE; } if (solve_linear5(jacobian, rhs, delta) != SIM_KIN_OK) return SIM_KIN_ERR_SINGULAR; for (int column = 0; column < 5; ++column) q[column] -= delta[column]; } if (info != 0) { info->iterations = k->config.penta_iteration_limit; info->residual = residual; info->branch_id = (flags & SIM_KIN_GENHEX_ALTERNATE_SEED) ? 1U : 0U; } return SIM_KIN_ERR_RANGE; } int sim_kinematics_forward_ex(const sim_kinematics *k, const double *joints, uint32_t joint_count, uint64_t inverse_flags, sim_posemath_pose *pose, sim_kinematics_result_info *info) { double a, b, c, x, y, z; initialize_info(info, 0, inverse_flags); if (k == 0 || joints == 0 || pose == 0) return SIM_KIN_ERR_ARGUMENT; if (!k->configured || joint_count != kinematics_joint_count(&k->config)) return SIM_KIN_ERR_CONFIGURATION; if (effective_mode(&k->config) == SIM_KIN_MODE_INVERSE_ONLY) return SIM_KIN_ERR_UNSUPPORTED; if (!finite_values(joints, joint_count)) return SIM_KIN_ERR_NONFINITE; if (in_limits(k, joints) != SIM_KIN_OK) return SIM_KIN_ERR_RANGE; zero_pose(pose); x = joint(k, joints, 0); y = joint(k, joints, 1); z = joint(k, joints, 2); if (k->config.type == SIM_KIN_TRIVKINS) { pose->tran.x = x; pose->tran.y = y; pose->tran.z = z; set_optional_axes(k, joints, pose); return SIM_KIN_OK; } if (k->config.type == SIM_KIN_SCARA) return scara_forward(k, joints, pose); if (k->config.type == SIM_KIN_SCORBOT) return scorbot_forward(k, joints, pose); if (k->config.type == SIM_KIN_PUMA) { uint32_t branch = 0; int result = puma_forward(k, joints, pose, &branch); if (info != 0) info->branch_id = branch; return result; } if (k->config.type == SIM_KIN_GENSER) return genser_forward(k, joints, pose); if (k->config.type == SIM_KIN_LINEAR_DELTA) { uint32_t branch = 0; int result = linear_delta_forward(k, joints, inverse_flags, pose, &branch); if (info != 0) { info->branch_id = branch; info->iterations = 0; info->residual = 0.0; } return result; } if (k->config.type == SIM_KIN_ROTARY_DELTA) { uint32_t branch = 0; int result = rotary_delta_forward(k, joints, inverse_flags, pose, &branch); if (info != 0) { info->branch_id = branch; info->iterations = 0; info->residual = 0.0; } return result; } if (k->config.type == SIM_KIN_TRIPOD) { uint32_t branch = 0; int result = tripod_forward(k, joints, inverse_flags, pose, &branch); if (info != 0) { info->branch_id = branch; info->iterations = 0; info->residual = 0.0; } return result; } if (k->config.type == SIM_KIN_GENHEX) { return genhex_forward(k, joints, inverse_flags, pose, info); } if (k->config.type == SIM_KIN_PENTAPOD) { return pentapod_forward(k, joints, inverse_flags, pose, info); } if (k->config.type == SIM_KIN_COREXY) { pose->tran.x = 0.5 * (x + y); pose->tran.y = 0.5 * (x - y); pose->tran.z = z; set_optional_axes(k, joints, pose); return SIM_KIN_OK; } if (k->config.type == SIM_KIN_ROSE) { double theta = z * DEG_TO_RAD; pose->tran.x = x * cos(theta); pose->tran.y = x * sin(theta); pose->tran.z = y; return sim_kinematics_validate_pose(pose); } c = joint(k, joints, 5) * DEG_TO_RAD; if (k->config.type == SIM_KIN_XYZAC_TRT) { double dy = k->config.y_offset; double dz = k->config.z_offset + k->config.tool_offset; a = joint(k, joints, 3) * DEG_TO_RAD; pose->tran.x = cos(c) * (x - k->config.x_rot_point) + sin(c) * cos(a) * (y - dy - k->config.y_rot_point) + sin(c) * sin(a) * (z - dz - k->config.z_rot_point) + sin(c) * dy + k->config.x_rot_point; pose->tran.y = -sin(c) * (x - k->config.x_rot_point) + cos(c) * cos(a) * (y - dy - k->config.y_rot_point) + cos(c) * sin(a) * (z - dz - k->config.z_rot_point) + cos(c) * dy + k->config.y_rot_point; pose->tran.z = -sin(a) * (y - dy - k->config.y_rot_point) + cos(a) * (z - dz - k->config.z_rot_point) + dz + k->config.z_rot_point; } else if (k->config.type == SIM_KIN_XYZBC_TRT) { double dx = k->config.x_offset; double dz = k->config.z_offset + k->config.tool_offset; b = joint(k, joints, 4) * DEG_TO_RAD; pose->tran.x = cos(c) * cos(b) * (x - dx - k->config.x_rot_point) + sin(c) * (y - k->config.y_rot_point) - cos(c) * sin(b) * (z - dz - k->config.z_rot_point) + cos(c) * dx + k->config.x_rot_point; pose->tran.y = -sin(c) * cos(b) * (x - dx - k->config.x_rot_point) + cos(c) * (y - k->config.y_rot_point) + sin(c) * sin(b) * (z - dz - k->config.z_rot_point) - sin(c) * dx + k->config.y_rot_point; pose->tran.z = sin(b) * (x - dx - k->config.x_rot_point) + cos(b) * (z - dz - k->config.z_rot_point) + dz + k->config.z_rot_point; } else if (k->config.type == SIM_KIN_XYZBCW || k->config.type == SIM_KIN_5AXISKINS) { double radius = k->config.pivot_length + joint(k, joints, 8); double theta = c; double phi = (180.0 - joint(k, joints, 4)) * DEG_TO_RAD; double rx = radius * sin(phi) * cos(theta); double ry = radius * sin(phi) * sin(theta); double rz = radius * cos(phi); pose->tran.x = x + rx; pose->tran.y = y + ry; pose->tran.z = z + k->config.pivot_length + rz; } else if (k->config.type == SIM_KIN_MAXKINS) { double pivot_radius = k->config.pivot_length + joint(k, joints, 8); double zb; double xb; double xyr; double xytheta; double zv; double xv; b = joint(k, joints, 4) * DEG_TO_RAD; zb = pivot_radius * cos(b); xb = pivot_radius * sin(b); xyr = hypot(x, y); xytheta = atan2(y, x) + c; zv = joint(k, joints, 6) * sin(b); xv = joint(k, joints, 6) * cos(b); pose->tran.x = xyr * cos(xytheta) + xb - xv; pose->tran.y = xyr * sin(xytheta) - joint(k, joints, 7); pose->tran.z = z - zb + zv + k->config.pivot_length; } else if (k->config.type == SIM_KIN_ROTATEKINS) { pose->tran.x = x * cos(-c) - y * sin(-c); pose->tran.y = x * sin(-c) + y * cos(-c); pose->tran.z = z; } else return SIM_KIN_ERR_UNKNOWN_TYPE; set_optional_axes(k, joints, pose); return sim_kinematics_validate_pose(pose); } int sim_kinematics_forward(const sim_kinematics *k, const double *joints, uint32_t joint_count, sim_posemath_pose *pose) { return sim_kinematics_forward_ex(k, joints, joint_count, 0, pose, 0); } static void set_joint(double *joints, const sim_kinematics *k, int axis, double value) { int index = config_axis(k, axis); if (index >= 0) joints[index] = value; } static void fill_duplicate_joints(const sim_kinematics *k, double *joints) { uint32_t index; for (index = 0; index < kinematics_joint_count(&k->config); ++index) { int axis = axis_index((char)(k->config.coordinates[index] >= 'a' && k->config.coordinates[index] <= 'z' ? k->config.coordinates[index] - 'a' + 'A' : k->config.coordinates[index])); if (axis >= 0) joints[index] = joints[config_axis(k, axis)]; } } int sim_kinematics_inverse_ex(sim_kinematics *k, const sim_posemath_pose *pose, uint32_t joint_count, uint64_t forward_flags, double *joints, sim_kinematics_result_info *info) { double a, b, c; initialize_info(info, forward_flags, 0); if (k == 0 || pose == 0 || joints == 0) return SIM_KIN_ERR_ARGUMENT; if (!k->configured || joint_count != kinematics_joint_count(&k->config)) return SIM_KIN_ERR_CONFIGURATION; if (effective_mode(&k->config) == SIM_KIN_MODE_FORWARD_ONLY) return SIM_KIN_ERR_UNSUPPORTED; if (sim_kinematics_validate_pose(pose) != SIM_KIN_OK) return SIM_KIN_ERR_NONFINITE; memset(joints, 0, sizeof(double) * joint_count); if (k->config.type == SIM_KIN_TRIVKINS) { set_joint(joints, k, 0, pose->tran.x); set_joint(joints, k, 1, pose->tran.y); set_joint(joints, k, 2, pose->tran.z); set_joint(joints, k, 3, pose->a); set_joint(joints, k, 4, pose->b); set_joint(joints, k, 5, pose->c); set_joint(joints, k, 6, pose->u); set_joint(joints, k, 7, pose->v); set_joint(joints, k, 8, pose->w); } else if (k->config.type == SIM_KIN_SCARA) { uint32_t branch = 0; int result = scara_inverse(k, pose, joints, forward_flags, &branch); if (info != 0) info->branch_id = branch; return result == SIM_KIN_OK ? in_limits(k, joints) : result; } else if (k->config.type == SIM_KIN_SCORBOT) { int result = scorbot_inverse(k, pose, joints); return result == SIM_KIN_OK ? in_limits(k, joints) : result; } else if (k->config.type == SIM_KIN_PUMA) { uint32_t branch = 0; int result = puma_inverse(k, pose, joints, forward_flags, &branch); if (info != 0) info->branch_id = branch; return result == SIM_KIN_OK ? in_limits(k, joints) : result; } else if (k->config.type == SIM_KIN_GENSER) { return genser_inverse(k, pose, joints, k->config.max_iterations, k->config.convergence_tolerance, info); } else if (k->config.type == SIM_KIN_LINEAR_DELTA) { uint32_t branch = 0; int result = linear_delta_inverse(k, pose, forward_flags, joints, &branch); if (info != 0) { info->branch_id = branch; info->iterations = 0; info->residual = 0.0; } if (result == SIM_KIN_OK) k->branch_id = branch; return result == SIM_KIN_OK ? in_limits(k, joints) : result; } else if (k->config.type == SIM_KIN_ROTARY_DELTA) { uint32_t branch = 0; int result = rotary_delta_inverse(k, pose, forward_flags, joints, &branch); if (info != 0) { info->branch_id = branch; info->iterations = 0; info->residual = 0.0; } if (result == SIM_KIN_OK) k->branch_id = branch; return result == SIM_KIN_OK ? in_limits(k, joints) : result; } else if (k->config.type == SIM_KIN_TRIPOD) { uint32_t branch = 0; int result = tripod_inverse(k, pose, joints, &branch); if (info != 0) { info->forward_flags = branch; info->branch_id = branch; info->iterations = 0; info->residual = 0.0; } if (result == SIM_KIN_OK) k->branch_id = branch; return result == SIM_KIN_OK ? in_limits(k, joints) : result; } else if (k->config.type == SIM_KIN_GENHEX) { int result = genhex_inverse(k, pose, joints); if (info != 0) { info->forward_flags = pose->tran.z < 0.0 ? SIM_KIN_GENHEX_ALTERNATE_SEED : 0; info->branch_id = pose->tran.z < 0.0 ? 1U : 0U; info->iterations = 0; info->residual = 0.0; } if (result == SIM_KIN_OK) k->branch_id = pose->tran.z < 0.0 ? 1U : 0U; return result == SIM_KIN_OK ? in_limits(k, joints) : result; } else if (k->config.type == SIM_KIN_PENTAPOD) { int result = pentapod_inverse(k, pose, joints); if (info != 0) { info->forward_flags = pose->tran.z < 0.0 ? SIM_KIN_GENHEX_ALTERNATE_SEED : 0; info->branch_id = pose->tran.z < 0.0 ? 1U : 0U; info->iterations = 0; info->residual = 0.0; } if (result == SIM_KIN_OK) k->branch_id = pose->tran.z < 0.0 ? 1U : 0U; return result == SIM_KIN_OK ? in_limits(k, joints) : result; } else if (k->config.type == SIM_KIN_COREXY) { set_joint(joints, k, 0, pose->tran.x + pose->tran.y); set_joint(joints, k, 1, pose->tran.x - pose->tran.y); set_joint(joints, k, 2, pose->tran.z); set_joint(joints, k, 3, pose->a); set_joint(joints, k, 4, pose->b); set_joint(joints, k, 5, pose->c); set_joint(joints, k, 6, pose->u); set_joint(joints, k, 7, pose->v); set_joint(joints, k, 8, pose->w); } else if (k->config.type == SIM_KIN_ROSE) { uint32_t quadrant; double theta; double bigtheta; if (pose->tran.x >= 0.0 && pose->tran.y >= 0.0) quadrant = 1; else if (pose->tran.x < 0.0 && pose->tran.y >= 0.0) quadrant = 2; else if (pose->tran.x < 0.0 && pose->tran.y < 0.0) quadrant = 3; else quadrant = 4; if (k->previous_quadrant == 2 && quadrant == 3) k->revolution_count += 1; if (k->previous_quadrant == 3 && quadrant == 2) k->revolution_count -= 1; theta = atan2(pose->tran.y, pose->tran.x); bigtheta = theta + 2.0 * 3.14159265358979323846264338327950288 * k->revolution_count; set_joint(joints, k, 0, hypot(pose->tran.x, pose->tran.y)); set_joint(joints, k, 1, pose->tran.z); set_joint(joints, k, 2, bigtheta * RAD_TO_DEG); k->previous_quadrant = quadrant; k->branch_id = (uint32_t)k->revolution_count; if (info != 0) info->branch_id = k->branch_id; } else if (k->config.type == SIM_KIN_XYZAC_TRT) { double dy = k->config.y_offset; double dz = k->config.z_offset + k->config.tool_offset; a = pose->a * DEG_TO_RAD; c = pose->c * DEG_TO_RAD; set_joint(joints, k, 3, pose->a); set_joint(joints, k, 5, pose->c); set_joint(joints, k, 0, cos(c) * (pose->tran.x - k->config.x_rot_point) - sin(c) * (pose->tran.y - k->config.y_rot_point) + k->config.x_rot_point); set_joint(joints, k, 1, sin(c) * cos(a) * (pose->tran.x - k->config.x_rot_point) + cos(c) * cos(a) * (pose->tran.y - k->config.y_rot_point) - sin(a) * (pose->tran.z - k->config.z_rot_point) - cos(a) * dy + sin(a) * dz + dy + k->config.y_rot_point); set_joint(joints, k, 2, sin(c) * sin(a) * (pose->tran.x - k->config.x_rot_point) + cos(c) * sin(a) * (pose->tran.y - k->config.y_rot_point) + cos(a) * (pose->tran.z - k->config.z_rot_point) - sin(a) * dy - cos(a) * dz + dz + k->config.z_rot_point); } else if (k->config.type == SIM_KIN_XYZBC_TRT) { double dx = k->config.x_offset; double dz = k->config.z_offset + k->config.tool_offset; b = pose->b * DEG_TO_RAD; c = pose->c * DEG_TO_RAD; if (fabs(cos(b)) < 1e-12) return SIM_KIN_ERR_SINGULAR; set_joint(joints, k, 4, pose->b); set_joint(joints, k, 5, pose->c); set_joint(joints, k, 0, cos(c) * cos(b) * (pose->tran.x - k->config.x_rot_point) - sin(c) * cos(b) * (pose->tran.y - k->config.y_rot_point) + sin(b) * (pose->tran.z - k->config.z_rot_point) - cos(b) * dx - sin(b) * dz + dx + k->config.x_rot_point); set_joint(joints, k, 1, sin(c) * (pose->tran.x - k->config.x_rot_point) + cos(c) * (pose->tran.y - k->config.y_rot_point) + k->config.y_rot_point); set_joint(joints, k, 2, -cos(c) * sin(b) * (pose->tran.x - k->config.x_rot_point) + sin(c) * sin(b) * (pose->tran.y - k->config.y_rot_point) + cos(b) * (pose->tran.z - k->config.z_rot_point) + sin(b) * dx - cos(b) * dz + dz + k->config.z_rot_point); } else if (k->config.type == SIM_KIN_XYZBCW || k->config.type == SIM_KIN_5AXISKINS) { double radius = k->config.pivot_length + pose->w; double phi = (180.0 - pose->b) * DEG_TO_RAD; double theta = pose->c * DEG_TO_RAD; set_joint(joints, k, 4, pose->b); set_joint(joints, k, 5, pose->c); set_joint(joints, k, 8, pose->w); set_joint(joints, k, 0, pose->tran.x - radius * sin(phi) * cos(theta)); set_joint(joints, k, 1, pose->tran.y - radius * sin(phi) * sin(theta)); set_joint(joints, k, 2, pose->tran.z - k->config.pivot_length - radius * cos(phi)); } else if (k->config.type == SIM_KIN_MAXKINS) { double pivot_radius = k->config.pivot_length + pose->w; double zb; double xb; double xyr; double xytheta; double zv; double xv; b = pose->b * DEG_TO_RAD; c = pose->c * DEG_TO_RAD; zb = pivot_radius * cos(b); xb = pivot_radius * sin(b); xyr = hypot(pose->tran.x, pose->tran.y); xytheta = atan2(pose->tran.y, pose->tran.x) - c; zv = pose->u * sin(b); xv = pose->u * cos(b); set_joint(joints, k, 0, xyr * cos(xytheta) - xb + xv); set_joint(joints, k, 1, xyr * sin(xytheta) + pose->v); set_joint(joints, k, 2, pose->tran.z + zb + zv - k->config.pivot_length); set_joint(joints, k, 4, pose->b); set_joint(joints, k, 5, pose->c); set_joint(joints, k, 8, pose->w); } else if (k->config.type == SIM_KIN_ROTATEKINS) { c = pose->c * DEG_TO_RAD; set_joint(joints, k, 0, pose->tran.x * cos(c) - pose->tran.y * sin(c)); set_joint(joints, k, 1, pose->tran.x * sin(c) + pose->tran.y * cos(c)); set_joint(joints, k, 2, pose->tran.z); set_joint(joints, k, 4, pose->b); set_joint(joints, k, 5, pose->c); set_joint(joints, k, 8, pose->w); } else return SIM_KIN_ERR_UNKNOWN_TYPE; set_joint(joints, k, 3, pose->a); set_joint(joints, k, 6, pose->u); set_joint(joints, k, 7, pose->v); fill_duplicate_joints(k, joints); return in_limits(k, joints); } int sim_kinematics_inverse(sim_kinematics *k, const sim_posemath_pose *pose, double *joints, uint32_t joint_count) { return sim_kinematics_inverse_ex(k, pose, joint_count, 0, joints, 0); } int sim_kinematics_home(const sim_kinematics *k, double *joints, uint32_t joint_count, sim_posemath_pose *pose) { if (k == 0 || joints == 0 || pose == 0) return SIM_KIN_ERR_ARGUMENT; if (!k->configured || joint_count != kinematics_joint_count(&k->config)) return SIM_KIN_ERR_CONFIGURATION; memset(joints, 0, sizeof(double) * joint_count); if (k->config.type == SIM_KIN_XYZBCW || k->config.type == SIM_KIN_5AXISKINS) joints[config_axis(k, 8)] = 0.0; if (k->config.type == SIM_KIN_TRIPOD) { double z = 1.0; joints[config_axis(k, 0)] = z; joints[config_axis(k, 1)] = hypot(k->config.tripod_bx, z); joints[config_axis(k, 2)] = hypot(hypot(k->config.tripod_cx, k->config.tripod_cy), z); } if (k->config.type == SIM_KIN_GENHEX) { sim_posemath_pose seed = {0}; seed.tran.z = k->config.genhex_initial_pose[2]; if (sim_kinematics_inverse((sim_kinematics *)k, &seed, joints, joint_count) != SIM_KIN_OK) return SIM_KIN_ERR_RANGE; } if (k->config.type == SIM_KIN_PENTAPOD) { sim_posemath_pose seed = {0}; seed.tran.x = k->config.penta_initial_pose[0]; seed.tran.y = k->config.penta_initial_pose[1]; seed.tran.z = k->config.penta_initial_pose[2]; seed.a = k->config.penta_initial_pose[3] * RAD_TO_DEG; seed.b = k->config.penta_initial_pose[4] * RAD_TO_DEG; if (sim_kinematics_inverse((sim_kinematics *)k, &seed, joints, joint_count) != SIM_KIN_OK) return SIM_KIN_ERR_RANGE; } return sim_kinematics_forward(k, joints, joint_count, pose); } uint64_t sim_kinematics_state_hash(const sim_kinematics *k, const double *joints, uint32_t joint_count, const sim_posemath_pose *pose) { unsigned char bytes[sizeof(sim_kinematics_config) + sizeof(double) * SIM_KIN_MAX_JOINTS + sizeof(sim_posemath_pose) + sizeof(int32_t) + sizeof(uint32_t) * 2]; double normalized_joints[SIM_KIN_MAX_JOINTS] = {0}; sim_posemath_pose normalized_pose; size_t offset = 0; if (k == 0 || joints == 0 || pose == 0 || joint_count > SIM_KIN_MAX_JOINTS) return 0; for (size_t index = 0; index < joint_count; ++index) normalized_joints[index] = hash_normalize(joints[index]); normalized_pose = *pose; normalized_pose.tran.x = hash_normalize(normalized_pose.tran.x); normalized_pose.tran.y = hash_normalize(normalized_pose.tran.y); normalized_pose.tran.z = hash_normalize(normalized_pose.tran.z); normalized_pose.a = hash_normalize(normalized_pose.a); normalized_pose.b = hash_normalize(normalized_pose.b); normalized_pose.c = hash_normalize(normalized_pose.c); normalized_pose.u = hash_normalize(normalized_pose.u); normalized_pose.v = hash_normalize(normalized_pose.v); normalized_pose.w = hash_normalize(normalized_pose.w); memcpy(bytes + offset, &k->config, sizeof(k->config)); offset += sizeof(k->config); memcpy(bytes + offset, normalized_joints, sizeof(double) * joint_count); offset += sizeof(double) * joint_count; memcpy(bytes + offset, &normalized_pose, sizeof(normalized_pose)); offset += sizeof(normalized_pose); memcpy(bytes + offset, &k->revolution_count, sizeof(k->revolution_count)); offset += sizeof(k->revolution_count); memcpy(bytes + offset, &k->previous_quadrant, sizeof(k->previous_quadrant)); offset += sizeof(k->previous_quadrant); memcpy(bytes + offset, &k->branch_id, sizeof(k->branch_id)); offset += sizeof(k->branch_id); return fnv1a64(bytes, offset); }