A C++17 simulation and visualization project for a 3-DOF, two-link manipulator with a fixed base in 3D space. The arm executes a simple pick-and-place loop using an analytic inverse kinematics solver and a linear end-effector trajectory.
- Joint 0 (base): revolute yaw about the +Z axis at the origin
(0,0,0). - Joints 1–2 (links): revolute pitch angles defining motion in the arm’s vertical plane (relative to the X–Y plane).
- IK: reduces the 3D target to a 2D planar problem in
(r,z), solves with the law of cosines, and supports elbow-up / elbow-down branches. - Visualization: rendered with raylib (fetched by CMake), including an overlay panel and a simple suction-tool “ball” pick-and-place animation.
Build system: CMake • Rendering: raylib • Language: C++17 • IK: Closed-form
- About this repository
- Repository structure
- Robotics: kinematics, dynamics, and inverse kinematics
- Building the project
- Running the simulator
- Repository file guide (full explanation)
- Simulation video
- Troubleshooting
This project simulates a 3-DOF, two-link manipulator mounted on a fixed base at the origin:
- The base joint rotates around the Z axis (yaw).
- The two link joints rotate as pitch angles (relative to the X–Y plane) in the arm’s vertical plane.
- The end-effector (EE) tracks a sequence of target points with analytic inverse kinematics.
- A small “ball” is used to visualize a pick-and-place loop:
- HOME → START
- PICK at START (attach ball)
- START → GOAL
- PLACE at GOAL (detach ball)
- GOAL → HOME
- wait briefly and repeat
User inputs (via console prompt):
- START position
(x y z)and GOAL position(x y z) - Constraint enforced by IK: z must be ≥ 0
- Reachability enforced by IK:
|p|must be within the arm’s reachable shell.
Manipulator3D/
CMakeLists.txt
README.md
src/
main.cpp
robot/
RobotArm.h
RobotArm.cpp
sim/
Trajectory.h
Trajectory.cpp
ui/
Overlay.h
Overlay.cpp
render/
DrawUtils.h
DrawUtils.cpp- World frame origin is at the center of the base revolute joint: (0,0,0).
- Joint angles:
q0_yaw: rotation about +Z (sets the arm’s radial direction in the X–Y plane)q1_pitch: shoulder elevation angle relative to the X–Y planeq2_pitch: elbow pitch angle (relative bend in the same vertical plane)
We use the radial unit direction in the X–Y plane:
Let link lengths be
- Elbow position:
- End-effector position:
This is implemented in:
robot::RobotArm::ForwardKinematics()
This manipulator is effectively a 2-link arm in a vertical plane, rotated by yaw. Therefore the reachable radius must satisfy:
The implementation enforces:
target.z >= 0|p|within[MinReach(), MaxReach()]
Given target
Step A — base yaw
Step B — reduce to planar IK in
Now solve a 2-link planar problem for the triangle formed by
Step C — elbow angle from law of cosines
Clamp to
This sign is the elbow-up / elbow-down branch selection.
Step D — shoulder angle
Define:
Then:
This is implemented in:
robot::RobotArm::SolveIK()
Practical note (this repo):
- The main program uses the default elbow branch (elbow-down) by passing
elbowUp = false.
This repository is primarily a kinematic + trajectory simulator:
- The EE follows a commanded Cartesian trajectory.
- IK converts EE targets into joint angles.
- FK is used for rendering joint/link positions.
However, the code also defines mass and inertia properties for each link (uniform rod approximations):
- About center of mass:
- About the joint at one end:
These are computed in:
robot::LinkParams::RecomputeInertia()
Currently, those values are used for display/inspection (overlay panel) and as a foundation for extending the project to:
- forward dynamics (torques → accelerations),
- gravity and Coriolis terms,
- joint-space controllers (PD, computed torque, etc.).
- CMake 3.20+
- A C++17 compiler (MSVC / Clang / GCC)
- raylib is fetched automatically by CMake (FetchContent)
This project supports out-of-source builds:
mkdir -p build
cd build
cmake ..
cmake --build . --config ReleaseFrom the build/ directory:
./Release/Manipulator3D.exeNotes (important across platforms/generators):
- The
./Release/Manipulator3D.exepath is typical for multi-config generators (e.g., Visual Studio). - On single-config generators (Makefiles/Ninja), the output is often simply:
./Manipulator3D(Linux/macOS), or.\Manipulator3D.exe(Windows)
- At startup, the console asks for:
- START
(x y z)and GOAL(x y z)
- START
- Mouse wheel: zoom camera
- F11: toggle fullscreen
- Overlay includes a PAUSE/PLAY button and reachability feedback
This section explains every important file in the repository and its role.
Key responsibilities:
- sets C++17 standard
- fetches and builds raylib 5.0 using CMake FetchContent
- builds the executable
Manipulator3Dfrom:src/main.cppsrc/robot/RobotArm.cppsrc/sim/Trajectory.cppsrc/ui/Overlay.cppsrc/render/DrawUtils.cpp
- links raylib and includes
src/as an include directory
This is the “application” entry point and runtime loop:
- prompts user for START and GOAL targets
- validates reachability using IK for:
- fixed HOME EE point
- START
- GOAL
- defines a pick-and-place finite-state machine:
- HOME → START → PICK → GOAL → PLACE → HOME → WAIT → LOOP
- generates a linear Cartesian trajectory between targets
- runs IK each frame to get joint angles for the current EE target
- calls FK for rendering joint/link positions
- renders:
- robot geometry, axes, ball, suction tool
- overlay panel (status, parameters, pause button)
Declares the robot model and kinematics API:
LinkParams: link length, mass, inertia approximations (rod model)JointAngles: the 3 joint variables (yaw + 2 pitches)FKResult: base/joints/EE positions for renderingIKResult: reachability + solution angles + messageRobotArmclass:SolveIK()ForwardKinematics()- reach limits:
MinReach(),MaxReach()
Implements analytic FK and IK:
- IK:
- rejects invalid targets (z < 0)
- checks radius bounds (min/max reach)
- computes yaw from
atan2(y,x) - reduces to planar IK in
(r,z) - solves elbow angle via law of cosines
- solves shoulder angle via triangle decomposition
- FK:
- constructs radial axis from yaw
- builds elbow and EE positions from link lengths and pitch angles
Declares a minimal trajectory generator:
sim::LinearTrajectory:Reset(from,to,duration)Update(dt)Position()Finished()
Implements linear interpolation in 3D:
- uses a normalized time parameter:
$\alpha = \mathrm{clamp}(t/T, 0, 1)$
- outputs:
$\mathbf{p}(\alpha) = \mathbf{a} + (\mathbf{b}-\mathbf{a})\alpha$
Declares UI overlay:
OverlayStatus:- reachability flags (START/GOAL)
- runtime error text
- phase label text
DrawOverlayPanel(...):- draws a panel with link properties, workspace limits, and status
- returns updated paused state
Implements the overlay rendering:
- draws a semi-transparent panel
- displays:
- link lengths, masses, inertias
- workspace bounds
- start/goal norms and coordinates
- phase text and reachability warnings
- implements a clickable PAUSE/PLAY button
Declares rendering helpers:
- text helpers:
DrawTextBold()DrawTextSmall()
- robot visuals:
DrawRobotBasePedestal()DrawRobotJointHousing()DrawTaperedLink()DrawSuctionTool()
Implements rendering utilities using raylib primitives:
- “machined” robot look:
- pedestal + base flange
- joint housings (sphere + collar)
- tapered link cylinders with end caps
- suction tool at the EE
Below is a link to the simulation video on YouTube.
- Make sure you have a working C/C++ toolchain installed (MSVC Build Tools / Xcode CLT / GCC).
- Delete the
build/folder and reconfigure:rm -rf build mkdir build && cd build cmake ..
- That path is typical for Visual Studio multi-config builds.
- Try running from
build/:- Windows:
.\Manipulator3D.exeor.\Release\Manipulator3D.exe - Linux/macOS:
./Manipulator3D
- Windows:
- Ensure:
z >= 0|p|is within the workspace shell:[|L1 - L2|, L1 + L2]