diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6a09f31ceaa2365cb06d9058c2962f364b890a91..3665b17773c0e18ad6591df9081bc4b63d8e2dbe 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -38,10 +38,10 @@ stages:
   -   git checkout devel
   -   git pull
   -   git checkout $WOLF_CORE_BRANCH
+  -   git pull
   - else
-  -   git clone ssh://git@gitlab.iri.upc.edu:2202/mobile_robotics/wolf_projects/wolf_lib/wolf.git
+  -   git clone -b $WOLF_CORE_BRANCH ssh://git@gitlab.iri.upc.edu:2202/mobile_robotics/wolf_projects/wolf_lib/wolf.git
   -   cd wolf
-  -   git checkout $WOLF_CORE_BRANCH
   - fi
   - mkdir -pv build
   - cd build
@@ -147,9 +147,9 @@ stages:
 ############ LICENSE HEADERS ############
 license_headers:
   stage: license
-  image: labrobotica/wolf_deps:16.04
+  image: labrobotica/wolf_deps:20.04
   cache:
-    - key: wolf-xenial
+    - key: wolf-focal
       paths:
       - ci_deps/wolf/
   except:
@@ -160,99 +160,6 @@ license_headers:
   script:
     - *license_header_definition
 
-############ UBUNTU 16.04 TESTS ############
-build_and_test_none:xenial:
-  stage: none
-  image: labrobotica/wolf_deps:16.04
-  cache:
-    - key: laserscanutils-xenial
-      paths:
-      - ci_deps/laser_scan_utils/
-    - key: wolf-xenial
-      paths:
-      - ci_deps/wolf/
-  except:
-    - master
-  before_script:
-    - *preliminaries_definition
-    - *install_wolf_definition
-    - *install_laserscanutils_definition
-  script:
-    - *build_and_test_definition
-
-build_and_test_csm:xenial:
-  stage: csm
-  image: labrobotica/wolf_deps:16.04
-  cache:
-    - key: wolf-xenial
-      paths:
-      - ci_deps/wolf/
-    - key: laserscanutils-xenial
-      paths:
-      - ci_deps/laser_scan_utils/
-    - key: csm-xenial
-      paths:
-      - ci_deps/csm/
-  except:
-    - master
-  before_script:
-    - *preliminaries_definition
-    - *install_wolf_definition
-    - *install_csm_definition
-    - *install_laserscanutils_definition
-  script:
-    - *build_and_test_definition
-
-build_and_test_falko:xenial:
-  stage: falko
-  image: labrobotica/wolf_deps:16.04
-  cache:
-    - key: wolf-xenial
-      paths:
-      - ci_deps/wolf/
-    - key: laserscanutils-xenial
-      paths:
-      - ci_deps/laser_scan_utils/
-    - key: falko-xenial
-      paths:
-      - ci_deps/falkolib/
-  except:
-    - master
-  before_script:
-    - *preliminaries_definition
-    - *install_wolf_definition
-    - *install_falko_definition
-    - *install_laserscanutils_definition
-  script:
-    - *build_and_test_definition
-    
-build_and_test_csm_falko:xenial:
-  stage: csm_falko
-  image: labrobotica/wolf_deps:16.04
-  cache:
-    - key: wolf-xenial
-      paths:
-      - ci_deps/wolf/
-    - key: laserscanutils-xenial
-      paths:
-      - ci_deps/laser_scan_utils/
-    - key: csm-xenial
-      paths:
-      - ci_deps/csm/
-    - key: falko-xenial
-      paths:
-      - ci_deps/falkolib/
-  except:
-    - master
-  before_script:
-    - *preliminaries_definition
-    - *install_wolf_definition
-    - *install_falko_definition
-    - *install_csm_definition
-    - *install_laserscanutils_definition
-  script:
-    - *build_and_test_definition
-
 ############ UBUNTU 18.04 TESTS ############
 build_and_test_none:bionic:
   stage: none
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 88c9fea397911a5317573b7376ac4e3c9cdeec03..6a8ad1eab8d460fd4c256b2f48b0bed9106b361e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -175,26 +175,24 @@ if (falkolib_FOUND)
   SET(HDRS_PROCESSOR ${HDRS_PROCESSOR}
     include/laser/processor/processor_loop_closure_falko.h
     )
-    # falko & CSM
-    if (csm_FOUND)
-      SET(HDRS_PROCESSOR ${HDRS_PROCESSOR}
-        include/laser/processor/processor_loop_closure_falko_icp.h
-        )
-      SET(SRCS_PROCESSOR ${SRCS_PROCESSOR}
-        src/processor/processor_loop_closure_falko_icp.cpp
-        )
-
-    endif()
+  SET(SRCS_PROCESSOR ${SRCS_PROCESSOR}
+    src/processor/processor_loop_closure_falko.cpp
+    )
   SET(HDRS_FEATURE ${HDRS_FEATURE}
     include/laser/feature/feature_scene_falko.h
     )
   SET(SRCS_FEATURE ${SRCS_FEATURE}
     src/feature/feature_scene_falko.cpp
     )
