diff --git a/examples/regrasping/crocoddyl/pendulum/fwd_sim_pendulum_scipy.py b/examples/regrasping/crocoddyl/pendulum/fwd_sim_pendulum_scipy.py
new file mode 100644
index 0000000000000000000000000000000000000000..a76255a9edeb29a710572192ed5f74a0e8b0cf80
--- /dev/null
+++ b/examples/regrasping/crocoddyl/pendulum/fwd_sim_pendulum_scipy.py
@@ -0,0 +1,159 @@
+import crocoddyl
+from pendulum_friction_computation import pendulum_coulomb, pendulum_lugre
+
+import numpy as np
+import pinocchio as pin
+from argparse import Namespace
+from scipy.integrate import solve_ivp
+
+
+def stateEquation(
+    t: float,  # required by solver API
+    x: np.ndarray,  # required by solver API
+    # our arguments from here on
+    args: Namespace,
+    i,
+    mu_tors: float,
+    model_pin: pin.Model,
+    data_pin: pin.Data,
+    data_actuation: crocoddyl.ActuationDataAbstract,
+    u: np.ndarray,
+    t_from_ocp: np.ndarray,
+    history: dict[str, np.ndarray],
+):
+    """
+    stateEquation
+    ----------------
+    the ode solver calls stateEquation to integrate x_dot = f(t, x).
+    hence the problem needs to be written in state-space from.
+    inputs are:
+       - t - passed by the solver because it does non-homogeneous steps
+       - x - system state (everything that changes with time)
+    outputs are:
+       - x_dot - first derivative
+    Because the solver will call stateEquation many times to estimate derivatives,
+    we can't store results as it runs.
+    Instead, we re-run the equations with the solved states
+    and re-calculate the non-state quantities we want to save.
+    the hacky history arguments lets us do this by calling stateEquation,
+    which is much better than copy-pasting and slightly changing stateEquation.
+
+    state -> TODO: investigate how storing theta as ctheta, stheta can be forced to work to clean up the mess
+    -----
+    0 : 3 - pendulum rotatational joint angles
+    4     - lugre state variable
+
+
+    controller - list of grasping forces, provided by ocp solver.
+                 there should be an option between holding them constant,
+                 or linearly interpolating between them.
+
+    TODO: pass other arguments as a tuple args=(...),
+    TODO: fix evil pythonic memory accessing of variables outside of the function scope
+    """
+    x_dot = np.zeros(5)
+    q = x[:2]
+    v = x[2:4]
+
+    u_tau_1 = np.interp(t, t_from_ocp, u[:, 0])
+    u_fn = np.interp(t, t_from_ocp, u[:, 1])
+    u_t = np.array([u_tau_1, u_fn])
+
+    if args.friction_model == "coulomb":
+        F_f, _ = pendulum_coulomb(
+            args,
+            mu_tors,
+            x,
+            u_t,
+            model_pin,
+            data_pin,
+            data_actuation,
+        )
+
+    if args.friction_model == "lugre":
+        F_f = pendulum_lugre(
+            args,
+            x,
+            u_t,
+            data_actuation,
+        )
+
+    # Forward dynamics
+    tau = np.zeros(2)
+    tau[0] = u_tau_1
+    tau[1] = F_f
+    q_ddot = pin.aba(model_pin, data_pin, q, v, tau)
+    x_dot[:4] = q_ddot
+    x_dot[4] = data_actuation.z_dot
+
+    if history != None:
+        history["q"][i] = q.copy()
+        history["q_ddot"][i] = q_ddot.copy()
+        history["tau"][i] = tau.copy()
+        history["F_f"][i] = F_f.copy()
+
+        return x_dot
+
+
+def fwdSimPendulum(
+    args: Namespace,
+    dt: float,
+    T: int,
+    x0: np.ndarray,
+    t_final: float,
+    mu_tors: float,
+    model_pin: pin.Model,
+    data_pin: pin.Data,
+    data_actuation: crocoddyl.ActuationDataAbstract,
+    u: np.ndarray,
+    t_from_ocp: np.ndarray,
+):
+    t_init = 0
+    t_final = T * dt
+    history = None
+
+    result = solve_ivp(
+        stateEquation,
+        [t_init, t_final],
+        x0,
+        method="LSODA",
+        max_step=args.dt,
+        args=(
+            args,
+            0,
+            mu_tors,
+            model_pin,
+            data_pin,
+            data_actuation,
+            u,
+            t_from_ocp,
+            history,
+        ),
+    )
+    N = result.y.shape[1]
+    history = {
+        "t": np.zeros((N,)),
+        "q": np.zeros((N, 2)),
+        "v": np.zeros((N, 2)),
+        "z": np.zeros((N,)),
+        "F_f": np.zeros((N,)),
+        "q_ddot": np.zeros((N, 2)),
+    }
+    history["q"] = result.y[0 : model_pin.nv].T
+    history["v"] = result.y[model_pin.nv : model_pin.nv * 2].T
+    history["z"] = result.y[model_pin.nv * 2 : model_pin.nv * 2 + 1].T
+    history["t"] = result.t
+    for i in range(N):
+        stateEquation(
+            result.t[i],
+            result.y[:, i],
+            args,
+            i,
+            mu_tors,
+            model_pin,
+            data_pin,
+            data_actuation,
+            u,
+            t_from_ocp,
+            history,
+        )
diff --git a/examples/regrasping/crocoddyl/pendulum/multibody_with_lugre_state.py b/examples/regrasping/crocoddyl/pendulum/multibody_with_lugre_state.py
new file mode 120000
index 0000000000000000000000000000000000000000..117ddc71fa9f038ded76eebc7fe3cd111b45b356
--- /dev/null
+++ b/examples/regrasping/crocoddyl/pendulum/multibody_with_lugre_state.py
@@ -0,0 +1 @@
+../multibody_with_lugre_state.py
\ No newline at end of file
diff --git a/examples/regrasping/crocoddyl/pendulum_croco_models.py b/examples/regrasping/crocoddyl/pendulum/pendulum_croco_models.py
similarity index 63%
rename from examples/regrasping/crocoddyl/pendulum_croco_models.py
rename to examples/regrasping/crocoddyl/pendulum/pendulum_croco_models.py
index d09af4230955c812232c5d0ffc40a95ce21d42ad..994736d106f9847e0878ff75f059db7429efe2ed 100644
--- a/examples/regrasping/crocoddyl/pendulum_croco_models.py
+++ b/examples/regrasping/crocoddyl/pendulum/pendulum_croco_models.py
@@ -1,4 +1,5 @@
 import numpy as np
