diff --git a/source/RobotAPI/libraries/core/CMakeLists.txt b/source/RobotAPI/libraries/core/CMakeLists.txt
index de555d6f62ccbc533487c91e37a55440947b1f7f..824c8063e03b459c1eeabd706577e7122284099e 100644
--- a/source/RobotAPI/libraries/core/CMakeLists.txt
+++ b/source/RobotAPI/libraries/core/CMakeLists.txt
@@ -42,6 +42,7 @@ set(LIB_FILES
     math/TimeSeriesUtils.cpp
     CartesianVelocityController.cpp
     CartesianPositionController.cpp
+    CartesianWaypointController.cpp
     CartesianFeedForwardPositionController.cpp
     CartesianVelocityRamp.cpp
     JointVelocityRamp.cpp
@@ -85,6 +86,7 @@ set(LIB_HEADERS
     math/TimeSeriesUtils.h
     CartesianVelocityController.h
     CartesianPositionController.h
+    CartesianWaypointController.h
     CartesianFeedForwardPositionController.h
     CartesianVelocityRamp.h
     JointVelocityRamp.h
diff --git a/source/RobotAPI/libraries/core/CartesianWaypointController.cpp b/source/RobotAPI/libraries/core/CartesianWaypointController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5b7f29e761f5df89151b333a61a026b64f59b287
--- /dev/null
+++ b/source/RobotAPI/libraries/core/CartesianWaypointController.cpp
@@ -0,0 +1,259 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * Copyright (C) 2012-2016, High Performance Humanoid Technologies (H2T),
+ * Karlsruhe Institute of Technology (KIT), all rights reserved.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * ArmarX is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author     Raphael Grimm (raphael dot grimm at kit dot edu)
+ * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
+ *             GNU General Public License
+ */
+
+#include <VirtualRobot/math/Helpers.h>
+
+#include <ArmarXCore/core/time/TimeUtil.h>
+
+#include <RobotAPI/libraries/core/CartesianVelocityController.h>
+
+#include "CartesianWaypointController.h"
+
+namespace armarx
+{
+    CartesianWaypointController::CartesianWaypointController(
+        const VirtualRobot::RobotNodeSetPtr& rns,
+        const Eigen::VectorXf& currentJointVelocity,
+        float maxPositionAcceleration,
+        float maxOrientationAcceleration,
+        float maxNullspaceAcceleration,
+        const VirtualRobot::RobotNodePtr& tcp
+    ) :
+        ctrlCartesianVelWithRamps
+    {
+        rns,
+        currentJointVelocity,
+        VirtualRobot::IKSolver::CartesianSelection::All,
+        maxPositionAcceleration,
+        maxOrientationAcceleration,
+        maxNullspaceAcceleration,
+        tcp ? tcp : rns->getTCP()
+    },
+    ctrlCartesianPos2Vel(tcp ? tcp : rns->getTCP()),
+                         currentWaypointIndex(0)
+    {
+        ARMARX_CHECK_NOT_NULL(rns);
+        _out = Eigen::VectorXf::Zero(rns->getSize());
+        _jnv = Eigen::VectorXf::Zero(rns->getSize());
+    }
+
+    const Eigen::VectorXf& CartesianWaypointController::calculate(float dt)
+    {
+        //calculate cartesian velocity + some management stuff
+        if (!isLastWaypoint() && isCurrentTargetNear())
+        {
+            currentWaypointIndex++;
+        }
+        cartesianVelocity = ctrlCartesianPos2Vel.calculate(getCurrentTarget(), VirtualRobot::IKSolver::All) + feedForwardVelocity;
+
+        if (autoClearFeedForward)
+        {
+            clearFeedForwardVelocity();
+        }
+
+        //calculate joint velocity
+        if (nullSpaceControlEnabled)
+        {
+            //avoid joint limits
+            _jnv = KpJointLimitAvoidance * ctrlCartesianVelWithRamps.controller.calculateJointLimitAvoidance() +
+                   ctrlCartesianVelWithRamps.controller.calculateNullspaceVelocity(
+                       cartesianVelocity,
+                       jointLimitAvoidanceScale,
+                       VirtualRobot::IKSolver::All
+                   );
+        }
+        else
+        {
+            //don't avoid joint limits
+            _jnv *= 0;
+        }
+        _out = ctrlCartesianVelWithRamps.calculate(cartesianVelocity, _jnv, dt);
+        return _out;
+    }
+
+    void CartesianWaypointController::setNewWaypoints(const std::vector<Eigen::Matrix4f>& waypoints)
+    {
+        this->waypoints = waypoints;
+        currentWaypointIndex = 0;
+    }
+
+    void CartesianWaypointController::addWaypoint(const Eigen::Matrix4f& waypoint)
+    {
+        this->waypoints.push_back(waypoint);
+    }
+
+    void CartesianWaypointController::setTarget(const Eigen::Matrix4f& target)
+    {
+        waypoints.clear();
+        waypoints.push_back(target);
+        currentWaypointIndex = 0;
+    }
+
+    void CartesianWaypointController::setFeedForwardVelocity(const Eigen::Vector6f& feedForwardVelocity)
+    {
+        this->feedForwardVelocity = feedForwardVelocity;
+    }
+
+    void CartesianWaypointController::setFeedForwardVelocity(const Eigen::Vector3f& feedForwardVelocityPos, const Eigen::Vector3f& feedForwardVelocityOri)
+    {
+        feedForwardVelocity.block<3, 1>(0, 0) = feedForwardVelocityPos;
+        feedForwardVelocity.block<3, 1>(3, 0) = feedForwardVelocityOri;
+    }
+
+    void CartesianWaypointController::clearFeedForwardVelocity()
+    {
+        feedForwardVelocity = Eigen::Vector6f::Zero();
+    }
+
+    float CartesianWaypointController::getPositionError() const
+    {
+        return ctrlCartesianPos2Vel.getPositionError(getCurrentTarget());
+    }
+
+    float CartesianWaypointController::getOrientationError() const
+    {
+        return ctrlCartesianPos2Vel.getOrientationError(getCurrentTarget());
+    }
+
+    bool CartesianWaypointController::isCurrentTargetReached() const
+    {
+        return getPositionError() < thresholdPositionReached && getOrientationError() < thresholdOrientationReached;
+    }
+
+    bool CartesianWaypointController::isCurrentTargetNear() const
+    {
+        return getPositionError() < thresholdPositionNear && getOrientationError() < thresholdOrientationNear;
+    }
+
+    bool CartesianWaypointController::isFinalTargetReached() const
+    {
+        return isLastWaypoint() && isCurrentTargetReached();
+    }
+
+    bool CartesianWaypointController::isFinalTargetNear() const
+    {
+        return isLastWaypoint() && isCurrentTargetNear();
+    }
+
+    bool CartesianWaypointController::isLastWaypoint() const
+    {
+        return currentWaypointIndex + 1 >= waypoints.size();
+    }
+
+    const Eigen::Matrix4f& CartesianWaypointController::getCurrentTarget() const
+    {
+        return waypoints.at(currentWaypointIndex);
+    }
+
+    const Eigen::Vector3f CartesianWaypointController::getCurrentTargetPosition() const
+    {
+        return math::Helpers::GetPosition(waypoints.at(currentWaypointIndex));
+    }
+
+    size_t CartesianWaypointController::skipToClosestWaypoint(float rad2mmFactor)
+    {
+        float dist = FLT_MAX;
+        size_t minIndex = 0;
+        for (size_t i = 0; i < waypoints.size(); i++)
+        {
+            float posErr = ctrlCartesianPos2Vel.getPositionError(waypoints.at(i));
+            float oriErr = ctrlCartesianPos2Vel.getOrientationError(waypoints.at(i));
+            float d = posErr + oriErr * rad2mmFactor;
+            if (d < dist)
+            {
+                minIndex = i;
+                dist = d;
+            }
+        }
+        currentWaypointIndex = minIndex;
+        return currentWaypointIndex;
+    }
+
+    void CartesianWaypointController::setNullSpaceControl(bool enabled)
+    {
+        nullSpaceControlEnabled = enabled;
+    }
+
+    std::string CartesianWaypointController::getStatusText()
+    {
+        std::stringstream ss;
+        ss.precision(2);
+        ss << std::fixed << "Waypoint: " << (currentWaypointIndex + 1) << "/" << waypoints.size() << " distance: " << getPositionError() << " mm " << VirtualRobot::MathTools::rad2deg(getOrientationError()) << " deg";
+        return ss.str();
+    }
+
+    //    bool CartesianWaypointController::OptimizeNullspace(const VirtualRobot::RobotNodePtr& tcp, const VirtualRobot::RobotNodeSetPtr& rns, const Eigen::Matrix4f& target, const NullspaceOptimizationArgs& args)
+    //    {
+    //        CartesianPositionController posHelper(tcp);
+
+    //        CartesianVelocityController cartesianVelocityController{rns, tcp, args.invJacMethod};
+
+    //        const float errorOriInitial = posHelper.getOrientationError(target);
+    //        const float errorPosInitial = posHelper.getPositionError(target);
+
+    //        const float stepLength = args.stepLength;
+    //        const float eps = args.eps;
+
+    //        std::vector<float> initialJointAngles =  rns->getJointValues();
+
+    //        for (int i = 0; i < args.loops; i++)
+    //        {
+    //            Eigen::VectorXf tcpVelocities = posHelper.calculate(target, args.cartesianSelection);
+    //            Eigen::VectorXf nullspaceVel = cartesianVelocityController.calculateJointLimitAvoidance(); // calculateNullspaceVelocity(tcpVelocities, 1.0f, vrmode);
+    //            const float nullspaceLen = nullspaceVel.norm();
+    //            if (nullspaceLen > stepLength)
+    //            {
+    //                nullspaceVel = nullspaceVel / nullspaceLen * stepLength;
+    //            }
+    //            Eigen::VectorXf jointVelocities = cartesianVelocityController.calculate(tcpVelocities, nullspaceVel, args.cartesianSelection);
+
+    //            //jointVelocities = jointVelocities * stepLength;
+    //            float len = jointVelocities.norm();
+    //            if (len > stepLength)
+    //            {
+    //                jointVelocities = jointVelocities / len * stepLength;
+    //            }
+
+    //            for (size_t n = 0; n < rns->getSize(); n++)
+    //            {
+    //                rns->getNode(n)->setJointValue(rns->getNode(n)->getJointValue() + jointVelocities(n));
+    //            }
+    //            if (len < eps)
+    //            {
+    //                break;
+    //            }
+    //        }
+
+    //        float errorOriAfter = posHelper.getOrientationError(target);
+    //        float errorPosAfter = posHelper.getPositionError(target);
+    //        if (errorOriAfter < errorOriInitial + 1.f / 180 * M_PI && errorPosAfter < errorPosInitial + 1.f)
+    //        {
+    //            return true;
+    //        }
+    //        else
+    //        {
+    //            rns->setJointValues(initialJointAngles);
+    //            return false;
+    //        }
+    //    }
+}
diff --git a/source/RobotAPI/libraries/core/CartesianWaypointController.h b/source/RobotAPI/libraries/core/CartesianWaypointController.h
new file mode 100644
index 0000000000000000000000000000000000000000..c42772fcab483141672a963c2ed97c36aa78b984
--- /dev/null
+++ b/source/RobotAPI/libraries/core/CartesianWaypointController.h
@@ -0,0 +1,119 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * Copyright (C) 2012-2016, High Performance Humanoid Technologies (H2T),
+ * Karlsruhe Institute of Technology (KIT), all rights reserved.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * ArmarX is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author     Raphael Grimm (raphael dot grimm at kit dot edu)
+ * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
+ *             GNU General Public License
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <Eigen/Dense>
+
+#include <VirtualRobot/Robot.h>
+
+#include <RobotAPI/libraries/core/CartesianPositionController.h>
+#include <RobotAPI/libraries/core/Pose.h>
+
+#include "CartesianVelocityControllerWithRamp.h"
+
+namespace Eigen
+{
+    using Vector6f = Matrix<float, 6, 1>;
+}
+
+
+namespace armarx
+{
+
+    class CartesianWaypointController;
+    using CartesianWaypointControllerPtr = std::shared_ptr<CartesianWaypointController>;
+
+    class CartesianWaypointController
+    {
+    public:
+        CartesianWaypointController(const VirtualRobot::RobotNodeSetPtr& rns,
+                                    const Eigen::VectorXf& currentJointVelocity,
+                                    float maxPositionAcceleration,
+                                    float maxOrientationAcceleration,
+                                    float maxNullspaceAcceleration,
+                                    const VirtualRobot::RobotNodePtr& tcp = nullptr
+                                   );
+
+
+        void setCurrentJointVelocity(const Eigen::VectorXf& currentJointVelocity)
+        {
+            ctrlCartesianVelWithRamps.setCurrentJointVelocity(currentJointVelocity);
+        }
+
+        const Eigen::VectorXf& calculate(float dt);
+
+        void setNewWaypoints(const std::vector<Eigen::Matrix4f>& waypoints);
+        void addWaypoint(const Eigen::Matrix4f& waypoint);
+        void setTarget(const Eigen::Matrix4f& target);
+        void setFeedForwardVelocity(const Eigen::Vector6f& feedForwardVelocity);
+        void setFeedForwardVelocity(const Eigen::Vector3f& feedForwardVelocityPos, const Eigen::Vector3f& feedForwardVelocityOri);
+        void clearFeedForwardVelocity();
+
+        float getPositionError() const;
+
+        float getOrientationError() const;
+
+        bool isCurrentTargetReached() const;
+        bool isCurrentTargetNear() const;
+        bool isFinalTargetReached() const;
+        bool isFinalTargetNear() const;
+
+        bool isLastWaypoint() const;
+
+        const Eigen::Matrix4f& getCurrentTarget() const;
+        const Eigen::Vector3f getCurrentTargetPosition() const;
+
+        size_t skipToClosestWaypoint(float rad2mmFactor);
+
+        void setNullSpaceControl(bool enabled);
+
+        std::string getStatusText();
+
+        CartesianVelocityControllerWithRamp  ctrlCartesianVelWithRamps;
+        CartesianPositionController ctrlCartesianPos2Vel;
+
+        std::vector<Eigen::Matrix4f> waypoints;
+        size_t currentWaypointIndex         = 0.f;
+
+        float thresholdPositionReached      = 0.f;
+        float thresholdOrientationReached   = 0.f;
+        float thresholdPositionNear         = 0.f;
+        float thresholdOrientationNear      = 0.f;
+
+        Eigen::Vector6f feedForwardVelocity = Eigen::Vector6f::Zero();
+        Eigen::Vector6f cartesianVelocity = Eigen::Vector6f::Zero();
+        bool autoClearFeedForward           = true;
+
+
+        bool nullSpaceControlEnabled        = true;
+        float jointLimitAvoidanceScale      = 0.f;
+        float KpJointLimitAvoidance         = 0.f;
+
+    private:
+        Eigen::VectorXf _out;
+        Eigen::VectorXf _jnv;
+    };
+}