-
-  SET(SRCS_PROCESSOR ${SRCS_PROCESSOR}
-    src/processor/processor_loop_closure_falko.cpp
+  # falko & CSM
+  if (csm_FOUND)
+    SET(HDRS_PROCESSOR ${HDRS_PROCESSOR}
+    include/laser/processor/processor_loop_closure_falko_icp.h
+    )
+    SET(SRCS_PROCESSOR ${SRCS_PROCESSOR}
+    src/processor/processor_loop_closure_falko_icp.cpp
     )
+  endif()
 endif()
 
 # CSM
diff --git a/include/laser/processor/processor_odom_icp.h b/include/laser/processor/processor_odom_icp.h
index a802d9232fb0362cef9892721f5d83c81ec9da00..9ef7904912e713aebad83e338fcca6bd9cce2e7f 100644
--- a/include/laser/processor/processor_odom_icp.h
+++ b/include/laser/processor/processor_odom_icp.h
@@ -29,9 +29,7 @@
 #include "core/processor/processor_tracker.h"
 #include "core/processor/motion_provider.h"
 #include "laser/processor/params_icp.h"
-#include "laser/capture/capture_laser_2d.h"
-#include "core/factor/factor_relative_pose_2d_with_extrinsics.h"
-#include "laser/feature/feature_icp_align.h"
+#include "laser/sensor/sensor_laser_2d.h"
 
 /**************************
  *      ICP includes      *
@@ -99,9 +97,11 @@ class ProcessorOdomIcp : public ProcessorTracker, public MotionProvider
         laserscanutils::icpOutput trf_origin_last_;
         laserscanutils::icpOutput trf_origin_incoming_;
         laserscanutils::icpOutput trf_last_incoming_;
-        Eigen::Vector3d odom_origin_;
-        Eigen::Vector3d odom_last_;
-        Eigen::Vector3d odom_incoming_;
+        Eigen::VectorXd odom_origin_;
+        Eigen::VectorXd odom_last_;
+        Eigen::VectorXd odom_incoming_;
+
+        Eigen::Isometry2d rl_T_sl_, ro_T_so_;
 
     public:
         ParamsProcessorOdomIcpPtr params_odom_icp_;
@@ -117,6 +117,8 @@ class ProcessorOdomIcp : public ProcessorTracker, public MotionProvider
         VectorComposite getState(const TimeStamp& _ts, const StateStructure& _structure = "") const override;
         void setProblem(ProblemPtr _problem_ptr) override;
 
+        Eigen::Vector3d getOriginLastTransform(double& dt) const;
+
     protected:
         void preProcess() override;
 
@@ -137,8 +139,41 @@ class ProcessorOdomIcp : public ProcessorTracker, public MotionProvider
         bool voteForKeyFrameAngle() const;
         bool voteForKeyFrameTime() const;
         bool voteForKeyFrameMatchQuality() const;
+
+        template<typename D1, typename D2>
+        void convert2dTo3d(const Eigen::Isometry2d& T2, Eigen::MatrixBase<D1>& p3, Eigen::QuaternionBase<D2>& q3) const;
+
+        template<typename D1>
+        void convert2dTo3d(const Eigen::Isometry2d& T2, Eigen::MatrixBase<D1>& x3) const;
+        Eigen::Isometry2d computeIsometry2d(const Eigen::VectorXd& x1, const Eigen::VectorXd& x2 ) const;
+
+        void updateExtrinsicsIsometries();
 };
 
 }
 
+#include "core/math/rotations.h"
+namespace wolf {
+
+template<typename D1, typename D2>
+inline void ProcessorOdomIcp::convert2dTo3d(const Eigen::Isometry2d& T2, Eigen::MatrixBase<D1>& p3, Eigen::QuaternionBase<D2>& q3) const
+{
+    MatrixSizeCheck<3, 1>::check(p3);
+
+    p3.head(2) = T2.translation();
+    p3(2) = 0;
+    q3 = e2q((Eigen::Vector3d() << 0, 0, Rotation2Dd(T2.rotation()).angle()).finished());
+}
+
+template<typename D1>
+inline void ProcessorOdomIcp::convert2dTo3d(const Eigen::Isometry2d& T2, Eigen::MatrixBase<D1>& x3) const
+{
+    MatrixSizeCheck<7, 1>::check(x3);
+
+    x3.head(2) = T2.translation();
+    x3(2) = 0;
+    x3.tail(4) = e2q((Eigen::Vector3d() << 0, 0, Rotation2Dd(T2.rotation()).angle()).finished()).coeffs();
+}
+}
+
 #endif // SRC_PROCESSOR_ODOM_ICP_H_
diff --git a/include/laser/sensor/sensor_laser_2d.h b/include/laser/sensor/sensor_laser_2d.h
index 5568961c21fe2049e5472759cb32ba28323b49cd..047de263741d3b3de6321ed0f337b7f13b4f7914 100644
--- a/include/laser/sensor/sensor_laser_2d.h
+++ b/include/laser/sensor/sensor_laser_2d.h
@@ -125,9 +125,6 @@ class SensorLaser2d : public SensorBase
          **/
         const laserscanutils::LaserScanParams & getScanParams() const;
 
-    public:
-//        static ParamsSensorBasePtr createParams(const std::string& _filename_dot_yaml);
-
 };
 
 } // namespace wolf
diff --git a/src/processor/processor_odom_icp.cpp b/src/processor/processor_odom_icp.cpp
index 100dd66185496d1c923ba4e1ba48fe24f549a7b4..aa4bbd55eb9e30eee6606fa94ace0d0c8ba68e30 100644
--- a/src/processor/processor_odom_icp.cpp
+++ b/src/processor/processor_odom_icp.cpp
@@ -22,17 +22,26 @@
 #include "laser/processor/processor_odom_icp.h"
 #include "laser/math/laser_tools.h"
 #include "core/math/covariance.h"