+from pendulum_friction_computation import pendulum_coulomb, pendulum_lugre
 
 import crocoddyl
 import pinocchio as pin
@@ -66,54 +67,29 @@ class ActuationModelDoublePendulum(crocoddyl.ActuationModelAbstract):
 
     def calc(self, data, x, u):
         data.tau[0] = u[0]
-        # now we need to calculate the sticking condition.
-        # it's whether tau[1] is bigger than mu_tors * fn.
-        # but what is tau[1]?
-        # well, if there's sticking, then v[1] = 0.
-        # if v[1] > 0, then we're already sliding so there's nothing to check.
         if self.args.friction_model == "coulomb":
-            self.sticking_condition = True
-            if np.abs(x[3]) > 0.0:
-                self.sticking_condition = False
-            # if there was sticking before, then a[1] = 0.
-            # now if this is the onset of sliping, it won't be.
-            # NOTE: but for the purposes of this calculation it is, right?
-            if (np.abs(x[3]) < 1e-1) or self.sticking_condition:
-                M = pin.crba(self.model_pin, self.data_pin, x[:2])
-                M_inv = np.linalg.inv(M)
-                data.nle_tau = pin.nle(self.model_pin, self.data_pin, x[:2], x[2:])
-                tau2 = -1 * (M_inv[1, 0] * u[0] - data.nle_tau[1]) / M_inv[1, 1]
-                if np.abs(tau2) > self.mu_tors * u[1]:
-                    self.sticking_condition = False
-                else:
-                    self.sticking_condition = True
+            F_f, self.sticking_condition = pendulum_coulomb(
+                self.args,
+                self.mu_tors,
+                x,
+                u,
+                self.model_pin,
+                self.data_pin,
+                data,
+            )
 
-            if self.sticking_condition:
-                data.tau[1] = tau2
-            else:
-                data.tau[1] = -1 * np.sign(x[3]) * self.mu_tors * u[1]
-                # data.tau[1] = np.clip(data.tau[1], -10, 10)
-            # print(data.tau[1], x[3], self.sticking_condition)
-            if not self.args.control_gripping_force:
-                data.tau[1] = 0.0
         if self.args.friction_model == "lugre":
