diff --git a/source/armarx/navigation/algorithms/CMakeLists.txt b/source/armarx/navigation/algorithms/CMakeLists.txt
index d408bd1b341ca823ba5bfd3606102dee36146359..5507cdeb2f34973cd052216cd079f8087617595c 100644
--- a/source/armarx/navigation/algorithms/CMakeLists.txt
+++ b/source/armarx/navigation/algorithms/CMakeLists.txt
@@ -8,6 +8,7 @@ armarx_add_library(algorithms
         ./algorithms.cpp
         # A*
         ./astar/AStarPlanner.cpp
+        ./astar/NavigationCostMap.cpp
         ./astar/Node.cpp
         ./astar/Planner2D.cpp
         # smoothing
@@ -17,6 +18,7 @@ armarx_add_library(algorithms
         ./algorithms.h
         # A*
         ./astar/AStarPlanner.h
+        ./astar/NavigationCostMap.h
         ./astar/Node.h
         ./astar/Planner2D.h
         # smoothing
diff --git a/source/armarx/navigation/algorithms/astar/NavigationCostMap.cpp b/source/armarx/navigation/algorithms/astar/NavigationCostMap.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..08bfd27ad97f59aae72bbce55a41e31fdd9ae2c8
--- /dev/null
+++ b/source/armarx/navigation/algorithms/astar/NavigationCostMap.cpp
@@ -0,0 +1,199 @@
+#include "NavigationCostMap.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include <boost/geometry.hpp>
+#include <boost/geometry/algorithms/detail/intersects/interface.hpp>
+
+#include <Eigen/Geometry>
+
+#include <IceUtil/Time.h>
+
+#include <VirtualRobot/BoundingBox.h>
+#include <VirtualRobot/CollisionDetection/CollisionChecker.h>
+#include <VirtualRobot/CollisionDetection/CollisionModel.h>
+#include <VirtualRobot/XML/ObjectIO.h>
+
+#include <ArmarXCore/core/exceptions/local/ExpressionException.h>
+#include <ArmarXCore/core/logging/Logging.h>
+
+#include <armarx/navigation/conversions/eigen.h>
+
+namespace bg = boost::geometry;
+
+namespace armarx::navigation::algorithm::astar
+{
+
+    bool
+    intersects(const VirtualRobot::BoundingBox& bb1, const VirtualRobot::BoundingBox& bb2)
+    {
+        using point_t = bg::model::point<double, 3, bg::cs::cartesian>;
+        using box_t = bg::model::box<point_t>;
+
+        const auto toPoint = [](const Eigen::Vector3f& pt)
+        { return point_t(pt.x(), pt.y(), pt.z()); };
+
+        box_t box1(toPoint(bb1.getMin()), toPoint(bb1.getMax()));
+        box_t box2(toPoint(bb2.getMin()), toPoint(bb2.getMax()));
+
+        return bg::intersects(box1, box2);
+    }
+
+    NavigationCostMap::NavigationCostMap(VirtualRobot::RobotPtr robot,
+                                         VirtualRobot::SceneObjectSetPtr obstacles,
+                                         const Parameters& parameters) :
+        robot(robot), obstacles(obstacles), parameters(parameters)
+    {
+    }
+
+    NavigationCostMap::Position
+    NavigationCostMap::toPosition(const NavigationCostMap::Index& index) const
+    {
+        Eigen::Vector2f pos;
+        // TODO check if x and y must be switched
+        pos.x() = sceneBoundsMin.x() + index.x() * parameters.cellSize + parameters.cellSize / 2;
+        pos.y() = sceneBoundsMin.y() + index.y() * parameters.cellSize + parameters.cellSize / 2;
+
+        return pos;
+    }
+
+    void
+    NavigationCostMap::createUniformGrid()
+    {
+        ARMARX_TRACE;
+
+        ARMARX_DEBUG << "Scene bounds are " << sceneBoundsMin << " and " << sceneBoundsMax;
+
+        //+1 for explicit rounding up
+        c_x = (sceneBoundsMax.x() - sceneBoundsMin.x()) / parameters.cellSize + 1;
+        c_y = (sceneBoundsMax.y() - sceneBoundsMin.y()) / parameters.cellSize + 1;
+
+        ARMARX_DEBUG << "Grid size: " << c_y << ", " << c_x;
+
+        grid = Eigen::ArrayXf(c_x, c_y);
+    }
+
+
+    float
+    NavigationCostMap::computeCost(const NavigationCostMap::Position& position)
+    {
+        ARMARX_TRACE;
+        ARMARX_CHECK_NOT_NULL(robotCollisionModel);
+        ARMARX_CHECK_NOT_NULL(VirtualRobot::CollisionChecker::getGlobalCollisionChecker());
+        ARMARX_CHECK_NOT_NULL(obstacles);
+
+        const core::Pose globalPose = Eigen::Translation3f(conv::to3D(position)) *
+                                      Eigen::AngleAxisf(M_PI_2f32, Eigen::Vector3f::UnitX());
+
+        robotCollisionModel->setGlobalPose(globalPose.matrix());
+
+        VirtualRobot::BoundingBox robotBbox = robotCollisionModel->getBoundingBox(true);
+
+        Eigen::Vector3f P1;
+        Eigen::Vector3f P2;
+        int id1, id2;
+
+        float minDistance = std::numeric_limits<float>::max();
+
+        // TODO omp...
+        for (size_t i = 0; i < obstacles->getSize(); i++)
+        {
+            // cheap collision check
+            // VirtualRobot::BoundingBox obstacleBbox =
+            //     obstacles->getSceneObject(i)->getCollisionModel()->getBoundingBox(true);
+            // if (not intersects(robotBbox, obstacleBbox))
+            // {
+            //     continue;
+            // }
+
+            // precise collision check
+            const float dist =
+                VirtualRobot::CollisionChecker::getGlobalCollisionChecker()->calculateDistance(
+                    robotCollisionModel,
+                    obstacles->getSceneObject(i)->getCollisionModel(),
+                    P1,
+                    P2,
+                    &id1,
+                    &id2);
+
+            // check if objects collide
+            if (dist <= parameters.cellSize / 2)
+            {
+                minDistance = 0;
+                break;
+            }
+
+            minDistance = std::min(minDistance, dist);
+        }
+        // return n->position.x() >= sceneBoundsMin.x() && n->position.x() <= sceneBoundsMax.x() &&
+        //        n->position.y() >= sceneBoundsMin.y() && n->position.y() <= sceneBoundsMax.y();
+
+        return minDistance;
+    }
+
+    NavigationCostMap::Vertex
+    NavigationCostMap::toVertex(Eigen::Vector2f v)
+    {
+        float v_y = (v.y() - parameters.cellSize / 2 - sceneBoundsMin.y()) / parameters.cellSize;
+        float v_x = (v.x() - parameters.cellSize / 2 - sceneBoundsMin.x()) / parameters.cellSize;
+
+        ARMARX_CHECK(v_y >= 0.F);
+        ARMARX_CHECK(v_x >= 0.F);
+
+        ARMARX_CHECK(v_y <= (c_y - 1));
+        ARMARX_CHECK(v_x <= (c_x - 1));
+
+        return Vertex{.index = Index{static_cast<int>(v_x), static_cast<int>(v_y)}, .position = v};
+    }
+
+    void
+    NavigationCostMap::createCostmap()
+    {
+        ARMARX_TRACE;
+        std::vector<Eigen::Vector2f> result;
+
+        ARMARX_CHECK_NOT_NULL(robot);
+
+        // ARMARX_CHECK(robot->hasRobotNode(robotColModelName)) << "No robot node " << robotColModelName;
+        // ARMARX_CHECK(robot->getRobotNode(robotColModelName)->getCollisionModel())
+        // << "No collision model for robot node " << robotColModelName;
+
+        // robotCollisionModel = robot->getRobotNode(robotColModelName)->getCollisionModel()->clone();
+        robotCollisionModel->setGlobalPose(robot->getGlobalPose());
+
+        // static auto cylinder = VirtualRobot::ObjectIO::loadObstacle("/home/fabi/cylinder.xml");
+        // ARMARX_CHECK_NOT_NULL(cylinder);
+        // cylinder->setGlobalPose(robot->getGlobalPose());
+
+        // robotCollisionModel = cylinder->getCollisionModel();
+
+        ARMARX_CHECK_NOT_NULL(robotCollisionModel);
+
+        createUniformGrid();
+
+        fillGridCosts();
+    }
+
+    void
+    NavigationCostMap::setRobotCollisionModel(const VirtualRobot::CollisionModelPtr& collisionModel)
+    {
+        robotCollisionModel = collisionModel;
+    }
+
+    void NavigationCostMap::fillGridCosts()
+    {
+        for (int x = 0; x < c_x; x++)
+        {
+            for (int y = 0; y < c_y; y++)
+            {
+                const Index index{x, y};
+                const Position position = toPosition(index);
+
+                grid(x, y) = computeCost(position);
+            }
+        }
+    }
+
+} // namespace armarx::navigation::algorithm::astar
diff --git a/source/armarx/navigation/algorithms/astar/NavigationCostMap.h b/source/armarx/navigation/algorithms/astar/NavigationCostMap.h
new file mode 100644
index 0000000000000000000000000000000000000000..d6bacdcf88c82bf2a97c76be19d6251e33b380db
--- /dev/null
+++ b/source/armarx/navigation/algorithms/astar/NavigationCostMap.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include <cstddef>
+
+#include <Eigen/Core>
+
+#include <VirtualRobot/CollisionDetection/CollisionModel.h>
+
+#include "Node.h"
+#include "Planner2D.h"
+
+namespace armarx::navigation::algorithm::astar
+{
+
+
+    /**
+ * The A* planner
+ */
+    class NavigationCostMap
+    {
+    public:
+        EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+
+        struct Parameters
+        {
+            // if set to false, distance to obstacles will be computed and not only a binary collision check
+            bool binaryGrid{false}; 
+
+            /// How big each cell is in the uniform grid.
+            float cellSize = 100.f;
+        };
+
+        NavigationCostMap(VirtualRobot::RobotPtr robot,
+                          VirtualRobot::SceneObjectSetPtr obstacles,
+                          const Parameters& parameters);
+
+        void createCostmap();
+
+        void setRobotCollisionModel(const VirtualRobot::CollisionModelPtr& collisionModel);
+
+        using Index = Eigen::Array2i;
+        using Position = Eigen::Vector2f;
+
+        struct Vertex
+        {
+            Index index; // row corresponds to y; column corresponds to x
+            Position position;
+        };
+
+    private:
+        void createUniformGrid();
+        float computeCost(const NavigationCostMap::Position& position);
+        Vertex toVertex(Position v);
+
+        void fillGridCosts();
+
+    private:
+
+        Eigen::ArrayXf grid;
+
+        Position toPosition(const Index& index) const;
+
+
+
+        size_t c_x = 0;
+        size_t c_y = 0;
+
+
+        VirtualRobot::RobotPtr robot;
+        VirtualRobot::SceneObjectSetPtr obstacles;
+
+        VirtualRobot::CollisionModelPtr robotCollisionModel;
+
+        const Eigen::Vector2f sceneBoundsMin;
+        const Eigen::Vector2f sceneBoundsMax;
+
+        const Parameters parameters;
+    };
+} // namespace armarx::navigation::algorithm::astar