+#include "core/math/rotations.h"
+#include "core/math/SE3.h"
+#include "core/math/SE2.h"
+#include "laser/capture/capture_laser_2d.h"
+#include "core/factor/factor_relative_pose_2d_with_extrinsics.h"
+#include "core/factor/factor_relative_pose_3d.h"
+#include "laser/feature/feature_icp_align.h"
 
 using namespace laserscanutils;
 namespace wolf
 {
 
 ProcessorOdomIcp::ProcessorOdomIcp(ParamsProcessorOdomIcpPtr _params):
-                    ProcessorTracker("ProcessorOdomIcp", "PO", 2, _params),
+                    ProcessorTracker("ProcessorOdomIcp", "PO", 0, _params),
                     MotionProvider("PO", _params),
-                    odom_origin_(Eigen::Vector3d::Zero()),
-                    odom_last_(Eigen::Vector3d::Zero()),
-                    odom_incoming_(Eigen::Vector3d::Zero()),
+                    odom_origin_(Eigen::VectorXd::Zero(3)),
+                    odom_last_(Eigen::VectorXd::Zero(3)),
+                    odom_incoming_(Eigen::VectorXd::Zero(3)),
+                    rl_T_sl_(Eigen::Isometry2d::Identity()),
+                    ro_T_so_(Eigen::Isometry2d::Identity()),
                     params_odom_icp_(_params)
 {
     // Icp algorithm
@@ -62,29 +71,62 @@ void ProcessorOdomIcp::preProcess()
     else if (params_odom_icp_->initial_guess == "state" )
     {
         odom_incoming_  = getProblem()->getState("PO").vector("PO");
+
         if(last_ptr_)
-        {
-            odom_last_      = getProblem()->getState(last_ptr_->getTimeStamp(), "PO").vector("PO");
-        }
+            odom_last_  = getProblem()->getState(last_ptr_->getTimeStamp(), "PO").vector("PO");
         if(origin_ptr_)
-        {
-            odom_origin_    = getProblem()->getState(origin_ptr_->getTimeStamp(), "PO").vector("PO");
-        }
+            odom_origin_ = getProblem()->getState(origin_ptr_->getTimeStamp(), "PO").vector("PO");
     }
-
-    assert(odom_incoming_.size() == 3);
 }
 
 void ProcessorOdomIcp::configure(SensorBasePtr _sensor)
 {
     sensor_laser_       = std::static_pointer_cast<SensorLaser2d>(_sensor);
     laser_scan_params_  = sensor_laser_->getScanParams();
+
+    // initialize extrinsics if static
+    if (not sensor_laser_->isStateBlockDynamic('P') and not sensor_laser_->isStateBlockDynamic('O') )
+    {
+        ro_T_so_ = laser::trf2isometry(sensor_laser_->getP()->getState(), sensor_laser_->getO()->getState());
+        rl_T_sl_ = ro_T_so_;
+    }
+}
+
+void ProcessorOdomIcp::updateExtrinsicsIsometries()
+{
+    // dynamics          
+    if (sensor_laser_->isStateBlockDynamic('P') or sensor_laser_->isStateBlockDynamic('O'))
+    {
+        ro_T_so_ = laser::trf2isometry(origin_ptr_->getSensorP()->getState(), origin_ptr_->getSensorP()->getState());
+        rl_T_sl_ = laser::trf2isometry(last_ptr_->getSensorP()->getState(), last_ptr_->getSensorP()->getState());
+    }   
+    // statics not fixed (otherwise nothing to do)
+    else if (not sensor_laser_->getP()->isFixed() or not sensor_laser_->getO()->isFixed())
+    {
+        ro_T_so_ = laser::trf2isometry(sensor_laser_->getP()->getState(), sensor_laser_->getO()->getState());
+        rl_T_sl_ = ro_T_so_;
+    }
+}
+
+Eigen::Isometry2d ProcessorOdomIcp::computeIsometry2d(const Eigen::VectorXd& x1, const Eigen::VectorXd& x2 ) const
+{
+    // any vector not properly filled
+    if (not (x1.size() == 3 and x2.size() == 3) and not (x1.size() == 7 and  x2.size() == 7))
+        return Eigen::Isometry2d::Identity();
+
+    // 2D
+    if (x1.size() == 3)
+        return laser::trf2isometry(Eigen::Rotation2Dd(-x1(2)) * (x2.head<2>() - x1.head<2>()),
+                                   Eigen::Vector1d(x2(2) - x1(2)));
+
+    // 3D
+    auto dx = SE3::between(x1, x2);
+    return laser::trf2isometry(dx.head<2>(), q2e(Eigen::Quaterniond(dx.tail<4>())).tail<1>());
 }
 
 unsigned int ProcessorOdomIcp::processKnown()
 {
     // Match the incoming with the origin, passing the transform from origin to last as initialization
-
     if (origin_ptr_) // Make sure it's not the FIRST_TIME
     {
         CaptureLaser2dPtr origin_ptr    = std::static_pointer_cast<CaptureLaser2d>(origin_ptr_);
@@ -93,9 +135,13 @@ unsigned int ProcessorOdomIcp::processKnown()
         Eigen::Vector3d initial_guess = this->trf_origin_last_.res_transf;
         if (params_odom_icp_->initial_guess == "odom" or params_odom_icp_->initial_guess == "state" )
         {
-            //TODO: Add sensor extrinsics
-            initial_guess.head(2) += Eigen::Rotation2Dd(-odom_origin_(2)) * (odom_last_.head(2) - odom_incoming_.head(2));
-            initial_guess(2) += -(odom_incoming_(2) - odom_last_(2));
+            // update extrinsics (if necessary)
+            updateExtrinsicsIsometries();
+
+            auto ri_T_ro = computeIsometry2d(odom_incoming_, odom_origin_);
+            auto si_T_so = rl_T_sl_.inverse() * ri_T_ro * ro_T_so_;
+            initial_guess.head<2>() = si_T_so.translation();
+            initial_guess(2) = Rotation2Dd(si_T_so.rotation()).angle();
         }
         else if (params_odom_icp_->initial_guess != "zero")
             throw std::runtime_error("unknown value for param 'initial_guess'. Should be 'odom', 'state' or 'zero'");
@@ -104,14 +150,12 @@ unsigned int ProcessorOdomIcp::processKnown()
                                                      origin_ptr->getScan(),
                                                      this->laser_scan_params_,
                                                      params_odom_icp_->icp_params,
-                                                     initial_guess); // Check order
+                                                     initial_guess);
         WOLF_DEBUG("ProcessorOdomIcp::processKnown odom_origin_: ", odom_origin_.transpose());
         WOLF_DEBUG("ProcessorOdomIcp::processKnown odom_incoming_: ", odom_incoming_.transpose());
         WOLF_DEBUG("ProcessorOdomIcp::processKnown initial guess: ", initial_guess.transpose());
         WOLF_DEBUG("ProcessorOdomIcp::processKnown ICP transform: ", trf_origin_incoming_.res_transf.transpose());
         WOLF_DEBUG("ProcessorOdomIcp::processKnown ICP cov: \n", trf_origin_incoming_.res_covar);
-
-        //trf_origin_incoming_.valid = trf_origin_incoming_.valid && trf_origin_incoming_.error / trf_origin_incoming_.nvalid < 5e-2;
     }
     return 0;
 }