-            """
-            s(v) = F_C + (F_S - F_C) exp(-(v/v_s)**delta_vs)
-            z_dot = v - ((sigma_0 * abs(v)) / (s(v))) * z
-            F = sigma_0 * z + sigma_1 * z_dot + sigma_2 * v
-            """
-            self.g_v = (
-                u[-1]
-                * (self.args.mu_c + (self.args.mu_s - self.args.mu_c))
-                * np.exp(-1 * (x[3] / self.args.v_s) ** 2)
+            F_f = pendulum_lugre(
+                self.args,
+                self.mu_tors,
+                x,
+                u,
+                self.model_pin,
+                self.data_pin,
+                data,
             )
-            self.z_dot = x[3] - ((self.args.sigma_0 * np.abs(x[3])) / self.g_v) * x[-1]
-            F_f = (
-                self.args.sigma_0 * x[-1]
-                + self.args.sigma_1 * self.z_dot
-                + self.args.sigma_2 * x[-1]
-            )
-            data.tau[1] = F_f
+
+        data.tau[1] = F_f
 
     # calc is always called first
     def calcDiff(self, data, x, u):
diff --git a/examples/regrasping/crocoddyl/pendulum/pendulum_friction_computation.py b/examples/regrasping/crocoddyl/pendulum/pendulum_friction_computation.py
new file mode 100644
index 0000000000000000000000000000000000000000..86755b73350c7a9d55b047864f6f128e1d886c26
--- /dev/null
+++ b/examples/regrasping/crocoddyl/pendulum/pendulum_friction_computation.py
@@ -0,0 +1,99 @@
+import pinocchio as pin
+import numpy as np
+from argparse import Namespace
+import crocoddyl
+
+
+def computeStickingCondition(mu_tors, model_pin, data_pin, x, u):
+    sticking_condition = True
+    if np.abs(x[3]) > 0.0:
+        sticking_condition = False
+    # if there was sticking before, then a[1] = 0.
+    # now if this is the onset of sliping, it won't be.
+    # NOTE: but for the purposes of this calculation it is, right?
+    if (np.abs(x[3]) < 1e-1) or sticking_condition:
+        M = pin.crba(model_pin, data_pin, x[:2])
+        M_inv = np.linalg.inv(M)
+        nle_tau = pin.nle(model_pin, data_pin, x[:2], x[2:])
+        tau2 = -1 * (M_inv[1, 0] * u[0] - nle_tau[1]) / M_inv[1, 1]
+        print(x[3], tau2, -1 * np.sign(x[3]) * mu_tors * u[1])
+        if np.abs(tau2) > mu_tors * u[1]:
+            sticking_condition = False
+        else:
+            sticking_condition = True
+    return sticking_condition
+
+
+def pendulum_coulomb(
+    args: Namespace,
+    mu_tors: float,
+    x: np.ndarray,
+    u: np.ndarray,
+    model_pin: pin.Model,
+    data_pin: pin.Data,
+    data_actuation: crocoddyl.ActuationDataAbstract,
+) -> tuple[np.ndarray, bool]:
+    """
+    x : [q1, q2, v1, v2]
+    u : [tau1, fn]
+    """
+    # now we need to calculate the sticking condition.
+    # it's whether tau[1] is bigger than mu_tors * fn.
+    # but what is tau[1]?
+    # well, if there's sticking, then v[1] = 0.
+    # if v[1] > 0, then we're already sliding so there's nothing to check.
+    sticking_condition = True
+    F_f = 0.0
+    tau2 = 0.0
+    if np.abs(x[3]) > 0.0:
+        sticking_condition = False
+    # NOTE:if there was sticking before, then a[1] = 0.
+    # now if this is the onset of sliping, it won't be.
+    # but for the purposes of this calculation it is, right?
+    if (np.abs(x[3]) < args.min_nonsticking_velocity) or sticking_condition:
+        M = pin.crba(model_pin, data_pin, x[:2])
+        M_inv = np.linalg.inv(M)
+        data_actuation.nle_tau = pin.nle(model_pin, data_pin, x[:2], x[2:])
+        tau2 = -1 * (M_inv[1, 0] * u[0] - data_actuation.nle_tau[1]) / M_inv[1, 1]
+        if np.abs(tau2) > mu_tors * u[1]:
+            sticking_condition = False
+        else:
+            sticking_condition = True
+
+    if sticking_condition:
+        F_f = tau2
+    else:
+        F_f = -1 * np.sign(x[3]) * mu_tors * u[1]
+        # data.tau[1] = np.clip(data.tau[1], -10, 10)
+    # print(data.tau[1], x[3], self.sticking_condition)
+    if not args.control_gripping_force:
+        F_f = 0.0
+
+    return F_f, sticking_condition
+
+
+def pendulum_lugre(
+    args: Namespace,
+    x: np.ndarray,
+    u: np.ndarray,
+    data_actuation: crocoddyl.ActuationDataAbstract,
+) -> np.ndarray:
+    """
+    s(v) = F_C + (F_S - F_C) exp(-(v/v_s)**delta_vs)
+    z_dot = v - ((sigma_0 * abs(v)) / (s(v))) * z
+    F = sigma_0 * z + sigma_1 * z_dot + sigma_2 * v
+    """
+    data_actuation.g_v = (
+        u[-1]
+        * (args.mu_c + (args.mu_s - args.mu_c))
+        * np.exp(-1 * (x[3] / args.v_s) ** 2)
+    )
+    data_actuation.z_dot = (
+        x[3] - ((args.sigma_0 * np.abs(x[3])) / data_actuation.g_v) * x[-1]
+    )
+    F_f = (
+        args.sigma_0 * x[-1]
+        + args.sigma_1 * data_actuation.z_dot
+        + args.sigma_2 * x[-1]
+    )
+    return F_f
diff --git a/examples/regrasping/crocoddyl/pendulum_regrasp.py b/examples/regrasping/crocoddyl/pendulum/pendulum_regrasp.py
similarity index 93%
rename from examples/regrasping/crocoddyl/pendulum_regrasp.py
rename to examples/regrasping/crocoddyl/pendulum/pendulum_regrasp.py
index d9c61a1a20fe06633b8fd1eec29f771de0f7a67f..9dae3590ba0c25d19e290434cbbdf01ee73acc37 100644
--- a/examples/regrasping/crocoddyl/pendulum_regrasp.py
+++ b/examples/regrasping/crocoddyl/pendulum/pendulum_regrasp.py
@@ -2,7 +2,8 @@ from pendulum_croco_models import (
     ActuationModelDoublePendulum,
     CostModelDoublePendulum,
 )
-from utils import get_args, computeStickingCondition
+from utils import get_args
+from pendulum_friction_computation import computeStickingCondition
 from multibody_with_lugre_state import MultiBodyWithLugreState
 
 import matplotlib.pyplot as plt
@@ -96,9 +97,16 @@ else:
 solver.solve()
 # Plotting the entire motion
 log = solver.getCallbacks()[1]
-print(x0)
 xs = np.array(log.xs)
 us = np.array(log.us)
+t = np.linspace(0, T * dt, T)
+
+xs_good_simulation = fwdSimPendulum()
+
+print(
+    "both of these should be the same x0, but they're sometimes not, so please check things make sense :)"
+)
+print(x0)
 print(xs[0])
 if WITHPLOT:
     crocoddyl.plotOCSolution(log.xs, log.us, figIndex=1, show=False)
diff --git a/examples/regrasping/crocoddyl/pendulum/utils.py b/examples/regrasping/crocoddyl/pendulum/utils.py
new file mode 120000
index 0000000000000000000000000000000000000000..50fbc6d8f5567dcbb19d623b7c39e80ad017e79e
--- /dev/null
+++ b/examples/regrasping/crocoddyl/pendulum/utils.py
@@ -0,0 +1 @@
+../utils.py
\ No newline at end of file
diff --git a/examples/regrasping/crocoddyl/friction/friction.py b/examples/regrasping/crocoddyl/planar_object/friction/friction.py
similarity index 100%
rename from examples/regrasping/crocoddyl/friction/friction.py
rename to examples/regrasping/crocoddyl/planar_object/friction/friction.py
diff --git a/examples/regrasping/crocoddyl/friction/friction_utils.py b/examples/regrasping/crocoddyl/planar_object/friction/friction_utils.py
similarity index 100%
rename from examples/regrasping/crocoddyl/friction/friction_utils.py
rename to examples/regrasping/crocoddyl/planar_object/friction/friction_utils.py
diff --git a/examples/regrasping/crocoddyl/planar_object/fwd_sim_scipy.py b/examples/regrasping/crocoddyl/planar_object/fwd_sim_scipy.py
new file mode 100644
index 0000000000000000000000000000000000000000..9249156848f09d0b558d67b2bed8106200caa88e
--- /dev/null
+++ b/examples/regrasping/crocoddyl/planar_object/fwd_sim_scipy.py
@@ -0,0 +1,295 @@
+import numpy as np
+import pinocchio as pin
+
+# from pin_to_vedo_plot_util import *
+from dynamics_utils import *
+from controllers import *
+
+"""
+#######################################################################
+#              physical quantities and their coordinates              #
+#######################################################################
+{J} - planar joint frame
+{b} - body CoM frame
+{c1}, {c2} - contact frames
+q_J_b - SE(2) pose in of body CoM {J} (x,y,theta)
+a_b_se2 - body se(2) acceleration given in {b} frame, aba output
+v_b_se2 - body se(2) twist given in {b} frame
+tau_f_b - joint-space torques and forces due to the object moving and friction
+tau - all joint-space torques and forces
+aba(q_J_b4, v_b_se2, tau) --> a_b_se2
+"""
+
+
+# def stateEquation(t, x, history=None, history_controller=None):
+def stateEquation(
+    t,
+    x,
+    args,
+    i,
+    model,
+    data,
+    controller,
+    fic_c1,
+    fic_c2,
+    box_dimensions,
+    OBJECT_JOINT_ID,
+    history,
+    history_controller,
+):
+    """
+    stateEquation
+    ----------------
+    the ode solver calls stateEquation to integrate x_dot = f(t, x).
+    hence the problem needs to be written in state-space from.
+    inputs are:
+       - t - passed by the solver because it does non-homogeneous steps
+       - x - system state (everything that changes with time)
+    outputs are:
+       - x_dot - first derivative
+    Because the solver will call stateEquation many times to estimate derivatives,
+    we can't store results as it runs.
+    Instead, we re-run the equations with the solved states
+    and re-calculate the non-state quantities we want to save.
+    the hacky history arguments lets us do this by calling stateEquation,
+    which is much better than copy-pasting and slightly changing stateEquation.
+
+    state -> TODO: investigate how storing theta as ctheta, stheta can be forced to work to clean up the mess
+    -----
+    0 : 5 - arm rotatational joint angles
+    6 : 7 - p1, p2 - jaw gripper left and right finger
+    8 : 10 - x,y,theta of the "planar joint", i.e. held object
+    11 : 16 - qdot of rotational joints
+    17 : 18 - qdot of jaw gripper left and right finger
+    19 : 21 - v_b_se2 of the "planar joint", i.e. held object
+    22 : 24 - z1, z of contact 1
+    25 : 27 - z2, z of contact 2
+    if AdaptiveController:
+    28 : 29 - adaptive parameters estimates
+
+    TODO: pass other arguments as a tuple args=(...),
+    TODO: fix evil pythonic memory accessing of variables outside of the function scope
+
+    and put this function in a different file or at least
+    don't have it as main.
+    if necessary, shove all parameters etc in a class or dictionary
+    to make it less messy.
+    class option is probably better so that different things happen
+    depending on the controller etc (ex. do you need gravity compensation,
+    or are you running a controller for the arm as well)
+    """
+    x[10] = x[10] % (2 * np.pi)
+    q_J_b3 = np.array([x[8], x[9], x[10]])
+    q_J_b4 = np.array([x[8], x[9], np.cos(x[10]), np.sin(x[10])])  # , x[3]])
+
+    q = np.zeros(model.nq)
+    q[0:8] = x[0:8]
+    q[8:12] = q_J_b4
+    # do T_b_J on q_dot
+    # got to here
+    v_b_se2 = np.array([x[19], x[20], x[21]])
+    v_b_se3 = pin.Motion(
+        np.array([v_b_se2[0], v_b_se2[1], 0.0]), np.array([0.0, 0.0, v_b_se2[2]])
+    )
+
+    v = x[11:22]
+
+    z_c1 = np.array([x[22], x[23], x[24]]).reshape((3, 1))
+    z_c2 = np.array([x[25], x[26], x[27]]).reshape((3, 1))
+
+    if controller.__class__.__name__ == "AdaptiveController":
+        controller.setEstimates(x[28], x[29])
+
+    fic_c1.lugre["z"] = z_c1  # set friction model state
+    fic_c2.lugre["z"] = z_c2  # set friction model state
+
+    v_of_c1 = pin.Motion()
+    v_of_c1.setZero()
+    v_of_c2 = pin.Motion()
+    v_of_c2.setZero()
+
+    f_f_c1_se3 = pin.Force()
+    f_f_c1_se3.setZero()
+    f_f_c2_se3 = pin.Force()
+    f_f_c2_se3.setZero()
+    # f_f_b_se3 = pin.Force()
+    # f_f_b_se3.setZero()
+    f_f_b_se3 = np.zeros(6)
+
+    # get T_w_b - needed to map to friction contacts
+    pin.forwardKinematics(model, data, q)
+
+    # need to add length to get to the end of the fingertip
+    # to fingertip
+    T_w_b = data.oMi[OBJECT_JOINT_ID].copy()
+    # need to add length to get to the end of the fingertip
+    # to fingertip
+    # TODO: move this to a side function
+    to_finger_tip_offset = pin.SE3.Identity()
+    to_finger_tip_offset.translation[2] = 0.045
+    T_w_c1 = data.oMi[-1].act(to_finger_tip_offset)
+    T_w_c2 = data.oMi[-2].act(to_finger_tip_offset)
+    # this should be correct
+    contact_flag = isGripperInContact(T_w_c1, T_w_b, box_dimensions)
+    # realign T_w_b to make have contact and box axis aligned so that the
+    # se2 to se3 math checks out
+    align_T_w_b_matrix = pin.SE3.Identity()
+    align_T_w_b_matrix.rotation = pin.rpy.rpyToMatrix(0, np.pi / 2, 0)
+    # we also need the contact frames z axis to be pointing into the object
+    align_c1_matrix = pin.SE3.Identity()
+    align_c1_matrix.rotation = pin.rpy.rpyToMatrix(0.0, np.pi / 2, 0.0)
+    T_w_c1 = T_w_c1.act(align_c1_matrix)
+    align_c2_matrix = pin.SE3.Identity()
+    align_c2_matrix.rotation = pin.rpy.rpyToMatrix(0.0, np.pi / 2, 0.0)
+    T_w_c2 = T_w_c2.act(align_c2_matrix)
+
+    # let's calculate gravity compensation - without it, the robot just falls down lel.
+    # we want to do the compensation only for the arm and the gripper
+    tau_gravity = pin.computeGeneralizedGravity(model, data, q)
+    # we need tau_g (as in torque only) in controllers that assume only rotational motion
+    tau_g_rotation_object = getObjectTauG(tau_gravity[8:11], T_w_b, T_w_c1, T_w_c2)
+    # we don't want magical access to object forces
+    tau_gravity[8:11] = 0
+
+    # fic_c1.set_fn(5 - t * 2.5)
+    # fic_c2.set_fn(5 - t * 2.5)
+
+    if contact_flag:
+        u, controller_log_item = controller.computeControl(
+            x[10], x[21], t, tau_g_rotation_object
+        )
+        fic_c1.set_fn(u)
+        fic_c2.set_fn(u)
+        # fic_c1.set_fn(0)
+        # fic_c2.set_fn(0)
+        # we want v_c1 in body coordinates.
+        # we have v_b (body twist) and need to compute v_c1 twist from it.
+        # SE3.act(twist) is:
+        # | R  [p]R |
+        # | 0    R  | which calcs the angular -> linear effect (and inv is the correct inv ofc).
+        # also, the angular velocity of a rigid body is the same for every point, so that doesn't change.
+        T_c1_b = T_w_c1.actInv(T_w_b)
+        T_c2_b = T_w_c2.actInv(T_w_b)
+
+        # distance from contact to center of mass
+        # z-axes of c1 and b are parallel by construction,
+        # and we don't care about z offset
+        # due to contact symmetry
+        l_com = np.linalg.norm(T_c1_b.translation[:2])
+
+        v_body_c1 = T_c1_b.act(v_b_se3)
+        v_body_c1 = np.array(
+            [v_body_c1.linear[0], v_body_c1.linear[1], v_body_c1.angular[2]]
+        )
+        v_body_c2 = T_c2_b.act(v_b_se3)
+        v_body_c2 = np.array(
+            [v_body_c2.linear[0], v_body_c2.linear[1], v_body_c2.angular[2]]
+        )
+
+        dy_c1, f_f_c1_dict = fic_c1.ode_step(t, z_c1, v_body_c1)
+        dy_c2, f_f_c2_dict = fic_c2.ode_step(t, z_c2, v_body_c2)
+        f_f_c1_se3.linear = np.array([f_f_c1_dict["x"], f_f_c1_dict["y"], 0])
+        f_f_c1_se3.angular = np.array([0, 0, f_f_c1_dict["tau"]])
+        f_f_c2_se3.linear = np.array([f_f_c2_dict["x"], f_f_c2_dict["y"], 0])
+        f_f_c2_se3.angular = np.array([0, 0, f_f_c2_dict["tau"]])
+
+        f_f_b_c1 = T_c1_b.action.T @ f_f_c1_se3.vector
+        f_f_b_c2 = T_c2_b.action.T @ f_f_c2_se3.vector
+        f_f_b_se3 = f_f_b_c1 + f_f_b_c2
+        J = pin.computeJointJacobian(model, data, q, OBJECT_JOINT_ID)
+        tau_f_b = J.T @ f_f_b_se3
+        # TODO compensate negative friction
+        # this is a hot-fix
+        tau_f_b[0:6] = 0.0
+        # there are two taus because contact forces will be added at some later point
+    else:
+        u = 0.0
+        controller_log_item = None
+        fic_c1.set_fn(0.0)
+        fic_c2.set_fn(0.0)
+        dy_c1, f_f_c1_dict = fic_c1.ode_step(t, z_c1, np.zeros(3))
+        dy_c2, f_f_c2_dict = fic_c2.ode_step(t, z_c2, np.zeros(3))
+        tau_f_b = np.zeros(model.nv)
+
+    # adding up all of the torques (with forces being mapped to joint space)
+    # TODO: remove comment!!!
+    # tau = tau_f_b + tau_gravity
+
+    # TODO: remove this
+    tau_something = np.zeros(model.nv)
+    tau_something[0] = 1.1
+    tau_something[1] = -0.1
+    tau_something[8:11] = 0
+    tau = tau_f_b + tau_gravity + tau_something
+
+    # Forward dynamics
+    q_ddot = pin.aba(model, data, q, v, tau)
+    # estimate position derivative numerically rather than compute.
+    # it works fine and makes the code way simpler.
+    # our dts aren't that small either.
+    q_next4 = (pin.integrate(model, q, v * args.dt) - q) / args.dt
+    x_dot = np.zeros(model.nv)
+    x_dot[:10] = q_next4[:10]
+    x_dot[10] = v_b_se2[2]
+    # no gripping
+    x_dot[6:8] = np.zeros(2)
+
+    if history != None:
+        # history["x_dot"][i] = x_dot.copy()
+        # TODO: change to all joints and extract only the object one
+        a_g = pin.aba(model, data, q, np.zeros(model.nv), np.zeros(model.nv))
+        history["nle"][i] = a_g.copy()
+        history["q"][i] = q.copy()
+        history["q_ddot"][i] = q_ddot.copy()
+        history["tau"][i] = tau.copy()
+        history["tau_f"][i] = tau_f_b.copy()
+        # history["f_f_b"][i] = np.array([f_f_b_se3.linear[0], f_f_b_se3.linear[1], f_f_b_se3.angular[2]])
+        history["f_f_b"][i] = np.array([f_f_b_se3[0], f_f_b_se3[1], f_f_b_se3[5]])
+        history["v_of_c1"][i] = v_of_c1.vector.copy()
+        history["v_b_se2"][i] = v_b_se2.copy()
+        history["T_w_b"].append(T_w_b.copy())
+        history["T_w_c1"].append(T_w_c1.copy())
+        history["T_w_c2"].append(T_w_c2.copy())
+        history["contact_flag"][i] = contact_flag
+        history["fn"][i] = u
+
+    if history_controller != None and controller_log_item != None:
+        history_controller["theta_t"][i] = controller_log_item["theta_t"]
+        history_controller["theta_d_t"][i] = controller_log_item["theta_d_t"]
+        history_controller["theta_dot_t"][i] = controller_log_item["theta_dot_t"]
+        history_controller["theta_dot_d_t"][i] = controller_log_item["theta_dot_d_t"]
+        history_controller["theta_ddot_r_t"][i] = controller_log_item["theta_ddot_r_t"]
+        history_controller["u"][i] = controller_log_item["u"]
+        history_controller["s"][i] = controller_log_item["s"]
+        history_controller["h_hat"][i] = controller_log_item["h_hat"]
+        history_controller["b_hat"][i] = controller_log_item["b_hat"]
+
+    x_dot = np.hstack((x_dot, q_ddot))
+    # evil python array concatenation syntax
+    # adding lugre parameters
+    if not controller.__class__.__name__ == "AdaptiveController":
+        return np.array(
+            x_dot.tolist()
+            + [
+                fic_c1.lugre["dz"][0][0],
+                fic_c1.lugre["dz"][1][0],
+                fic_c1.lugre["dz"][2][0],
+                fic_c2.lugre["dz"][0][0],
+                fic_c2.lugre["dz"][1][0],
+                fic_c2.lugre["dz"][2][0],
+            ]
+        )
+    # adding adaptive control numbers
+    else:
+        return np.array(
+            x_dot.tolist()
+            + [
+                fic_c1.lugre["dz"][0][0],
+                fic_c1.lugre["dz"][1][0],
+                fic_c1.lugre["dz"][2][0],
+                fic_c2.lugre["dz"][0][0],
+                fic_c2.lugre["dz"][1][0],
+                fic_c2.lugre["dz"][2][0],
+            ]
+            + controller.computeEstimateDerivative()
+        )
diff --git a/examples/regrasping/crocoddyl/gripper_and_object_croco_model.py b/examples/regrasping/crocoddyl/planar_object/gripper_and_object_croco_model.py
similarity index 100%
rename from examples/regrasping/crocoddyl/gripper_and_object_croco_model.py
rename to examples/regrasping/crocoddyl/planar_object/gripper_and_object_croco_model.py
diff --git a/examples/regrasping/crocoddyl/pin_models.py b/examples/regrasping/crocoddyl/planar_object/pin_models.py
similarity index 71%
rename from examples/regrasping/crocoddyl/pin_models.py
rename to examples/regrasping/crocoddyl/planar_object/pin_models.py
index bf7d9dfc492c58436fd507625b7623c56d2804c4..608fca03308117f3a04405766c04c06dae1367ed 100644
--- a/examples/regrasping/crocoddyl/pin_models.py
+++ b/examples/regrasping/crocoddyl/planar_object/pin_models.py
@@ -3,55 +3,6 @@ import numpy as np
 import hppfcl as fcl
 
 
-def make_cartpole(ub=True):
-    model = pin.Model()
-
-    m1 = 1.0
-    m2 = 0.1
-    length = 0.5
-    base_sizes = (0.4, 0.2, 0.05)
-
-    base = pin.JointModelPX()
-    base_id = model.addJoint(0, base, pin.SE3.Identity(), "base")
-
-    if ub:
-        pole = pin.JointModelRUBY()
-    else:
-        pole = pin.JointModelRY()
-    pole_id = model.addJoint(1, pole, pin.SE3.Identity(), "pole")
-
-    base_inertia = pin.Inertia.FromBox(m1, *base_sizes)
-    pole_inertia = pin.Inertia(
-        m2,
-        np.array([0.0, 0.0, length / 2]),
-        m2 / 5 * np.diagflat([1e-2, length**2, 1e-2]),
-    )
-
-    base_body_pl = pin.SE3.Identity()
-    pole_body_pl = pin.SE3.Identity()
-    pole_body_pl.translation = np.array([0.0, 0.0, length / 2])
-
-    model.appendBodyToJoint(base_id, base_inertia, base_body_pl)
-    model.appendBodyToJoint(pole_id, pole_inertia, pole_body_pl)
-
-    # make visual/collision models
-    collision_model = pin.GeometryModel()
-    shape_base = fcl.Box(*base_sizes)
-    radius = 0.01
-    shape_pole = fcl.Capsule(radius, length)
-    RED_COLOR = np.array([1, 0.0, 0.0, 1.0])
-    WHITE_COLOR = np.array([1, 1.0, 1.0, 1.0])
-    geom_base = pin.GeometryObject("link_base", base_id, shape_base, base_body_pl)
-    geom_base.meshColor = WHITE_COLOR
-    geom_pole = pin.GeometryObject("link_pole", pole_id, shape_pole, pole_body_pl)
-    geom_pole.meshColor = RED_COLOR
-
-    collision_model.addGeometryObject(geom_base)
-    collision_model.addGeometryObject(geom_pole)
-    visual_model = collision_model
-    return model, collision_model, visual_model
-
-
 def gripper_and_object(
     object_dimensions: np.ndarray,
 ) -> tuple[pin.Model, pin.GeometryModel, pin.GeometryModel, pin.Data]:
diff --git a/examples/regrasping/crocoddyl/regrasp.py b/examples/regrasping/crocoddyl/planar_object/regrasp.py
similarity index 100%
rename from examples/regrasping/crocoddyl/regrasp.py
rename to examples/regrasping/crocoddyl/planar_object/regrasp.py
diff --git a/examples/regrasping/crocoddyl/utils.py b/examples/regrasping/crocoddyl/utils.py
index 37fa7b49839fe6869af717f967440ff9d6ca88d8..15da30f1fe77bc50e3ef9783f4df2026bdae7bff 100644
--- a/examples/regrasping/crocoddyl/utils.py
+++ b/examples/regrasping/crocoddyl/utils.py
@@ -43,6 +43,12 @@ def get_args():
         choices=["coulomb", "lugre"],
         help="select friction model from list of available ones",
     )
