/* ========================================================================
* Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* 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.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
namespace Robotics
{
///
/// Inverse kinematics for the sample arm, used to place the tool centre point over a
/// table slot.
///
///
/// Only the shoulder and elbow are solved. The wrist is then set so the tool points
/// straight down, which is how a part is picked off and set onto a table. The solution
/// is checked against by the tests, so the arm provably
/// reaches the slot rather than merely looking as though it does.
///
public static class RobotArmSolver
{
private const double UpperArm = 0.6800;
private const double ForearmX = 0.6700;
private const double ForearmZ = -0.0350;
///
/// The distance from the wrist to the tool centre point, in metres.
///
public const double WristToToolCentrePoint = 0.1580 + RobotKinematics.ToolCentrePointOffsetX;
///
/// Solves the arm so the tool centre point sits at a target expressed in the robot's
/// own frame.
///
///
/// The horizontal distance ahead of the platform, in metres.
///
/// The horizontal offset to the left, in metres.
/// The height above the platform origin, in metres.
/// The six axis positions, in degrees.
/// true when the target is reachable.
public static bool TrySolve(
double forward,
double lateral,
double height,
Span axesDegrees)
{
if (axesDegrees.Length != RobotKinematics.AxisCount)
{
throw new ArgumentException(
$"Expected {RobotKinematics.AxisCount} axis positions.",
nameof(axesDegrees));
}
// The shoulder sits above the platform origin; everything below is solved in the
// vertical plane the first axis swings to.
double azimuth = Math.Atan2(lateral, forward);
double planar = Math.Sqrt((forward * forward) + (lateral * lateral));
const double shoulderHeight = 0.3650 + 0.2900 + 0.3850;
const double shoulderForward = 0.2600;
// The wrist has to sit above the target by the tool length, so the tool points
// straight down onto the slot.
double u = planar - shoulderForward;
double v = shoulderHeight - (height + WristToToolCentrePoint);
double forearm = Math.Sqrt((ForearmX * ForearmX) + (ForearmZ * ForearmZ));
double beta = Math.Atan2(-ForearmZ, ForearmX);
double distanceSquared = (u * u) + (v * v);
double cosGamma = (distanceSquared - (UpperArm * UpperArm) - (forearm * forearm)) /
(2.0 * UpperArm * forearm);
if (cosGamma is < -1.0 or > 1.0)
{
return false;
}
double gamma = Math.Acos(cosGamma);
double a2 = Math.Atan2(v, u) -
Math.Atan2(forearm * Math.Sin(gamma), UpperArm + (forearm * Math.Cos(gamma)));
double a3 = gamma - beta;
const double toDegrees = 180.0 / Math.PI;
axesDegrees[0] = azimuth * toDegrees;
axesDegrees[1] = a2 * toDegrees;
axesDegrees[2] = a3 * toDegrees;
axesDegrees[3] = 0.0;
axesDegrees[4] = 90.0 - ((a2 + a3) * toDegrees);
axesDegrees[5] = 0.0;
return true;
}
}
}