@@ -125,8 +169,13 @@ unsigned int ProcessorOdomIcp::processNew(const int& _max_features)
     Eigen::Vector3d initial_guess(Eigen::Vector3d::Zero());
     if (params_odom_icp_->initial_guess == "odom" or params_odom_icp_->initial_guess == "state" )
     {
-          initial_guess.head(2) = Eigen::Rotation2Dd(-odom_incoming_(2)) * (odom_last_.head(2) - odom_incoming_.head(2));
-          initial_guess(2) = -(odom_incoming_(2) - odom_last_(2));
+        // update extrinsics (if necessary)
+        updateExtrinsicsIsometries();
+
+        auto ri_T_rl = computeIsometry2d(odom_incoming_, odom_last_);
+        auto si_T_sl = rl_T_sl_.inverse() * ri_T_rl * rl_T_sl_;
+        initial_guess.head<2>() = si_T_sl.translation();
+        initial_guess(2) = Rotation2Dd(si_T_sl.rotation()).angle();
     }
     else if (params_odom_icp_->initial_guess != "zero")
       throw std::runtime_error("unknown value for param 'initial_guess'. Should be 'odom', 'state' or 'zero'");
@@ -208,12 +257,37 @@ void ProcessorOdomIcp::advanceDerived()
 
     odom_last_ = odom_incoming_;
 
+    // init odometry_
+    if (odometry_.empty())
+        odometry_ = getProblem()->stateZero("PO");
+
+    // update extrinsics (if necessary)
+    updateExtrinsicsIsometries();
+
     // computing odometry
     auto       so_T_si = laser::trf2isometry(trf_origin_incoming_.res_transf);
     auto       so_T_sl = laser::trf2isometry(trf_origin_last_.res_transf);
-    Isometry2d sl_T_si = so_T_sl.inverse() * so_T_si;
-    odometry_['P'] = sl_T_si.translation();
-    odometry_['O'] = Vector1d(Rotation2Dd(sl_T_si.rotation()).angle());
+    Isometry2d rl_T_ri = rl_T_sl_ * so_T_sl.inverse() * so_T_si * rl_T_sl_.inverse();
+    
+    // 2D
+    if (getProblem()->getDim() == 2)
+    {
+        auto    m_T_rl = laser::trf2isometry(odometry_['P'], odometry_['O']);
+        auto    m_T_ri = m_T_rl * rl_T_ri;
+        odometry_['P'] = m_T_ri.translation();
+        odometry_['O'] = Vector1d(Rotation2Dd(m_T_ri.rotation()).angle());
+    }
+    // 3D
+    else
+    {
+        Eigen::Vector7d rl_T3d_ri;
+
+        convert2dTo3d(rl_T_ri, rl_T3d_ri);
+        
+        SE3::compose(odometry_['P'], odometry_['O'], 
+                     rl_T3d_ri.head<3>(), rl_T3d_ri.tail<4>(), 
+                     odometry_['P'], odometry_['O']);
+    }
 
     // advance transforms
     trf_origin_last_ = trf_origin_incoming_;