+    parser.add_argument(
+        "--min-nonsticking-velocity",
+        type=float,
+        default=1e-2,
+        help="if the sliding velocity is below this, enter sticking mode",
+    )
     # NOTE: most are taken from gabriel
     parser.add_argument(
         "--mu-s", type=float, default=1.2, help="static coulomb friction coefficient"
@@ -125,23 +131,3 @@ def get_args():
     args = parser.parse_args()
 
     return args
-
-
-def computeStickingCondition(mu_tors, model_pin, data_pin, x, u):
-    sticking_condition = True
-    if np.abs(x[3]) > 0.0:
-        sticking_condition = False
-    # if there was sticking before, then a[1] = 0.
-    # now if this is the onset of sliping, it won't be.
-    # NOTE: but for the purposes of this calculation it is, right?
-    if (np.abs(x[3]) < 1e-1) or sticking_condition:
-        M = pin.crba(model_pin, data_pin, x[:2])
-        M_inv = np.linalg.inv(M)
-        nle_tau = pin.nle(model_pin, data_pin, x[:2], x[2:])
-        tau2 = -1 * (M_inv[1, 0] * u[0] - nle_tau[1]) / M_inv[1, 1]
-        print(x[3], tau2, -1 * np.sign(x[3]) * mu_tors * u[1])
-        if np.abs(tau2) > mu_tors * u[1]:
-            sticking_condition = False
-        else:
-            sticking_condition = True
-    return sticking_condition