@@ -224,16 +298,36 @@ void ProcessorOdomIcp::resetDerived()
     // Using processing_step_ to ensure that origin, last and incoming are different
     if (processing_step_ != FIRST_TIME_WITH_KEYFRAME && processing_step_ != FIRST_TIME_WITHOUT_KEYFRAME)
     {
-        // Notation explanation:
-        // x1_R_x2: Rotation from frame x1 to frame x2
-        // x1_p_y1_y2: translation vector from y1 to y2 expressed in frame x1
+        // init odometry_
+        if (odometry_.empty())
+            odometry_ = getProblem()->stateZero("PO");
+
+        // update extrinsics (if necessary)
+        updateExtrinsicsIsometries();
 
         // computing odometry
         auto       so_T_si = laser::trf2isometry(trf_origin_incoming_.res_transf);
         auto       so_T_sl = laser::trf2isometry(trf_origin_last_.res_transf);
-        Isometry2d sl_T_si = so_T_sl.inverse() * so_T_si;
-        odometry_['P'] = sl_T_si.translation();
-        odometry_['O'] = Vector1d(Rotation2Dd(sl_T_si.rotation()).angle());
+        Isometry2d rl_T_ri = rl_T_sl_ * so_T_sl.inverse() * so_T_si * rl_T_sl_.inverse();
+        // 2D
+        if (getProblem()->getDim() == 2)
+        {
+            auto m_T_rl = laser::trf2isometry(odometry_['P'], odometry_['O']);
+            auto m_T_ri = m_T_rl * rl_T_ri;
+            odometry_['P'] = m_T_ri.translation();
+            odometry_['O'] = Vector1d(Rotation2Dd(m_T_ri.rotation()).angle());
+        }
+        // 3D
+        else
+        {
+            Eigen::Vector7d rl_T3d_ri;
+
+            convert2dTo3d(rl_T_ri, rl_T3d_ri);
+            
+            SE3::compose(odometry_['P'], odometry_['O'], 
+                         rl_T3d_ri.head<3>(), rl_T3d_ri.tail<4>(), 
+                         odometry_['P'], odometry_['O']);
+        }
 
         // advance transforms
         trf_origin_last_ = trf_last_incoming_;
@@ -259,20 +353,51 @@ FeatureBasePtr ProcessorOdomIcp::emplaceFeature(CaptureBasePtr _capture_laser)
     if (not isCovariance(trf_origin_last_.res_covar))
         trf_origin_last_.res_covar = 1e-4 * Eigen::Matrix3d::Identity();
 
-    return FeatureBase::emplace<FeatureICPAlign>(_capture_laser,
-                                                trf_origin_last_);
+    // 2D
+    if (getProblem()->getDim() == 2)
+        return FeatureBase::emplace<FeatureICPAlign>(_capture_laser,
+                                                     trf_origin_last_);
+    // 3D
+    else
+    {
+        // update extrinsics (if necessary)
+        updateExtrinsicsIsometries();
+
+        Eigen::Vector7d meas_3d = Eigen::Vector7d::Zero();
+        auto so_T_sl = ro_T_so_.inverse() * laser::trf2isometry(trf_origin_last_.res_transf) * rl_T_sl_;
+        convert2dTo3d(so_T_sl, meas_3d);
+
+        Eigen::Matrix6d cov_3d = Eigen::Matrix6d::Identity() * Constants::EPS;
+        cov_3d.topLeftCorner<2,2>()     = trf_origin_last_.res_covar.topLeftCorner<2,2>();
+        cov_3d.topRightCorner<2,1>()    = trf_origin_last_.res_covar.topRightCorner<2,1>();
+        cov_3d.bottomLeftCorner<1,2>()  = trf_origin_last_.res_covar.bottomLeftCorner<1,2>();
+        cov_3d.bottomRightCorner<1,1>() = trf_origin_last_.res_covar.bottomRightCorner<1,1>();
+
+        return FeatureBase::emplace<FeatureBase>(_capture_laser, "FeatureICPAlign", meas_3d, cov_3d);
+    }
 }
 
 FactorBasePtr ProcessorOdomIcp::emplaceFactor(FeatureBasePtr _feature)
 {
     WOLF_DEBUG("ProcessorOdomIcp::emplaceFactor: feature = ", _feature->getMeasurement().transpose());
     WOLF_DEBUG("ProcessorOdomIcp::emplaceFactor: cov = \n", _feature->getMeasurementCovariance());
-    return FactorBase::emplace<FactorRelativePose2dWithExtrinsics>(_feature,
-                                                                   _feature,
-                                                                   origin_ptr_->getFrame(),
-                                                                   shared_from_this(),
-                                                                   params_->apply_loss_function,
-                                                                   TOP_MOTION);
+
+    // 2D
+    if (getProblem()->getDim() == 2)
+        return FactorBase::emplace<FactorRelativePose2dWithExtrinsics>(_feature,
+                                                                       _feature,
+                                                                       origin_ptr_->getFrame(),
+                                                                       shared_from_this(),
+                                                                       params_->apply_loss_function,
+                                                                       TOP_MOTION);
+    // 3D
+    else
+        return FactorBase::emplace<FactorRelativePose3d>(_feature,
+                                                         _feature,
+                                                         origin_ptr_->getFrame(),
+                                                         shared_from_this(),
+                                                         params_->apply_loss_function,
+                                                         TOP_MOTION);
 }
 
 VectorComposite ProcessorOdomIcp::getState( const StateStructure& _structure ) const
@@ -281,27 +406,48 @@ VectorComposite ProcessorOdomIcp::getState( const StateStructure& _structure ) c
         origin_ptr_->isRemoving() or
         last_ptr_ == nullptr or
         last_ptr_->getFrame() == nullptr) // We do not have any info of where to find a valid state
-                                                                  // Further checking here for origin_ptr is redundant: if last=null, then origin=null too.
+                                          // Further checking here for origin_ptr is redundant: if last=null, then origin=null too.
     {
-        WOLF_DEBUG("Processor has no state. Returning an empty VectorComposite with no blocks");
+        WOLF_WARN("Processor has no state. Returning an empty VectorComposite");
         return VectorComposite(); // return empty state
     }
     
-    auto  w_T_ro = laser::trf2isometry(origin_ptr_->getFrame()->getP()->getState(), origin_ptr_->getFrame()->getO()->getState());
-    auto ro_T_so = laser::trf2isometry(origin_ptr_->getSensorP()->getState(), origin_ptr_->getSensorO()->getState());
-    const auto& rl_T_sl = ro_T_so; // A reference just to have nice names without computing overhead
+    // we cannot update extrinsics since the function is const
 
-    auto      so_T_sl = laser::trf2isometry(trf_origin_last_.res_transf);
-    Isometry2d w_T_rl = w_T_ro * ro_T_so * so_T_sl * rl_T_sl.inverse();
+    // origin-last transf.
+    auto so_T_sl = laser::trf2isometry(trf_origin_last_.res_transf);
+    
+    // 2D
+    if (getProblem()->getDim() == 2)
+    {
+        auto m_T_ro = laser::trf2isometry(origin_ptr_->getFrame()->getP()->getState(), origin_ptr_->getFrame()->getO()->getState());
+        auto m_T_rl = m_T_ro * ro_T_so_ * so_T_sl * rl_T_sl_.inverse();
+        
+        return VectorComposite("PO", {m_T_rl.translation(), Eigen::Vector1d(Eigen::Rotation2Dd(m_T_rl.rotation()).angle())});
+    }
+    // 3D
+    else
+    {
+        auto ro_T_rl = ro_T_so_ * so_T_sl * rl_T_sl_.inverse();
+
+        Eigen::Vector7d ro_T3d_rl;
+        Eigen::Vector3d last_p;
+        Eigen::Vector4d last_q;
 
-    VectorComposite state("PO", {w_T_rl.translation(), Vector1d(Rotation2Dd(w_T_rl.rotation()).angle())});
+        convert2dTo3d(ro_T_rl, ro_T3d_rl);
+        
+        SE3::compose(origin_ptr_->getFrame()->getState("P").vector("P"), 
+                     origin_ptr_->getFrame()->getState("O").vector("O"), 
+                     ro_T3d_rl.head<3>(), ro_T3d_rl.tail<4>(), 
+                     last_p, last_q);
 
-    return state;
+        return VectorComposite("PO", {last_p, last_q});
+    }
 }
 
 TimeStamp ProcessorOdomIcp::getTimeStamp() const
 {
-    if( last_ptr_ == nullptr )
+    if ( last_ptr_ == nullptr )
         return TimeStamp::Invalid();
     else
         return last_ptr_->getTimeStamp();
@@ -309,9 +455,7 @@ TimeStamp ProcessorOdomIcp::getTimeStamp() const
 
 VectorComposite ProcessorOdomIcp::getState(const TimeStamp& _ts, const StateStructure& _structure) const
 {
-    // todo fix this code to get any state in the whole trajectory!
-
-    //
+    // valid last...
     if (last_ptr_ != nullptr)
     {
         double d = fabs(_ts - last_ptr_->getTimeStamp());
@@ -321,20 +465,21 @@ VectorComposite ProcessorOdomIcp::getState(const TimeStamp& _ts, const StateStru
         }
         else
         {
-            WOLF_WARN("Requested state with time stamp out of tolerance. Returning zero state");
-            return getProblem()->stateZero("PO"); // return zero
+            WOLF_WARN(getName(), ": Requested state with time stamp out of tolerance. Returning empty VectorComposite");
+            return VectorComposite();
         }
     }
     // return state at origin if possible
     else
     {
-        if (origin_ptr_ == nullptr || fabs(_ts - last_ptr_->getTimeStamp()) > params_->time_tolerance)
-            return VectorComposite("PO", {Vector2d(0,0), Vector1d(0)});
+        if (origin_ptr_ == nullptr || fabs(_ts - origin_ptr_->getTimeStamp()) > params_->time_tolerance)
+        {
+            WOLF_WARN(getName(), ": Requested state with time stamp out of tolerance. Returning empty VectorComposite");
+            return VectorComposite();
+        }
         else
             return origin_ptr_->getFrame()->getState("PO");
     }
-
-
 }
 
 void ProcessorOdomIcp::setProblem(ProblemPtr _problem_ptr)
@@ -343,6 +488,28 @@ void ProcessorOdomIcp::setProblem(ProblemPtr _problem_ptr)
     addToProblem(_problem_ptr, std::dynamic_pointer_cast<MotionProvider>(shared_from_this()));
 }
 
+Eigen::Vector3d ProcessorOdomIcp::getOriginLastTransform(double& dt) const
+{
+    // not ready
+    if (not last_ptr_ or not origin_ptr_)
+    {
+        dt = -1;
+        return Eigen::Vector3d::Zero();
+    }
+
+    // dt
+    dt = last_ptr_->getTimeStamp() - origin_ptr_->getTimeStamp();
+
+    // origin-last transf.
+    auto so_T_sl = laser::trf2isometry(trf_origin_last_.res_transf);
+    auto ro_T_rl = ro_T_so_ * so_T_sl * rl_T_sl_.inverse();
+    Eigen::Vector3d ro_v_rl;
+    ro_v_rl.head<2>() = ro_T_rl.translation();
+    ro_v_rl(2) = Eigen::Rotation2Dd(ro_T_rl.rotation()).angle();
+
+    return ro_v_rl;
+}
+
 } // namespace wolf
 
 
diff --git a/test/gtest_processor_odom_icp.cpp b/test/gtest_processor_odom_icp.cpp
index 4b09cad83ef2e1200bd9c14503b1f6d093fa8f58..b0f24d0d3bbedd22b82cda213ec0499925ea2669 100644
--- a/test/gtest_processor_odom_icp.cpp
+++ b/test/gtest_processor_odom_icp.cpp
@@ -32,7 +32,9 @@
 #include <core/ceres_wrapper/solver_ceres.h>
 #include <core/utils/utils_gtest.h>
 
-#include "laser/processor/processor_odom_icp.h" // THIS AT THE END OTHERWISE IT FAILS COMPILING
+#include "laser/processor/processor_odom_icp.h"
+#include "laser/capture/capture_laser_2d.h"
+
 
 using namespace wolf;
 using namespace Eigen;
@@ -66,12 +68,13 @@ class ProcessorOdomIcp_Test : public testing::Test
 
         TimeStamp t0;
         VectorComposite x0; // prior state
-        VectorComposite s0; // prior sigmas
         FrameBasePtr F0, F; // keyframes
 
+        SizeEigen dim = 2;
+
         void SetUp() override
         {
-            problem     = Problem::create("PO", 2);
+            problem     = Problem::create("PO", dim);
             solver      = std::make_shared<SolverCeres>(problem);
             auto sen    = problem->installSensor("SensorLaser2d", "lidar", Eigen::Vector3d(0,0,0), laser_root_dir + "/test/yaml/sensor_laser_2d.yaml");
 
@@ -83,9 +86,8 @@ class ProcessorOdomIcp_Test : public testing::Test
             ranges = std::vector<float>({49.97591781616211, 49.996429443359375, 49.999759674072266, 50.0, 50.0, 50.0, 49.998634338378906, 50.0, 50.0, 49.99236297607422, 49.99384307861328, 50.0, 49.9869270324707, 50.0, 49.99005889892578, 49.99773406982422, 50.0, 49.98741912841797, 50.0, 49.99842071533203, 50.0, 49.99243927001953, 49.997291564941406, 50.0, 50.0, 49.98580551147461, 49.991844177246094, 49.98896026611328, 50.0, 50.0, 49.9897346496582, 49.998111724853516, 49.99882125854492, 50.0, 50.0, 50.0, 50.0, 49.999698638916016, 50.0, 50.0, 50.0, 50.0, 49.991397857666016, 49.99360275268555, 49.999027252197266, 49.99750900268555, 49.99100112915039, 49.998714447021484, 49.98794174194336, 50.0, 50.0, 50.0, 50.0, 50.0, 49.98186492919922, 50.0, 50.0, 50.0, 49.99155807495117, 49.997196197509766, 49.98872375488281, 49.99138259887695, 50.0, 49.99021530151367, 49.99164581298828, 50.0, 49.991390228271484, 50.0, 50.0, 50.0, 49.997249603271484, 50.0, 49.991851806640625, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 49.97983169555664, 49.98630142211914, 50.0, 20.6888370513916, 49.9797477722168, 49.98846435546875, 49.99418640136719, 50.0, 50.0, 50.0, 49.99342346191406, 50.0, 49.9906005859375, 50.0, 49.99853515625, 49.989444732666016, 38.552852630615234, 38.28703689575195, 38.04865264892578, 37.78112030029297, 37.54747772216797, 37.28171157836914, 37.206565856933594, 36.835411071777344, 36.61864471435547, 36.39655685424805, 36.1579475402832, 35.95964431762695, 35.75605773925781, 35.552188873291016, 35.75926208496094, 36.27781677246094, 35.16993713378906, 34.99699401855469, 34.82910919189453, 34.6483039855957, 34.48637390136719, 34.32539749145508, 34.16202163696289, 34.232051849365234, 33.86043167114258, 33.71691131591797, 33.566650390625, 33.42384338378906, 33.2882080078125, 33.16693115234375, 33.041419982910156, 32.906009674072266, 33.008323669433594, 33.706356048583984, 34.43825149536133, 35.25088119506836, 36.05652618408203, 36.930118560791016, 37.83384704589844, 33.12321472167969, 33.02351760864258, 32.9192008972168, 32.82925796508789, 32.74382781982422, 32.64959716796875, 32.580204010009766, 32.49120330810547, 33.05714797973633, 32.343536376953125, 32.26381301879883, 32.21063232421875, 32.12488555908203, 32.06965255737305, 32.0222282409668, 31.954957962036133, 31.903532028198242, 31.83578872680664, 32.51456832885742, 34.189456939697266, 31.12668228149414, 31.076339721679688, 31.047151565551758, 30.967018127441406, 30.956220626831055, 30.924589157104492, 30.893285751342773, 30.869199752807617, 30.843050003051758, 32.791847229003906, 30.809431076049805, 30.79128074645996, 30.779237747192383, 30.776460647583008, 30.74305534362793, 30.74994468688965, 30.7137393951416, 30.734609603881836, 30.719928741455078, 30.71673011779785, 49.99970626831055, 50.0, 49.987911224365234, 33.68583679199219, 31.76846694946289, 31.8026123046875, 31.802202224731445, 31.818490982055664, 31.85223960876465, 31.86141014099121, 31.906801223754883, 31.93423843383789, 31.964210510253906, 33.567230224609375, 32.055015563964844, 32.07001876831055, 32.13076400756836, 32.16000747680664, 32.22781753540039, 32.26890563964844, 32.323944091796875, 32.36326217651367, 32.430908203125, 49.980655670166016, 34.32135772705078, 33.09465789794922, 32.27247619628906, 32.33710479736328, 32.41763687133789, 32.498661041259766, 32.57213592529297, 32.67158126831055, 32.74591827392578, 32.814476013183594, 32.93477249145508, 33.04751968383789, 33.136863708496094, 33.23999786376953, 33.34675979614258, 33.42970657348633, 33.53573226928711, 33.66716003417969, 33.78378677368164, 33.905670166015625, 34.02836608886719, 34.151817321777344, 34.2794189453125, 34.41516876220703, 34.551273345947266, 34.702728271484375, 34.84151840209961, 34.986881256103516, 35.162757873535156, 35.332794189453125, 35.47941970825195, 35.65633010864258, 35.82624435424805, 36.00060272216797, 36.17729187011719, 36.36515808105469, 36.55763626098633, 36.744773864746094, 38.46407699584961, 37.869808197021484, 37.767921447753906, 37.958900451660156, 38.20857620239258, 38.38622283935547, 38.68323516845703, 38.871334075927734, 39.151519775390625, 39.377750396728516, 39.68268966674805, 39.89873123168945, 40.197330474853516, 40.47549819946289, 40.73743438720703, 41.04566955566406, 42.33650207519531, 41.92591094970703, 41.43912124633789, 41.045528411865234, 41.32114028930664, 41.581878662109375, 41.944580078125, 42.318912506103516, 42.6725959777832, 43.07264709472656, 43.443477630615234, 43.83216094970703, 44.19996643066406, 44.63225555419922, 45.06049346923828, 45.468536376953125, 45.89896774291992, 46.330604553222656, 46.778343200683594, 47.31666946411133, 47.789310455322266, 48.26376724243164, 48.826602935791016, 49.33188247680664, 49.990909576416016, 50.0, 50.0, 50.0, 50.0, 49.995697021484375, 49.99568176269531, 49.98163986206055, 50.0, 50.0, 49.9764289855957, 50.0, 50.0, 49.98639678955078, 49.99431228637695, 50.0, 50.0, 50.0, 50.0, 49.9874267578125, 50.0, 49.98714828491211, 50.0, 49.99470901489258, 49.99113464355469, 50.0, 50.0, 50.0, 49.985504150390625, 49.99067306518555, 50.0, 49.997161865234375, 50.0, 50.0, 50.0, 49.995513916015625, 49.993038177490234, 50.0, 49.99763107299805, 50.0, 49.98752975463867, 50.0, 49.988037109375, 50.0, 50.0, 50.0, 49.9975700378418, 50.0, 49.998756408691406, 49.97819519042969, 49.99104690551758, 49.99087905883789, 49.94268798828125, 49.85968017578125, 49.786617279052734, 49.70594787597656, 49.62562561035156, 49.56686782836914, 49.50475311279297, 49.416934967041016, 49.35699462890625, 49.308589935302734, 49.990482330322266, 50.0, 49.998836517333984, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 49.980472564697266, 49.99903869628906, 50.0, 50.0, 49.989845275878906, 49.98395919799805, 50.0, 49.99302673339844, 49.99530792236328, 49.99745559692383, 50.0, 49.99560546875, 21.569303512573242});
 
             t0 = 0.0;
-            x0 = VectorComposite("PO", {Vector2d(0,0), Vector1d(0)});
-            s0 = VectorComposite("PO", {Vector2d(1,1), Vector1d(1)});
-            F0 = problem->setPriorFactor(x0, s0, t0);
+            x0 = problem->stateZero();
+            F0 = problem->setPriorFix(x0, t0);
         }
         void TearDown() override{}
 };
@@ -209,6 +211,49 @@ TEST_F(ProcessorOdomIcp_Test, solve)
     }
 }
 
+// 3D
+TEST_F(ProcessorOdomIcp_Test, setup_3D)
+{
+    dim = 3;
+    SetUp();
+}
+
+TEST_F(ProcessorOdomIcp_Test, solve_3D)
+{
+    dim = 3;
+    SetUp();
+
+    TimeStamp t(t0);
+
+    for (int i = 0; i < 6; i++)
+    {
+        std::cout << "\n========== Process " << t.getSeconds() << " sec. ==========" << std::endl;
+
+        CaptureLaser2dPtr scan = std::make_shared<CaptureLaser2d>(t, lidar, ranges);
+        scan->process();
+
+        // check that feature and factor has been emplaced
+        ASSERT_TRUE(i <= 1 or processor->getOrigin()->getFeatureList().size() > 0);
+
+        FactorBasePtrList factor_list;
+        processor->getOrigin()->getFactorList(factor_list);
+        ASSERT_TRUE(i <= 1 or factor_list.size() > 0);
+
+        t += 1.0;
+    }
+
+    for (auto F : *problem->getTrajectory())
+        F->perturb(0.5);
+
+    std::string report =    solver->solve(SolverManager::ReportVerbosity::BRIEF);
+    WOLF_TRACE(report);
+
+    for (auto F : *problem->getTrajectory())
+    {
+        ASSERT_MATRIX_APPROX(F->getState().vector("PO") , x0.vector("PO") , 1e-6);
+    }
+}
+
 
 int main(int argc, char **argv)
 {